diff --git a/.betterer.results b/.betterer.results index cb77689214a..8cf81d8d286 100644 --- a/.betterer.results +++ b/.betterer.results @@ -8,14 +8,14 @@ exports[`no enzyme tests`] = { "packages/grafana-ui/src/components/QueryField/QueryField.test.tsx:2976628669": [ [0, 26, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.test.js:1676554632": [ - [14, 19, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.test.tsx:793800575": [ + [14, 35, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.js:186764954": [ - [14, 19, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.tsx:596989456": [ + [14, 35, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js:1734982398": [ - [14, 26, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.tsx:3266788928": [ + [14, 56, 13, "RegExp match", "2409514259"] ], "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.js:989353473": [ [15, 17, 13, "RegExp match", "2409514259"] @@ -4298,6 +4298,9 @@ exports[`better eslint`] = { "public/app/features/plugins/admin/components/PluginDetailsBody.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/plugins/admin/components/PluginDetailsPage.tsx:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "public/app/features/plugins/admin/components/SearchField.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -4316,9 +4319,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "2"], [0, 0, 0, "Do not use any type assertions.", "3"] ], - "public/app/features/plugins/admin/pages/PluginDetails.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/features/plugins/admin/state/actions.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -4556,10 +4556,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], - "public/app/features/scenes/core/SceneTimeRange.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] - ], "public/app/features/scenes/core/sceneGraph.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -5490,14 +5486,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], "public/app/plugins/datasource/elasticsearch/QueryBuilder.test.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"], - [0, 0, 0, "Unexpected any. Specify a different type.", "7"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/plugins/datasource/elasticsearch/QueryBuilder.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -6933,11 +6922,12 @@ exports[`better eslint`] = { "public/app/plugins/panel/barchart/bars.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Do not use any type assertions.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Do not use any type assertions.", "6"] + [0, 0, 0, "Unexpected any. Specify a different type.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"], + [0, 0, 0, "Unexpected any. Specify a different type.", "4"], + [0, 0, 0, "Do not use any type assertions.", "5"], + [0, 0, 0, "Unexpected any. Specify a different type.", "6"], + [0, 0, 0, "Do not use any type assertions.", "7"] ], "public/app/plugins/panel/barchart/module.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -6948,7 +6938,8 @@ exports[`better eslint`] = { ], "public/app/plugins/panel/barchart/utils.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"], + [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], "public/app/plugins/panel/candlestick/CandlestickPanel.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -7499,9 +7490,7 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "16"], [0, 0, 0, "Do not use any type assertions.", "17"], [0, 0, 0, "Do not use any type assertions.", "18"], - [0, 0, 0, "Do not use any type assertions.", "19"], - [0, 0, 0, "Do not use any type assertions.", "20"], - [0, 0, 0, "Do not use any type assertions.", "21"] + [0, 0, 0, "Do not use any type assertions.", "19"] ], "public/app/plugins/panel/histogram/Histogram.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -7571,24 +7560,13 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], - "public/app/plugins/panel/nodeGraph/useCategorizeFrames.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/plugins/panel/nodeGraph/usePanning.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Do not use any type assertions.", "2"], [0, 0, 0, "Do not use any type assertions.", "3"] ], - "public/app/plugins/panel/nodeGraph/useZoom.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/plugins/panel/nodeGraph/utils.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] - ], - "public/app/plugins/panel/piechart/PieChart.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], @@ -7601,25 +7579,16 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "public/app/plugins/panel/stat/StatMigrations.test.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/plugins/panel/stat/StatMigrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Do not use any type assertions.", "2"] ], - "public/app/plugins/panel/stat/common.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] - ], "public/app/plugins/panel/state-timeline/TimelineChart.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "public/app/plugins/panel/state-timeline/migrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -7642,23 +7611,10 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/plugins/panel/state-timeline/utils.test.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"], - [0, 0, 0, "Unexpected any. Specify a different type.", "7"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/plugins/panel/state-timeline/utils.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/plugins/panel/table-old/column_options.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -8024,16 +7980,10 @@ exports[`better eslint`] = { "public/test/core/redux/reduxTester.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Do not use any type assertions.", "3"], - [0, 0, 0, "Do not use any type assertions.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Do not use any type assertions.", "6"], - [0, 0, 0, "Do not use any type assertions.", "7"], - [0, 0, 0, "Do not use any type assertions.", "8"], - [0, 0, 0, "Unexpected any. Specify a different type.", "9"], - [0, 0, 0, "Unexpected any. Specify a different type.", "10"], - [0, 0, 0, "Unexpected any. Specify a different type.", "11"] + [0, 0, 0, "Unexpected any. Specify a different type.", "2"], + [0, 0, 0, "Unexpected any. Specify a different type.", "3"], + [0, 0, 0, "Unexpected any. Specify a different type.", "4"], + [0, 0, 0, "Unexpected any. Specify a different type.", "5"] ], "public/test/core/thunk/thunkTester.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -8047,43 +7997,32 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "8"] ], "public/test/global-jquery-shim.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/test/helpers/convertToStoreState.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] - ], - "public/test/helpers/createFetchResponse.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/test/helpers/getDashboardModel.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/test/helpers/initTemplateSrv.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/test/jest-setup.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"] + [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], "public/test/jest-shim.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Do not use any type assertions.", "3"], + [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"], - [0, 0, 0, "Unexpected any. Specify a different type.", "7"] + [0, 0, 0, "Unexpected any. Specify a different type.", "5"] ], "public/test/lib/common.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/test/matchers/index.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -8107,10 +8046,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "4"] ], "public/test/matchers/utils.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/test/mocks/angular.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -8127,16 +8063,11 @@ exports[`better eslint`] = { ], "public/test/mocks/datasource_srv.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Do not use any type assertions.", "4"], - [0, 0, 0, "Do not use any type assertions.", "5"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "public/test/mocks/workers.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "public/test/specs/helpers.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -8148,14 +8079,13 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "6"], [0, 0, 0, "Unexpected any. Specify a different type.", "7"], [0, 0, 0, "Unexpected any. Specify a different type.", "8"], - [0, 0, 0, "Do not use any type assertions.", "9"], + [0, 0, 0, "Unexpected any. Specify a different type.", "9"], [0, 0, 0, "Unexpected any. Specify a different type.", "10"], [0, 0, 0, "Unexpected any. Specify a different type.", "11"], [0, 0, 0, "Unexpected any. Specify a different type.", "12"], [0, 0, 0, "Unexpected any. Specify a different type.", "13"], [0, 0, 0, "Unexpected any. Specify a different type.", "14"], - [0, 0, 0, "Unexpected any. Specify a different type.", "15"], - [0, 0, 0, "Unexpected any. Specify a different type.", "16"] + [0, 0, 0, "Unexpected any. Specify a different type.", "15"] ] }` }; diff --git a/.betterer.ts b/.betterer.ts index dc3789d46ad..6d40d7a9179 100644 --- a/.betterer.ts +++ b/.betterer.ts @@ -36,7 +36,10 @@ function countEslintErrors() { }; const isTestFile = - filePath.endsWith('.test.tsx') || filePath.endsWith('.test.ts') || filePath.includes('__mocks__'); + filePath.endsWith('.test.tsx') || + filePath.endsWith('.test.ts') || + filePath.includes('__mocks__') || + filePath.includes('public/test/'); if (!isTestFile) { rules['@typescript-eslint/consistent-type-assertions'] = [ diff --git a/.drone.star b/.drone.star index 2504f1a394c..1051ac1918c 100644 --- a/.drone.star +++ b/.drone.star @@ -7,17 +7,45 @@ load('scripts/drone/events/pr.star', 'pr_pipelines') load('scripts/drone/events/main.star', 'main_pipelines') load('scripts/drone/pipelines/docs.star', 'docs_pipelines') -load('scripts/drone/events/release.star', 'oss_pipelines', 'enterprise_pipelines', 'enterprise2_pipelines', 'publish_artifacts_pipelines', 'publish_npm_pipelines', 'publish_packages_pipeline', 'artifacts_page_pipeline') -load('scripts/drone/pipelines/publish_images.star', 'publish_image_pipelines_public', 'publish_image_pipelines_security') +load( + 'scripts/drone/events/release.star', + 'oss_pipelines', + 'enterprise_pipelines', + 'enterprise2_pipelines', + 'publish_artifacts_pipelines', + 'publish_npm_pipelines', + 'publish_packages_pipeline', + 'artifacts_page_pipeline', +) +load( + 'scripts/drone/pipelines/publish_images.star', + 'publish_image_pipelines_public', + 'publish_image_pipelines_security', +) load('scripts/drone/version.star', 'version_branch_pipelines') load('scripts/drone/events/cron.star', 'cronjobs') load('scripts/drone/vault.star', 'secrets') + def main(ctx): - edition = 'oss' - return pr_pipelines(edition=edition) + main_pipelines(edition=edition) + oss_pipelines() + enterprise_pipelines() + enterprise2_pipelines() + \ - enterprise2_pipelines(prefix='custom-', trigger = {'event': ['custom']},) + \ - publish_image_pipelines_public() + publish_image_pipelines_security() + \ - publish_artifacts_pipelines('security') + publish_artifacts_pipelines('public') + \ - publish_npm_pipelines('public') + publish_packages_pipeline() + artifacts_page_pipeline() + \ - version_branch_pipelines() + cronjobs(edition=edition) + secrets() + return ( + pr_pipelines() + + main_pipelines() + + oss_pipelines() + + enterprise_pipelines() + + enterprise2_pipelines() + + enterprise2_pipelines( + prefix='custom-', + trigger={'event': ['custom']}, + ) + + publish_image_pipelines_public() + + publish_image_pipelines_security() + + publish_artifacts_pipelines('security') + + publish_artifacts_pipelines('public') + + publish_npm_pipelines() + + publish_packages_pipeline() + + artifacts_page_pipeline() + + version_branch_pipelines() + + cronjobs() + + secrets() + ) diff --git a/.drone.yml b/.drone.yml index afa440e9753..b8ebfe2ace7 100644 --- a/.drone.yml +++ b/.drone.yml @@ -575,7 +575,7 @@ steps: - failure - commands: - yarn storybook:build - - ./bin/grabpl verify-storybook + - ./bin/build verify-storybook depends_on: - build-frontend - build-frontend-packages @@ -1430,7 +1430,7 @@ steps: - failure - commands: - yarn storybook:build - - ./bin/grabpl verify-storybook + - ./bin/build verify-storybook depends_on: - build-frontend - build-frontend-packages @@ -2155,7 +2155,7 @@ steps: - failure - commands: - yarn storybook:build - - ./bin/grabpl verify-storybook + - ./bin/build verify-storybook depends_on: - build-frontend - build-frontend-packages @@ -3857,6 +3857,10 @@ platform: os: linux services: [] steps: +- commands: + - echo $DRONE_RUNNER_NAME + image: alpine:3.15.6 + name: identify-runner - commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.17/grabpl @@ -3949,6 +3953,10 @@ platform: os: linux services: [] steps: +- commands: + - echo $DRONE_RUNNER_NAME + image: alpine:3.15.6 + name: identify-runner - commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.17/grabpl @@ -4024,6 +4032,10 @@ platform: os: linux services: [] steps: +- commands: + - echo $DRONE_RUNNER_NAME + image: alpine:3.15.6 + name: identify-runner - commands: - mkdir -p bin - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.17/grabpl @@ -4687,7 +4699,7 @@ steps: - failure - commands: - yarn storybook:build - - ./bin/grabpl verify-storybook + - ./bin/build verify-storybook depends_on: - build-frontend - build-frontend-packages @@ -6306,6 +6318,6 @@ kind: secret name: packages_secret_access_key --- kind: signature -hmac: e7746a4b35fba9e1a7cb3096b947a874786b082f41e4252448ac6acde7ee3ccf +hmac: dcf24226fae30872050cdc031430374d811e6bbe13158ce0fbf234c90c1d83f9 ... diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b405e168fe3..bd8bab5fa4a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -103,9 +103,11 @@ go.sum @grafana/backend-platform # Plugins /pkg/api/pluginproxy @grafana/plugins-platform-backend +/pkg/infra/httpclient @grafana/plugins-platform-backend /pkg/plugins @grafana/plugins-platform-backend /pkg/services/datasourceproxy @grafana/plugins-platform-backend /pkg/services/datasources @grafana/plugins-platform-backend +/pkg/services/pluginsintegration @grafana/plugins-platform-backend /pkg/plugins/pfs @grafana/plugins-platform-backend @grafana/grafana-as-code # Dashboard previews / crawler (behind feature flag) @@ -146,6 +148,7 @@ tsconfig.json @grafana/frontend-ops /public/app/features/geo/ @grafana/grafana-edge-squad /public/app/features/storage/ @grafana/grafana-edge-squad /public/app/features/live/ @grafana/grafana-edge-squad +/public/app/features/query-library/ @grafana/grafana-edge-squad /public/app/features/explore/ @grafana/observability-experience-squad /public/app/features/plugins @grafana/plugins-platform-frontend /public/app/features/transformers/spatial @grafana/grafana-edge-squad @@ -228,6 +231,7 @@ lerna.json @grafana/frontend-ops # Grafana Partnerships Team /pkg/infra/httpclient/httpclientprovider/sigv4_middleware.go @grafana/grafana-partnerships-team +/pkg/infra/httpclient/httpclientprovider/sigv4_middleware_test.go @grafana/grafana-partnerships-team # Kind system and code generation embed.go @grafana/grafana-as-code diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 235131251d8..a5396c0faa9 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -30,6 +30,7 @@ "react-dom", "react-test-renderer" ], + "includePaths": ["package.json", "packages/*"], "ignorePaths": ["packages/grafana-toolkit/package.json", "emails/**", "plugins-bundled/**", "**/mocks/**"], "labels": ["area/frontend", "dependencies", "no-backport", "no-changelog"], "packageRules": [ diff --git a/.github/workflows/publish-technical-documentation-next.yml b/.github/workflows/publish-technical-documentation-next.yml new file mode 100644 index 00000000000..30666f2ebb0 --- /dev/null +++ b/.github/workflows/publish-technical-documentation-next.yml @@ -0,0 +1,31 @@ +name: "publish-technical-documentation-next" + +on: + push: + branches: + - "main" + paths: + - "docs/sources/**" + - "packages/grafana-*/**" + workflow_dispatch: +jobs: + sync: + if: "github.repository == 'grafana/grafana'" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout Grafana repo" + uses: "actions/checkout@v3" + + - name: "Clone website-sync Action" + run: "git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync" + + - name: "Publish to website repository (next)" + uses: "./.github/actions/website-sync" + id: "publish-next" + with: + repository: "grafana/website" + branch: "master" + host: "github.com" + github_pat: "${{ secrets.GH_BOT_ACCESS_TOKEN }}" + source_folder: "docs/sources" + target_folder: "content/docs/grafana/next" diff --git a/.github/workflows/publish-technical-documentation-release.yml b/.github/workflows/publish-technical-documentation-release.yml new file mode 100644 index 00000000000..2e795855dac --- /dev/null +++ b/.github/workflows/publish-technical-documentation-release.yml @@ -0,0 +1,61 @@ +name: "publish-technical-documentation-release" + +on: + push: + branches: + - v[0-9]+.[0-9]+.[0-9]+ + tags: + - v[0-9]+.[0-9]+.[0-9]+ + paths: + - "docs/sources/**" + - "packages/grafana-*/**" + workflow_dispatch: +jobs: + sync: + if: "github.repository == 'grafana/grafana'" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout Grafana repo" + uses: "actions/checkout@v3" + with: + fetch-depth: 0 + + - name: "Checkout Actions library" + uses: "actions/checkout@v3" + with: + repository: "grafana/grafana-github-actions" + path: "./actions" + + - name: "Install Actions from library" + run: "npm install --production --prefix ./actions" + + - name: "Determine if there is a matching release tag" + id: "has-matching-release-tag" + uses: "./actions/has-matching-release-tag" + with: + ref_name: "${{ github.ref_name }}" + release_tag_regexp: "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$" + release_branch_regexp: "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.x$" + + - name: "Determine technical documentation version" + if: "steps.has-matching-release-tag.outputs.bool == 'true'" + uses: "./actions/docs-target" + id: "target" + with: + ref_name: "${{ github.ref_name }}" + + - name: "Clone website-sync Action" + if: "steps.has-matching-release-tag.outputs.bool == 'true'" + run: "git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync" + + - name: "Publish to website repository (release)" + if: "steps.has-matching-release-tag.outputs.bool == 'true'" + uses: "./.github/actions/website-sync" + id: "publish-release" + with: + repository: "grafana/website" + branch: "master" + host: "github.com" + github_pat: "${{ secrets.GH_BOT_ACCESS_TOKEN }}" + source_folder: "docs/sources" + target_folder: "content/docs/grafana/${{ steps.target.outputs.target }}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 95207729772..00000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: publish_docs - -on: - push: - branches: - - main - paths: - - 'docs/sources/**' - - 'packages/grafana-*/**' - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - run: git clone --single-branch --no-tags --depth 1 -b master https://grafanabot:${{ secrets.GH_BOT_ACCESS_TOKEN }}@github.com/grafana/website-sync ./.github/actions/website-sync - - name: setup node - uses: actions/setup-node@v3.5.1 - with: - node-version: '16' - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - uses: actions/cache@v3.0.11 - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - yarn- - - run: yarn install --immutable - - name: publish-to-git - uses: ./.github/actions/website-sync - id: publish - with: - repository: grafana/website - branch: master - host: github.com - github_pat: '${{ secrets.GH_BOT_ACCESS_TOKEN }}' - source_folder: docs/sources - target_folder: content/docs/grafana/next - allow_no_changes: 'true' - - shell: bash - run: | - test -n "${{ steps.publish.outputs.commit_hash }}" - test -n "${{ steps.publish.outputs.working_directory }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index f8ed5a9e9dd..24b2b1f8785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,81 @@ + + +# 9.3.1 (2022-11-30) + +### Features and enhancements + +- **Connections:** Update "Your connections/Data sources" page. [#58589](https://github.com/grafana/grafana/pull/58589), [@mikkancso](https://github.com/mikkancso) + +### Bug fixes + +- **Accessibility:** Increase badge constrast to be WCAG AA compliant. [#59531](https://github.com/grafana/grafana/pull/59531), [@eledobleefe](https://github.com/eledobleefe) + + + + +# 9.3.0 (2022-11-30) + +### Features and enhancements + +- **Alerting:** Enable interpolation for notification policies in file provisioning. [#58956](https://github.com/grafana/grafana/pull/58956), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) +- **Azure Monitor Logs:** Avoid warning when the response is empty. [#59211](https://github.com/grafana/grafana/pull/59211), [@andresmgot](https://github.com/andresmgot) +- **Azure Monitor:** Add support to customized routes. [#54829](https://github.com/grafana/grafana/pull/54829), [@ms-hujia](https://github.com/ms-hujia) +- **Canvas:** Add icon value mapping. [#59013](https://github.com/grafana/grafana/pull/59013), [@nmarrs](https://github.com/nmarrs) +- **CloudWatch:** Cross-account querying support. [#59362](https://github.com/grafana/grafana/pull/59362), [@sunker](https://github.com/sunker) +- **Docs:** Update `merge-pull-request.md` regarding backport policies. [#59239](https://github.com/grafana/grafana/pull/59239), [@dsotirakis](https://github.com/dsotirakis) +- **GaugePanel:** Setting the neutral-point of a gauge. [#53989](https://github.com/grafana/grafana/pull/53989), [@sfranzis](https://github.com/sfranzis) +- **Geomap:** Improve location editor. [#58017](https://github.com/grafana/grafana/pull/58017), [@drew08t](https://github.com/drew08t) +- **Internationalization:** Enable internationalization by default. [#59204](https://github.com/grafana/grafana/pull/59204), [@joshhunt](https://github.com/joshhunt) +- **Logs:** Add `Download logs` button to log log-browser. [#55163](https://github.com/grafana/grafana/pull/55163), [@svennergr](https://github.com/svennergr) +- **Loki:** Add `gzip` compression to resource calls. [#59059](https://github.com/grafana/grafana/pull/59059), [@svennergr](https://github.com/svennergr) +- **Loki:** Add improvements to loki label browser. [#59387](https://github.com/grafana/grafana/pull/59387), [@gwdawson](https://github.com/gwdawson) +- **Loki:** Make label browser accessible in query builder. [#58525](https://github.com/grafana/grafana/pull/58525), [@gwdawson](https://github.com/gwdawson) +- **Loki:** Remove raw query toggle. [#59125](https://github.com/grafana/grafana/pull/59125), [@gwdawson](https://github.com/gwdawson) +- **Middleware:** Add CSP Report Only support. [#58074](https://github.com/grafana/grafana/pull/58074), [@jcalisto](https://github.com/jcalisto) +- **Navigation:** Prevent viewer role accessing dashboard creation, import and folder creation. [#58842](https://github.com/grafana/grafana/pull/58842), [@lpskdl](https://github.com/lpskdl) +- **OAuth:** Refactor OAuth parameters handling to support obtaining refresh tokens for Google OAuth. [#58782](https://github.com/grafana/grafana/pull/58782), [@mgyongyosi](https://github.com/mgyongyosi) +- **Oauth:** Display friendly error message when role_attribute_strict=true and no valid role found. [#57818](https://github.com/grafana/grafana/pull/57818), [@kalleep](https://github.com/kalleep) +- **Preferences:** Add confirmation modal when saving org preferences. [#59119](https://github.com/grafana/grafana/pull/59119), [@JoaoSilvaGrafana](https://github.com/JoaoSilvaGrafana) +- **PublicDashboards:** Orphaned public dashboard deletion script added. [#57917](https://github.com/grafana/grafana/pull/57917), [@juanicabanas](https://github.com/juanicabanas) +- **Query Editor:** Hide overflow for long query names. [#58840](https://github.com/grafana/grafana/pull/58840), [@zuchka](https://github.com/zuchka) +- **Reports:** Configurable timezone. (Enterprise) +- **Solo Panel:** Configurable timezone. [#59153](https://github.com/grafana/grafana/pull/59153), [@spinillos](https://github.com/spinillos) +- **TablePanel:** Add support for Count calculation per column or per entire dataset. [#58134](https://github.com/grafana/grafana/pull/58134), [@mdvictor](https://github.com/mdvictor) +- **Tempo:** Send the correct start time when making a TraceQL query. [#59128](https://github.com/grafana/grafana/pull/59128), [@CrypticSignal](https://github.com/CrypticSignal) +- **Various Panels:** Remove beta label from Bar Chart, Candlestick, Histogram, State Timeline, & Status History Panels. [#58557](https://github.com/grafana/grafana/pull/58557), [@codeincarnate](https://github.com/codeincarnate) + +### Bug fixes + +- **Access Control:** Clear user's permission cache after resource creation. [#59307](https://github.com/grafana/grafana/pull/59307), [@grafanabot](https://github.com/grafanabot) +- **Access Control:** Clear user's permission cache after resource creation. [#59101](https://github.com/grafana/grafana/pull/59101), [@IevaVasiljeva](https://github.com/IevaVasiljeva) +- **Accessibility:** Improve keyboard accessibility in `AnnoListPanel`. [#58971](https://github.com/grafana/grafana/pull/58971), [@ashharrison90](https://github.com/ashharrison90) +- **Accessibility:** Improve keyboard accessibility in `Collapse`. [#59022](https://github.com/grafana/grafana/pull/59022), [@ashharrison90](https://github.com/ashharrison90) +- **Accessibility:** Improve keyboard accessibility in `GettingStarted` panel. [#58966](https://github.com/grafana/grafana/pull/58966), [@ashharrison90](https://github.com/ashharrison90) +- **Accessibility:** Improve keyboard accessibility of `FilterPill`. [#58976](https://github.com/grafana/grafana/pull/58976), [@ashharrison90](https://github.com/ashharrison90) +- **Admin:** Fix broken links to image assets in email templates. [#58729](https://github.com/grafana/grafana/pull/58729), [@zuchka](https://github.com/zuchka) +- **Azure Monitor:** Fix namespace selection for storageaccounts. [#56449](https://github.com/grafana/grafana/pull/56449), [@andresmgot](https://github.com/andresmgot) +- **Calcs:** Fix difference percent in legend. [#59243](https://github.com/grafana/grafana/pull/59243), [@zoltanbedi](https://github.com/zoltanbedi) +- **DataLinks:** Improve Data-Links AutoComplete Logic. [#58934](https://github.com/grafana/grafana/pull/58934), [@zuchka](https://github.com/zuchka) +- **Explore:** Fix a11y issue with logs navigation buttons. [#58944](https://github.com/grafana/grafana/pull/58944), [@Elfo404](https://github.com/Elfo404) +- **Heatmap:** Fix blurry text & rendering. [#59260](https://github.com/grafana/grafana/pull/59260), [@leeoniya](https://github.com/leeoniya) +- **Heatmap:** Fix tooltip y range of top and bottom buckets in calculated heatmaps. [#59172](https://github.com/grafana/grafana/pull/59172), [@leeoniya](https://github.com/leeoniya) +- **Logs:** Fix misalignment of LogRows. [#59279](https://github.com/grafana/grafana/pull/59279), [@svennergr](https://github.com/svennergr) +- **Navigation:** Stop clearing search state when opening a result in a new tab. [#58880](https://github.com/grafana/grafana/pull/58880), [@ashharrison90](https://github.com/ashharrison90) +- **OptionsUI:** SliderValueEditor does not get auto focused on slider change. [#59209](https://github.com/grafana/grafana/pull/59209), [@eledobleefe](https://github.com/eledobleefe) +- **PanelEdit:** Fixes bug with not remembering panel options pane collapse/expand state. [#59265](https://github.com/grafana/grafana/pull/59265), [@torkelo](https://github.com/torkelo) +- **Query Caching:** Skip 207 status codes. (Enterprise) +- **Quota:** Fix failure in store due to missing scope parameters. [#58874](https://github.com/grafana/grafana/pull/58874), [@papagian](https://github.com/papagian) +- **Quota:** Fix failure when checking session limits. [#58865](https://github.com/grafana/grafana/pull/58865), [@papagian](https://github.com/papagian) +- **Reports:** Fix time preview. (Enterprise) +- **StateTimeline:** Prevent label text from overflowing state rects. [#59169](https://github.com/grafana/grafana/pull/59169), [@leeoniya](https://github.com/leeoniya) +- **Tempo:** Fix search table duration unit. [#58642](https://github.com/grafana/grafana/pull/58642), [@joey-grafana](https://github.com/joey-grafana) +- **TraceView:** Fix broken rendering when scrolling in Dashboard panel in Firefox. [#56642](https://github.com/grafana/grafana/pull/56642), [@zdg-github](https://github.com/zdg-github) + +### Plugin development fixes & changes + +- **GrafanaUI:** Add disabled option for menu items. [#58980](https://github.com/grafana/grafana/pull/58980), [@going-confetti](https://github.com/going-confetti) + + # 9.3.0-beta1 (2022-11-15) @@ -167,6 +245,24 @@ The following functions and classes related to logs are deprecated in the `grafa - **Toolkit:** Deprecate `component:create` command. [#56086](https://github.com/grafana/grafana/pull/56086), [@academo](https://github.com/academo) - **Toolkit:** Remove changelog command. [#56073](https://github.com/grafana/grafana/pull/56073), [@gitstart](https://github.com/gitstart) + + +# 9.2.7 (2022-11-29) + +### Bug fixes + +- **Access Control:** Clear user's permission cache after resource creation. [#59318](https://github.com/grafana/grafana/pull/59318), [@IevaVasiljeva](https://github.com/IevaVasiljeva) +- **Azure Monitor:** Fix empty/errored responses for Logs variables. [#59240](https://github.com/grafana/grafana/pull/59240), [@andresmgot](https://github.com/andresmgot) +- **Azure Monitor:** Fix resource picker selection for subresources. [#56392](https://github.com/grafana/grafana/pull/56392), [@andresmgot](https://github.com/andresmgot) +- **Navigation:** Fix crash when Help is disabled. [#58919](https://github.com/grafana/grafana/pull/58919), [@lpskdl](https://github.com/lpskdl) +- **PostgreSQL:** Fix missing CA field from configuration. [#59280](https://github.com/grafana/grafana/pull/59280), [@oscarkilhed](https://github.com/oscarkilhed) +- **SQL Datasources:** Fix annotation migration. [#59438](https://github.com/grafana/grafana/pull/59438), [@zoltanbedi](https://github.com/zoltanbedi) +- **SQL:** Fix code editor for SQL datasources. [#58116](https://github.com/grafana/grafana/pull/58116), [@zoltanbedi](https://github.com/zoltanbedi) +- **SSE:** Make sure to forward headers, user and cookies/OAuth token. [#58897](https://github.com/grafana/grafana/pull/58897), [@kylebrandt](https://github.com/kylebrandt) +- **TimeseriesPanel:** Preserve string fields for data link interpolation. [#58424](https://github.com/grafana/grafana/pull/58424), [@mdvictor](https://github.com/mdvictor) + + + # 9.2.6 (2022-11-22) diff --git a/Makefile b/Makefile index 887df780d6a..5c3cf3f5256 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,7 @@ gen-cue: ## Do all CUE/Thema code generation go generate ./pkg/plugins/plugindef go generate ./kinds/gen.go go generate ./pkg/framework/coremodel - go generate ./public/app/plugins + go generate ./public/app/plugins/gen.go gen-go: $(WIRE) gen-cue @echo "generate go files" @@ -233,5 +233,8 @@ drone: $(DRONE) $(DRONE) lint .drone.yml --trusted $(DRONE) --server https://drone.grafana.net sign --save grafana/grafana +format-drone: + black --include '\.star$$' -S scripts/drone/ .drone.star + help: ## Display this help. @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) diff --git a/conf/defaults.ini b/conf/defaults.ini index 49c4582a280..cedead7db8e 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -85,6 +85,11 @@ cdn_url = # `0` means there is no timeout for reading the request. read_timeout = 0 +# This setting enables you to specify additional headers that the server adds to HTTP(S) responses. +[server.custom_response_headers] +#exampleHeader1 = exampleValue1 +#exampleHeader2 = exampleValue2 + #################################### Database ############################ [database] # You can configure the database connection by specifying type, host, name, user and password @@ -157,6 +162,12 @@ type = database # memcache: 127.0.0.1:11211 connstr = +# prefix prepended to all the keys in the remote cache +prefix = + +# This enables encryption of values stored in the remote cache +encryption = + #################################### Data proxy ########################### [dataproxy] @@ -712,6 +723,10 @@ managed_identity_client_id = # If enabled, cache permissions in a in memory cache permission_cache = true +# Reset basic roles permissions on boot +# Warning left to true, basic roles permissions will be reset on every boot +reset_basic_roles = false + #################################### SMTP / Emailing ##################### [smtp] enabled = false @@ -1060,6 +1075,7 @@ url = https://grafana.com [grafana_com] url = https://grafana.com +api_url = https://grafana.com/api #################################### Distributed tracing ############ # Opentracing is deprecated use opentelemetry instead @@ -1272,7 +1288,10 @@ license_path = # enable = feature1,feature2 enable = -internationalization = true +# Some features are enabled by default, see: +# https://grafana.com/docs/grafana/next/setup-grafana/configure-grafana/feature-toggles/ +# To enable features by default, set `Expression: "true"` in: +# https://github.com/grafana/grafana/blob/main/pkg/services/featuremgmt/registry.go # feature1 = true # feature2 = false @@ -1359,3 +1378,14 @@ index_update_interval = 10s # Move a specific app plugin page (referenced by its `path` field) to a specific navigation section # Format: =
[navigation.app_standalone_pages] + + +#################################### Secure Socks5 Datasource Proxy ##################################### +[secure_socks_datasource_proxy] +enabled = false +root_ca_cert = +client_key = +client_cert = +server_name = +# The address of the socks5 proxy datasources should connect to +proxy_address = \ No newline at end of file diff --git a/conf/sample.ini b/conf/sample.ini index 7c27b34819b..30e4e65cdcc 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -86,6 +86,11 @@ # `0` means there is no timeout for reading the request. ;read_timeout = 0 +# This setting enables you to specify additional headers that the server adds to HTTP(S) responses. +[server.custom_response_headers] +#exampleHeader1 = exampleValue1 +#exampleHeader2 = exampleValue2 + #################################### Database #################################### [database] # You can configure the database connection by specifying type, host, name, user and password @@ -164,6 +169,12 @@ # memcache: 127.0.0.1:11211 ;connstr = +# prefix prepended to all the keys in the remote cache +; prefix = + +# This enables encryption of values stored in the remote cache +;encryption = + #################################### Data proxy ########################### [dataproxy] @@ -698,6 +709,11 @@ #################################### Role-based Access Control ########### [rbac] ;permission_cache = true + +# Reset basic roles permissions on boot +# Warning left to true, basic roles permissions will be reset on every boot +#reset_basic_roles = false + #################################### SMTP / Emailing ########################## [smtp] ;enabled = false @@ -1029,6 +1045,7 @@ # Url used to import dashboards directly from Grafana.com [grafana_com] ;url = https://grafana.com +;api_url = https://grafana.com/api #################################### Distributed tracing ############ # Opentracing is deprecated use opentelemetry instead @@ -1285,3 +1302,13 @@ [navigation.app_standalone_pages] # The following will move the page with the path "/a/my-app-id/starred-content" from `my-app-id` to the `starred` section # /a/my-app-id/starred-content = starred + +#################################### Secure Socks5 Datasource Proxy ##################################### +[secure_socks_datasource_proxy] +; enabled = false +; root_ca_cert = +; client_key = +; client_cert = +; server_name = +# The address of the socks5 proxy datasources should connect to +; proxy_address = \ No newline at end of file diff --git a/devenv/dev-dashboards/panel-barchart/barchart-label-rotation-skipping.json b/devenv/dev-dashboards/panel-barchart/barchart-label-rotation-skipping.json new file mode 100644 index 00000000000..43d7655053e --- /dev/null +++ b/devenv/dev-dashboards/panel-barchart/barchart-label-rotation-skipping.json @@ -0,0 +1,320 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 530, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decmbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 11, + "x": 0, + "y": 0 + }, + "id": 2, + "maxDataPoints": 30, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "vertical", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelMaxLength": 6, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 100 + }, + "targets": [ + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"fields\": [\n {\n \"name\": \"label\",\n \"type\": \"string\",\n \"typeInfo\": {\n \"frame\": \"string\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 600000\n }\n },\n {\n \"name\": \"A-series\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {},\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n \"acquisition\",\n \"extension\",\n \"conductor\",\n \"authorise\",\n \"architect\",\n \"illusion\",\n \"congress\",\n \"highlight\",\n \"partnership\",\n \"understanding\",\n \"disagreement\",\n \"personality\",\n \"commerce\",\n \"systematic\",\n \"hesitate\",\n \"business\",\n \"manufacture\",\n \"incredible\",\n \"constitutional\",\n \"prevalence\",\n \"professor\",\n \"entitlement\",\n \"cooperation\",\n \"sickness\",\n \"contrast\",\n \"reference\",\n \"audience\",\n \"discount\",\n \"apparatus\",\n \"disturbance\",\n \"automatic\",\n \"refrigerator\",\n \"elaborate\",\n \"sympathetic\",\n \"integration\",\n \"president\"\n ],\n [\n 306.78931659492116,\n 200.00696051101917,\n 164.90889283973593,\n 518.9385023737021,\n 999.9040675564702,\n 613.9689830172349,\n 773.2337077340269,\n 317.47395634701644,\n 748.3318338316539,\n 606.8039493787173,\n 426.27771317792866,\n 376.47735643253924,\n 66.30635081800493,\n 401.70654338415505,\n 108.86259550477234,\n 182.40284186231278,\n 867.7047958572101,\n 959.3957783599242,\n 396.7606089549935,\n 455.9625595614323,\n 685.4792456298062,\n 368.6567303946707,\n 157.06596562976327,\n 59.54120602048763,\n 406.72723615743973,\n 440.18247585615575,\n 516.0267558264891,\n 258.76006051667315,\n 952.966531725171,\n 554.8746357628739,\n 86.7279280805682,\n 781.2422516386563,\n 754.2723802427706,\n 435.0305712850233,\n 384.43181614983,\n 459.04164596738127\n ]\n ]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "title": "Panel Title", + "type": "barchart" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 0, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 7, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 13, + "x": 11, + "y": 0 + }, + "id": 5, + "options": { + "barRadius": 0, + "barWidth": 1, + "groupWidth": 0.82, + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "text": {}, + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 0 + }, + "targets": [ + { + "csvContent": "Name,Stat1,Stat2\nStockholm, 10, 15\nNew York, 19, -5\nLondon, 10, 1\nLong value, 15,10", + "refId": "A", + "scenarioId": "csv_content" + } + ], + "title": "Auto sizing & auto show values", + "type": "barchart" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decmbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 18, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 3, + "maxDataPoints": 20, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelMaxLength": 5, + "xTickLabelRotation": 45, + "xTickLabelSpacing": 100 + }, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "Panel Title", + "type": "barchart" + } + ], + "schemaVersion": 37, + "style": "dark", + "tags": [ + "gdev", + "panel-tests", + "barchart", + "graph-ng" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BarChart - Label Rotation & Skipping", + "uid": "xCmMwXdVz", + "version": 20, + "weekStart": "" + } diff --git a/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json b/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json index 2d36cdd2f4f..778cbde0638 100644 --- a/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json +++ b/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json @@ -24,7 +24,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 61, "links": [ { "asDropdown": true, @@ -49,6 +48,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -105,7 +106,8 @@ "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -157,6 +159,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -214,7 +218,8 @@ "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -274,6 +279,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -331,7 +338,8 @@ "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -410,6 +418,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -466,7 +476,8 @@ "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -518,6 +529,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -575,7 +588,8 @@ "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -635,6 +649,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -692,7 +708,8 @@ "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -771,6 +788,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -859,7 +878,8 @@ "calcs": [], "displayMode": "list", "isVisible": true, - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -914,6 +934,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -1002,7 +1024,8 @@ "calcs": [], "displayMode": "list", "isVisible": true, - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -1058,6 +1081,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -1113,7 +1138,8 @@ "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -1163,6 +1189,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -1220,7 +1248,8 @@ "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -1248,6 +1277,8 @@ "mode": "palette-classic" }, "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, @@ -1305,7 +1336,8 @@ "legend": { "calcs": [], "displayMode": "list", - "placement": "bottom" + "placement": "bottom", + "showLegend": true }, "tooltip": { "mode": "single", @@ -1325,9 +1357,549 @@ ], "title": "Always show points between gaps", "type": "timeseries" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 18, + "maxDataPoints": 150, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "100,52,82,50,93,117,97,139,172,152,177,213,237,248,249,275,272,295,339,317,274,272,225,240,214,179,170,161,117,99,79,100,107,147,195,153,144,143,117,76,88,43,65,114,155,170,187,236,214,245,255,260,262,264,268,287,297,310,332,339,303,316,317,330,323,352,383,367,334,306,289,311,269,319,313,350,374,346,391,348,386,352,328,303,298,305,346,323,313,296,274,252,301,337,357,360,383,384,339,332,346,394,428,463,461,476,478,491,522,487,536,547,593,595,614,632,588,594,584,566,529,541,586,621,618,635,682,684,693,648,658,659,628,618,662,638,652,665,673,651,653,675,685,709,749,721,707,750,712,724,734,753,716,694,732,741,694,702,732,746,748,700,707,690,668,659,646,677,688,701,670,625,628,611,587,570,589,621,598,604" + } + ], + "title": "Low density (wide)", + "type": "timeseries" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 41 + }, + "id": 20, + "maxDataPoints": 150, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "100,52,82,50,93,117,97,139,172,152,177,213,237,248,249,275,272,295,339,317,274,272,225,240,214,179,170,161,117,99,79,100,107,147,195,153,144,143,117,76,88,43,65,114,155,170,187,236,214,245,255,260,262,264,268,287,297,310,332,339,303,316,317,330,323,352,383,367,334,306,289,311,269,319,313,350,374,346,391,348,386,352,328,303,298,305,346,323,313,296,274,252,301,337,357,360,383,384,339,332,346,394,428,463,461,476,478,491,522,487,536,547,593,595,614,632,588,594,584,566,529,541,586,621,618,635,682,684,693,648,658,659,628,618,662,638,652,665,673,651,653,675,685,709,749,721,707,750,712,724,734,753,716,694,732,741,694,702,732,746,748,700,707,690,668,659,646,677,688,701,670,625,628,611,587,570,589,621,598,604" + } + ], + "title": "Higher density (narrow)", + "type": "timeseries" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 41 + }, + "id": 22, + "maxDataPoints": 150, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "100,52,82,50,93,117,97,139,172,152,177,213,237,248,249,275,272,295,339,317,274,272,225,240,214,179,170,161,117,99,79,100,107,147,195,153,144,143,117,76,88,43,65,114,155,170,187,236,214,245,255,260,262,264,268,287,297,310,332,339,303,316,317,330,323,352,383,367,334,306,289,311,269,,,350,,,391,348,386,352,328,303,298,,346,,313,,274,252,301,337,357,360,383,384,339,332,346,394,428,463,461,476,478,491,522,487,536,547,593,595,614,632,588,594,584,566,529,541,586,621,618,635,682,684,693,648,658,659,628,618,,,652,665,673,,,675,,,749,721,707,750,712,724,734,753,716,694,732,741,694,702,732,746,748,700,707,690,668,659,646,677,688,701,670,625,628,611,587,570,589,621,598,604" + } + ], + "title": "Higher density (with gap-point-gap)", + "type": "timeseries" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 0, + "y": 51 + }, + "id": 24, + "maxDataPoints": 150, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + } + ], + "title": "Higher density (mostly nulls)", + "type": "timeseries" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 8, + "y": 51 + }, + "id": 26, + "maxDataPoints": 150, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + } + ], + "title": "Higher density (mostly nulls, one point)", + "type": "timeseries" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 51 + }, + "id": 28, + "maxDataPoints": 150, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "scenarioId": "csv_metric_values", + "stringInput": "8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8" + } + ], + "title": "Higher density (mostly nulls, no leading/trailing)", + "type": "timeseries" } ], - "schemaVersion": 36, + "schemaVersion": 37, "style": "dark", "tags": [ "gdev", diff --git a/devenv/jsonnet/dev-dashboards.libsonnet b/devenv/jsonnet/dev-dashboards.libsonnet index fa47dc594bf..f5787a61e6f 100644 --- a/devenv/jsonnet/dev-dashboards.libsonnet +++ b/devenv/jsonnet/dev-dashboards.libsonnet @@ -107,6 +107,13 @@ local dashboard = grafana.dashboard; id: 0, } }, + dashboard.new('barchart-label-rotation-skipping', import '../dev-dashboards/panel-barchart/barchart-label-rotation-skipping.json') + + resource.addMetadata('folder', 'dev-dashboards') + + { + spec+: { + id: 0, + } + }, dashboard.new('barchart-thresholds-mappings', import '../dev-dashboards/panel-barchart/barchart-thresholds-mappings.json') + resource.addMetadata('folder', 'dev-dashboards') + { diff --git a/docs/sources/administration/organization-preferences/index.md b/docs/sources/administration/organization-preferences/index.md index ccd7c593060..e18fd09294f 100644 --- a/docs/sources/administration/organization-preferences/index.md +++ b/docs/sources/administration/organization-preferences/index.md @@ -101,7 +101,7 @@ Here is an example of the light theme. ### Change server UI theme -Grafana server administrators can change the Grafana UI theme for all users on the server by setting the [default_theme]({{< relref "../../setup-grafana/configure-grafana/#default-theme" >}}) option in the Grafana configuration file. +As a Grafana server administrator, you can change the default Grafana UI theme for all users who are on the server by setting the [default_theme]({{< relref "../../setup-grafana/configure-grafana/#default-theme" >}}) option in the Grafana configuration file. To see what the current settings are, refer to [View server settings]({{< relref "../stats-and-license#view-server-settings" >}}). @@ -111,17 +111,18 @@ Organization administrators can change the UI theme for all users in an organiza 1. Hover your cursor over the **Configuration** (gear) icon. 1. Click **Preferences**. -1. In the Preferences section, select the **UI theme**. +1. In the **Preferences** section, select the **UI theme**. 1. Click **Save**. ### Change team UI theme -Organization and team administrators can change the UI theme for all users in a team. +Organization and team administrators can change the UI theme for all users on a team. 1. Hover your cursor over the **Configuration** (gear) icon in the side menu. 1. Click **Teams**. Grafana displays the team list. -1. Click on the team that you want to change the UI theme for and then navigate to the **Settings** tab. -1. In the Preferences section, select the **UI theme**. +1. Click the team for which you want to change the UI theme. +1. Click **Settings**. +1. In the **Preferences** section, select the **UI theme**. 1. Click **Save**. ### Change your personal UI theme @@ -129,7 +130,7 @@ Organization and team administrators can change the UI theme for all users in a You can change the UI theme for your user account. This setting overrides UI theme settings at higher levels. 1. On the left menu, hover your cursor over your avatar and then click **Preferences**. -1. In the Preferences section, select the **UI theme**. +1. In the **Preferences** section, select the **UI theme**. 1. Click **Save**. ## Change the Grafana default timezone @@ -153,11 +154,12 @@ Organization administrators can choose a default timezone for their organization ### Set team timezone -Organization administrators and team administrators can choose a default timezone for all users in a team. +Organization administrators and team administrators can choose a default timezone for all users on a team. 1. Hover your cursor over the **Configuration** (gear) icon in the side menu. 1. Click **Teams**. Grafana displays the team list. -1. Click on the team you that you want to change the timezone for and then navigate to the **Settings** tab. +1. Click the team for which you want to change the timezone. +1. Click **Settings** 1. Click to select an option in the **Timezone** list. **Default** is either the browser local timezone or the timezone selected at a higher level. Refer to [[Time range controls]({{< relref "../../dashboards/manage-dashboards/#configure-dashboard-time-range-controls" >}}) for more information about Grafana time settings. 1. Click **Save**. @@ -207,7 +209,7 @@ default_home_dashboard_path = data/main-dashboard.json ### Set the home dashboard for your organization -Organization administrators can choose a home dashboard for their organization. +Organization administrators can choose a default home dashboard for their organization. 1. Navigate to the dashboard you want to set as the home dashboard. 1. Click the star next to the dashboard title to mark the dashboard as a favorite if it is not already. @@ -218,13 +220,14 @@ Organization administrators can choose a home dashboard for their organization. ### Set home dashboard for your team -Organization administrators and Team Admins can choose a home dashboard for a team. +Organization administrators and Team Admins can set a default home dashboard for all users on a team. 1. Navigate to the dashboard you want to set as the home dashboard. 1. Click the star next to the dashboard title to mark the dashboard as a favorite if it is not already. 1. Hover your cursor over the **Configuration** (gear) icon in the side menu. 1. Click **Teams**. Grafana displays the team list. -1. Click on the team that you want to change the home dashboard for and then navigate to the **Settings** tab. +1. Click the team for which you want to change the home dashboard. +1. Click **Settings**. 1. In the **Home Dashboard** field, select the dashboard that you want to use for your home dashboard. Options include all starred dashboards. 1. Click **Save**. @@ -237,3 +240,37 @@ You can choose your own personal home dashboard. This setting overrides all home 1. On the left menu, hover your cursor over your avatar and then click **Preferences**. 1. In the **Home Dashboard** field, select the dashboard that you want to use for your home dashboard. Options include all starred dashboards. 1. Click **Save**. + +## Change Grafana language + +### Change server language + +Grafana server administrators can change the default Grafana UI language for all users on the server by setting the [default_language]({{< relref "../../setup-grafana/configure-grafana/#default-language" >}}) option in the Grafana configuration file. + +### Change organization language + +Organization administrators can change the language for all users in an organization. + +1. Hover your cursor over the **Configuration** (gear) icon. +1. Click **Preferences**. +1. In the **Preferences** section, select the **Language**. +1. Click **Save**. + +### Change team language + +Organization and team administrators can set a default language for all users on a team. + +1. Hover your cursor over the **Configuration** (gear) icon in the side menu. +1. Click **Teams**. Grafana displays the team list. +1. Click the team for which you want to change the language. +1. Click **Settings** +1. In the **Preferences** section, select the **Language**. +1. Click **Save**. + +### Change your personal language + +You can change the language for your user account. This setting overrides language settings at higher levels. + +1. On the left menu, hover your cursor over your avatar and then click **Preferences**. +1. In the **Preferences** section, select the **language**. +1. Click **Save**. diff --git a/docs/sources/administration/roles-and-permissions/access-control/configure-rbac/index.md b/docs/sources/administration/roles-and-permissions/access-control/configure-rbac/index.md index 05f9e61cbce..3de508026a2 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/configure-rbac/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/configure-rbac/index.md @@ -18,6 +18,7 @@ The table below describes all RBAC configuration options. Like any other Grafana | ------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `permission_cache` | No | Enable to use in memory cache for loading and evaluating users' permissions. | `true` | | `permission_validation_enabled` | No | Grafana enforces validation for permissions when a user creates or updates a role. The system checks the internal list of scopes and actions for each permission to determine they are valid. By default, if a scope or action is not recognized, Grafana logs a warning message. When set to `true`, Grafana returns an error. | `false` | +| `reset_basic_roles` | No | Reset Grafana's basic roles' (Viewer, Editor, Admin, Grafana Admin) permissions to their default. Warning, if this configuration option is left to `true` this will be done on every reboot. | `true` | ## Example RBAC configuration diff --git a/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md b/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md index 125390be437..1bc77408b09 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md @@ -309,7 +309,29 @@ You can also change basic roles' permissions using the API. Refer to the [RBAC H ## Reset basic roles to their default -This section describes how to reset the basic roles to their default: +This section describes how to reset the basic roles to their default. + +You have two options to reset the basic roles permissions to their default. + +### Use the configuration option + +> **Note**: Available as of Grafana Enterprise 9.4. + +> Warning: If this option is left to true, permissions will be reset on every boot. + +Use the [reset_basic_roles]({{< relref "../configure-rbac/#configure-rbac-in-grafana" >}}) option to reset +basic roles permissions to their default on Grafana instance boot up. + +1. Open you configuration file and update the rbac section as follow: + +```bash +[rbac] +reset_basic_roles = true +``` + +### Use the http endpoint + +An alternative to the configuration option is to use the HTTP endpoint. 1. Open the YAML configuration file and locate the `roles` section. @@ -327,7 +349,7 @@ This section describes how to reset the basic roles to their default: permissions: # Permission allowing to reset basic roles - action: 'roles:write' - scope: 'permissions:type:escalate' + scope: 'permissions:type:escalate' ``` 1. As a `Grafana Admin`, call the API endpoint to reset the basic roles to their default. Refer to the [RBAC HTTP API]({{< relref "../../../../developers/http_api/access_control/#reset-basic-roles-to-their-default" >}}) for more details. diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md index ee92d91a5cf..9060e7d2517 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md @@ -21,7 +21,7 @@ The following tables list permissions associated with basic and fixed roles. | Basic role | Associated fixed roles | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Grafana Admin | `fixed:roles:reader`
`fixed:roles:writer`
`fixed:users:reader`
`fixed:users:writer`
`fixed:org.users:reader`
`fixed:org.users:writer`
`fixed:ldap:reader`
`fixed:ldap:writer`
`fixed:stats:reader`
`fixed:settings:reader`
`fixed:settings:writer`
`fixed:provisioning:writer`
`fixed:organization:reader`
`fixed:organization:maintainer`
`fixed:licensing:reader`
`fixed:licensing:writer`
`fixed:datasources.caching:reader`
`fixed:datasources.caching:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader` | Default [Grafana server administrator]({{< relref "../#grafana-server-administrators" >}}) assignments. | -| Admin | `fixed:reports:reader`
`fixed:reports:writer`
`fixed:datasources:reader`
`fixed:datasources:writer`
`fixed:organization:writer`
`fixed:datasources.permissions:reader`
`fixed:datasources.permissions:writer`
`fixed:teams:writer`
`fixed:dashboards:reader`
`fixed:dashboards:writer`
`fixed:dashboards.permissions:reader`
`fixed:dashboards.permissions:writer`
`fixed:folders:reader`
`fixes:folders:writer`
`fixed:folders.permissions:reader`
`fixed:folders.permissions:writer`
`fixed:alerting:writer`
`fixed:apikeys:reader`
`fixed:apikeys:writer`
`fixed:alerting.provisioning:writer`
`fixed:datasources.caching:reader`
`fixed:datasources.caching:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader` | Default [Grafana organization administrator]({{< relref "../#organization-users-and-permissions" >}}) assignments. | +| Admin | `fixed:reports:reader`
`fixed:reports:writer`
`fixed:datasources:reader`
`fixed:datasources:writer`
`fixed:organization:writer`
`fixed:datasources.permissions:reader`
`fixed:datasources.permissions:writer`
`fixed:teams:writer`
`fixed:dashboards:reader`
`fixed:dashboards:writer`
`fixed:dashboards.permissions:reader`
`fixed:dashboards.permissions:writer`
`fixed:folders:reader`
`fixed:folders:writer`
`fixed:folders.permissions:reader`
`fixed:folders.permissions:writer`
`fixed:alerting:writer`
`fixed:apikeys:reader`
`fixed:apikeys:writer`
`fixed:alerting.provisioning:writer`
`fixed:datasources.caching:reader`
`fixed:datasources.caching:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader` | Default [Grafana organization administrator]({{< relref "../#organization-users-and-permissions" >}}) assignments. | | Editor | `fixed:datasources:explorer`
`fixed:dashboards:creator`
`fixed:folders:creator`
`fixed:annotations:writer`
`fixed:teams:creator` if the `editors_can_admin` configuration flag is enabled
`fixed:alerting:writer`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader` | Default [Editor]({{< relref "../#organization-users-and-permissions" >}}) assignments. | | Viewer | `fixed:datasources:id:reader`
`fixed:organization:reader`
`fixed:annotations:reader`
`fixed:annotations.dashboard:writer`
`fixed:alerting:reader`
`fixed:plugins.app:reader`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader` | Default [Viewer]({{< relref "../#organization-users-and-permissions" >}}) assignments. | diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-grafana-provisioning/index.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-grafana-provisioning/index.md index 29a5b6eac9a..7215f5ab73d 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/rbac-grafana-provisioning/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-grafana-provisioning/index.md @@ -26,13 +26,17 @@ Grafana performs provisioning during startup. After you make a change to the con 1. Sign in to the Grafana server. -2. Locate the Grafana provisioning folder. +1. Locate the Grafana provisioning folder. -3. Create a new YAML in the following folder: **provisioning/access-control**. For example, `provisioning/access-control/custom-roles.yml` +1. Create a new YAML in the following folder: **provisioning/access-control**. For example, `provisioning/access-control/custom-roles.yml` -4. Add RBAC provisioning details to the configuration file. See [manage RBAC roles]({{< relref "./manage-rbac-roles/" >}}) and [assign RBAC roles]({{< relref "./assign-rbac-roles/" >}}) for instructions, and see this [example role provisioning file]({{< relref "./rbac-provisioning/#example" >}}) for a complete example of a provisioning file. +1. Add RBAC provisioning details to the configuration file. -5. Reload the provisioning configuration file. + Refer to [Manage RBAC roles]({{< relref "./manage-rbac-roles/" >}}) and [Assign RBAC roles]({{< relref "./assign-rbac-roles/" >}}) for instructions. + + Refer to [example role provisioning file]({{< relref "#example-role-configuration-file-using-grafana-provisioning" >}}) for a complete example of a provisioning file. + +1. Reload the provisioning configuration file. For more information about reloading the provisioning configuration at runtime, refer to [Reload provisioning configurations]({{< relref "../../../../developers/http_api/admin/#reload-provisioning-configurations" >}}). @@ -46,7 +50,7 @@ The following example shows a complete YAML configuration file that: - Assign roles to teams - Revoke assignments of roles to teams -## Example +### Example ```yaml --- diff --git a/docs/sources/administration/user-management/user-preferences/index.md b/docs/sources/administration/user-management/user-preferences/index.md index 3f4cc1e0189..3b654836ffb 100644 --- a/docs/sources/administration/user-management/user-preferences/index.md +++ b/docs/sources/administration/user-management/user-preferences/index.md @@ -29,9 +29,7 @@ You can change your Grafana password at any time. 1. Sign in to Grafana. 1. Hover your mouse over the user icon in the lower-left corner of the page. -1. Click **Change Password**. - Grafana opens the **Change Password** tab. - +1. Click **Change Password**. Grafana opens the **Change Password** tab. 1. Enter your old password and a new password. 1. Confirm your new password. 1. Click **Change Password**. @@ -54,6 +52,7 @@ You can choose the way you would like data to appear in Grafana, including the U - **Home dashboard** refers to the dashboard you see when you sign in to Grafana. By default, this is set to the Home dashboard. - **Timezone** is used by dashboards when you set time ranges, so that you view data in your timezone instead of UTC. - **Week start** is the first day of the week you want to use in dashboard time ranges, for example, `This week`. +- **Language** determines the language used for parts of the Grafana interface. **To edit your preferences**: diff --git a/docs/sources/alerting/_index.md b/docs/sources/alerting/_index.md index e308da45d39..758bff12934 100644 --- a/docs/sources/alerting/_index.md +++ b/docs/sources/alerting/_index.md @@ -69,6 +69,4 @@ With mute timings, you can specify a time interval when you don’t want new not - [Role-based access control]({{< relref "../administration/roles-and-permissions/access-control/" >}}) in Grafana Enterprise. -- [Alertmanager]({{< relref "fundamentals/alertmanager/" >}}) - - [High availability](https://grafana.com/docs/grafana/next/alerting/fundamentals/high-availability/) diff --git a/docs/sources/alerting/fundamentals/annotation-label/example-template-functions.md b/docs/sources/alerting/fundamentals/annotation-label/example-template-functions.md deleted file mode 100644 index b6f13464476..00000000000 --- a/docs/sources/alerting/fundamentals/annotation-label/example-template-functions.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/alerting/contact-points/message-templating/example-template-functions/ - - /docs/grafana/latest/alerting/fundamentals/annotation-label/example-template-functions/ -keywords: - - grafana - - alerting - - guide - - contact point - - templating -title: Examples of template functions -weight: 130 ---- - -# Examples of template functions - -## humanize - -**Template string** `{ humanize $value }` - -**Input** `1234567.0` - -**Expected** `1.235M` - -## humanize1024 - -**TemplateString** `{ humanize1024 $value } ` - -**Input** `1048576.0` - -**Expected** `1Mi` - -## humanizeDuration - -**TemplateString** `{ humanizeDuration $value }` - -**Input** `899.99` - -**Expected** `14m 59s` - -### humanizePercentage - -**TemplateString** `{ humanizePercentage $value }` - -**Input** `0.1234567` - -**Expected** `12.35%` - -## humanizeTimestamp - -**TemplateString** `{ $value | humanizeTimestamp }` - -**Input** `1435065584.128` - -**Expected** `2015-06-23 13:19:44.128 +0000 UTC` - -## title - -**TemplateString** `{ $value | title }` - -**Input** `aa bb CC` - -**Expected** `Aa Bb Cc` - -### toUpper - -**TemplateString** `{ $value | toUpper }` - -**Input** `aa bb CC` - -**Expected** `AA BB CC` - -### toLower - -**TemplateString** `{ $value | toLower }` - -**Input** `aA bB CC` - -**Expected** `aa bb cc` - -## match - -**TemplateString** `{ match "a+" $labels.instance }` - -**Input** `aa` - -**Expected** `true` - -## reReplaceAll - -**TemplateString** `{{ reReplaceAll "localhost:(.*)" "my.domain:$1" $labels.instance }}` - -**Input** `localhost:3000` - -**Expected** `my.domain:3000` - -### graphLink - -**TemplateString** `{{ graphLink "{\"expr\": \"up\", \"datasource\": \"gdev-prometheus\"}" }}` - -**Expected** `/explore?left=["now-1h","now","gdev-prometheus",{"datasource":"gdev-prometheus","expr":"up","instant":false,"range":true}]` - -### tableLink - -**TemplateString** `{{ tableLink "{\"expr\": \"up\", \"datasource\": \"gdev-prometheus\"}" }}` - -**Expected** `/explore?left=["now-1h","now","gdev-prometheus",{"datasource":"gdev-prometheus","expr":"up","instant":true,"range":false}]` - -## args - -**TemplateString** `{{define "x"}}{{.arg0}} {{.arg1}}{{end}}{{template "x" (args 1 "2")}}` - -**Expected** `1 2` - -## externalURL - -**TemplateString** `{ externalURL }` - -**Expected** `http://localhost/path/prefix` - -## pathPrefix - -**TemplateString** `{ pathPrefix }` - -**Expected** `/path/prefix` diff --git a/docs/sources/alerting/fundamentals/annotation-label/template-functions.md b/docs/sources/alerting/fundamentals/annotation-label/template-functions.md deleted file mode 100644 index c7c3635c525..00000000000 --- a/docs/sources/alerting/fundamentals/annotation-label/template-functions.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/alerting/contact-points/message-templating/template-functions/ - - /docs/grafana/latest/alerting/message-templating/template-functions/ - - /docs/grafana/latest/alerting/unified-alerting/message-templating/template-functions/ - - /docs/grafana/latest/alerting/fundamentals/annotation-label/template-functions/ - - /docs/grafana/latest/alerting/unified-alerting/fundamentals/annotation-label/template-functions/ -keywords: - - grafana - - alerting - - guide - - contact point - - templating -title: Template functions -weight: 125 ---- - -# Template Functions - -Template functions allow you to process alert evaluation results to generate dynamic notifications. - -| Name | Argument type | Return type | Description | -| ----------------------------------------- | ------------------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| [humanize](#humanize) | number or string | string | Converts a number to a more readable format, using metric prefixes. | -| [humanize1024](#humanize1024) | number or string | string | Like humanize, but uses 1024 as the base rather than 1000. | -| [humanizeDuration](#humanizeduration) | number or string | string | Converts a duration in seconds to a more readable format. | -| [humanizePercentage](#humanizepercentage) | number or string | string | Converts a ratio value to a fraction of 100. | -| [humanizeTimestamp](#humanizetimestamp) | number or string | string | Converts a Unix timestamp in seconds to a more readable format. | -| [title](#title) | string | string | strings.Title, capitalises first character of each word. | -| [toUpper](#toupper) | string | string | strings.ToUpper, converts all characters to upper case. | -| [toLower](#tolower) | string | string | strings.ToLower, converts all characters to lower case. | -| [match](#match) | pattern, text | boolean | regexp.MatchString Tests for a unanchored regexp match. | -| [reReplaceAll](#rereplaceall) | pattern, replacement, text | string | Regexp.ReplaceAllString Regexp substitution, unanchored. | -| [graphLink](#graphlink) | string - JSON Object with `"expr"` and `"datasource"` fields | string | Returns the path to graphical view in [Explore](https://grafana.com/docs/grafana/latest/explore/) for the given expression and data source. | -| [tableLink](#tablelink) | string- JSON Object with `"expr"` and `"datasource"` fields | string | Returns the path to tabular view in [Explore](https://grafana.com/docs/grafana/latest/explore/) for the given expression and data source. | -| [args](#args) | []interface{} | map[string]interface{} | Converts a list of objects to a map with keys, for example, arg0, arg1. Use this function to pass multiple arguments to templates. | -| [externalURL](#externalurl) | nothing | string | Returns a string representing the external URL. | -| [pathPrefix](#pathprefix) | nothing | string | Returns the path of the external URL. | - -## Examples - -### humanize - -**Template string** `{ humanize $value }` - -**Input** `1234567.0` - -**Expected** `1.235M` - -### humanize1024 - -**TemplateString** `{ humanize1024 $value } ` - -**Input** `1048576.0` - -**Expected** `1Mi` - -### humanizeDuration - -**TemplateString** `{ humanizeDuration $value }` - -**Input** `899.99` - -**Expected** `14m 59s` - -### humanizePercentage - -**TemplateString** `{ humanizePercentage $value }` - -**Input** `0.1234567` - -**Expected** `12.35%` - -### humanizeTimestamp - -**TemplateString** `{ $value | humanizeTimestamp }` - -**Input** `1435065584.128` - -**Expected** `2015-06-23 13:19:44.128 +0000 UTC` - -### title - -**TemplateString** `{ $value | title }` - -**Input** `aa bb CC` - -**Expected** `Aa Bb Cc` - -### toUpper - -**TemplateString** `{ $value | toUpper }` - -**Input** `aa bb CC` - -**Expected** `AA BB CC` - -### toLower - -**TemplateString** `{ $value | toLower }` - -**Input** `aA bB CC` - -**Expected** `aa bb cc` - -### match - -**TemplateString** `{ match "a+" $labels.instance }` - -**Input** `aa` - -**Expected** `true` - -### reReplaceAll - -**TemplateString** `{{ reReplaceAll "localhost:(.*)" "my.domain:$1" $labels.instance }}` - -**Input** `localhost:3000` - -**Expected** `my.domain:3000` - -### graphLink - -**TemplateString** `{{ graphLink "{\"expr\": \"up\", \"datasource\": \"gdev-prometheus\"}" }}` - -**Expected** `/explore?left=["now-1h","now","gdev-prometheus",{"datasource":"gdev-prometheus","expr":"up","instant":false,"range":true}]` - -### tableLink - -**TemplateString** `{{ tableLink "{\"expr\": \"up\", \"datasource\": \"gdev-prometheus\"}" }}` - -**Expected** `/explore?left=["now-1h","now","gdev-prometheus",{"datasource":"gdev-prometheus","expr":"up","instant":true,"range":false}]` - -### args - -**TemplateString** `{{define "x"}}{{.arg0}} {{.arg1}}{{end}}{{template "x" (args 1 "2")}}` - -**Expected** `1 2` - -### externalURL - -**TemplateString** `{ externalURL }` - -**Expected** `http://localhost/path/prefix` - -### pathPrefix - -**TemplateString** `{ pathPrefix }` - -**Expected** `/path/prefix` diff --git a/docs/sources/alerting/fundamentals/annotation-label/variables-label-annotation.md b/docs/sources/alerting/fundamentals/annotation-label/variables-label-annotation.md index 4bd6e1c3a3b..a6f92bd47c6 100644 --- a/docs/sources/alerting/fundamentals/annotation-label/variables-label-annotation.md +++ b/docs/sources/alerting/fundamentals/annotation-label/variables-label-annotation.md @@ -13,77 +13,241 @@ weight: 117 # Templating labels and annotations -In Grafana it is possible to template labels and annotations just like you would in Prometheus. Those who have used Prometheus before should be familiar with the `$labels` variable which holds the label key/value pairs of the alert instance and the `$value` variable which holds the evaluated value of the alert instance. +In Grafana it is possible to template labels and annotations just like in Prometheus. Those who have used Prometheus before should be familiar with `$labels` and `$value` as these variables contain the labels and value of the alert. You can use the same variables in Grafana to template labels and annotations, even if the alert does not use a Prometheus datasource. -In Grafana it is possible to use the same variables from Prometheus to template labels and annotations, even if your alert does not use a Prometheus datasource. - -For example, let's suppose we want to create an alert in Grafana that tells us when one of our instances is down for more than 5 minutes. Like in Prometheus, we can add a summary annotation to show the instance which is down: +For example, suppose you want to create an alert rule in Grafana that fires when one of your instances is down for more than 5 minutes, and that each alert fired should have a summary annotation to tell you which instance is down: ``` Instance {{ $labels.instance }} has been down for more than 5 minutes ``` -For alerts where we also want to know the value of the condition at the time the alert fired, we can use both the -`$labels` and the `$value` variable to add even more informative summaries: +## Labels with dots + +If the label contains a dot (full stop or period) in its name then the following will not work: ``` -{{ $labels.instance }} has a 95th percentile request latency above 1s: {{ $value }}) +Instance {{ $labels.instance.name }} has been down for more than 5 minutes ``` -One difference between Grafana and Prometheus is that Grafana uses `$value` to hold both the labels and the value -of the condition at the time the alert fired. For example: +This is because it is attempting to use a non-existing field `name` in `$labels.instance` rather than `instance.name` in `$labels`. Instead use the `index` function to print `instance.name`: ``` -[ var='B' labels={instance=http_server} value=10 ] +Instance {{ index $labels "instance.name" }} has been down for more than 5 minutes ``` -## Alert rules with two or more queries or expressions +## Use values in labels and annotations -In the case where an alert rule has two or more queries, or uses reduce and math expressions, it is possible to template the reduced result of each query and expression with the `$values` variable. This variable holds the labels and value of each reduced query, and the results of any math expressions. However, it does not hold the samples for each query. +Grafana supports `$value` when templating labels and annotations. However, while `$value` in Prometheus is a floating point number contains the value of the expression, `$value` in Grafana is a string containing the labels and values of all Threshold, Reduce and Maths expressions. It does not contain the value of queries as a single query can return anywhere from 1 to 10,000s of rows or metrics. -For example, suppose you have the following alert rule: +This `$value` variable is called the Value String. If you were to use it in the template of a summary annotation: + +``` +{{ $labels.instance }} has an average 95th percentile request latency above 1s: {{ $value }}) +``` + +you will get the following summary: + +``` +http_server has an average 95th percentile request latency above 1s: [ var='B' labels={instance=http_server} value=10 ] +``` + +To get just the value of `B` you can instead use `$values`: + +``` +{{ $labels.instance }} has an average 95th percentile request latency above 1s: {{ $values.B }} +``` + +and then you will get this summary: + +``` +http_server has an average 95th percentile request latency above 1s: 11 +``` + +## Alert rules with multiple queries, or expressions + +If you have an alert rule with multiple queries and expressions, or a single query with a Reduce and Math expression, like in the following example: {{< figure src="/static/img/docs/alerting/unified/grafana-alerting-histogram-quantile.png" class="docs-image--no-shadow" caption="An alert rule that uses histogram_quantile to compute 95th percentile" >}} -Should this rule create an alert instance `$values` will hold the result of the reduce expression `B` and the math expression `C`. It will not hold the results returned by query `A` because query `A` does not return a single value but rather a series of values over time. +Then the Value String will not just include the value of the alert condition, but the labels and values of all Threshold, Reduce and Maths expressions. -If we were to write a summary annotation such as: +**Example 1**: The Value String of an alert rule with a single query and Reduce expression `B`: ``` -{{ $labels.instance }} has a 95th percentile request latency above 1s: {{ $value }}) +[ var='B' labels={instance=http_server} value=11 ] ``` -We would find that because the condition of the alert, the math expression `C` must be a boolean comparison, it must return either a `0` or a `1`. What we want instead is the 95th percentile from the reduce expression `B`: +**Example 2**: The Value String of an alert rule with a single query, Reduce expression `B` and a Math expression `C`: ``` -{{ $labels.instance }} has a 95th percentile request latency above 1s: {{ $values.B }}) +[ var='B' labels={instance=http_server} value=11, var='C' labels={instance=http_server} value=1 ] ``` -We can also show the labels of `B`, however since this alert rule has just one query the labels of `B` are equivalent to `$labels`: +If you were to write a summary annotation such as: ``` -{{ $values.B.Labels.instance }} has a 95th percentile request latency above 1s: {{ $values.B }}) +{{ $labels.instance }} has an average 95th percentile request latency above 1s: {{ $values.C }}) ``` -### No data and execution errors or timeouts +You would find that because the condition of the alert `C` is a Math expression with a boolean comparison, it must return either a `0` or a `1`. What you want instead is the average of the 95th percentile, and you can get this from the reduce expression `B`: + +``` +{{ $labels.instance }} has an average 95th percentile request latency above 1s: {{ $values.B }}) +``` + +## No data and execution errors or timeouts Should query `A` return no data then the reduce expression `B` will also return no data. This means that -`{{ $values.B }}` will be nil. To ensure that labels and annotations can still be templated even when a query returns no data, we can use an if statement to check for `$values.B`: +`{{ $values.B }}` will be nil. To ensure that labels and annotations can still be templated even when a query returns no data, we can use an if statement to check for this condition: ``` {{ if $values.B }}{{ $labels.instance }} has a 95th percentile request latency above 1s: {{ $values.B }}){{ end }} ``` -## Classic conditions +## Classic Conditions -If the rule uses a classic condition instead of a reduce and math expression, then `$values` contains the combination of the `refID` and position of the condition. For example, `{{ $values.A0 }}` and `{{ $values.A1 }}`. +If the rule uses Classic Conditions instead of Reduce and Math expressions, then `$values` contains the combination of the Ref ID and position of the condition. For example, `{{ $values.A0 }}` and `{{ $values.A1 }}`. -## Variables +## Reference -The following template variables are available when expanding labels and annotations. +### Variables + +The following template variables are available when expanding labels and annotations: | Name | Description | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | $labels | The labels from the query or condition. For example, `{{ $labels.instance }}` and `{{ $labels.job }}`. This is unavailable when the rule uses a [classic condition]({{< relref "../../alerting-rules/create-grafana-managed-rule/#single-and-multi-dimensional-rule" >}}). | | $values | The values of all reduce and math expressions that were evaluated for this alert rule. For example, `{{ $values.A }}`, `{{ $values.A.Labels }}` and `{{ $values.A.Value }}` where `A` is the `refID` of the reduce or math expression. If the rule uses a classic condition instead of a reduce and math expression, then `$values` contains the combination of the `refID` and position of the condition. | | $value | The value string of the alert instance. For example, `[ var='A' labels={instance=foo} value=10 ]`. | + +### Functions + +The following functions are also available when expanding labels and annotations: + +| Name | Argument type | Return type | Description | +| ----------------------------------------- | ------------------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| [humanize](#humanize) | number or string | string | Converts a number to a more readable format, using metric prefixes. | +| [humanize1024](#humanize1024) | number or string | string | Like humanize, but uses 1024 as the base rather than 1000. | +| [humanizeDuration](#humanizeduration) | number or string | string | Converts a duration in seconds to a more readable format. | +| [humanizePercentage](#humanizepercentage) | number or string | string | Converts a ratio value to a fraction of 100. | +| [humanizeTimestamp](#humanizetimestamp) | number or string | string | Converts a Unix timestamp in seconds to a more readable format. | +| [title](#title) | string | string | strings.Title, capitalises first character of each word. | +| [toUpper](#toupper) | string | string | strings.ToUpper, converts all characters to upper case. | +| [toLower](#tolower) | string | string | strings.ToLower, converts all characters to lower case. | +| [match](#match) | pattern, text | boolean | regexp.MatchString Tests for a unanchored regexp match. | +| [reReplaceAll](#rereplaceall) | pattern, replacement, text | string | Regexp.ReplaceAllString Regexp substitution, unanchored. | +| [graphLink](#graphlink) | string - JSON Object with `"expr"` and `"datasource"` fields | string | Returns the path to graphical view in [Explore](https://grafana.com/docs/grafana/latest/explore/) for the given expression and data source. | +| [tableLink](#tablelink) | string- JSON Object with `"expr"` and `"datasource"` fields | string | Returns the path to tabular view in [Explore](https://grafana.com/docs/grafana/latest/explore/) for the given expression and data source. | +| [args](#args) | []interface{} | map[string]interface{} | Converts a list of objects to a map with keys, for example, arg0, arg1. Use this function to pass multiple arguments to templates. | +| [externalURL](#externalurl) | nothing | string | Returns a string representing the external URL. | +| [pathPrefix](#pathprefix) | nothing | string | Returns the path of the external URL. | + +#### humanize + +**Template string** `{ humanize $value }` + +**Input** `1234567.0` + +**Expected** `1.235M` + +#### humanize1024 + +**TemplateString** `{ humanize1024 $value } ` + +**Input** `1048576.0` + +**Expected** `1Mi` + +#### humanizeDuration + +**TemplateString** `{ humanizeDuration $value }` + +**Input** `899.99` + +**Expected** `14m 59s` + +#### humanizePercentage + +**TemplateString** `{ humanizePercentage $value }` + +**Input** `0.1234567` + +**Expected** `12.35%` + +#### humanizeTimestamp + +**TemplateString** `{ $value | humanizeTimestamp }` + +**Input** `1435065584.128` + +**Expected** `2015-06-23 13:19:44.128 +0000 UTC` + +#### title + +**TemplateString** `{ $value | title }` + +**Input** `aa bb CC` + +**Expected** `Aa Bb Cc` + +#### toUpper + +**TemplateString** `{ $value | toUpper }` + +**Input** `aa bb CC` + +**Expected** `AA BB CC` + +#### toLower + +**TemplateString** `{ $value | toLower }` + +**Input** `aA bB CC` + +**Expected** `aa bb cc` + +#### match + +**TemplateString** `{ match "a+" $labels.instance }` + +**Input** `aa` + +**Expected** `true` + +#### reReplaceAll + +**TemplateString** `{{ reReplaceAll "localhost:(.*)" "my.domain:$1" $labels.instance }}` + +**Input** `localhost:3000` + +**Expected** `my.domain:3000` + +#### graphLink + +**TemplateString** `{{ graphLink "{\"expr\": \"up\", \"datasource\": \"gdev-prometheus\"}" }}` + +**Expected** `/explore?left=["now-1h","now","gdev-prometheus",{"datasource":"gdev-prometheus","expr":"up","instant":false,"range":true}]` + +#### tableLink + +**TemplateString** `{{ tableLink "{\"expr\": \"up\", \"datasource\": \"gdev-prometheus\"}" }}` + +**Expected** `/explore?left=["now-1h","now","gdev-prometheus",{"datasource":"gdev-prometheus","expr":"up","instant":true,"range":false}]` + +#### args + +**TemplateString** `{{define "x"}}{{.arg0}} {{.arg1}}{{end}}{{template "x" (args 1 "2")}}` + +**Expected** `1 2` + +#### externalURL + +**TemplateString** `{ externalURL }` + +**Expected** `http://localhost/path/prefix` + +#### pathPrefix + +**TemplateString** `{ pathPrefix }` + +**Expected** `/path/prefix` diff --git a/docs/sources/alerting/fundamentals/contact-points/index.md b/docs/sources/alerting/fundamentals/contact-points/index.md deleted file mode 100644 index caaf4235969..00000000000 --- a/docs/sources/alerting/fundamentals/contact-points/index.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/alerting/contact-points/ - - /docs/grafana/latest/alerting/unified-alerting/contact-points/ - - /docs/grafana/latest/alerting/fundamentals/contact-points/contact-point-types/ -description: Create or edit contact point -keywords: - - grafana - - alerting - - guide - - contact point - - notification channel - - create -title: Contact points -weight: 410 ---- - -# Contact points - -Use contact points to define how your contacts are notified when an alert rule fires. A contact point can have one or more contact point types, for example, email, slack, webhook, and so on. When an alert rule fires, a notification is sent to all contact point types listed for a contact point. Contact points can be configured for the Grafana Alertmanager as well as external alertmanagers. - -You can also use message templating to customize notification messages for contact point types. - -## Supported contact point types - -The following table lists the contact point types supported by Grafana. - -| Name | Type | Grafana Alertmanager | Other Alertmanagers | -| ------------------------------------------------ | ------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------- | -| [DingDing](https://www.dingtalk.com/en) | `dingding` | Supported | N/A | -| [Discord](https://discord.com/) | `discord` | Supported | N/A | -| [Email](#email) | `email` | Supported | Supported | -| [Google Hangouts](https://hangouts.google.com/) | `googlechat` | Supported | N/A | -| [Kafka](https://kafka.apache.org/) | `kafka` | Supported | N/A | -| [Line](https://line.me/en/) | `line` | Supported | N/A | -| [Microsoft Teams](https://teams.microsoft.com/) | `teams` | Supported | N/A | -| [Opsgenie](https://atlassian.com/opsgenie/) | `opsgenie` | Supported | Supported | -| [Pagerduty](https://www.pagerduty.com/) | `pagerduty` | Supported | Supported | -| [Prometheus Alertmanager](https://prometheus.io) | `prometheus-alertmanager` | Supported | N/A | -| [Pushover](https://pushover.net/) | `pushover` | Supported | Supported | -| [Sensu Go](https://docs.sensu.io/sensu-go/) | `sensugo` | Supported | N/A | -| [Slack](https://slack.com/) | `slack` | Supported | Supported | -| [Telegram](https://telegram.org/) | `telegram` | Supported | N/A | -| [Threema](https://threema.ch/) | `threema` | Supported | N/A | -| [VictorOps](https://help.victorops.com/) | `victorops` | Supported | Supported | -| [Webhook](#webhook) | `webhook` | Supported | Supported ([different format](https://prometheus.io/docs/alerting/latest/configuration/#webhook_config)) | -| [Cisco Webex Teams](#webex) | `webex` | Supported | Supported | -| [WeCom](#wecom) | `wecom` | Supported | N/A | -| [Zenduty](https://www.zenduty.com/) | `webhook` | Supported | N/A | - -## Useful links - -[Manage contact points](https://grafana.com/docs/grafana/next/alerting/manage-notifications/create-contact-point/) - -[Create and edit message templates](https://grafana.com/docs/grafana/next/alerting/manage-notifications/create-message-template/) diff --git a/docs/sources/alerting/fundamentals/notifications.md b/docs/sources/alerting/fundamentals/notifications.md new file mode 100644 index 00000000000..9de3184f658 --- /dev/null +++ b/docs/sources/alerting/fundamentals/notifications.md @@ -0,0 +1,75 @@ +--- +aliases: + - /docs/grafana/latest/alerting/notifications/ +description: Notifications +keywords: + - grafana + - alerting + - alertmanager + - notification policies + - contact points + - silences +title: Notifications +weight: 410 +--- + +# Notifications + +Grafana uses Alertmanagers to send notifications for firing and resolved alerts. Grafana has its own Alertmanager, referred to as "Grafana" in the user interface, but also supports sending notifications from other Alertmanagers too, such as the [Prometheus Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/). The Grafana Alertmanager uses notification policies and contact points to configure how and where a notification is sent; how often a notification should be sent; and whether alerts should all be sent in the same notification, sent in grouped notifications based on a set of labels, or as separate notifications. + +## Notification policies + +Notification policies control when and where notifications are sent. A notification policy can choose to send all alerts together in the same notification, send alerts in grouped notifications based on a set of labels, or send alerts as separate notifications. You can configure each notification policy to control how often notifications should be sent as well as having one or more mute timings to inhibit notifications at certain times of the day and on certain days of the week. + +Notification policies are organized in a tree structure where at the root of the tree there is a notification policy called the root policy. There can be only one root policy and the root policy cannot be deleted. + +Specific routing policies are children of the root policy and can be used to match either all alerts or a subset of alerts based on a set of matching labels. A notification policy matches an alert when its matching labels match the labels in the alert. + +A specific routing policy can have its own child policies, called nested policies, which allow for additional matching of alerts. An example of a specific routing policy could be sending infrastructure alerts to the Ops team; while a child policy might send high priority alerts to Pagerduty and low priority alerts as emails. + +All alerts, irrespective of their labels, match the root policy. However, when the root policy receives an alert it looks at each specific routing policy and sends the alert to the first specific routing policy that matches the alert. If the specific routing policy has further child policies, then it can attempt to the match the alert against one of its nested policies. If no nested policies match the alert then the specific routing policy is the matching policy. If there are no specific routing policies, or no specific routing policies match the alert, then the root policy is the matching policy. + +## Contact points + +Contact points contain the configuration for sending notifications. A contact point is a list of integrations, each of which sends a notification to a particular email address, service or URL. Contact points can have multiple integrations of the same kind, or a combination of integrations of different kinds. For example, a contact point could contain a Pagerduty integration; an email and Slack integration; or a Pagerduty integration, a Slack integration, and two email integrations. You can also configure a contact point with no integrations; in which case no notifications are sent. + +A contact point cannot send notifications until it has been added to a notification policy. A notification policy can only send alerts to one contact point, but a contact point can be added to a number of notification policies at the same time. When an alert matches a notification policy, the alert is sent to the contact point in that notification policy, which then sends a notification to each integration in its configuration. + +### Supported integrations + +The following table contains the integrations supported in Grafana: + +| Name | Type | Grafana Alertmanager | Other Alertmanagers | +| ------------------------------------------------ | ------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------- | +| [DingDing](https://www.dingtalk.com/en) | `dingding` | Supported | N/A | +| [Discord](https://discord.com/) | `discord` | Supported | N/A | +| [Email](#email) | `email` | Supported | Supported | +| [Google Hangouts](https://hangouts.google.com/) | `googlechat` | Supported | N/A | +| [Kafka](https://kafka.apache.org/) | `kafka` | Supported | N/A | +| [Line](https://line.me/en/) | `line` | Supported | N/A | +| [Microsoft Teams](https://teams.microsoft.com/) | `teams` | Supported | N/A | +| [Opsgenie](https://atlassian.com/opsgenie/) | `opsgenie` | Supported | Supported | +| [Pagerduty](https://www.pagerduty.com/) | `pagerduty` | Supported | Supported | +| [Prometheus Alertmanager](https://prometheus.io) | `prometheus-alertmanager` | Supported | N/A | +| [Pushover](https://pushover.net/) | `pushover` | Supported | Supported | +| [Sensu Go](https://docs.sensu.io/sensu-go/) | `sensugo` | Supported | N/A | +| [Slack](https://slack.com/) | `slack` | Supported | Supported | +| [Telegram](https://telegram.org/) | `telegram` | Supported | N/A | +| [Threema](https://threema.ch/) | `threema` | Supported | N/A | +| [VictorOps](https://help.victorops.com/) | `victorops` | Supported | Supported | +| [Webhook](#webhook) | `webhook` | Supported | Supported ([different format](https://prometheus.io/docs/alerting/latest/configuration/#webhook_config)) | +| [Cisco Webex Teams](#webex) | `webex` | Supported | Supported | +| [WeCom](#wecom) | `wecom` | Supported | N/A | +| [Zenduty](https://www.zenduty.com/) | `webhook` | Supported | N/A | + +## Templating notifications + +You can customize notifications with templates. For example, templates can be used to change the subject and message of an email, or the title and message of notifications sent to Slack. + +Templates are not limited to an individual integration or contact point, but instead can be used in a number of integrations in the same contact point and even integrations across different contact points. For example, a Grafana user can create a template called `custom_subject_or_title` and use it for both templating subjects in emails and titles of Slack messages without having to create two separate templates. + +All notifications templates are written in [Go's templating language](https://pkg.go.dev/text/template), and are in the Contact points tab on the Alerting page. + +## Silences + +You can use silences to mute notifications from one or more firing rules. Silences do not stop alerts from firing or being resolved, or hide firing alerts in the user interface. A silence lasts as long as its duration which can be configured in minutes, hours, days, months or years. diff --git a/docs/sources/alerting/fundamentals/silences/index.md b/docs/sources/alerting/fundamentals/silences/index.md deleted file mode 100644 index 33b8dcac3c7..00000000000 --- a/docs/sources/alerting/fundamentals/silences/index.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/alerting/silences/ - - /docs/grafana/latest/alerting/unified-alerting/silences/ - - /docs/grafana/latest/alerting/fundamentals/silences -description: Silences -keywords: - - grafana - - alerting - - silence - - mute -title: Silences -weight: 420 ---- - -# Silences - -Use silences to stop notifications from one or more alerting rules. Silences do not prevent alert rules from being evaluated. They also do not stop alert instances from being shown in the user interface. Silences only stop notifications from getting created. A silence lasts for only a specified window of time. - -You can configure Grafana managed silences as well as silences for an external Alertmanager data source. - -## Useful links - -[Create silences](https://grafana.com/docs/grafana/latest/alerting/manage-notifications/create-silence/) diff --git a/docs/sources/alerting/fundamentals/alertmanager.md b/docs/sources/alerting/manage-notifications/alertmanager.md similarity index 99% rename from docs/sources/alerting/fundamentals/alertmanager.md rename to docs/sources/alerting/manage-notifications/alertmanager.md index 816ef05c921..180baa1a1be 100644 --- a/docs/sources/alerting/fundamentals/alertmanager.md +++ b/docs/sources/alerting/manage-notifications/alertmanager.md @@ -4,7 +4,7 @@ aliases: - /docs/grafana/latest/alerting/metrics/ - /docs/grafana/latest/alerting/unified-alerting/fundamentals/alertmanager/ title: Alertmanager -weight: 116 +weight: 460 --- # Alertmanager diff --git a/docs/sources/alerting/manage-notifications/images-in-notifications.md b/docs/sources/alerting/manage-notifications/images-in-notifications.md index 6a8260a2db9..e9f6d4bbdd5 100644 --- a/docs/sources/alerting/manage-notifications/images-in-notifications.md +++ b/docs/sources/alerting/manage-notifications/images-in-notifications.md @@ -7,7 +7,7 @@ keywords: - images - notifications title: Use images in notifications -weight: 460 +weight: 480 --- # Use images in notifications diff --git a/docs/sources/dashboards/build-dashboards/manage-library-panels/index.md b/docs/sources/dashboards/build-dashboards/manage-library-panels/index.md index e29bd4c3146..470ad372f35 100644 --- a/docs/sources/dashboards/build-dashboards/manage-library-panels/index.md +++ b/docs/sources/dashboards/build-dashboards/manage-library-panels/index.md @@ -28,7 +28,6 @@ When you create a library panel, the panel on the source dashboard is converted 1. In the panel display options, click the down arrow option to bring changes to the visualization. {{< figure src="/static/img/docs/library-panels/create-lib-panel-from-edit-8-0.png" class="docs-image--no-shadow" max-width= "800px" caption="Screenshot of the edit panel" >}} 1. Click the **Library panels** option, and then click **Create library panel** to open the create dialog. - {{< figure src="/static/img/docs/library-panels/create-lib-panel-8-0.png" class="docs-image--no-shadow" max-width= "500px" caption="Screenshot of the create library panel dialog" >}} 1. In **Library panel name**, enter the name. 1. In **Save in folder**, select the folder to save the library panel. 1. Click **Create library panel** to save your changes. diff --git a/docs/sources/dashboards/create-manage-playlists/index.md b/docs/sources/dashboards/create-manage-playlists/index.md index 165965d5816..9f26a6e5eb5 100644 --- a/docs/sources/dashboards/create-manage-playlists/index.md +++ b/docs/sources/dashboards/create-manage-playlists/index.md @@ -20,8 +20,6 @@ Grafana automatically scales dashboards to any resolution, which makes them perf You can access the Playlist feature from Grafana's side menu, in the Dashboards submenu. -{{< figure src="/static/img/docs/v50/playlist.png" max-width="25rem">}} - ## Access, share, and control a playlist Use the information in this section to access existing playlists. Start and control the display of a playlist using one of the five available modes. @@ -80,8 +78,6 @@ You can create a playlist to present dashboards in a sequence, with a set order 1. Optionally, remove a dashboard from the playlist by clicking the x icon beside dashboard. 1. Click **Save**. -{{< figure src="/static/img/docs/dashboards/create-playlist-8-2.png" max-width="25rem">}} - ## Save a playlist You can save a playlist and add it to your **Playlists** page, where you can start it. Be sure that all the dashboards you want to appear in your playlist are added when creating or editing the playlist before saving it. diff --git a/docs/sources/dashboards/create-reports/index.md b/docs/sources/dashboards/create-reports/index.md index 03e1e7d98ae..46ab5d93d3e 100644 --- a/docs/sources/dashboards/create-reports/index.md +++ b/docs/sources/dashboards/create-reports/index.md @@ -70,12 +70,6 @@ Only organization administrators can create reports by default. You can customiz - **Send test email:** To verify that the configuration works as expected. You can choose to send this email to the recipients configured for the report, or to a different set of email addresses only used for testing. 1. Preview and save the report. -{{< figure src="/static/img/docs/enterprise/reports/select-dashboard.png" max-width="500px" class="docs-image--no-shadow" >}} -{{< figure src="/static/img/docs/enterprise/reports/format-report.png" max-width="500px" class="docs-image--no-shadow" >}} -{{< figure src="/static/img/docs/enterprise/reports/schedule.png" max-width="500px" class="docs-image--no-shadow" >}} -{{< figure src="/static/img/docs/enterprise/reports/share.png" max-width="500px" class="docs-image--no-shadow" >}} -{{< figure src="/static/img/docs/enterprise/reports/confirm.png" max-width="500px" class="docs-image--no-shadow" >}} - ### Choose template variables > **Note:** Available in [Grafana Enterprise]({{< relref "../../introduction/grafana-enterprise/" >}}) version 7.5 and later behind the `reportVariables` feature flag, Grafana Enterprise version 8.0 and later without a feature flag, and [Grafana Cloud Pro and Advanced](/docs/grafana-cloud/). @@ -142,8 +136,6 @@ A background job runs every 10 minutes and removes temporary CSV files. You can Scheduled reports can be sent once, or repeated on an hourly, daily, weekly, or monthly basis, or sent at custom intervals. You can also disable scheduling by selecting **Never**, for example to send the report via the API. -{{< figure src="/static/img/docs/enterprise/reports_scheduler_8.1.png" max-width="500px" class="docs-image--no-shadow" >}} - **Send now or schedule for later** - **Send now** sends the report immediately after you save it. To stop sending the report at some point in the future, add an end date. If you leave the end date empty, the report is sent out indefinitely. @@ -169,8 +161,6 @@ When you schedule a report with a monthly frequency, and set the start date betw The last saved version of the report will be sent to selected emails. You can use this to verify emails are working and to make sure the report is generated and displayed as you expect. -{{< figure src="/static/img/docs/enterprise/reports_send_test_mail.png" max-width="500px" class="docs-image--no-shadow" >}} - ### Pause a report > **Note:** Available in [Grafana Enterprise]({{< relref "../../introduction/grafana-enterprise/" >}}) version 8.0 and later, and [Grafana Cloud Pro and Advanced](/docs/grafana-cloud/). @@ -257,8 +247,6 @@ Email branding: - **Footer link text:** Text of the link in the report email footer. Defaults to `Grafana`. - **Footer link URL:** Link of the report email footer. -{{< figure src="/static/img/docs/enterprise/reports_settings.png" max-width="500px" class="docs-image--no-shadow" >}} - ## Troubleshoot reporting To troubleshoot and get more log information, enable debug logging in the configuration file. Refer to [Configuration]({{< relref "../../setup-grafana/configure-grafana/#filters" >}}) for more information. diff --git a/docs/sources/dashboards/manage-dashboards/index.md b/docs/sources/dashboards/manage-dashboards/index.md index 5a2e19566d8..b30d114d9c7 100644 --- a/docs/sources/dashboards/manage-dashboards/index.md +++ b/docs/sources/dashboards/manage-dashboards/index.md @@ -58,8 +58,6 @@ When you save a dashboard, you can either select a folder for the dashboard to b ## Manage dashboards -{{< figure src="/static/img/docs/v50/manage_dashboard_menu.png" max-width="300px" class="docs-image--right" >}} - On the **Manage dashboards and folders** page, you can: - create a folder @@ -99,8 +97,6 @@ The dashboard export action creates a Grafana JSON file that contains everything Grafana downloads a JSON file to your local machine. -{{< figure src="/static/img/docs/export/export-modal.png" max-width="800px" >}} - #### Make a dashboard portable If you want to export a dashboard for others to use, you can add template variables for things like a metric prefix (use a constant variable) and server name. @@ -110,9 +106,6 @@ A template variable of the type `Constant` will automatically be hidden in the d ### Import a dashboard 1. Click **Dashboards > Import** in the side menu. - - {{< figure src="/static/img/docs/v70/import_step1.png" max-width="700px" >}} - 1. Perform one of the following steps: - Upload a dashboard JSON file diff --git a/docs/sources/dashboards/share-dashboards-panels/index.md b/docs/sources/dashboards/share-dashboards-panels/index.md index 764e7339136..48516233ab7 100644 --- a/docs/sources/dashboards/share-dashboards-panels/index.md +++ b/docs/sources/dashboards/share-dashboards-panels/index.md @@ -59,8 +59,6 @@ You can share a dashboard as a direct link or as a snapshot. You can also export The share dialog opens and shows the **Link** tab. - ![Dashboard share direct link](/static/img/docs/sharing/share-dashboard-direct-link-7-3.png) - ### Share a direct link The **Link** tab shows the current time range, template variables, and the default theme. You can also share a shortened URL. @@ -77,8 +75,6 @@ A dashboard snapshot shares an interactive dashboard publicly. Grafana strips se You can publish snapshots to your local instance or to [snapshots.raintank.io](http://snapshots.raintank.io). The latter is a free service provided by Grafana Labs that enables you to publish dashboard snapshots to an external Grafana instance. Anyone with the link can view it. You can set an expiration time if you want the snapshot removed after a certain time period. -![Dashboard share snapshot](/static/img/docs/sharing/share-dashboard-snapshot-7-3.png) - 1. Click **Local Snapshot** or **Publish to snapshots.raintank.io**. Grafana generates a link of the snapshot. @@ -91,8 +87,6 @@ If you created a snapshot by mistake, click **Delete snapshot** to remove the sn Grafana dashboards can easily be exported and imported. For more information, refer to [Export and import dashboards]({{< relref "./manage-dashboards/#export-and-import-dashboards" >}}). -![Export](/static/img/docs/sharing/share-dashboard-export-7-3.png) - ## Export dashboard as PDF You can generate and save PDF files of any dashboard. @@ -114,8 +108,6 @@ You can share a panel as a direct link, as a snapshot, or as an embedded link. Y The share dialog opens and shows the **Link** tab. - ![Panel share direct link](/static/img/docs/sharing/share-panel-direct-link-8-0.png) - ### Use direct link The **Link** tab shows the current time range, template variables, and the default theme. You can optionally enable a shortened URL to share. @@ -149,8 +141,6 @@ A panel snapshot shares an interactive panel publicly. Grafana strips sensitive You can publish snapshots to your local instance or to [snapshots.raintank.io](http://snapshots.raintank.io). The latter is a free service provided by [Grafana Labs](https://grafana.com), that enables you to publish dashboard snapshots to an external Grafana instance. You can optionally set an expiration time if you want the snapshot to be removed after a certain time period. -![Panel share snapshot](/static/img/docs/sharing/share-panel-snapshot-8-0.png) - 1. In the **Share Panel** dialog, click **Snapshot** to open the tab. 1. Click **Local Snapshot** or **Publish to snapshots.raintank.io**. @@ -166,8 +156,6 @@ You can embed a panel using an iframe on another web site. A viewer must be sign **> Note:** As of Grafana 8.0, anonymous access permission is no longer available for Grafana Cloud. -![Panel share embed](/static/img/docs/sharing/share-panel-embedded-link-8-0.png) - Here is an example of the HTML code: ```html @@ -179,16 +167,13 @@ Here is an example of the HTML code: > ``` -The result is an interactive Grafana graph embedded in an iframe: - - +The result is an interactive Grafana graph embedded in an iframe. ### Library panel To create a library panel from the **Share Panel** dialog: 1. Click **Library panel**. - {{< figure src="/static/img/docs/library-panels/create-lib-panel-8-0.png" class="docs-image--no-shadow" max-width= "900px" caption="Screenshot of the create library panel dialog" >}} 1. In **Library panel name**, enter the name. 1. In **Save in folder**, select the folder in which to save the library panel. By default, the General folder is selected. 1. Click **Create library panel** to save your changes. diff --git a/docs/sources/datasources/alertmanager/_index.md b/docs/sources/datasources/alertmanager/_index.md index 120c4cc3dc7..dfacbf4a668 100644 --- a/docs/sources/datasources/alertmanager/_index.md +++ b/docs/sources/datasources/alertmanager/_index.md @@ -17,17 +17,22 @@ weight: 150 # Alertmanager data source -Grafana includes built-in support for Prometheus Alertmanager. Once you add it as a data source, you can use the [Grafana Alerting UI](/docs/grafana/latest/alerting/) to manage silences, contact points as well as notification policies. A drop-down option in these pages allows you to switch between Grafana and any configured Alertmanager data sources. +Grafana includes built-in support for Alertmanager implementations in Prometheus and Mimir. +Once you add it as a data source, you can use the [Grafana Alerting UI](/docs/grafana/latest/alerting/) to manage silences, contact points, and notification policies. +To switch between Grafana and any configured Alertmanager data sources, you can select your preference from a drop-down option in those databases' data source settings pages. ## Alertmanager implementations -[Prometheus](https://prometheus.io/) and [Grafana Mimir](/docs/mimir/latest/) (default) implementations of Alertmanager are supported. You can specify implementation in the data source settings page. In case of Prometheus contact points and notification policies are read-only in the Grafana Alerting UI, as it does not support updating configuration via HTTP API. +The data source supports [Prometheus](https://prometheus.io/) and [Grafana Mimir](https://grafana.com/docs/mimir/latest/) (default) implementations of Alertmanager. +You can specify the implementation in the data source's Settings page. +When using Prometheus, contact points and notification policies are read-only in the Grafana Alerting UI, because it doesn't support updates to the configuration using HTTP API. -## Provision the data source +## Provision the Alertmanager data source -Configure the Alertmanager data sources by updating Grafana's configuration files. For more information on how it works and the settings available, refer to the [provisioning docs page]({{< relref "../../administration/provisioning#data-sources" >}}). +You can provision Alertmanager data sources by updating Grafana's configuration files. +For more information on provisioning, and common settings available, refer to the [provisioning docs page]({{< relref "../administration/provisioning/#datasources" >}}). -For example, this YAML provisions an Alertmanager data source running on port 9093, with proxy access and basic authentication: +Here is an example for provisioning the Alertmanager data source: ```yaml apiVersion: 1 @@ -38,6 +43,8 @@ datasources: url: http://localhost:9093 access: proxy jsonData: + # Options for implementation include prometheus and mimir + implementation: prometheus # optionally basicAuth: true basicAuthUser: my_user diff --git a/docs/sources/datasources/jaeger/_index.md b/docs/sources/datasources/jaeger/_index.md index c85e02fcb67..fc606fdd115 100644 --- a/docs/sources/datasources/jaeger/_index.md +++ b/docs/sources/datasources/jaeger/_index.md @@ -53,8 +53,6 @@ You can also configure settings specific to the Jaeger data source: The **Trace to logs** section configures the [trace to logs feature]({{< relref "../../explore/trace-integration/" >}}). Select a target data source, limited to Loki and Splunk \[logs\] data sources, and which tags to use in the logs query. -{{< figure src="/static/img/docs/explore/trace-to-log-8-2.png" class="docs-image--no-shadow" caption="Screenshot of the trace to logs settings" >}} - | Name | Description | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Data source** | Sets the target data source. | diff --git a/docs/sources/datasources/mssql/template-variables/index.md b/docs/sources/datasources/mssql/template-variables/index.md index 191aa6a30df..aa0d5219837 100644 --- a/docs/sources/datasources/mssql/template-variables/index.md +++ b/docs/sources/datasources/mssql/template-variables/index.md @@ -88,7 +88,7 @@ ORDER BY atimestamp ### Disabling Quoting for Multi-value Variables -Grafana automatically creates a quoted, comma-separated string for multi-value variables. For example: if `server01` and `server02` are selected then it will be formatted as: `'server01', 'server02'`. Do disable quoting, use the csv formatting option for variables: +Grafana automatically creates a quoted, comma-separated string for multi-value variables. For example: if `server01` and `server02` are selected then it will be formatted as: `'server01', 'server02'`. To disable quoting, use the csv formatting option for variables: `${servers:csv}` diff --git a/docs/sources/datasources/prometheus/_index.md b/docs/sources/datasources/prometheus/_index.md index a275990a658..e6e0394dcc6 100644 --- a/docs/sources/datasources/prometheus/_index.md +++ b/docs/sources/datasources/prometheus/_index.md @@ -43,21 +43,28 @@ For more information on how to query other Prometheus-compatible projects from G Set the data source's basic configuration options carefully: -| Name | Description | -| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Name** | Sets the name you use to refer to the data source in panels and queries. | -| **Default** | Sets whether the data source is pre-selected for new panels. | -| **Url** | Sets the URL of your Prometheus server, such as `http://prometheus.example.org:9090`. | -| **Access** | Only Server access mode is functional. If Server mode is already selected, this option is hidden. Otherwise, change this to Server mode to prevent errors. | -| **Basic Auth** | Enables basic authentication to the Prometheus data source. | -| **User** | Sets the user name for basic authentication. | -| **Password** | Sets the password for basic authentication. | -| **Scrape interval** | Sets the scrape and evaluation interval. We recommend the same value as the typical configured in Prometheus. Defaults to 15s. | -| **Type** | Defines the type of your Prometheus server. Valid values are `Prometheus`, `Cortex`, `Thanos`, `Mimir`. When selected, the Prometheus version field attempts to detect the version automatically using the Prometheus [buildinfo](https://semver.org/) API. Some Prometheus types, such as Cortex, don't support this API, and you must provide their version. | -| **Version** | Defines the version of your Prometheus server. This field is visible only after the **Type** field is defined. | -| **HTTP method** | Sets the HTTP method used to query your data source. We recommend POST, which is pre-selected, because it allows for larger queries. Use GET if the Prometheus version is older than 2.1, or if POST requests are restricted in your network. | -| **Disable metrics lookup** | Disables the metrics chooser and metric/label support in the query field's autocompletion. This can prevent performance issues with larger Prometheus instances. | -| **Custom query parameters** | Adds custom parameters to the Prometheus query URL, such as `timeout`, `partial_response`, `dedup`, or `max_source_resolution`. Concatenate multiple parameters with '&'. | +| Name | Description | +| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Name` | The data source name. This is how you refer to the data source in panels and queries. | +| `Default` | Default data source that is pre-selected for new panels. | +| `URL` | The URL of your Prometheus server, for example, `http://prometheus.example.org:9090`. | +| `Access` | Only Server access mode is functional. If Server mode is already selected this option is hidden. Otherwise change to Server mode to prevent errors. | +| `Basic Auth` | Enable basic authentication to the Prometheus data source. | +| `User` | User name for basic authentication. | +| `Password` | Password for basic authentication. | +| `Manage alerts via Alerting UI` | Toggle whether to enable Alertmanager integration for this data source. | +| `Scrape interval` | Set this to the typical scrape and evaluation interval configured in Prometheus. Defaults to 15s. | +| `HTTP method` | Use either POST or GET HTTP method to query your data source. POST is the recommended and pre-selected method as it allows bigger queries. Change this to GET if you have a Prometheus version older than 2.1 or if POST requests are restricted in your network. | +| `Type` | The type of your Prometheus server; `Prometheus`, `Cortex`, `Thanos`, `Mimir`. When selected, the **Version** field attempts to populate automatically using the Prometheus [buildinfo](https://semver.org/) API. Some Prometheus types, such as Cortex, don't support this API and must be manually populated. | +| `Version` | The version of your Prometheus server, note that this field is not visible until the Prometheus type is selected. | +| `Disable metrics lookup` | Checking this option will disable the metrics chooser and metric/label support in the query field's autocomplete. This helps if you have performance issues with bigger Prometheus instances. | +| `Custom query parameters` | Add custom parameters to the Prometheus query URL. For example `timeout`, `partial_response`, `dedup`, or `max_source_resolution`. Multiple parameters should be concatenated together with an '&'. | +| **Exemplars configuration** | | +| `Internal link` | Enable this option is you have an internal link. When you enable this option, you will see a data source selector. Select the backend tracing data store for your exemplar data. | +| `Data source` | You will see this option only if you enable `Internal link` option. Select the backend tracing data store for your exemplar data. | +| `URL` | You will see this option only if the `Internal link` option is disabled. Enter the full URL of the external link. You can interpolate the value from the field with `${__value.raw }` macro. | +| `URL Label` | (Optional) add a custom display label to override the value of the `Label name` field. | +| `Label name` | Add a name for the exemplar traceID property. | **Exemplars configuration:** @@ -87,6 +94,9 @@ datasources: url: http://localhost:9090 jsonData: httpMethod: POST + manageAlerts: true + prometheusType: Prometheus + prometheusVersion: 2.37.0 exemplarTraceIdDestinations: # Field with internal link pointing to data source in Grafana. # datasourceUid value can be anything, but it should be unique across all defined data source uids. diff --git a/docs/sources/datasources/zipkin/_index.md b/docs/sources/datasources/zipkin/_index.md index 941d9390841..202e502ec94 100644 --- a/docs/sources/datasources/zipkin/_index.md +++ b/docs/sources/datasources/zipkin/_index.md @@ -50,10 +50,6 @@ Set the data source's basic configuration options carefully: The **Trace to logs** section configures the [trace to logs feature]({{< relref "../../explore/trace-integration/" >}}). Select a target data source, limited to Loki and Splunk \[logs\] data sources, and which tags to use in the logs query. - - | Name | Description | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Data source** | Sets the target data source. | diff --git a/docs/sources/developers/http_api/auth.md b/docs/sources/developers/http_api/auth.md index 7576437375a..2c020e3b98a 100644 --- a/docs/sources/developers/http_api/auth.md +++ b/docs/sources/developers/http_api/auth.md @@ -57,8 +57,6 @@ curl http://admin:admin@localhost:3000/api/org Open the sidemenu and click the organization dropdown and select the `API Keys` option. -![](/static/img/docs/v2/orgdropdown_api_keys.png) - You use the token in all requests in the `Authorization` header, like this: **Example**: diff --git a/docs/sources/developers/plugins/legacy/apps.md b/docs/sources/developers/plugins/legacy/apps.md index 9a43032325b..e5868886cdf 100644 --- a/docs/sources/developers/plugins/legacy/apps.md +++ b/docs/sources/developers/plugins/legacy/apps.md @@ -15,8 +15,6 @@ App plugins are Grafana plugins that can bundle data source and panel plugins wi Data source and panel plugins will show up like normal plugins. The app pages will be available in the main menu. -{{< figure class="float-right" src="/static/img/docs/v3/app-in-main-menu.png" caption="App in Main Menu" >}} - ## Enabling app plugins After installing an app, it has to be enabled before it shows up as a data source or panel. You can do that on the app page in the configuration tab. diff --git a/docs/sources/explore/_index.md b/docs/sources/explore/_index.md index 40e6f1303da..90ea35ad9e5 100644 --- a/docs/sources/explore/_index.md +++ b/docs/sources/explore/_index.md @@ -33,13 +33,11 @@ To access Explore: 1. Click on the Explore icon on the menu bar. - {{< figure src="/static/img/docs/explore/access-explore-7-4.png" max-width= "650px" caption="Screenshot of the new Explore Icon" >}} - An empty Explore tab opens. Alternately to start with an existing query in a panel, choose the Explore option from the Panel menu. This opens an Explore tab with the query from the panel and allows you to tweak or iterate in the query outside of your dashboard. -{{< figure src="/static/img/docs/explore/panel_dropdown-7-4.png" class="docs-image--no-shadow" max-width= "650px" caption="Screenshot of the new Explore option in the panel menu" >}} + {{< figure src="/static/img/docs/explore/panel_dropdown-7-4.png" class="docs-image--no-shadow" max-width= "650px" caption="Screenshot of the new Explore option in the panel menu" >}} 1. Choose your data source from the dropdown in the top left. [Prometheus](https://grafana.com/oss/prometheus/) has a custom Explore implementation, the other data sources use their standard query editor. 1. In the query field, write your query to explore your data. There are three buttons beside the query field, a clear button (X), an add query button (+) and the remove query button (-). Just like the normal query editor, you can add and remove multiple queries. diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index bf938890362..a258f2ab5b7 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -292,6 +292,17 @@ Sets the maximum time using a duration format (5s/5m/5ms) before timing out read
+## [server.custom_response_headers] + +This setting enables you to specify additional headers that the server adds to HTTP(S) responses. + +``` +exampleHeader1 = exampleValue1 +exampleHeader2 = exampleValue2 +``` + +
+ ## [database] Grafana needs a database to store users and dashboards (and other @@ -760,6 +771,10 @@ Text used as placeholder text on login page for password input. Set the default UI theme: `dark` or `light`. Default is `dark`. +### default_language + +This setting configures the default UI language, which must be a supported IETF language tag, such as `en-US`. + ### home_page Path to a custom home page. Users are only redirected to this if the default home dashboard is used. It should match a frontend route and contain a leading slash. diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md new file mode 100644 index 00000000000..14a668c0378 --- /dev/null +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -0,0 +1,111 @@ +--- +aliases: + - /docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles/ +description: Learn about toggles for experimental and beta features, which you can enable or disable. +title: Configure feature toggles +weight: 150 +--- + + + + +# Configure feature toggles + +You use feature toggles, also known as feature flags, to turn experimental or beta features on and off in Grafana. Although we do not recommend using these features in production, you can turn on feature toggles to try out new functionality in development or test environments. + +This page contains a list of available feature toggles. To learn how to turn on feature toggles, refer to our [Configure Grafana documentation]({{< relref "../_index.md/#feature_toggles" >}}). Feature toggles are also available to Grafana Cloud Advanced customers. If you use Grafana Cloud Advanced, you can open a support ticket and specify the feature toggles and stack for which you want them enabled. + +## Stable feature toggles + +Some stable features are enabled by default. You can disable a stable feature by setting the feature flag to "false" in the configuration. + +| Feature toggle name | Description | Enabled by default | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------ | +| `promQueryBuilder` | Show Prometheus query builder | Yes | +| `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | | +| `database_metrics` | Add Prometheus metrics for database tables | | +| `lokiMonacoEditor` | Access to Monaco query editor for Loki | Yes | +| `featureHighlights` | Highlight Grafana Enterprise features | | +| `commandPalette` | Enable command palette | Yes | +| `cloudWatchDynamicLabels` | Use dynamic labels instead of alias patterns in CloudWatch datasource | Yes | +| `prometheusBufferedClient` | Enable buffered (old) client for Prometheus datasource as default instead of streaming JSON parser client (new) | | +| `internationalization` | Enables internationalization | Yes | +| `accessTokenExpirationCheck` | Enable OAuth access_token expiration check and token refresh using the refresh_token | | + +## Beta feature toggles + +| Feature toggle name | Description | +| --------------------------------- | ------------------------------------------------------------------------------- | +| `trimDefaults` | Use cue schema to remove values that will be applied automatically | +| `panelTitleSearch` | Search for dashboards using panel title | +| `prometheusAzureOverrideAudience` | Experimental. Allow override default AAD audience for Azure Prometheus endpoint | +| `swaggerUi` | Serves swagger UI | +| `migrationLocking` | Lock database during migrations | +| `newDBLibrary` | Use jmoiron/sqlx rather than xorm for a few backend services | +| `validateDashboardsOnSave` | Validate dashboard JSON POSTed to api/dashboards/db | +| `autoMigrateGraphPanels` | Replace the angular graph panel with timeseries | +| `datasourceLogger` | Logs all datasource requests | + +## Alpha feature toggles + +These features are early in their development lifecycle and so are not yet supported in Grafana Cloud. +Alpha features might be changed or removed without prior notice. + +| Feature toggle name | Description | +| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `alertingBigTransactions` | Use big transactions for alerting database writes | +| `dashboardPreviews` | Create and show thumbnails for dashboard search results | +| `live-config` | Save Grafana Live configuration in SQL tables | +| `live-pipeline` | Enable a generic live processing pipeline | +| `live-service-web-worker` | This will use a webworker thread to processes events rather than the main thread | +| `queryOverLive` | Use Grafana Live WebSocket to execute backend queries | +| `tempoApmTable` | Show APM table | +| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | +| `publicDashboards` | Enables public access to dashboards | +| `lokiLive` | Support WebSocket streaming for loki (early prototype) | +| `lokiDataframeApi` | Use experimental loki api for WebSocket streaming (early prototype) | +| `dashboardComments` | Enable dashboard-wide comments | +| `annotationComments` | Enable annotation comments | +| `storage` | Configurable storage for dashboards, datasources, and resources | +| `exploreMixedDatasource` | Enable mixed datasource in Explore | +| `tracing` | Adds trace ID to error notifications | +| `correlations` | Correlations page | +| `datasourceQueryMultiStatus` | Introduce HTTP 207 Multi Status for api/ds/query | +| `traceToMetrics` | Enable trace to metrics links | +| `prometheusWideSeries` | Enable wide series responses in the Prometheus datasource | +| `canvasPanelNesting` | Allow elements nesting | +| `scenes` | Experimental framework to build interactive dashboards | +| `disableSecretsCompatibility` | Disable duplicated secret storage in legacy tables | +| `logRequestsInstrumentedAsUnknown` | Logs the path for requests that are instrumented as unknown | +| `dataConnectionsConsole` | Enables a new top-level page called Connections. This page is an experiment that provides a better experience when you install and configure data sources and other plugins. | +| `topnav` | New top nav and page layouts | +| `traceqlEditor` | Show the TraceQL editor in the explore page | +| `flameGraph` | Show the flame graph | +| `cloudWatchCrossAccountQuerying` | Use cross-account querying in CloudWatch datasource | +| `redshiftAsyncQueryDataSupport` | Enable async query data support for Redshift | +| `athenaAsyncQueryDataSupport` | Enable async query data support for Athena | +| `increaseInMemDatabaseQueryCache` | Enable more in memory caching for database queries | +| `newPanelChromeUI` | Show updated look and feel of grafana-ui PanelChrome: panel header, icons, and menu | +| `showDashboardValidationWarnings` | Show warnings when dashboards do not validate against the schema | +| `mysqlAnsiQuotes` | Use double quotes to escape keyword in a MySQL query | +| `elasticsearchBackendMigration` | Use Elasticsearch as backend data source | +| `secureSocksDatasourceProxy` | Enable secure socks tunneling for supported core datasources | +| `authnService` | Use new auth service to perform authentication | +| `sessionRemoteCache` | Enable using remote cache for user sessions | + +## Development feature toggles + +The following toggles require explicitly setting Grafana's [app mode]({{< relref "../_index.md/#app_mode" >}}) to 'development' before you can enable this feature toggle. These features tend to be experimental. + +| Feature toggle name | Description | +| -------------------------------------- | --------------------------------------------------------- | +| `dashboardPreviewsAdmin` | Manage the dashboard previews crawler process from the UI | +| `showFeatureFlagsInUI` | Show feature flags in the settings UI | +| `dashboardsFromStorage` | Load dashboards from the generic storage interface | +| `export` | Export grafana instance (to git, etc) | +| `azureMonitorResourcePickerForMetrics` | New UI for Azure Monitor Metrics Query | +| `grpcServer` | Run GRPC server | +| `entityStore` | SQL-based entity store (requires storage flag also) | +| `queryLibrary` | Reusable query library | +| `accessControlOnCall` | Access control primitives for OnCall | +| `nestedFolders` | Enable folder nesting | diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md index e279203eee6..4452f82e0e4 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md @@ -278,6 +278,40 @@ By default, new Grafana users using SAML authentication will have an account cre To use SAML Team sync, set [`assertion_attribute_groups`]({{< relref "../../../configure-grafana/enterprise-configuration/#assertion-attribute-groups" >}}) to the attribute name where you store user groups. Then Grafana will use attribute values extracted from SAML assertion to add user into the groups with the same name configured on the External group sync tab. +> **Note:** Teamsync allows you sync users from SAML to Grafana teams. It does not automatically create teams in Grafana. You need to create teams in Grafana before you can use this feature. + +Given the following partial SAML assertion: + +```xml + + admins_group + + division_1 + + +``` + +The configuration would look like this: + +```ini +[auth.saml] +# ... +assertion_attribute_groups = groups +``` + +The following `External Group ID`s would be valid for input in the desired team's _External group sync_ tab: + +- `admins_group` +- `division_1` + [Learn more about Team Sync]({{< relref "../../configure-team-sync/" >}}) ### Configure role sync diff --git a/docs/sources/setup-grafana/installation/docker/index.md b/docs/sources/setup-grafana/installation/docker/index.md index b8ca45f0975..d685ea47d66 100644 --- a/docs/sources/setup-grafana/installation/docker/index.md +++ b/docs/sources/setup-grafana/installation/docker/index.md @@ -113,7 +113,20 @@ docker run -d \ You can build your own customized image that includes plugins. This saves time if you are creating multiple images and you want them all to have the same plugins installed on build. -In the [Grafana GitHub repository](https://github.com/grafana/grafana) there is a folder called `packaging/docker/custom/`, which includes two Dockerfiles, `Dockerfile` and `ubuntu.Dockerfile`, that can be used to build a custom Grafana image. It accepts `GRAFANA_VERSION`, `GF_INSTALL_PLUGINS`, and `GF_INSTALL_IMAGE_RENDERER_PLUGIN` as build arguments. +In the [Grafana GitHub repository](https://github.com/grafana/grafana) there is a folder called `packaging/docker/custom/`, which includes a Dockerfile that can be used to build a custom Grafana image. It accepts `GRAFANA_VERSION`, `GF_INSTALL_PLUGINS`, and `GF_INSTALL_IMAGE_RENDERER_PLUGIN` as build arguments. + +The `GRAFANA_VERSION` build argument must be a valid `grafana/grafana` docker image tag. By default, this builds an Alpine-based image. To build an Ubuntu-based image, append `-ubuntu` to the `GRAFANA_VERSION` build argument (available in Grafana v6.5 and later). + +The following example shows you how to build and run a custom Grafana Docker image based on the latest official Ubuntu-based Grafana Docker image: + +```bash +cd packaging/docker/custom +docker build \ + --build-arg "GRAFANA_VERSION=latest-ubuntu" \ + -t grafana-custom . + +docker run -d -p 3000:3000 --name=grafana grafana-custom +``` ### Build with pre-installed plugins @@ -126,7 +139,7 @@ cd packaging/docker/custom docker build \ --build-arg "GRAFANA_VERSION=latest" \ --build-arg "GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource" \ - -t grafana-custom -f Dockerfile . + -t grafana-custom . docker run -d -p 3000:3000 --name=grafana grafana-custom ``` @@ -140,13 +153,11 @@ cd packaging/docker/custom docker build \ --build-arg "GRAFANA_VERSION=latest" \ --build-arg "GF_INSTALL_PLUGINS=http://plugin-domain.com/my-custom-plugin.zip;custom-plugin,grafana-clock-panel" \ - -t grafana-custom -f Dockerfile . + -t grafana-custom . docker run -d -p 3000:3000 --name=grafana grafana-custom ``` -Replace `Dockerfile` in above example with `ubuntu.Dockerfile` to build a custom Ubuntu based image (Grafana v6.5+). - ### Build with Grafana Image Renderer plugin pre-installed > Only available in Grafana v6.5 and later. This is experimental. @@ -160,13 +171,11 @@ cd packaging/docker/custom docker build \ --build-arg "GRAFANA_VERSION=latest" \ --build-arg "GF_INSTALL_IMAGE_RENDERER_PLUGIN=true" \ - -t grafana-custom -f Dockerfile . + -t grafana-custom . docker run -d -p 3000:3000 --name=grafana grafana-custom ``` -Replace `Dockerfile` in above example with `ubuntu.Dockerfile` to build a custom Ubuntu-based image (Grafana v6.5+). - ## Migrate from previous Docker containers versions This section contains important information if you want to migrate from previous Grafana container versions to a more current one. diff --git a/docs/sources/whatsnew/_index.md b/docs/sources/whatsnew/_index.md index 935ba017a0c..25a34aa1dd8 100644 --- a/docs/sources/whatsnew/_index.md +++ b/docs/sources/whatsnew/_index.md @@ -68,6 +68,7 @@ For a complete list of every change, with links to pull requests and related iss ## Grafana 9 +- [What's new in 9.3]({{< relref "whats-new-in-v9-3/" >}}) - [What's new in 9.2]({{< relref "whats-new-in-v9-2/" >}}) - [What's new in 9.1]({{< relref "whats-new-in-v9-1/" >}}) - [What's new in 9.0]({{< relref "whats-new-in-v9-0/" >}}) diff --git a/docs/sources/whatsnew/whats-new-in-v9-2.md b/docs/sources/whatsnew/whats-new-in-v9-2.md index baaf519f9fb..d90c9b5a246 100644 --- a/docs/sources/whatsnew/whats-new-in-v9-2.md +++ b/docs/sources/whatsnew/whats-new-in-v9-2.md @@ -1,6 +1,4 @@ --- -_build: - list: false aliases: - /docs/grafana/latest/guides/whats-new-in-v9-2/ description: Feature and improvement highlights for Grafana v9.2 diff --git a/docs/sources/whatsnew/whats-new-in-v9-3.md b/docs/sources/whatsnew/whats-new-in-v9-3.md new file mode 100644 index 00000000000..eec28b431e0 --- /dev/null +++ b/docs/sources/whatsnew/whats-new-in-v9-3.md @@ -0,0 +1,250 @@ +--- +aliases: + - /docs/grafana/latest/guides/whats-new-in-v9-3/ +description: Feature and improvement highlights for Grafana v9.3 +keywords: + - grafana + - new + - documentation + - '9.3' + - release notes +title: What's new in Grafana v9.3 +weight: -33 +--- + +# What’s new in Grafana v9.3 + +Welcome to Grafana 9.3! Read on to learn about our navigation overhaul, support for four new languages, new panels and transformations, several often-requested auth improvements, usability improvements to Alerting, and more. For even more detail about all the changes in this release, refer to the [changelog](https://github.com/grafana/grafana/blob/master/CHANGELOG.md). + +## New navigation + +Available in **beta** in all editions of Grafana + +Use Grafana’s redesigned navigation to get full visibility into the health of your systems, by quickly jumping between features as part of your incident response workflow. + +As Grafana has grown from a data visualization tool to an observability solution, we’ve added many new features along the way. This has resulted in pages that are visually inconsistent or hard to find. These updates to navigation give Grafana a new look and feel and make page layouts and navigation patterns more consistent. + +We’ve revamped the navigation menu and grouped related tools together, making it easier to find what you need. Pages in Grafana now leverage new layouts that include breadcrumbs and a sidebar, allowing you to quickly jump between pages. We’ve also introduced a header that appears on all pages in Grafana, making dashboard search accessible from any page. + +To try out Grafana’s new navigation, enable the `topnav` feature toggle. If you are a Cloud Advanced customer, open a ticket with our support team and we will enable it for you. + +**Note:** The Grafana and Grafana Cloud documentation has not yet been updated to reflect changes to the navigation - these changes will roll out when the new navigation becomes generally available. + +{{< figure src="/static/img/docs/navigation/navigation-9-3.png" max-width="750px" caption="New navigation for Grafana" >}} + +## View dashboards in Spanish, French, German, and Simplified Chinese + +Generally available in all editions of Grafana + +We have added four new languages to Grafana: Spanish, French, German, and Simplified Chinese. + +With millions of users across the globe, Grafana has a global footprint. In order to make it accessible to a wider audience, we have taken the first steps in localizing key workflows. You can now set Grafana’s language for the navigation, viewing dashboards, and some settings. This will cover the main activities a Viewer performs within Grafana. + +Read more about configuring the [default language for your organization]({{< relref "../administration/organization-preferences/" >}}) and [updating your profile]({{< relref "../administration/user-management/user-preferences/" >}}) in our documentation. + +{{< figure src="/static/img/docs/internationalization/internationalization-9-3.png" max-width="750px" caption="Grafana available in Spanish, French, German, and Simplified Chinese" >}} + +## Geomap panel + +Generally available in all editions of Grafana + +We have added a new alpha layer type in Geomap called photo layer. This layer enables you to render a photo at each data point. To learn more about the photo layer and the geomap panel, refer to [Photos layer]({{< relref "../panels-visualizations/visualizations/geomap/#photos-layer-alpha" >}}). + +{{< figure src="/static/img/docs/geomap-panel/geomap-photos-9-3-0.png" max-width="750px" caption="Geomap panel photos layer" >}} + +## Canvas panel + +Available in **beta** in all editions of Grafana + +Canvas is a new panel that combines the power of Grafana with the flexibility of custom elements. Canvas visualizations are extensible form-built panels that allow you to explicitly place elements within static and dynamic layouts. This empowers you to design custom visualizations and overlay data in ways that aren’t possible with standard Grafana panels, all within Grafana’s UI. If you’ve used popular UI and web design tools, then designing Canvas panels will feel very familiar. + +In Grafana v9.3, we have added icon value mapping support to the Canvas panel. This enables you to dynamically set which icon to display based on your data. To learn more about the Canvas panel, refer to [Canvas]({{< relref "../panels-visualizations/visualizations/canvas" >}}). + +{{< video-embed src="/static/img/docs/canvas-panel/canvas-icon-value-mapping-support-9-3-0.mp4" max-width="750px" caption="Canvas panel icon value mapping support" >}} + +## Public dashboards improvements + +We've made the following improvements to public dashboards. + +### Manage all of your public dashboards in one place + +Available in **experimental** in Grafana Open Source, Enterprise, and Cloud Advanced + +You can use Public Dashboards to make a given dashboard available to anyone on the internet without needing to sign in. In Grafana v9.3, we have introduced a new screen where you can manage all of your public dashboards. From here, you can view a list of all of the public dashboards in your Grafana instance, navigate to the underlying dashboard, see if it is enabled, link out to the public version of the dashboard, or update the public dashboard's configuration. You can see a public dashboard's configuration if you have view access to the dashboard itself, and you can edit its configuration if you have the Admin or Server Admin role or the "Public Dashboard writer" role if you are using RBAC in Grafana Enterprise or Cloud Advanced. + +To check out this new screen and configure your public dashboards, navigate to **Dashboards > Public Dashboards**. + +### Choose to display annotations in public dashboards + +Available in **experimental** in Grafana Open Source, Enterprise, and Cloud Advanced + +Annotations are now supported in public dashboards, with the exception of query annotations. They are turned off by default, but can be turned on in your public dashboard settings. + +Note that because Public Dashboards is an experimental feature, you need to enable it in Grafana using the `publicDashboards` [feature toggle]({{< relref "../setup-grafana/configure-grafana/#feature_toggles" >}}), or open a support ticket requesting public dashboards if you are a Cloud Advanced customer. + +To learn more about public dashboards, refer to [Public dashboards]({{< relref "../dashboards/dashboard-public/" >}}). + +## New transformation: Partition by values + +Available in **experimental** in all editions of Grafana + +This new transformation can help eliminate the need for multiple queries to the same datasource with different WHERE clauses when graphing multiple series. + +Consider a metrics SQL table with the following data: + +| Time | Region | Value | +| ------------------- | ------ | ----- | +| 2022-10-20 12:00:00 | US | 1520 | +| 2022-10-20 12:00:00 | EU | 2936 | +| 2022-10-20 01:00:00 | US | 1327 | +| 2022-10-20 01:00:00 | EU | 912 | + +Prior to v9.3, if you wanted to plot a red trendline for US and a blue one for EU in the same TimeSeries panel, you would likely have to split this into two queries: + +``` + SELECT Time, Value FROM metrics WHERE Time > ‘2022-10-20’ AND Region=’US’ + SELECT Time, Value FROM metrics WHERE Time > ‘2022-10-20’ AND Region=’EU’ +``` + +This approach also requires you to know ahead of time which regions exist in the metrics table. + +With the partition by values transformer, you can issue a single query and split the results by unique (enum) values from one or more columns (fields) of your choosing. In this case, Region. + +``` + SELECT Time, Region, Value FROM metrics WHERE Time > ‘2022-10-20’ +``` + +| Time | Region | Value | +| ------------------- | ------ | ----- | +| 2022-10-20 12:00:00 | US | 1520 | +| 2022-10-20 01:00:00 | US | 1327 | + +| Time | Region | Value | +| ------------------- | ------ | ----- | +| 2022-10-20 12:00:00 | EU | 2936 | +| 2022-10-20 01:00:00 | EU | 912 | + +## Reporting: Zoom in and out to fit your data better into a PDF + +Generally available in Grafana Enterprise, Cloud Pro, and Cloud Advanced. + +Because dashboards appear on a screen and reports are PDFs, it can be challenging to render data just the way you want to. Sometimes the report doesn't show enough columns in a table, or the titles appear too small. Now you can adjust the scale of your report to zoom in and make each text field and panel larger or zoom out to show more data. + +The zoom feature is located in the **Format Report** section of your reporting configuration. To learn more about reporting, refer to [Create and manage reports]({{< relref "../dashboards/create-reports/">}}). + +{{< figure src="/static/img/docs/enterprise/reports/report-zoom.png" max-width="750px" caption="Report zoom feature with PDF documents at three different zoom levels" >}} + +## Users and access + +We've made the following improvements to users and access. + +### OAuth: token handling improvements + +Generally available in all editions of Grafana + +As part of our efforts to improve the security of Grafana, we are introducing a long-awaited feature that enhances Grafana's OAuth 2.0 compatibility. When a user logs in using an OAuth provider, Grafana verifies on each request that the user's access token has not expired. Grafana uses the refresh token provided (if any exists) when an access token expires to obtain a new access token. + +Because this feature introduces a breaking change, it is behind the `accessTokenExpirationCheck` feature toggle and is disabled by default. Enabling this functionality without configuring refresh tokens for the specific OAuth provider will sign users out after their access token has expired, and they would need to sign in again every time. + +Complete documentation on how to configure obtaining a refresh token can be found on the [authentication configuration page]({{< relref "../setup-grafana/configure-security/configure-authentication/" >}}), in the instructions for your Oauth identity provider. + +### Resolve user conflicts in Grafana's CLI + +In the older versions of Grafana, usernames were case-sensitive. This created conflicts, where a user might sign in using two different methods (like SAML and OAuth) and have two accounts created, like `elastigirl@incredibles.com` and `ElastiGirl@incredibles.com`. Users in this situation might think they have lost their preferences and permissions. If this has occurred in your Grafana instance, you can use a new Grafana CLI command to resolve user identity conflicts between users within Grafana. + +> Note: If you use Grafana Cloud or you run Grafana with MySQL as your database, you will not experience any user identity conflicts and you do not need to use this tool. + +```bash +# lists all the conflicting users +$ grafana-cli user-manager conflicts list + +# creates a conflict patch file to edit +$ grafana-cli user-manager conflicts generate-file + +# reads edited conflict patch file for validation +$ grafana-cli user-manager conflicts validate-file + +# ingests the conflict users file. Can be executed once per file and will change the state of the database. +$ grafana-cli user-manager conflicts ingest-file +``` + +### LDAP: Role mapping improvements + +Generally available in all editions of Grafana + +If you use an LDAP directory to authenticate to Grafana but prefer to assign organizations and roles in the Grafana UI +or via API, you can now skip user organization role synchronization with your LDAP +directory. + +Use the `skip_org_role_sync` [LDAP authentication configuration option]({{< relref +"../setup-grafana/configure-security/configure-authentication/ldap/#disable-org-role-synchronization" >}}) +when configuring LDAP authentication to prevent the synchronization between your LDAP groups and organization roles +and make user roles editable manually. + +### Azure AD OAuth2: New option to always fetch groups from the Graph API + +Generally available in all editions of Grafana + +If you use Azure AD OAuth2 authentication and use `SecurityEnabled` groups that you don't want Azure to embed in the +authentication token, you can configure Grafana to use Microsoft's Graph API instead. + +Use the [`force_use_graph_api` configuration option]({{< relref +"../setup-grafana/configure-security/configure-authentication/azuread/#force-fetching-groups-from-microsoft-graph-api" >}}) +when configuring Azure AD authentication to force Grafana to fetch groups using Graph API. + +### RBAC: List token's permissions + +Generally available in Grafana Enterprise and Cloud Advanced + +We added a new endpoint to help users diagnose permissions-related issues with user and token authorization. +[This endpoint]({{< relref "../developers/http_api/access_control/#list-your-permissions" >}}) allows users to get the +full list of RBAC permissions associated with their token. + +For more details, refer to [Debug the permissions of a service account token]({{< relref +"../administration/service-accounts/#debug-the-permissions-of-a-service-account-token" >}}). + +### RBAC with Terraform: Extended support for provisioning permissions + +Generally available in Grafana Enterprise and Cloud Advanced + +All Grafana users can now use the latest release of [Terraform's Grafana provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs) (version 1.31.1+) to provision [user and team access to service accounts]({{< relref "../administration/service-accounts/#manage-users-and-teams-permissions-for-a-service-account-in-grafana" >}}). + +This allows full management of service accounts through Terraform - from creating a service account and allowing users to access it to assigning roles to the service account and generating service account tokens. + +Grafana Enterprise and Cloud Pro and Advanced users can now provision [access to data sources]({{< relref "../administration/data-source-management/#data-source-permissions" >}}) for Grafana's `Viewer`, `Editor`, and `Admin` basic roles, as well as assign `Edit` permission. + +We have also added [documentation on provisioning RBAC roles and role assignments]({{< relref "../administration/roles-and-permissions/access-control/rbac-terraform-provisioning/" >}}) to guide our Grafana Enterprise and Cloud Pro and Advanced users through this process. + +Finally, we have fixed several access control related bugs to ensure a smoother provisioning experience. + +## Alerting + +All of these new alerting features are generally available in all editions of Grafana. + +### Support for Webex Teams + +You can now use Cisco Webex Teams as a contact point, to send alerts to a Webex Teams space. + +### Edit alert rules created using the provisioning API + +Edit API-provisioned alert rules from the Grafana UI. To make a provisioned alert editable, add the `x-disable-provenance` header to the following requests when creating or editing your alert rules in the API: + +POST /api/v1/provisioning/alert-rules + +PUT /api/v1/provisioning/alert-rules/{UID} + +### Support values in notification templates + +Add alert values to notification templates, so that you can create a single template that prints the annotations, labels, and values for your alerts in a format of your choice. + +### View notification errors + +When an alert fails to fire, see when something is wrong with your contact point(s) and the reason for the error. The Receivers API contains information on the error, including a time stamp, duration of the attempt, and the error. You can also view the errors for each contact point in the UI. + +{{< figure src="/static/img/docs/alerting/alert-view-notification-errors-whats-new-v9.3.png" max-width="750px" caption="Alert notification errors" >}} + +### Redesign of the expressions pipeline + +We've redesigned the expressions pipeline editor to combine the expressions editor and the preview into a single view. + +{{< figure src="/static/img/docs/alerting/alert-expression-pipeline-whats-new-v9.3.png" max-width="750px" caption="Expression pipeline redesign" >}} diff --git a/go.mod b/go.mod index 15c6a52fe6e..1fe70309c60 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/go-sql-driver/mysql v1.6.0 github.com/go-stack/stack v1.8.1 github.com/gobwas/glob v0.2.3 - github.com/gofrs/uuid v4.3.0+incompatible // indirect + github.com/gofrs/uuid v4.3.0+incompatible github.com/gogo/protobuf v1.3.2 github.com/golang/mock v1.6.0 github.com/golang/snappy v0.0.4 @@ -56,11 +56,10 @@ require ( github.com/google/uuid v1.3.0 github.com/google/wire v0.5.0 github.com/gorilla/websocket v1.5.0 - github.com/gosimple/slug v1.12.0 github.com/grafana/cuetsy v0.1.1 github.com/grafana/grafana-aws-sdk v0.11.0 github.com/grafana/grafana-azure-sdk-go v1.3.1 - github.com/grafana/grafana-plugin-sdk-go v0.142.0 + github.com/grafana/grafana-plugin-sdk-go v0.145.0 github.com/grafana/thema v0.0.0-20221113112305-b441ed85a1fd github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/hashicorp/go-hclog v1.0.0 @@ -71,7 +70,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 github.com/json-iterator/go v1.1.12 github.com/jung-kurt/gofpdf v1.16.2 - github.com/lib/pq v1.10.4 + github.com/lib/pq v1.10.7 github.com/linkedin/goavro/v2 v2.10.0 github.com/m3db/prometheus_remote_client_golang v0.4.4 github.com/magefile/mage v1.13.0 @@ -90,7 +89,7 @@ require ( github.com/prometheus/prometheus v1.8.2-0.20211011171444-354d8d2ecfac github.com/robfig/cron/v3 v3.0.1 github.com/russellhaering/goxmldsig v1.1.1 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f github.com/uber/jaeger-client-go v2.29.1+incompatible @@ -106,23 +105,23 @@ require ( go.opentelemetry.io/otel/exporters/jaeger v1.0.0 go.opentelemetry.io/otel/sdk v1.7.0 go.opentelemetry.io/otel/trace v1.7.0 - golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d + golang.org/x/crypto v0.3.0 golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d - golang.org/x/net v0.1.0 // indirect - golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0 + golang.org/x/net v0.2.0 + golang.org/x/oauth2 v0.2.0 golang.org/x/sync v0.1.0 - golang.org/x/time v0.0.0-20220609170525-579cf78fd858 - golang.org/x/tools v0.1.12 + golang.org/x/time v0.2.0 + golang.org/x/tools v0.3.0 gonum.org/v1/gonum v0.11.0 google.golang.org/api v0.84.0 google.golang.org/grpc v1.47.0 google.golang.org/protobuf v1.28.1 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect - gopkg.in/ini.v1 v1.66.2 + gopkg.in/ini.v1 v1.67.0 gopkg.in/ldap.v3 v3.1.0 gopkg.in/mail.v2 v2.3.1 gopkg.in/square/go-jose.v2 v2.5.1 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 xorm.io/builder v0.3.6 xorm.io/core v0.7.3 @@ -204,7 +203,7 @@ require ( github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect github.com/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/olekukonko/tablewriter v0.0.5 github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e // indirect github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -221,16 +220,16 @@ require ( github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect github.com/sirupsen/logrus v1.9.0 // indirect - github.com/stretchr/objx v0.4.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/weaveworks/promrus v1.2.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect go.mongodb.org/mongo-driver v1.10.0 // indirect go.opencensus.io v0.23.0 // indirect - go.uber.org/atomic v1.9.0 + go.uber.org/atomic v1.10.0 go.uber.org/goleak v1.1.12 // indirect - golang.org/x/sys v0.1.0 // indirect + golang.org/x/sys v0.2.0 // indirect golang.org/x/text v0.4.0 golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect google.golang.org/appengine v1.6.7 // indirect @@ -255,8 +254,10 @@ require ( github.com/google/go-github/v45 v45.2.0 github.com/grafana/codejen v0.0.3 github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f + github.com/huandu/xstrings v1.3.1 github.com/jmoiron/sqlx v1.3.5 github.com/matryer/is v1.4.0 + github.com/parca-dev/parca v0.12.1 github.com/urfave/cli v1.22.9 go.etcd.io/etcd/api/v3 v3.5.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.32.0 @@ -284,14 +285,11 @@ require ( github.com/drone/envsubst v1.0.3 // indirect github.com/drone/runner-go v1.12.0 // indirect github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect - github.com/google/go-github/v31 v31.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa // indirect github.com/googleapis/go-type-adapters v1.0.0 // indirect - github.com/gosimple/unidecode v1.0.1 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/memberlist v0.4.0 // indirect - github.com/huandu/xstrings v1.3.1 // indirect github.com/invopop/yaml v0.1.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect @@ -301,13 +299,16 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect - github.com/parca-dev/parca v0.12.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.1.4 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.3.1 // indirect + github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect + github.com/unknwon/com v1.0.1 // indirect + github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 // indirect go.starlark.net v0.0.0-20221020143700-22309ac47eac // indirect + gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect ) require ( @@ -369,25 +370,11 @@ require ( // Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream replace github.com/crewjam/saml => github.com/grafana/saml v0.4.9-0.20220727151557-61cd9c9353fc -replace github.com/apache/thrift => github.com/apache/thrift v0.14.1 - -replace github.com/hashicorp/consul => github.com/hashicorp/consul v1.10.2 - -replace github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.7 - -replace github.com/tidwall/gjson => github.com/tidwall/gjson v1.14.1 - -// Upgraded to fix CVE-2020-26066. This can be removed when go.opentelemetry.io/collector and github.com/influxdata/telegraf are upgraded -// github.com/tidwall/match v1.0.1 should not be used. -replace github.com/tidwall/match => github.com/tidwall/match v1.1.1 - // Thema's thema CLI requires cobra, which eventually works its way down to go-hclog@v1.0.0. // Upgrading affects backend plugins: https://github.com/grafana/grafana/pull/47653#discussion_r850508593 // No harm to Thema because it's only a dependency in its main package. replace github.com/hashicorp/go-hclog => github.com/hashicorp/go-hclog v0.16.1 -replace github.com/microcosm-cc/bluemonday => github.com/microcosm-cc/bluemonday v1.0.18 - // This is a patched v0.8.2 intended to fix session.Find (and others) silently ignoring SQLITE_BUSY errors. This could // happen, for example, during a read when the sqlite db is under heavy write load. // This patch cherry picks compatible fixes from upstream xorm PR#1998 and can be reverted on upgrade to xorm v1.2.0+. diff --git a/go.sum b/go.sum index cb755504a11..48560c53758 100644 --- a/go.sum +++ b/go.sum @@ -144,6 +144,7 @@ github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9B github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v11.2.8+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= @@ -325,7 +326,9 @@ github.com/apache/arrow/go/arrow v0.0.0-20200923215132-ac86123a3f01/go.mod h1:QN github.com/apache/arrow/go/arrow v0.0.0-20210223225224-5bea62493d91/go.mod h1:c9sxoIT3YgLxH4UhLOCKaBlEojuMhVYpk4Ntv3opUTQ= github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 h1:q4dksr6ICHXqG5hm0ZW5IHyeEJXoIJSOZeBLmWPNeIQ= github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= -github.com/apache/thrift v0.14.1/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.14.2/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -866,6 +869,9 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= @@ -1053,10 +1059,13 @@ github.com/go-openapi/validate v0.22.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUri github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-redis/redis/v8 v8.0.0-beta.10.0.20200905143926-df7fe4e2ce72/go.mod h1:CJP1ZIHwhosNYwIdaHPZK9vHsM3+roNBaZ7U9Of1DXc= @@ -1241,8 +1250,6 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-github/v31 v31.0.0 h1:JJUxlP9lFK+ziXKimTCprajMApV1ecWD4NB6CCb0plo= -github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM= github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -1335,6 +1342,8 @@ github.com/gophercloud/gophercloud v0.18.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075 github.com/gophercloud/gophercloud v0.20.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= github.com/gophercloud/gophercloud v0.24.0 h1:jDsIMGJ1KZpAjYfQgGI2coNQj5Q83oPzuiGJRFWgMzw= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de h1:F7WD09S8QB4LrkEpka0dFPLSotH11HRpCsLIbIcJ7sU= github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= @@ -1354,10 +1363,6 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gosimple/slug v1.12.0 h1:xzuhj7G7cGtd34NXnW/yF0l+AGNfWqwgh/IXgFy7dnc= -github.com/gosimple/slug v1.12.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= -github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= -github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw= github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= github.com/grafana/cuetsy v0.1.1 h1:+1jaDDYCpvKlcOWJgBRbkc5+VZIClCEn5mbI+4PLZqM= @@ -1373,8 +1378,8 @@ github.com/grafana/grafana-azure-sdk-go v1.3.1/go.mod h1:rgrnK9m6CgKlgx4rH3FFP/6 github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58 h1:2ud7NNM7LrGPO4x0NFR8qLq68CqI4SmB7I2yRN2w9oE= github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE= github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= -github.com/grafana/grafana-plugin-sdk-go v0.142.0 h1:fDgA0EmWWy5+/7nX7fdHBfADR6pWuR1TZA5QL36VX7U= -github.com/grafana/grafana-plugin-sdk-go v0.142.0/go.mod h1:srvRQ+de4C5h7FqA5lSFUkFCs5pJolWT+PGV2AyBOFk= +github.com/grafana/grafana-plugin-sdk-go v0.145.0 h1:ZlRxxV3C6RA+wNWeGr+rLVD70pgsZwiLI9etzE0zu+Q= +github.com/grafana/grafana-plugin-sdk-go v0.145.0/go.mod h1:dFof/7GenWBFTmrfcPRCpLau7tgIED0ykzupWAlB0o0= github.com/grafana/prometheus-alertmanager v0.24.1-0.20221012142027-823cd9150293 h1:dJIdfHqu+XjKz+w9zXLqXKPdp6Jjx/UPSOwdeSfWdeQ= github.com/grafana/prometheus-alertmanager v0.24.1-0.20221012142027-823cd9150293/go.mod h1:HVHqK+BVPa/tmL8EMhLCCrPt2a1GdJpEyxr5hgur2UI= github.com/grafana/saml v0.4.9-0.20220727151557-61cd9c9353fc h1:1PY8n+rXuBNr3r1JQhoytWDCpc+pq+BibxV0SZv+Cr4= @@ -1662,6 +1667,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= @@ -1733,6 +1740,7 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/leanovate/gopter v0.2.4/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 h1:X/79QL0b4YJVO5+OsPH9rF2u428CIrGL/jLmPsoOQQ4= github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leoluk/perflib_exporter v0.1.0/go.mod h1:rpV0lYj7lemdTm31t7zpCqYqPnw7xs86f+BaaNBVYFM= @@ -1749,8 +1757,9 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.0/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= @@ -1928,6 +1937,7 @@ github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3P github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mozillazg/go-cos v0.13.0/go.mod h1:Zp6DvvXn0RUOXGJ2chmWt2bLEqRAnJnS3DnAZsJsoaE= @@ -2303,8 +2313,12 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/snowflakedb/gosnowflake v1.3.4/go.mod h1:NsRq2QeiMUuoNUJhp5Q6xGC4uBrsS9g6LwZVEkTWgsE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -2353,8 +2367,9 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -2366,8 +2381,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -2386,12 +2402,12 @@ github.com/thanos-io/thanos v0.13.1-0.20210224074000-659446cab117/go.mod h1:kdqF github.com/thanos-io/thanos v0.13.1-0.20210226164558-03dace0a1aa1/go.mod h1:gMCy4oCteKTT7VuXVvXLTPGzzjovX1VPE5p+HgL1hyU= github.com/thanos-io/thanos v0.13.1-0.20210401085038-d7dff0c84d17/go.mod h1:zU8KqE+6A+HksK4wiep8e/3UvCZLm+Wrw9AqZGaAm9k= github.com/thanos-io/thanos v0.22.0/go.mod h1:SZDWz3phcUcBr4MYFoPFRvl+Z9Nbi45HlwQlwSZSt+Q= -github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/tinylru v1.0.2/go.mod h1:HDVL7TsWeezQ4g44Um84TOVBMFcq7Xa9giqNc805KJ8= github.com/tidwall/wal v0.1.4/go.mod h1:ww7Pd44/KnyETODJPUPKrzLlYjI72GZWlucNKt7pOt0= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -2427,6 +2443,12 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= +github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= +github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= +github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= +github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 h1:4EYQaWAatQokdji3zqZloVIW/Ke1RQjYw2zHULyrHJg= +github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -2625,8 +2647,9 @@ go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.2.0/go.mod h1:YfO3fm683kQpzETxlTGZhGIVmXAhaw3gxeBADbpZtnU= go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= @@ -2711,8 +2734,9 @@ golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2866,8 +2890,9 @@ golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2895,8 +2920,8 @@ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0 h1:VnGaRqoLmqZH/3TMLJwYCEWkR4j1nuIU1U9TvbqsDUw= -golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU= +golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2968,6 +2993,7 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -3089,15 +3115,16 @@ golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -3124,8 +3151,8 @@ golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE= +golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -3229,8 +3256,9 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -3308,7 +3336,6 @@ google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6r google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= google.golang.org/api v0.84.0 h1:NMB9J4cCxs9xEm+1Z9QiO3eFvn7EnQj3Eo3hN6ugVlg= google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -3361,6 +3388,8 @@ gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBl gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -3369,8 +3398,8 @@ gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= -gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ldap.v3 v3.1.0 h1:DIDWEjI7vQWREh0S8X5/NFPCZ3MCVd55LmXKPW4XLGE= gopkg.in/ldap.v3 v3.1.0/go.mod h1:dQjCc0R0kfyFjIlWNMH1DORwUASZyDxo2Ry1B51dXaQ= gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= @@ -3492,7 +3521,6 @@ k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= diff --git a/kinds/gen.go b/kinds/gen.go index f9981203e17..8a16ebc3858 100644 --- a/kinds/gen.go +++ b/kinds/gen.go @@ -29,8 +29,8 @@ func main() { // Core kinds composite code generator. Produces all generated code in // grafana/grafana that derives from raw and structured core kinds. - coreKindsGen := codejen.JennyListWithNamer[*codegen.DeclForGen](func(decl *codegen.DeclForGen) string { - return decl.Meta.Common().MachineName + coreKindsGen := codejen.JennyListWithNamer(func(decl *codegen.DeclForGen) string { + return decl.Properties.Common().MachineName }) // All the jennies that comprise the core kinds generator pipeline @@ -63,12 +63,12 @@ func main() { continue } rel := filepath.Join(kindsys.CoreStructuredDeclParentPath, ent.Name()) - decl, err := kindsys.LoadCoreKind[kindsys.CoreStructuredMeta](rel, rt.Context(), nil) + decl, err := kindsys.LoadCoreKind[kindsys.CoreStructuredProperties](rel, rt.Context(), nil) if err != nil { die(fmt.Errorf("%s is not a valid kind: %s", rel, errors.Details(err, nil))) } - if decl.Meta.MachineName != ent.Name() { - die(fmt.Errorf("%s: kind's machine name (%s) must equal parent dir name (%s)", rel, decl.Meta.Name, ent.Name())) + if decl.Properties.MachineName != ent.Name() { + die(fmt.Errorf("%s: kind's machine name (%s) must equal parent dir name (%s)", rel, decl.Properties.Name, ent.Name())) } all = append(all, elsedie(codegen.ForGen(rt, decl.Some()))(rel)) @@ -82,19 +82,19 @@ func main() { continue } rel := filepath.Join(kindsys.RawDeclParentPath, ent.Name()) - decl, err := kindsys.LoadCoreKind[kindsys.RawMeta](rel, rt.Context(), nil) + decl, err := kindsys.LoadCoreKind[kindsys.RawProperties](rel, rt.Context(), nil) if err != nil { die(fmt.Errorf("%s is not a valid kind: %s", rel, errors.Details(err, nil))) } - if decl.Meta.MachineName != ent.Name() { - die(fmt.Errorf("%s: kind's machine name (%s) must equal parent dir name (%s)", rel, decl.Meta.Name, ent.Name())) + if decl.Properties.MachineName != ent.Name() { + die(fmt.Errorf("%s: kind's machine name (%s) must equal parent dir name (%s)", rel, decl.Properties.Name, ent.Name())) } dfg, _ := codegen.ForGen(nil, decl.Some()) all = append(all, dfg) } sort.Slice(all, func(i, j int) bool { - return nameFor(all[i].Meta) < nameFor(all[j].Meta) + return nameFor(all[i].Properties) < nameFor(all[j].Properties) }) jfs, err := coreKindsGen.GenerateFS(all...) @@ -111,18 +111,18 @@ func main() { } } -func nameFor(m kindsys.SomeKindMeta) string { +func nameFor(m kindsys.SomeKindProperties) string { switch x := m.(type) { - case kindsys.RawMeta: + case kindsys.RawProperties: return x.Name - case kindsys.CoreStructuredMeta: + case kindsys.CoreStructuredProperties: return x.Name - case kindsys.CustomStructuredMeta: + case kindsys.CustomStructuredProperties: return x.Name - case kindsys.ComposableMeta: + case kindsys.ComposableProperties: return x.Name default: - // unreachable so long as all the possibilities in KindMetas have switch branches + // unreachable so long as all the possibilities in KindProperties have switch branches panic("unreachable") } } diff --git a/kinds/structured/team/team_kind.cue b/kinds/structured/team/team_kind.cue new file mode 100644 index 00000000000..6b1d9da1f2e --- /dev/null +++ b/kinds/structured/team/team_kind.cue @@ -0,0 +1,34 @@ +package kind + +name: "Team" +maturity: "merged" + +lineage: seqs: [ + { + schemas: [ + // v0.0 + { + // OrgId is the ID of an organisation the team belongs to. + orgId: int64 @grafanamaturity(ToMetadata="sys") + // Name of the team. + name: string + // Email of the team. + email?: string + // AvatarUrl is the team's avatar URL. + avatarUrl?: string @grafanamaturity(MaybeRemove) + // MemberCount is the number of the team members. + memberCount: int64 @grafanamaturity(ToMetadata="kind") + // TODO - it seems it's a team_member.permission, unlikely it should belong to the team kind + permission: #Permission @grafanamaturity(ToMetadata="kind", MaybeRemove) + // AccessControl metadata associated with a given resource. + accessControl: [string]: bool @grafanamaturity(ToMetadata="sys") + // Created indicates when the team was created. + created: int64 @grafanamaturity(ToMetadata="sys") + // Updated indicates when the team was updated. + updated: int64 @grafanamaturity(ToMetadata="sys") + + #Permission: 1 | 2 | 4 @cuetsy(kind="enum",memberNames="viewer|editor|admin") + }, + ] + }, +] diff --git a/latest.json b/latest.json index 5d0d253a775..f617f9d7f46 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "9.2.6", - "testing": "9.3.0-beta1" + "stable": "9.3.1", + "testing": "9.3.0" } diff --git a/package.json b/package.json index 98fbcb97bea..f82549dbcb6 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,6 @@ "@testing-library/user-event": "14.4.3", "@types/angular": "1.8.4", "@types/angular-route": "1.7.2", - "@types/classnames": "2.3.0", "@types/common-tags": "^1.8.0", "@types/d3": "7.4.0", "@types/d3-force": "^2.1.0", @@ -137,7 +136,6 @@ "@types/papaparse": "5.3.5", "@types/pluralize": "^0.0.29", "@types/prismjs": "1.26.0", - "@types/rc-time-picker": "3.4.1", "@types/react": "17.0.42", "@types/react-beautiful-dnd": "13.1.2", "@types/react-dom": "17.0.14", @@ -152,13 +150,11 @@ "@types/react-window": "1.8.5", "@types/react-window-infinite-loader": "^1", "@types/redux-mock-store": "1.0.3", - "@types/reselect": "2.2.0", "@types/semver": "7.3.13", "@types/slate": "0.47.11", "@types/slate-plain-serializer": "0.7.2", "@types/slate-react": "0.22.9", "@types/testing-library__jest-dom": "5.14.5", - "@types/testing-library__react-hooks": "^3.2.0", "@types/tinycolor2": "1.4.3", "@types/uuid": "8.3.4", "@typescript-eslint/eslint-plugin": "5.42.0", @@ -283,7 +279,6 @@ "@sentry/browser": "6.19.7", "@sentry/types": "6.19.7", "@sentry/utils": "6.19.7", - "@types/rc-tree": "^3.0.0", "@types/react-resizable": "3.0.3", "@types/webpack-env": "1.18.0", "@visx/event": "2.6.0", @@ -348,7 +343,7 @@ "prop-types": "15.8.1", "pseudoizer": "^0.1.0", "rc-cascader": "3.7.0", - "rc-drawer": "4.4.3", + "rc-drawer": "6.0.1", "rc-slider": "10.0.1", "rc-time-picker": "3.7.3", "rc-tree": "5.7.0", @@ -397,7 +392,7 @@ "tether-drop": "https://github.com/torkelo/drop", "tinycolor2": "1.4.2", "tslib": "2.4.1", - "uplot": "1.6.22", + "uplot": "1.6.23", "uuid": "9.0.0", "vendor": "link:./public/vendor", "visjs-network": "4.25.0", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 537beb5f0af..6f0db335307 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -30,7 +30,9 @@ "scripts": { "build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts", "clean": "rimraf ./dist ./compiled ./package.tgz", - "typecheck": "tsc --emitDeclarationOnly false --noEmit" + "typecheck": "tsc --emitDeclarationOnly false --noEmit", + "prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js", + "postpack": "mv package.json.bak package.json" }, "dependencies": { "@braintree/sanitize-url": "6.0.1", @@ -51,7 +53,7 @@ "rxjs": "7.5.7", "tinycolor2": "1.4.2", "tslib": "2.4.1", - "uplot": "1.6.22", + "uplot": "1.6.23", "xss": "1.0.14" }, "devDependencies": { @@ -76,7 +78,6 @@ "@types/react-dom": "17.0.14", "@types/sinon": "10.0.13", "@types/testing-library__jest-dom": "5.14.5", - "@types/testing-library__react-hooks": "^3.2.0", "@types/tinycolor2": "1.4.3", "esbuild": "0.15.12", "react": "17.0.2", diff --git a/packages/grafana-data/src/themes/createTypography.ts b/packages/grafana-data/src/themes/createTypography.ts index 2971c6d605d..5dfe3ebaf47 100644 --- a/packages/grafana-data/src/themes/createTypography.ts +++ b/packages/grafana-data/src/themes/createTypography.ts @@ -63,7 +63,7 @@ export interface ThemeTypographyInput { htmlFontSize?: number; } -const defaultFontFamily = '"Roboto", "Helvetica", "Arial", sans-serif'; +const defaultFontFamily = '"Inter", "Helvetica", "Arial", sans-serif'; const defaultFontFamilyMonospace = "'Roboto Mono', monospace"; export function createTypography(colors: ThemeColors, typographyInput: ThemeTypographyInput = {}): ThemeTypography { diff --git a/packages/grafana-data/src/types/data.ts b/packages/grafana-data/src/types/data.ts index 360a299c564..d1f2e4614e1 100644 --- a/packages/grafana-data/src/types/data.ts +++ b/packages/grafana-data/src/types/data.ts @@ -177,3 +177,6 @@ export interface DataConfigSource { getFieldOverrideOptions: () => ApplyFieldOverrideOptions | undefined; snapshotData?: DataFrameDTO[]; } + +type Truthy = T extends false | '' | 0 | null | undefined ? never : T; +export const isTruthy = (value: T): value is Truthy => Boolean(value); diff --git a/packages/grafana-data/src/types/dataFrameTypes.ts b/packages/grafana-data/src/types/dataFrameTypes.ts index e84f4a0a97b..17103d77fde 100644 --- a/packages/grafana-data/src/types/dataFrameTypes.ts +++ b/packages/grafana-data/src/types/dataFrameTypes.ts @@ -7,8 +7,12 @@ export enum DataFrameType { TimeSeriesWide = 'timeseries-wide', TimeSeriesLong = 'timeseries-long', + + /** @deprecated in favor of TimeSeriesMulti */ TimeSeriesMany = 'timeseries-many', + TimeSeriesMulti = 'timeseries-multi', + /** Directory listing */ DirectoryListing = 'directory-listing', diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index da1723aa0fb..dabb4dfea2b 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -558,6 +558,10 @@ export interface DataSourceSettings '.drawer-content-wrapper', + rcContentWrapper: () => '.rc-drawer-content-wrapper', }, }, PanelEditor: { diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index a6d0faae018..9e20696760a 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -31,7 +31,7 @@ export const Pages = { url: '/datasources/new', /** @deprecated Use dataSourcePluginsV2 */ dataSourcePlugins: (pluginName: string) => `Data source plugin item ${pluginName}`, - dataSourcePluginsV2: (pluginName: string) => `Add data source ${pluginName}`, + dataSourcePluginsV2: (pluginName: string) => `Add new data source ${pluginName}`, }, ConfirmModal: { delete: 'Confirm Modal Danger Button', @@ -193,6 +193,7 @@ export const Pages = { CopyUrlInput: 'data-testid public dashboard copy url input', CopyUrlButton: 'data-testid public dashboard copy url button', TemplateVariablesWarningAlert: 'data-testid public dashboard disabled template variables alert', + UnsupportedDatasourcesWarningAlert: 'data-testid public dashboard unsupported datasources', }, }, Explore: { diff --git a/packages/grafana-e2e/cypress.json b/packages/grafana-e2e/cypress.json index eaa48eb162f..7eafaf4bf2b 100644 --- a/packages/grafana-e2e/cypress.json +++ b/packages/grafana-e2e/cypress.json @@ -1,5 +1,7 @@ { "projectId": "zb7k1c", "supportFile": "cypress/support/index.ts", - "videoCompression": 20 + "videoCompression": 20, + "viewportWidth": 1920, + "viewportHeight": 1080 } diff --git a/packages/grafana-e2e/package.json b/packages/grafana-e2e/package.json index 793424e5ca8..783cd4e4d65 100644 --- a/packages/grafana-e2e/package.json +++ b/packages/grafana-e2e/package.json @@ -42,7 +42,9 @@ "start": "cypress run --browser=chrome", "start-benchmark": "CYPRESS_NO_COMMAND_LOG=1 yarn start", "test": "pushd test && node ../dist/bin/grafana-e2e.js run", - "typecheck": "tsc --emitDeclarationOnly false --noEmit" + "typecheck": "tsc --emitDeclarationOnly false --noEmit", + "prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js", + "postpack": "mv package.json.bak package.json" }, "devDependencies": { "@rollup/plugin-node-resolve": "15.0.1", diff --git a/packages/grafana-e2e/src/flows/addDashboard.ts b/packages/grafana-e2e/src/flows/addDashboard.ts index 36dca49b86b..a8c02d8e1fe 100644 --- a/packages/grafana-e2e/src/flows/addDashboard.ts +++ b/packages/grafana-e2e/src/flows/addDashboard.ts @@ -236,9 +236,9 @@ const addVariable = (config: PartialAddVariableConfig, isFirst: boolean): AddVar e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2() .should('be.visible') .within(() => { - e2e.components.Select.singleValue().should('have.text', 'Query').click(); + e2e.components.Select.singleValue().should('have.text', 'Query').parent().click(); }); - e2e.components.Select.option().should('be.visible').contains(type).click(); + e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().find('input').type(`${type}{enter}`); } if (label) { diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index a724775f4fd..162a650a309 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -32,7 +32,9 @@ "build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts", "bundle": "rollup -c rollup.config.ts", "clean": "rimraf ./dist ./compiled ./package.tgz", - "typecheck": "tsc --emitDeclarationOnly false --noEmit" + "typecheck": "tsc --emitDeclarationOnly false --noEmit", + "prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js", + "postpack": "mv package.json.bak package.json" }, "dependencies": { "@grafana/data": "9.4.0-pre", diff --git a/packages/grafana-runtime/src/components/DataSourcePicker.test.tsx b/packages/grafana-runtime/src/components/DataSourcePicker.test.tsx index e41eaeaa4f0..bdd44487e7b 100644 --- a/packages/grafana-runtime/src/components/DataSourcePicker.test.tsx +++ b/packages/grafana-runtime/src/components/DataSourcePicker.test.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; @@ -24,5 +24,12 @@ describe('DataSourcePicker', () => { select.getByLabelText('select-clear-value'); }).toThrowError(); }); + + it('should pass disabled prop', async () => { + render(); + + const input = screen.getByLabelText('Select a data source'); + expect(input).toHaveProperty('disabled', true); + }); }); }); diff --git a/packages/grafana-runtime/src/components/DataSourcePicker.tsx b/packages/grafana-runtime/src/components/DataSourcePicker.tsx index 0ceba9231be..b9ad9c2b761 100644 --- a/packages/grafana-runtime/src/components/DataSourcePicker.tsx +++ b/packages/grafana-runtime/src/components/DataSourcePicker.tsx @@ -47,6 +47,8 @@ export interface DataSourcePickerProps { filter?: (dataSource: DataSourceInstanceSettings) => boolean; onClear?: () => void; invalid?: boolean; + disabled?: boolean; + isLoading?: boolean; } /** @@ -162,7 +164,17 @@ export class DataSourcePicker extends PureComponent onChange(e.target.value)} /> + ), +})); + +const server = setupServer(); + +beforeAll(() => { + setBackendSrv(backendSrv); + server.listen({ onUnhandledRequest: 'error' }); +}); + +beforeEach(() => { + server.resetHandlers(); +}); + +afterAll(() => { + server.close(); +}); + +const ui = { + inputs: { + name: byRole('textbox', { name: /rule name name for the alert rule\./i }), + expr: byTestId('expr'), + folderContainer: byTestId(selectors.components.FolderPicker.containerV2), + namespace: byTestId('namespace-picker'), + group: byTestId('group-picker'), + annotationValue: (idx: number) => byTestId(`annotation-value-${idx}`), + labelValue: (idx: number) => byTestId(`label-value-${idx}`), + }, + loadingIndicator: byText('Loading the rule'), + loadingGroupIndicator: byText('Loading...'), +}; + +function getProvidersWrapper() { + return function Wrapper({ children }: React.PropsWithChildren<{}>) { + const store = mockStore((store) => { + store.unifiedAlerting.dataSources['grafana'] = { + loading: false, + dispatched: true, + result: { + id: 'grafana', + name: 'grafana', + rulerConfig: { + dataSourceName: 'grafana', + apiVersion: 'legacy', + }, + }, + }; + store.unifiedAlerting.dataSources['my-prom-ds'] = { + loading: false, + dispatched: true, + result: { + id: 'my-prom-ds', + name: 'my-prom-ds', + rulerConfig: { + dataSourceName: 'my-prom-ds', + apiVersion: 'config', + }, + }, + }; + }); + + const formApi = useForm({ defaultValues: getDefaultFormValues() }); + + return ( + + + {children} + + + ); + }; +} + +describe('CloneRuleEditor', function () { + describe('Grafana-managed rules', function () { + it('should populate form values from the existing alert rule', async function () { + setDataSourceSrv(new MockDataSourceSrv({})); + + const originRule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( + { + for: '1m', + labels: { severity: 'critical', region: 'nasa' }, + annotations: { [Annotation.summary]: 'This is a very important alert rule' }, + }, + { uid: 'grafana-rule-1', title: 'First Grafana Rule', data: [] } + ); + + mockRulerRulesApiResponse(server, 'grafana', { + 'folder-one': [{ name: 'group1', interval: '20s', rules: [originRule] }], + }); + + mockSearchApiResponse(server, []); + + render(, { + wrapper: getProvidersWrapper(), + }); + + await waitForElementToBeRemoved(ui.loadingIndicator.query()); + await waitForElementToBeRemoved(ui.loadingGroupIndicator.query(), { container: ui.inputs.group.get() }); + + await waitFor(() => { + expect(ui.inputs.name.get()).toHaveValue('First Grafana Rule (copy)'); + expect(ui.inputs.folderContainer.get()).toHaveTextContent('folder-one'); + expect(ui.inputs.group.get()).toHaveTextContent('group1'); + expect(ui.inputs.labelValue(0).get()).toHaveTextContent('critical'); + expect(ui.inputs.labelValue(1).get()).toHaveTextContent('nasa'); + expect(ui.inputs.annotationValue(0).get()).toHaveTextContent('This is a very important alert rule'); + }); + }); + }); + + describe('Cloud rules', function () { + it('should populate form values from the existing alert rule', async function () { + const dsSettings = mockDataSource({ + name: 'my-prom-ds', + uid: 'my-prom-ds', + }); + config.datasources = { + 'my-prom-ds': dsSettings, + }; + + setDataSourceSrv(new MockDataSourceSrv({ 'my-prom-ds': dsSettings })); + + const originRule = mockRulerAlertingRule({ + for: '1m', + alert: 'First Ruler Rule', + expr: 'vector(1) > 0', + labels: { severity: 'critical', region: 'nasa' }, + annotations: { [Annotation.summary]: 'This is a very important alert rule' }, + }); + + mockRulerRulesApiResponse(server, 'my-prom-ds', { + 'namespace-one': [{ name: 'group1', interval: '20s', rules: [originRule] }], + }); + + mockRulerRulesGroupApiResponse(server, 'my-prom-ds', 'namespace-one', 'group1', { + name: 'group1', + interval: '20s', + rules: [originRule], + }); + + mockSearchApiResponse(server, []); + + render( + , + { + wrapper: getProvidersWrapper(), + } + ); + + await waitForElementToBeRemoved(ui.loadingIndicator.query()); + + await waitFor(() => { + expect(ui.inputs.name.get()).toHaveValue('First Ruler Rule (copy)'); + expect(ui.inputs.expr.get()).toHaveValue('vector(1) > 0'); + expect(ui.inputs.namespace.get()).toHaveTextContent('namespace-one'); + expect(ui.inputs.group.get()).toHaveTextContent('group1'); + expect(ui.inputs.labelValue(0).get()).toHaveTextContent('critical'); + expect(ui.inputs.labelValue(1).get()).toHaveTextContent('nasa'); + expect(ui.inputs.annotationValue(0).get()).toHaveTextContent('This is a very important alert rule'); + }); + }); + }); +}); + +describe('generateCopiedRuleTitle', () => { + it('should generate copy name', () => { + const fileName = 'my file'; + const expectedDuplicateName = 'my file (copy)'; + + const ruleWithLocation = { + rule: { + grafana_alert: { + title: fileName, + }, + }, + group: { + rules: [], + }, + } as unknown as RuleWithLocation; + + expect(generateCopiedRuleTitle(ruleWithLocation)).toEqual(expectedDuplicateName); + }); + + it('should generate copy name and number from original file', () => { + const fileName = 'my file'; + const duplicatedName = 'my file (copy)'; + const expectedDuplicateName = 'my file (copy 2)'; + + const ruleWithLocation = { + rule: { + grafana_alert: { + title: fileName, + }, + }, + group: { + rules: [{ grafana_alert: { title: fileName } }, { grafana_alert: { title: duplicatedName } }], + }, + } as RuleWithLocation; + + expect(generateCopiedRuleTitle(ruleWithLocation)).toEqual(expectedDuplicateName); + }); + + it('should generate copy name and number from duplicated file', () => { + const fileName = 'my file (copy)'; + const duplicatedName = 'my file (copy 2)'; + const expectedDuplicateName = 'my file (copy 3)'; + + const ruleWithLocation = { + rule: { + grafana_alert: { + title: fileName, + }, + }, + group: { + rules: [{ grafana_alert: { title: fileName } }, { grafana_alert: { title: duplicatedName } }], + }, + } as RuleWithLocation; + + expect(generateCopiedRuleTitle(ruleWithLocation)).toEqual(expectedDuplicateName); + }); + + it('should generate copy name and number from duplicated file in gap', () => { + const fileName = 'my file (copy)'; + const duplicatedName = 'my file (copy 3)'; + const expectedDuplicateName = 'my file (copy 2)'; + + const ruleWithLocation = { + rule: { + grafana_alert: { + title: fileName, + }, + }, + group: { + rules: [ + { + grafana_alert: { title: fileName }, + }, + { grafana_alert: { title: duplicatedName } }, + ], + }, + } as RuleWithLocation; + + expect(generateCopiedRuleTitle(ruleWithLocation)).toEqual(expectedDuplicateName); + }); +}); diff --git a/public/app/features/alerting/unified/CloneRuleEditor.tsx b/public/app/features/alerting/unified/CloneRuleEditor.tsx new file mode 100644 index 00000000000..e9a75b76a03 --- /dev/null +++ b/public/app/features/alerting/unified/CloneRuleEditor.tsx @@ -0,0 +1,87 @@ +import { cloneDeep } from 'lodash'; +import React from 'react'; +import { useAsync } from 'react-use'; + +import { locationService } from '@grafana/runtime/src'; +import { Alert, LoadingPlaceholder } from '@grafana/ui/src'; + +import { useDispatch } from '../../../types'; +import { RuleIdentifier, RuleWithLocation } from '../../../types/unified-alerting'; +import { RulerRuleDTO } from '../../../types/unified-alerting-dto'; + +import { AlertRuleForm } from './components/rule-editor/AlertRuleForm'; +import { fetchEditableRuleAction } from './state/actions'; +import { rulerRuleToFormValues } from './utils/rule-form'; +import { getRuleName, isAlertingRulerRule, isGrafanaRulerRule, isRecordingRulerRule } from './utils/rules'; +import { createUrl } from './utils/url'; + +export function CloneRuleEditor({ sourceRuleId }: { sourceRuleId: RuleIdentifier }) { + const dispatch = useDispatch(); + + const { + loading, + value: rule, + error, + } = useAsync(() => dispatch(fetchEditableRuleAction(sourceRuleId)).unwrap(), [sourceRuleId]); + + if (loading) { + return ; + } + + if (rule) { + const ruleClone = cloneDeep(rule); + changeRuleName(ruleClone.rule, generateCopiedRuleTitle(ruleClone)); + const formPrefill = rulerRuleToFormValues(ruleClone); + + // Provisioned alert rules have provisioned alert group which cannot be used in UI + if (isGrafanaRulerRule(rule.rule) && Boolean(rule.rule.grafana_alert.provenance)) { + formPrefill.group = ''; + } + + return ; + } + + if (error) { + return ( + + {error.message} + + ); + } + + return ( + locationService.replace(createUrl('/alerting/list'))} + /> + ); +} + +export function generateCopiedRuleTitle(originRuleWithLocation: RuleWithLocation): string { + const originName = getRuleName(originRuleWithLocation.rule); + const existingRulesNames = originRuleWithLocation.group.rules.map(getRuleName); + + const nonDuplicateName = originName.replace(/\(copy( [0-9]+)?\)$/, '').trim(); + + let newName = `${nonDuplicateName} (copy)`; + + for (let i = 2; existingRulesNames.includes(newName); i++) { + newName = `${nonDuplicateName} (copy ${i})`; + } + + return newName; +} + +function changeRuleName(rule: RulerRuleDTO, newName: string) { + if (isGrafanaRulerRule(rule)) { + rule.grafana_alert.title = newName; + } + if (isAlertingRulerRule(rule)) { + rule.alert = newName; + } + + if (isRecordingRulerRule(rule)) { + rule.record = newName; + } +} diff --git a/public/app/features/alerting/unified/RuleEditor.tsx b/public/app/features/alerting/unified/RuleEditor.tsx index abc6aa8a5b8..bc461f500ef 100644 --- a/public/app/features/alerting/unified/RuleEditor.tsx +++ b/public/app/features/alerting/unified/RuleEditor.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { useAsync } from 'react-use'; import { NavModelItem } from '@grafana/data'; @@ -7,10 +7,12 @@ import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { useDispatch } from 'app/types'; import { AlertWarning } from './AlertWarning'; +import { CloneRuleEditor } from './CloneRuleEditor'; import { ExistingRuleEditor } from './ExistingRuleEditor'; import { AlertingPageWrapper } from './components/AlertingPageWrapper'; import { AlertRuleForm } from './components/rule-editor/AlertRuleForm'; -import { fetchAllPromBuildInfoAction } from './state/actions'; +import { useURLSearchParams } from './hooks/useURLSearchParams'; +import { fetchRulesSourceBuildInfoAction } from './state/actions'; import { useRulesAccess } from './utils/accessControlHooks'; import * as ruleId from './utils/rule-id'; @@ -33,16 +35,23 @@ const getPageNav = (state: 'edit' | 'add') => { const RuleEditor = ({ match }: RuleEditorProps) => { const dispatch = useDispatch(); + const [searchParams] = useURLSearchParams(); + const { id } = match.params; const identifier = ruleId.tryParse(id, true); + const copyFromId = searchParams.get('copyFrom') ?? undefined; + const copyFromIdentifier = ruleId.tryParse(copyFromId); + const { loading = true } = useAsync(async () => { - await dispatch(fetchAllPromBuildInfoAction()); + if (identifier) { + await dispatch(fetchRulesSourceBuildInfoAction({ rulesSourceName: identifier.ruleSourceName })); + } }, [dispatch]); const { canCreateGrafanaRules, canCreateCloudRules, canEditRules } = useRulesAccess(); - const getContent = () => { + const getContent = useCallback(() => { if (loading) { return; } @@ -59,8 +68,12 @@ const RuleEditor = ({ match }: RuleEditorProps) => { return ; } + if (copyFromIdentifier) { + return ; + } + return ; - }; + }, [canCreateCloudRules, canCreateGrafanaRules, canEditRules, copyFromIdentifier, id, identifier, loading]); return ( diff --git a/public/app/features/alerting/unified/components/CollapseToggle.tsx b/public/app/features/alerting/unified/components/CollapseToggle.tsx index d5ae0433b00..9ea400226e6 100644 --- a/public/app/features/alerting/unified/components/CollapseToggle.tsx +++ b/public/app/features/alerting/unified/components/CollapseToggle.tsx @@ -29,6 +29,7 @@ export const CollapseToggle: FC = ({ + + + {rulerRuleRequests && ( <>
List of rules that belong to this group
@@ -407,25 +427,6 @@ export function EditCloudGroupModal(props: ModalProps): React.ReactElement { /> )} - - - - - @@ -437,6 +438,10 @@ const getStyles = (theme: GrafanaTheme2) => ({ modal: css` max-width: 560px; `, + modalButtons: css` + top: -24px; + position: relative; + `, formInput: css` width: 275px; & + & { diff --git a/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx index 0531ac03270..71e80704617 100644 --- a/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx @@ -2,20 +2,11 @@ import { css } from '@emotion/css'; import React, { FC, useState } from 'react'; import { useLocation } from 'react-router-dom'; -import { GrafanaTheme2, urlUtil } from '@grafana/data'; -import { config } from '@grafana/runtime'; -import { - Button, - ClipboardButton, - ConfirmModal, - HorizontalGroup, - LinkButton, - Tooltip, - useStyles2, - useTheme2, -} from '@grafana/ui'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Stack } from '@grafana/experimental'; +import { config, locationService } from '@grafana/runtime'; +import { Button, ClipboardButton, ConfirmModal, LinkButton, Tooltip, useStyles2 } from '@grafana/ui'; import { useAppNotification } from 'app/core/copy/appNotification'; -import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange'; import { useDispatch } from 'app/types'; import { CombinedRule, RulesSource } from 'app/types/unified-alerting'; @@ -25,6 +16,7 @@ import { getRulesSourceName, isCloudRulesSource } from '../../utils/datasource'; import { createViewLink } from '../../utils/misc'; import * as ruleId from '../../utils/rule-id'; import { isFederatedRuleGroup, isGrafanaRulerRule } from '../../utils/rules'; +import { createUrl } from '../../utils/url'; export const matchesWidth = (width: number) => window.matchMedia(`(max-width: ${width}px)`).matches; @@ -32,25 +24,6 @@ interface Props { rule: CombinedRule; rulesSource: RulesSource; } -function DontShowIfSmallDevice({ children }: { children: JSX.Element | string }) { - const theme = useTheme2(); - const smBreakpoint = theme.breakpoints.values.xxl; - const [isSmallScreen, setIsSmallScreen] = useState(matchesWidth(smBreakpoint)); - const style = useStyles2(getStyles); - - useMediaQueryChange({ - breakpoint: smBreakpoint, - onChange: (e) => { - setIsSmallScreen(e.matches); - }, - }); - - if (isSmallScreen) { - return null; - } else { - return
{children}
; - } -} export const RuleActionsButtons: FC = ({ rule, rulesSource }) => { const dispatch = useDispatch(); @@ -59,6 +32,7 @@ export const RuleActionsButtons: FC = ({ rule, rulesSource }) => { const style = useStyles2(getStyles); const { namespace, group, rulerRule } = rule; const [ruleToDelete, setRuleToDelete] = useState(); + const [provRuleCloneUrl, setProvRuleCloneUrl] = useState(undefined); const rulesSourceName = getRulesSourceName(rulesSource); @@ -96,56 +70,79 @@ export const RuleActionsButtons: FC = ({ rule, rulesSource }) => { return window.location.href.split('?')[0]; }; + const sourceName = getRulesSourceName(rulesSource); + if (!isViewMode) { buttons.push( - View - + > ); } - if (isEditable && rulerRule && !isFederated && !isProvisioned) { - const sourceName = getRulesSourceName(rulesSource); + if (isEditable && rulerRule && !isFederated) { const identifier = ruleId.fromRulerRule(sourceName, namespace.name, group.name, rulerRule); - const editURL = urlUtil.renderUrl( - `${config.appSubUrl}/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/edit`, - { + if (!isProvisioned) { + const editURL = createUrl(`/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/edit`, { returnTo, + }); + + if (isViewMode) { + buttons.push( + { + notifyApp.error('Error while copying URL', copiedText); + }} + className={style.button} + size="sm" + getText={buildShareUrl} + > + Copy link to rule + + ); } - ); - if (isViewMode) { buttons.push( - { - notifyApp.error('Error while copying URL', copiedText); - }} - className={style.button} - size="sm" - getText={buildShareUrl} - > - Copy link to rule - + + + ); } + const cloneUrl = createUrl('/alerting/new', { copyFrom: ruleId.stringifyIdentifier(identifier) }); + // For provisioned rules an additional confirmation step is required + // Users have to be aware that the cloned rule will NOT be marked as provisioned buttons.push( - - - Edit - + + setProvRuleCloneUrl(cloneUrl) : undefined} + /> ); } @@ -154,16 +151,15 @@ export const RuleActionsButtons: FC = ({ rule, rulesSource }) => { buttons.push( + /> ); } @@ -171,11 +167,11 @@ export const RuleActionsButtons: FC = ({ rule, rulesSource }) => { if (buttons.length) { return ( <> -
- - {buttons.length ? buttons.map((button, index) =>
{button}
) :
} - -
+ + {buttons.map((button, index) => ( + {button} + ))} + {!!ruleToDelete && ( = ({ rule, rulesSource }) => { onDismiss={() => setRuleToDelete(undefined)} /> )} + +

+ The new rule will NOT be marked as a provisioned rule. +

+

+ You will need to set a new alert group for the cloned rule because the original one has been provisioned + and cannot be used for rules created in the UI. +

+
+ } + confirmText="Clone" + onConfirm={() => provRuleCloneUrl && locationService.push(provRuleCloneUrl)} + onDismiss={() => setProvRuleCloneUrl(undefined)} + /> ); } @@ -199,20 +213,10 @@ function inViewMode(pathname: string): boolean { } export const getStyles = (theme: GrafanaTheme2) => ({ - wrapper: css` - display: flex; - flex-direction: row; - justify-content: space-between; - flex-wrap: wrap; - `, button: css` - height: 24px; - font-size: ${theme.typography.size.sm}; - svg { - margin-right: 0; - } + padding: 0 ${theme.spacing(2)}; `, - buttonText: css` - margin-left: 8px; + bold: css` + font-weight: ${theme.typography.fontWeightBold}; `, }); diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx index 00439bb23bd..cd66f5547fd 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import React, { FC, Fragment } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, textUtil } from '@grafana/data'; import { Button, HorizontalGroup, LinkButton, useStyles2 } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; import { AccessControlAction } from 'app/types'; @@ -61,7 +61,7 @@ export const RuleDetailsActionButtons: FC = ({ rule, rulesSource }) => { variant="primary" icon="book" target="__blank" - href={rule.annotations[Annotation.runbookURL]} + href={textUtil.sanitizeUrl(rule.annotations[Annotation.runbookURL])} > View runbook diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx index eec77d0658b..2d75fdcf86b 100644 --- a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx @@ -192,6 +192,7 @@ export const RulesGroup: FC = React.memo(({ group, namespace, expandAll,
{ it('Should render Edit button for users with the update permission', () => { mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: true }); renderRulesTable(grafanaRule); - expect(ui.actionButtons.edit.query()).toBeInTheDocument(); + expect(ui.actionButtons.edit.get()).toBeInTheDocument(); }); it('Should render Delete button for users with the delete permission', () => { mocks.useIsRuleEditable.mockReturnValue({ loading: false, isRemovable: true }); renderRulesTable(grafanaRule); - expect(ui.actionButtons.delete.query()).toBeInTheDocument(); + expect(ui.actionButtons.delete.get()).toBeInTheDocument(); }); }); @@ -110,13 +110,13 @@ describe('RulesTable RBAC', () => { it('Should render Edit button for users with the update permission', () => { mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: true }); renderRulesTable(cloudRule); - expect(ui.actionButtons.edit.query()).toBeInTheDocument(); + expect(ui.actionButtons.edit.get()).toBeInTheDocument(); }); it('Should render Delete button for users with the delete permission', () => { mocks.useIsRuleEditable.mockReturnValue({ loading: false, isRemovable: true }); renderRulesTable(cloudRule); - expect(ui.actionButtons.delete.query()).toBeInTheDocument(); + expect(ui.actionButtons.delete.get()).toBeInTheDocument(); }); }); }); diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.tsx index affa11b32f2..5e1c0fddb0e 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.tsx @@ -196,7 +196,7 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) { renderCell: ({ data: rule }) => { return ; }, - size: '290px', + size: '200px', }); return columns; diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 492a6841313..89b8742000c 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -121,6 +121,7 @@ export const mockRulerAlertingRule = (partial: Partial = { annotations: { summary: 'test alert', }, + ...partial, }); export const mockRulerRuleGroup = (partial: Partial = {}): RulerRuleGroupDTO => ({ diff --git a/public/app/features/alerting/unified/mocks/grafanaApi.ts b/public/app/features/alerting/unified/mocks/grafanaApi.ts new file mode 100644 index 00000000000..04c45c7bb65 --- /dev/null +++ b/public/app/features/alerting/unified/mocks/grafanaApi.ts @@ -0,0 +1,8 @@ +import { rest } from 'msw'; +import { SetupServerApi } from 'msw/node'; + +import { DashboardSearchItem } from '../../../search/types'; + +export function mockSearchApiResponse(server: SetupServerApi, searchResult: DashboardSearchItem[]) { + server.use(rest.get('/api/search', (req, res, ctx) => res(ctx.json(searchResult)))); +} diff --git a/public/app/features/alerting/unified/mocks/rulerApi.ts b/public/app/features/alerting/unified/mocks/rulerApi.ts new file mode 100644 index 00000000000..18cd13b6e4c --- /dev/null +++ b/public/app/features/alerting/unified/mocks/rulerApi.ts @@ -0,0 +1,30 @@ +import { rest } from 'msw'; +import { SetupServerApi } from 'msw/node'; + +import { RulerRuleGroupDTO, RulerRulesConfigDTO } from '../../../../types/unified-alerting-dto'; + +export function mockRulerRulesApiResponse( + server: SetupServerApi, + rulesSourceName: string, + response: RulerRulesConfigDTO +) { + server.use( + rest.get(`/api/ruler/${rulesSourceName}/api/v1/rules`, (req, res, ctx) => + res(ctx.json(response)) + ) + ); +} + +export function mockRulerRulesGroupApiResponse( + server: SetupServerApi, + rulesSourceName: string, + namespace: string, + group: string, + response: RulerRuleGroupDTO +) { + server.use( + rest.get(`/api/ruler/${rulesSourceName}/api/v1/rules/${namespace}/${group}`, (req, res, ctx) => + res(ctx.json(response)) + ) + ); +} diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index b2ed0b227ed..187983368b1 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -263,6 +263,7 @@ export function fetchRulerRulesIfNotFetchedYet(rulesSourceName: string): ThunkRe }; } +// TODO: memoize this or move to RTK Query so we can cache results! export function fetchAllPromBuildInfoAction(): ThunkResult> { return async (dispatch) => { const allRequests = getAllRulesSourceNames().map((rulesSourceName) => diff --git a/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts b/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts index 4dcf2e44fdd..9f250b46db0 100644 --- a/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts +++ b/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts @@ -330,6 +330,26 @@ export const cloudNotifierTypes: NotifierDTO[] = [ httpConfigOption, ], }, + { + name: 'Discord', + description: 'Sends notifications to Discord', + type: 'discord', + info: '', + heading: 'Discord settings', + options: [ + option('title', 'Title', 'Templated title of the message', { + placeholder: '{{ template "discord.default.title" . }}', + }), + option( + 'message', + 'Message Content', + 'Mention a group using @ or a user using <@ID> when notifying in a channel', + { placeholder: '{{ template "discord.default.message" . }}' } + ), + option('webhook_url', 'Webhook URL', '', { placeholder: 'Discord webhook URL', required: true }), + httpConfigOption, + ], + }, ]; export const globalConfigOptions: NotificationChannelOption[] = [ diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts index ef6a694a1bf..66ea5f64292 100644 --- a/public/app/features/alerting/unified/utils/misc.ts +++ b/public/app/features/alerting/unified/utils/misc.ts @@ -1,7 +1,6 @@ import { sortBy } from 'lodash'; -import { urlUtil, UrlQueryMap, Labels, DataSourceInstanceSettings, DataSourceJsonData } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { UrlQueryMap, Labels, DataSourceInstanceSettings, DataSourceJsonData } from '@grafana/data'; import { alertInstanceKey } from 'app/features/alerting/unified/utils/rules'; import { SortOrder } from 'app/plugins/panel/alertlist/types'; import { Alert, CombinedRule, FilterState, RulesSource, SilenceFilterState } from 'app/types/unified-alerting'; @@ -15,6 +14,7 @@ import { ALERTMANAGER_NAME_QUERY_KEY } from './constants'; import { getRulesSourceName } from './datasource'; import { getMatcherQueryParams } from './matchers'; import * as ruleId from './rule-id'; +import { createUrl } from './url'; export function createViewLink(ruleSource: RulesSource, rule: CombinedRule, returnTo: string): string { const sourceName = getRulesSourceName(ruleSource); @@ -22,11 +22,11 @@ export function createViewLink(ruleSource: RulesSource, rule: CombinedRule, retu const paramId = encodeURIComponent(ruleId.stringifyIdentifier(identifier)); const paramSource = encodeURIComponent(sourceName); - return urlUtil.renderUrl(`${config.appSubUrl}/alerting/${paramSource}/${paramId}/view`, { returnTo }); + return createUrl(`/alerting/${paramSource}/${paramId}/view`, { returnTo }); } export function createExploreLink(dataSourceName: string, query: string) { - return urlUtil.renderUrl(`${config.appSubUrl}/explore`, { + return createUrl(`/explore`, { left: JSON.stringify([ 'now-1h', 'now', @@ -95,15 +95,15 @@ export function makeLabelBasedSilenceLink(alertManagerSourceName: string, labels const matcherParams = getMatcherQueryParams(labels); matcherParams.forEach((value, key) => silenceUrlParams.append(key, value)); - return `${config.appSubUrl}/alerting/silence/new?${silenceUrlParams.toString()}`; + return createUrl('/alerting/silence/new', silenceUrlParams); } export function makeDataSourceLink(dataSource: DataSourceInstanceSettings) { - return `${config.appSubUrl}/datasources/edit/${dataSource.uid}`; + return createUrl(`/datasources/edit/${dataSource.uid}`); } export function makeFolderLink(folderUID: string): string { - return `${config.appSubUrl}/dashboards/f/${folderUID}`; + return createUrl(`/dashboards/f/${folderUID}`); } // keep retrying fn if it's error passes shouldRetry(error) and timeout has not elapsed yet diff --git a/public/app/features/alerting/unified/utils/rules.ts b/public/app/features/alerting/unified/utils/rules.ts index 3a2aa8b8884..6386541891a 100644 --- a/public/app/features/alerting/unified/utils/rules.ts +++ b/public/app/features/alerting/unified/utils/rules.ts @@ -145,3 +145,18 @@ export function getFirstActiveAt(promRule: AlertingRule) { export function isFederatedRuleGroup(group: CombinedRuleGroup) { return Array.isArray(group.source_tenants); } + +export function getRuleName(rule: RulerRuleDTO) { + if (isGrafanaRulerRule(rule)) { + return rule.grafana_alert.title; + } + if (isAlertingRulerRule(rule)) { + return rule.alert; + } + + if (isRecordingRulerRule(rule)) { + return rule.record; + } + + return ''; +} diff --git a/public/app/features/alerting/unified/utils/url.ts b/public/app/features/alerting/unified/utils/url.ts new file mode 100644 index 00000000000..8499598eaa0 --- /dev/null +++ b/public/app/features/alerting/unified/utils/url.ts @@ -0,0 +1,6 @@ +import { config } from '@grafana/runtime'; + +export function createUrl(path: string, queryParams?: string[][] | Record | string | URLSearchParams) { + const searchParams = new URLSearchParams(queryParams); + return `${config.appSubUrl}${path}?${searchParams.toString()}`; +} diff --git a/public/app/features/commandPalette/ResultItem.tsx b/public/app/features/commandPalette/ResultItem.tsx index 6f817fac456..036d70dffb4 100644 --- a/public/app/features/commandPalette/ResultItem.tsx +++ b/public/app/features/commandPalette/ResultItem.tsx @@ -34,6 +34,13 @@ export const ResultItem = React.forwardRef( const theme = useTheme2(); const styles = getResultItemStyles(theme, active); + let name = action.name; + + // TODO: does this needs adjusting for i18n? + if (action.children && !action.command?.perform && !name.endsWith('...')) { + name += '...'; + } + return (
@@ -47,7 +54,7 @@ export const ResultItem = React.forwardRef( › ))} - {action.name} + {name}
{action.subtitle && {action.subtitle}} @@ -94,9 +101,9 @@ const getResultItemStyles = (theme: GrafanaTheme2, isActive: boolean) => { }), actionContainer: css({ display: 'flex', - gap: theme.spacing(2), - alignitems: 'center', - fontsize: theme.typography.fontSize, + gap: theme.spacing(1), + alignItems: 'center', + fontSize: theme.typography.fontSize, }), textContainer: css({ display: 'flex', @@ -106,7 +113,7 @@ const getResultItemStyles = (theme: GrafanaTheme2, isActive: boolean) => { padding: theme.spacing(0, 1), background: shortcutBackgroundColor, borderRadius: theme.shape.borderRadius(), - fontsize: theme.typography.fontSize, + fontSize: theme.typography.fontSize, }), breadcrumbAncestor: css({ opacity: 0.5, diff --git a/public/app/features/commandPalette/actions/alerting.static.actions.ts b/public/app/features/commandPalette/actions/alerting.static.actions.ts deleted file mode 100644 index d4ceea7e138..00000000000 --- a/public/app/features/commandPalette/actions/alerting.static.actions.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Priority } from 'kbar'; - -import { locationService } from '@grafana/runtime'; - -import { NavBarActions } from './global.static.actions'; - -// Grafana Alerting and alerting sub navigation items -const alertingCommandPaletteStaticActions: NavBarActions[] = [ - { - url: '/alerting/list', - actions: [ - { - id: 'go/alerting', - name: 'Go to alerting', - keywords: 'alerting navigate', - perform: () => locationService.push('/alerting'), - section: 'Navigation', - priority: Priority.NORMAL, - }, - ], - }, - { - url: '/alerting/list', - actions: [ - { - id: 'go/alerting/rules', - name: 'Go to alert rules', - keywords: 'alerting navigate rules', - perform: () => locationService.push('/alerting/list'), - section: 'Navigation', - parent: 'go/alerting', - }, - ], - }, - { - url: '/alerting/notifications', - actions: [ - { - id: 'go/alerting/contact-points', - name: 'Go to contact points', - keywords: 'alerting navigate contact-points', - perform: () => locationService.push('/alerting/notifications'), - parent: 'go/alerting', - }, - ], - }, - { - url: '/alerting/routes', - actions: [ - { - id: 'go/alerting/notification-policies', - name: 'Go to notification policies', - keywords: 'alerting navigate notification-policies', - perform: () => locationService.push('/alerting/routes'), - parent: 'go/alerting', - }, - ], - }, - { - url: '/alerting/silences', - actions: [ - { - id: 'go/alerting/silences', - name: 'Go to silences', - keywords: 'alerting navigate silences', - perform: () => locationService.push('/alerting/silences'), - parent: 'go/alerting', - }, - ], - }, -]; - -export { alertingCommandPaletteStaticActions }; diff --git a/public/app/features/commandPalette/actions/global.static.actions.ts b/public/app/features/commandPalette/actions/global.static.actions.ts deleted file mode 100644 index 7fb13a30a0f..00000000000 --- a/public/app/features/commandPalette/actions/global.static.actions.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { Action, Priority } from 'kbar'; -import { flatMapDeep } from 'lodash'; - -import { NavModelItem } from '@grafana/data'; -import { locationService } from '@grafana/runtime'; - -import { alertingCommandPaletteStaticActions } from './alerting.static.actions'; - -export interface NavBarActions { - url: string; - actions: Action[]; -} - -export default (navBarTree: NavModelItem[]) => { - const globalActions: Action[] = [ - { - id: 'go/search', - name: 'Go to dashboard search', - keywords: 'navigate', - perform: () => locationService.push('?search=open'), - section: 'Navigation', - shortcut: ['s', 'o'], - }, - { - id: 'go/dashboard', - name: 'Go to dashboard...', - keywords: 'navigate', - section: 'Navigation', - priority: Priority.NORMAL, - }, - { - id: 'preferences/theme', - name: 'Change theme...', - keywords: 'interface color dark light', - section: 'Preferences', - shortcut: ['c', 't'], - }, - { - id: 'preferences/dark-theme', - name: 'Dark', - keywords: 'dark theme', - section: '', - perform: () => { - locationService.push({ search: '?theme=dark' }); - location.reload(); - }, - parent: 'preferences/theme', - }, - { - id: 'preferences/light-theme', - name: 'Light', - keywords: 'light theme', - section: '', - perform: () => { - locationService.push({ search: '?theme=light' }); - location.reload(); - }, - parent: 'preferences/theme', - }, - ]; - - // this maps actions to navbar by URL items for showing/hiding - // actions is an array for multiple child actions that would be under one navbar item - const navBarActionMap: NavBarActions[] = [ - { - url: '/dashboard/new', - actions: [ - { - id: 'management/create-folder', - name: 'Create folder', - keywords: 'new add', - perform: () => locationService.push('/dashboards/folder/new'), - section: 'Management', - }, - { - id: 'management/create-dashboard', - name: 'Create dashboard', - keywords: 'new add', - perform: () => locationService.push('/dashboard/new'), - section: 'Management', - }, - ], - }, - { - url: '/', - actions: [ - { - id: 'go/home', - name: 'Go to home', - keywords: 'navigate', - perform: () => locationService.push('/'), - section: 'Navigation', - shortcut: ['g', 'h'], - priority: Priority.HIGH, - }, - ], - }, - { - url: '/explore', - actions: [ - { - id: 'go/explore', - name: 'Go to explore', - keywords: 'navigate', - perform: () => locationService.push('/explore'), - section: 'Navigation', - priority: Priority.NORMAL, - }, - ], - }, - ...alertingCommandPaletteStaticActions, - { - url: '/profile', - actions: [ - { - id: 'go/profile', - name: 'Go to profile', - keywords: 'navigate preferences', - perform: () => locationService.push('/profile'), - section: 'Navigation', - shortcut: ['g', 'p'], - priority: Priority.LOW, - }, - ], - }, - { - url: '/datasources', - actions: [ - { - id: 'go/configuration', - name: 'Go to data sources configuration', - keywords: 'navigate settings ds', - perform: () => locationService.push('/datasources'), - section: 'Navigation', - }, - ], - }, - ]; - - const navBarActions: Action[] = []; - const navBarFlatUrls = flatMapDeep(navBarTree, (nbt) => nbt.children) - .map((nbf) => nbf?.url) - .filter((url) => url !== undefined); - - navBarActionMap.forEach((navBarAction) => { - const navBarItem = navBarFlatUrls.find((url) => navBarAction.url === url); - if (navBarItem) { - navBarActions.push(...navBarAction.actions); - } - }); - - return [...globalActions, ...navBarActions]; -}; diff --git a/public/app/features/commandPalette/actions/global.static.actions.tsx b/public/app/features/commandPalette/actions/global.static.actions.tsx new file mode 100644 index 00000000000..d376a93b461 --- /dev/null +++ b/public/app/features/commandPalette/actions/global.static.actions.tsx @@ -0,0 +1,112 @@ +import { Action, Priority } from 'kbar'; +import React from 'react'; + +import { isIconName, NavModelItem } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; +import { Icon } from '@grafana/ui'; +import { changeTheme } from 'app/core/services/theme'; + +const SECTION_PAGES = 'Pages'; +const SECTION_ACTIONS = 'Actions'; +const SECTION_PREFERENCES = 'Preferences'; + +export interface NavBarActions { + url: string; + actions: Action[]; +} + +// TODO: Clean this once ID is mandatory on nav items +function idForNavItem(navItem: NavModelItem) { + return 'navModel.' + navItem.id ?? navItem.url ?? navItem.text ?? navItem.subTitle; +} + +function navTreeToActions(navTree: NavModelItem[], parent?: NavModelItem): Action[] { + const navActions: Action[] = []; + + for (const navItem of navTree) { + const { url, text, isCreateAction, children } = navItem; + const hasChildren = Boolean(children?.length); + + if (!(url || hasChildren)) { + continue; + } + + const action: Action = { + id: idForNavItem(navItem), + name: text, // TODO: translate + section: isCreateAction ? SECTION_ACTIONS : SECTION_PAGES, + perform: url ? () => locationService.push(url) : undefined, + parent: parent && idForNavItem(parent), + + // Only show icons for top level items + icon: !parent && iconForNavItem(navItem), + }; + + navActions.push(action); + + if (children?.length) { + const childActions = navTreeToActions(children, navItem); + navActions.push(...childActions); + } + } + + return navActions; +} + +export default (navBarTree: NavModelItem[]) => { + const globalActions: Action[] = [ + { + // TODO: Figure out what section, if any, to put this in + id: 'go/dashboard', + name: 'Dashboards...', + keywords: 'navigate', + priority: Priority.NORMAL, + }, + { + id: 'go/search', + name: 'Search', + keywords: 'navigate', + icon: , + perform: () => locationService.push('?search=open'), + section: SECTION_PAGES, + shortcut: ['s', 'o'], + }, + { + id: 'preferences/theme', + name: 'Change theme...', + keywords: 'interface color dark light', + section: SECTION_PREFERENCES, + shortcut: ['c', 't'], + }, + { + id: 'preferences/dark-theme', + name: 'Dark', + keywords: 'dark theme', + section: '', + perform: () => changeTheme('dark'), + parent: 'preferences/theme', + }, + { + id: 'preferences/light-theme', + name: 'Light', + keywords: 'light theme', + section: '', + perform: () => changeTheme('light'), + parent: 'preferences/theme', + }, + ]; + + const navBarActions = navTreeToActions(navBarTree); + + return [...globalActions, ...navBarActions]; +}; + +function iconForNavItem(navItem: NavModelItem) { + if (navItem.icon && isIconName(navItem.icon)) { + return ; + } else if (navItem.img) { + return ; + } + + return undefined; +} diff --git a/public/app/features/connections/Connections.test.tsx b/public/app/features/connections/Connections.test.tsx index d6c8404848c..043e09321d1 100644 --- a/public/app/features/connections/Connections.test.tsx +++ b/public/app/features/connections/Connections.test.tsx @@ -4,6 +4,7 @@ import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; import { locationService } from '@grafana/runtime'; +import { contextSrv } from 'app/core/services/context_srv'; import { getMockDataSources } from 'app/features/datasources/__mocks__'; import * as api from 'app/features/datasources/api'; import { configureStore } from 'app/store/configureStore'; @@ -14,6 +15,7 @@ import Connections from './Connections'; import { navIndex } from './__mocks__/store.navIndex.mock'; import { ROUTE_BASE_ID, ROUTES } from './constants'; +jest.mock('app/core/services/context_srv'); jest.mock('app/features/datasources/api'); const renderPage = ( @@ -36,6 +38,7 @@ describe('Connections', () => { beforeEach(() => { (api.getDataSources as jest.Mock) = jest.fn().mockResolvedValue(mockDatasources); + (contextSrv.hasPermission as jest.Mock) = jest.fn().mockReturnValue(true); }); test('shows the "Data sources" page by default', async () => { @@ -43,7 +46,8 @@ describe('Connections', () => { expect(await screen.findByText('Datasources')).toBeVisible(); expect(await screen.findByText('Manage your existing datasource connections')).toBeVisible(); - expect(await screen.findByRole('link', { name: /add data source/i })).toBeVisible(); + expect(await screen.findByText('Sort by A–Z')).toBeVisible(); + expect(await screen.findByRole('link', { name: /add new data source/i })).toBeVisible(); expect(await screen.findByText(mockDatasources[0].name)).toBeVisible(); }); @@ -57,7 +61,15 @@ describe('Connections', () => { expect(screen.queryByText('Manage your existing datasource connections')).not.toBeInTheDocument(); }); - test('renders the "Connect data" page using a plugin in case it is a standalone plugin page', async () => { + test('renders the core "Connect data" page in case there is no standalone plugin page override for it', async () => { + renderPage(ROUTES.ConnectData); + + // We expect to see no results and "Data sources" as a header (we only have data sources in OSS Grafana at this point) + expect(await screen.findByText('Data sources')).toBeVisible(); + expect(await screen.findByText('No results matching your query were found.')).toBeVisible(); + }); + + test('does not render anything for the "Connect data" page in case it is displayed by a standalone plugin page', async () => { // We are overriding the navIndex to have the "Connect data" page registered by a plugin const standalonePluginPage = { id: 'standalone-plugin-page-/connections/connect-data', @@ -83,7 +95,10 @@ describe('Connections', () => { renderPage(ROUTES.ConnectData, store); - // We expect not to see the same text as if it was rendered by core. + // We expect not to see the text that would be rendered by the core "Connect data" page + // (Instead we expect to see the default route "Datasources") + expect(await screen.findByText('Datasources')).toBeVisible(); + expect(await screen.findByText('Manage your existing datasource connections')).toBeVisible(); expect(screen.queryByText('No results matching your query were found.')).not.toBeInTheDocument(); }); }); diff --git a/public/app/features/connections/constants.ts b/public/app/features/connections/constants.ts index 561e44eed99..29d9c00ab3b 100644 --- a/public/app/features/connections/constants.ts +++ b/public/app/features/connections/constants.ts @@ -1,6 +1,3 @@ -// The ID of the app plugin that we render under that "Cloud Integrations" tab -export const CLOUD_ONBOARDING_APP_ID = 'grafana-easystart-app'; - // The ID of the main nav-tree item (the main item in the NavIndex) export const ROUTE_BASE_ID = 'connections'; diff --git a/public/app/features/connections/pages/DataSourceDetailsPage.tsx b/public/app/features/connections/pages/DataSourceDetailsPage.tsx index e39e0cafb8e..09e7a1795b5 100644 --- a/public/app/features/connections/pages/DataSourceDetailsPage.tsx +++ b/public/app/features/connections/pages/DataSourceDetailsPage.tsx @@ -1,24 +1,41 @@ import * as React from 'react'; +import { useParams } from 'react-router-dom'; -import { Page } from 'app/core/components/Page/Page'; -import { StoreState, useSelector } from 'app/types'; +import { Alert, Badge } from '@grafana/ui'; +import { PluginDetailsPage } from 'app/features/plugins/admin/components/PluginDetailsPage'; +import { StoreState, useSelector, AppNotificationSeverity } from 'app/types'; + +import { ROUTES } from '../constants'; export function DataSourceDetailsPage() { const overrideNavId = 'standalone-plugin-page-/connections/connect-data'; + const { id } = useParams<{ id: string }>(); const navIndex = useSelector((state: StoreState) => state.navIndex); const isConnectDataPageOverriden = Boolean(navIndex[overrideNavId]); const navId = isConnectDataPageOverriden ? overrideNavId : 'connections-connect-data'; // The nav id changes (gets a prefix) if it is overriden by a plugin return ( - } + notFoundNavModel={{ + text: 'Unknown datasource', + subTitle: 'No datasource with this ID could be found.', active: true, }} - > - Data Source Details (no exposed component from plugins yet) - + /> + ); +} + +function NotFoundDatasource() { + const { id } = useParams<{ id: string }>(); + + return ( + + Maybe you mistyped the URL or the plugin with the id is unavailable. +
+ To see a list of available datasources please click here. +
); } diff --git a/public/app/features/connections/pages/DataSourcesListPage.tsx b/public/app/features/connections/pages/DataSourcesListPage.tsx index 518cc4c39db..17612363ba4 100644 --- a/public/app/features/connections/pages/DataSourcesListPage.tsx +++ b/public/app/features/connections/pages/DataSourcesListPage.tsx @@ -1,11 +1,12 @@ import * as React from 'react'; import { Page } from 'app/core/components/Page/Page'; +import { DataSourceAddButton } from 'app/features/datasources/components/DataSourceAddButton'; import { DataSourcesList } from 'app/features/datasources/components/DataSourcesList'; export function DataSourcesListPage() { return ( - + diff --git a/public/app/features/correlations/CorrelationsPage.test.tsx b/public/app/features/correlations/CorrelationsPage.test.tsx index 2de67a10d60..d891c39bee5 100644 --- a/public/app/features/correlations/CorrelationsPage.test.tsx +++ b/public/app/features/correlations/CorrelationsPage.test.tsx @@ -1,4 +1,13 @@ -import { render, waitFor, screen, fireEvent, waitForElementToBeRemoved, within, Matcher } from '@testing-library/react'; +import { + render, + waitFor, + screen, + fireEvent, + waitForElementToBeRemoved, + within, + Matcher, + getByRole, +} from '@testing-library/react'; import { merge, uniqueId } from 'lodash'; import React from 'react'; import { DeepPartial } from 'react-hook-form'; @@ -411,7 +420,7 @@ describe('CorrelationsPage', () => { }); it('correctly sorts by source', async () => { - const sourceHeader = getHeaderByName('Source'); + const sourceHeader = getByRole(getHeaderByName('Source'), 'button'); fireEvent.click(sourceHeader); let cells = queryCellsByColumnName('Source'); cells.forEach((cell, i, allCells) => { diff --git a/public/app/features/correlations/CorrelationsPage.tsx b/public/app/features/correlations/CorrelationsPage.tsx index 730b32c9952..303d0827862 100644 --- a/public/app/features/correlations/CorrelationsPage.tsx +++ b/public/app/features/correlations/CorrelationsPage.tsx @@ -1,11 +1,22 @@ import { css } from '@emotion/css'; import { negate } from 'lodash'; import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { CellProps, SortByFn } from 'react-table'; import { GrafanaTheme2 } from '@grafana/data'; import { isFetchError, reportInteraction } from '@grafana/runtime'; -import { Badge, Button, DeleteButton, HorizontalGroup, LoadingPlaceholder, useStyles2, Alert } from '@grafana/ui'; +import { + Badge, + Button, + DeleteButton, + HorizontalGroup, + LoadingPlaceholder, + useStyles2, + Alert, + InteractiveTable, + type Column, + type CellProps, + type SortByFn, +} from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { useNavModel } from 'app/core/hooks/useNavModel'; @@ -14,7 +25,6 @@ import { AccessControlAction } from 'app/types'; import { AddCorrelationForm } from './Forms/AddCorrelationForm'; import { EditCorrelationForm } from './Forms/EditCorrelationForm'; import { EmptyCorrelationsCTA } from './components/EmptyCorrelationsCTA'; -import { Column, Table } from './components/Table'; import type { RemoveCorrelationParams } from './types'; import { CorrelationData, useCorrelations } from './useCorrelations'; @@ -97,8 +107,9 @@ export default function CorrelationsPage() { const columns = useMemo>>( () => [ { + id: 'info', cell: InfoCell, - shrink: true, + disableGrow: true, visible: (data) => data.some(isSourceReadOnly), }, { @@ -115,8 +126,9 @@ export default function CorrelationsPage() { }, { id: 'label', header: 'Label', sortType: 'alphanumeric' }, { + id: 'actions', cell: RowActions, - shrink: true, + disableGrow: true, visible: (data) => canWriteCorrelations && data.some(negate(isSourceReadOnly)), }, ], @@ -166,7 +178,7 @@ export default function CorrelationsPage() { {isAdding && setIsAdding(false)} onCreated={handleAdded} />} {data && data.length >= 1 && ( - ( = React.memo( ref={ref} >
- + + + ); +}; + +export const getStyles = (theme: GrafanaTheme2) => { + return { + editor: css``, + submitButton: css` + align-self: flex-end; + margin-bottom: 25px; + margin-top: 25px; + `, + }; +}; diff --git a/public/app/features/query-library/components/DatasourceTypePicker.tsx b/public/app/features/query-library/components/DatasourceTypePicker.tsx new file mode 100644 index 00000000000..e5c34bd52f4 --- /dev/null +++ b/public/app/features/query-library/components/DatasourceTypePicker.tsx @@ -0,0 +1,113 @@ +// Libraries +import { uniqBy } from 'lodash'; +import React from 'react'; + +// Components +import { DataSourceInstanceSettings, isUnsignedPluginSignature } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { getDataSourceSrv } from '@grafana/runtime/src'; +import { HorizontalGroup, PluginSignatureBadge, Select } from '@grafana/ui'; + +export type DatasourceTypePickerProps = { + onChange: (ds: string | null) => void; + current: string | null; // type + hideTextValue?: boolean; + onBlur?: () => void; + autoFocus?: boolean; + openMenuOnFocus?: boolean; + placeholder?: string; + tracing?: boolean; + mixed?: boolean; + dashboard?: boolean; + metrics?: boolean; + type?: string | string[]; + annotations?: boolean; + variables?: boolean; + alerting?: boolean; + pluginId?: string; + /** If true,we show only DSs with logs; and if true, pluginId shouldnt be passed in */ + logs?: boolean; + width?: number; + inputId?: string; + filter?: (dataSource: DataSourceInstanceSettings) => boolean; + onClear?: () => void; +}; + +const getDataSourceTypeOptions = (props: DatasourceTypePickerProps) => { + const { alerting, tracing, metrics, mixed, dashboard, variables, annotations, pluginId, type, filter, logs } = props; + + return uniqBy( + getDataSourceSrv() + .getList({ + alerting, + tracing, + metrics, + logs, + dashboard, + mixed, + variables, + annotations, + pluginId, + filter, + type, + }) + .map((ds) => { + if (ds.type === 'datasource') { + return { + value: ds.type, + label: ds.type, + imgUrl: ds.meta.info.logos.small, + meta: ds.meta, + }; + } + + return { + value: ds.type, + label: ds.type, + imgUrl: ds.meta.info.logos.small, + meta: ds.meta, + }; + }), + (opt) => opt.value + ); +}; + +export const DatasourceTypePicker = (props: DatasourceTypePickerProps) => { + const { autoFocus, onBlur, onChange, current, openMenuOnFocus, placeholder, width, inputId } = props; + const options = getDataSourceTypeOptions(props); + + return ( +
+
+ + + + + + + + + + + {!Boolean(found?.length) && ( + + + + )} + {Boolean(found?.length) && + found!.map((item, key) => { + return ( + setReload(reload + 1)} + author={key < authors.length ? authors[key] : authors[key - authors.length]} + date={key < dates.length ? dates[key] : dates[key - dates.length]} + /> + ); + })} + +
+ StatusName and raw queryData SourceUserDate +
+ + + +
No data
+
+ + +
+ ); + }} + + ); + }} + +
+ + ); +}; + +export default QueryLibrarySearchTable; + +export const getStyles = (theme: GrafanaTheme2) => { + return { + tableWrapper: css` + height: 100%; + margin-top: 20px; + margin-bottom: 20px; + display: flex; + flex-direction: column; + align-items: flex-start; + `, + autosizer: css` + margin-top: 40px; + `, + createQueryButton: css` + text-align: center; + `, + filtersGroup: css` + padding-top: 10px; + margin-top: 30px; + `, + searchBy: css` + margin-right: 15px; + `, + table: css` + font-size: 14px; + &tbody { + &tr: { + background: ${theme.colors.background.secondary}; + } + } + `, + noData: css` + color: ${theme.colors.text.secondary}; + `, + transparentBg: css` + background: transparent !important; + `, + }; +}; diff --git a/public/app/features/query-library/components/QueryListItem.tsx b/public/app/features/query-library/components/QueryListItem.tsx new file mode 100644 index 00000000000..878ab9f96aa --- /dev/null +++ b/public/app/features/query-library/components/QueryListItem.tsx @@ -0,0 +1,198 @@ +import { css, cx } from '@emotion/css'; +import { uniq } from 'lodash'; +import React, { memo, useEffect, useState } from 'react'; + +import { DataSourceApi, GrafanaTheme2 } from '@grafana/data/src'; +import { getDataSourceSrv } from '@grafana/runtime/src'; +import { Icon, Tooltip } from '@grafana/ui'; +import { Badge, IconButton, useStyles2 } from '@grafana/ui/src'; + +import { useAppNotification } from '../../../core/copy/appNotification'; +import { getSavedQuerySrv } from '../api/SavedQueriesSrv'; +import { QueryItem } from '../types'; +import { implementationComingSoonAlert } from '../utils'; + +import { QueryEditorDrawer } from './QueryEditorDrawer'; + +type QueryListItemProps = { + query: QueryItem; + showModal: (component: React.ComponentType, props: T) => void; + hideModal: () => void; + updateComponent: () => void; + author: string; + date: string; +}; + +const options = { + type: 'edit', +} as const; + +export const QueryListItem = memo( + ({ query, showModal, hideModal, updateComponent, author, date }: QueryListItemProps) => { + const notifyApp = useAppNotification(); + + const styles = useStyles2(getStyles); + const [dsInfo, setDsInfo] = useState([]); + + useEffect(() => { + const getQueryDsInstance = async () => { + const uniqueUids = uniq(query?.ds_uid ?? []); + setDsInfo((await Promise.all(uniqueUids.map((dsUid) => getDataSourceSrv().get(dsUid)))).filter(Boolean)); + }; + + getQueryDsInstance(); + }, [query.ds_uid]); + + const closeDrawer = () => { + hideModal(); + updateComponent(); + }; + + const openDrawer = async () => { + const result = await getSavedQuerySrv().getSavedQueries([{ uid: query.uid }]); + const savedQuery = result[0]; + + showModal(QueryEditorDrawer, { onDismiss: closeDrawer, savedQuery: savedQuery, options }); + }; + + const deleteQuery = async () => { + await getSavedQuerySrv().deleteSavedQuery({ uid: query.uid }); + updateComponent(); + }; + + const getDsType = () => { + const dsType = dsInfo?.length > 1 ? 'mixed' : dsInfo?.[0]?.type ?? 'datasource'; + return startWithUpperCase(dsType); + }; + + const startWithUpperCase = (dsType: string) => { + return dsType.charAt(0).toUpperCase() + dsType.slice(1); + }; + + const getTooltip = () => { + return ( +
+
    + {dsInfo.map((dsI, key) => { + return ( +
  • + datasource +   + {startWithUpperCase(dsI.type)} +
  • + ); + })} +
+
+ ); + }; + + const copyToClipboard = async () => { + const models = await getSavedQuerySrv().getSavedQueries([{ uid: query.uid }]); + if (!models?.length) { + implementationComingSoonAlert(); + return; + } + + await navigator.clipboard.writeText( + JSON.stringify( + { + ...models[0], + uid: undefined, + storageOptions: undefined, + }, + null, + 2 + ) + ); + notifyApp.success('Query JSON copied to clipboard!'); + }; + + return ( + + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions*/} + + + + + + + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions*/} + {query.title} + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions*/} + + datasource +   {getDsType()}  + {getDsType() === 'Mixed' && ( + + + + )} + + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions*/} + + {`Avatar +   {author} + + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions*/} + {date} + + + + + + + + + ); + } +); + +QueryListItem.displayName = 'QueryListItem'; + +const getStyles = (theme: GrafanaTheme2) => { + return { + row: css` + height: 70px; + cursor: pointer; + `, + tableTr: css` + display: flex; + justify-content: space-between; + margin-top: 22px; + `, + disabled: css` + color: ${theme.colors.text.secondary}; + `, + gitIcon: css` + width: 30px; + height: 30px; + margin-left: 10px; + margin-top: 1px; + opacity: 0.8; + `, + infoIcon: css` + margin-top: -2px; + `, + dsTooltipIcon: css` + width: 11px; + height: 11px; + `, + dsIcon: css` + width: 16px !important; + height: 16px !important; + `, + dsTooltipList: css` + list-style-type: none; + `, + }; +}; diff --git a/public/app/features/query-library/components/QueryName.tsx b/public/app/features/query-library/components/QueryName.tsx new file mode 100644 index 00000000000..447411c2233 --- /dev/null +++ b/public/app/features/query-library/components/QueryName.tsx @@ -0,0 +1,119 @@ +import { css } from '@emotion/css'; +import React, { useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, Input, FieldValidationMessage, HorizontalGroup, useStyles2 } from '@grafana/ui'; + +export interface QueryNameProps { + name: string; + editingEnabled: boolean; + onChange: (v: string) => void; +} + +export const QueryName = ({ name, onChange, editingEnabled }: QueryNameProps) => { + const styles = useStyles2(getStyles); + + const [isEditing, setIsEditing] = useState(false); + const [validationError, setValidationError] = useState(null); + + const onEditQueryName = (event: React.SyntheticEvent) => { + setIsEditing(true); + }; + + const onEndEditName = (newName: string) => { + setIsEditing(false); + + if (validationError) { + setValidationError(null); + return; + } + + if (name !== newName) { + onChange(newName); + } + }; + + const onInputChange = (event: React.SyntheticEvent) => { + const newName = event.currentTarget.value.trim(); + + if (newName.length === 0) { + setValidationError('An empty name is not allowed'); + return; + } + + if (validationError) { + setValidationError(null); + } + }; + + const onEditLayerBlur = (event: React.SyntheticEvent) => { + onEndEditName(event.currentTarget.value.trim()); + }; + + const onFocus = (event: React.FocusEvent) => { + event.target.select(); + }; + + const onKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + if (!(event.target instanceof HTMLInputElement)) { + return; + } + + onEndEditName(event.target.value); + } + }; + + return ( + <> +
+ {!isEditing && ( + +

{name}

+ {editingEnabled && } +
+ )} + + {isEditing && ( + <> + + {validationError && {validationError}} + + )} +
+ + ); +}; + +const getStyles = (theme: GrafanaTheme2) => { + return { + wrapper: css` + display: flex; + align-items: center; + margin-left: ${theme.v1.spacing.xs}; + `, + nameEditIcon: css` + cursor: pointer; + color: ${theme.colors.text.secondary}; + width: 12px; + height: 12px; + `, + nameInput: css` + max-width: 300px; + margin: -8px 0; + `, + h2Style: css` + margin-bottom: 0; + `, + }; +}; diff --git a/public/app/features/query-library/components/SaveQueryWorkflowModal.tsx b/public/app/features/query-library/components/SaveQueryWorkflowModal.tsx new file mode 100644 index 00000000000..674376f9991 --- /dev/null +++ b/public/app/features/query-library/components/SaveQueryWorkflowModal.tsx @@ -0,0 +1,79 @@ +import { css } from '@emotion/css'; +import React, { useState } from 'react'; + +import { selectors } from '@grafana/e2e-selectors'; +import { Button, Form, Modal, VerticalGroup, TextArea } from '@grafana/ui'; + +import { WorkflowID } from '../../storage/types'; +import { SavedQuery } from '../api/SavedQueriesApi'; + +interface FormDTO { + message: string; +} + +export interface SaveQueryOptions { + savedQuery: SavedQuery; + workflow: WorkflowID; + message?: string; +} + +export type SaveProps = { + onCancel: () => void; + onSuccess: () => void; + onSubmit?: (options: SaveQueryOptions) => Promise<{ success: boolean }>; + options: SaveQueryOptions; + onOptionsChange: (opts: SaveQueryOptions) => void; +}; + +export const SaveQueryWorkflowModal = ({ options, onSubmit, onCancel, onSuccess }: SaveProps) => { + const [saving, setSaving] = useState(false); + + return ( + +
{ + console.log('hello submitting!'); + if (!onSubmit) { + return; + } + setSaving(true); + options = { ...options, message: data.message }; + const result = await onSubmit(options); + if (result.success) { + onSuccess(); + } else { + setSaving(false); + } + }} + > + {({ register, errors }) => ( + +