diff --git a/.betterer.results b/.betterer.results index 6df3eefe0a5..069fc1a0b7a 100644 --- a/.betterer.results +++ b/.betterer.results @@ -29,9 +29,6 @@ exports[`no enzyme tests`] = { "packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js:1734982398": [ [14, 26, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js:1241675783": [ - [16, 19, 13, "RegExp match", "2409514259"] - ], "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.test.js:174536706": [ [14, 19, 13, "RegExp match", "2409514259"] ], @@ -44,17 +41,11 @@ exports[`no enzyme tests`] = { "packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.js:551014442": [ [13, 26, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/index.test.js:1541367299": [ - [14, 19, 13, "RegExp match", "2409514259"] - ], "public/app/core/components/Select/MetricSelect.test.tsx:1074737147": [ [0, 19, 13, "RegExp match", "2409514259"] ], "public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.test.tsx:145048794": [ [0, 17, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:2983010995": [ - [1, 19, 13, "RegExp match", "2409514259"] ] }` }; @@ -1075,12 +1066,10 @@ 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, "Unexpected any. Specify a different type.", "9"], - [0, 0, 0, "Unexpected any. Specify a different type.", "10"], - [0, 0, 0, "Do not use any type assertions.", "11"], - [0, 0, 0, "Do not use any type assertions.", "12"], - [0, 0, 0, "Unexpected any. Specify a different type.", "13"], - [0, 0, 0, "Do not use any type assertions.", "14"] + [0, 0, 0, "Do not use any type assertions.", "9"], + [0, 0, 0, "Do not use any type assertions.", "10"], + [0, 0, 0, "Unexpected any. Specify a different type.", "11"], + [0, 0, 0, "Do not use any type assertions.", "12"] ], "packages/grafana-runtime/src/utils/analytics.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -1109,26 +1098,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], - "packages/grafana-toolkit/src/cli/tasks/changelog.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.", "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.", "12"] - ], - "packages/grafana-toolkit/src/cli/tasks/cherrypick.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"] - ], "packages/grafana-toolkit/src/cli/tasks/component.create.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -1292,9 +1261,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], - "packages/grafana-ui/src/components/FilterInput/FilterInput.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -1900,36 +1866,16 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], - "packages/grafana-ui/src/utils/reactUtils.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], "packages/grafana-ui/src/utils/storybook/withTheme.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "packages/grafana-ui/src/utils/table.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"] ], "packages/grafana-ui/src/utils/useAsyncDependency.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "packages/grafana-ui/src/utils/useCombinedRefs.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] - ], - "packages/grafana-ui/src/utils/useDelayedSwitch.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, "Unexpected any. Specify a different type.", "3"] - ], - "packages/grafana-ui/src/utils/validate.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "packages/jaeger-ui-components/src/ScrollManager.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], @@ -2878,8 +2824,7 @@ exports[`better eslint`] = { [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.", "17"], - [0, 0, 0, "Unexpected any. Specify a different type.", "18"] + [0, 0, 0, "Unexpected any. Specify a different type.", "17"] ], "public/app/core/services/context_srv.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -4424,8 +4369,7 @@ exports[`better eslint`] = { "public/app/features/explore/state/main.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, "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.", "2"] ], "public/app/features/explore/state/query.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -6045,11 +5989,9 @@ exports[`better eslint`] = { [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, "Do not use any type assertions.", "15"], - [0, 0, 0, "Unexpected any. Specify a different type.", "16"], - [0, 0, 0, "Unexpected any. Specify a different type.", "17"], - [0, 0, 0, "Unexpected any. Specify a different type.", "18"] + [0, 0, 0, "Do not use any type assertions.", "14"], + [0, 0, 0, "Unexpected any. Specify a different type.", "15"], + [0, 0, 0, "Unexpected any. Specify a different type.", "16"] ], "public/app/plugins/datasource/elasticsearch/components/AddRemove.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -6857,43 +6799,10 @@ exports[`better eslint`] = { "public/app/plugins/datasource/opentsdb/migrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "public/app/plugins/datasource/opentsdb/query_ctrl.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.", "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.", "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.", "17"], - [0, 0, 0, "Unexpected any. Specify a different type.", "18"], - [0, 0, 0, "Unexpected any. Specify a different type.", "19"], - [0, 0, 0, "Unexpected any. Specify a different type.", "20"], - [0, 0, 0, "Unexpected any. Specify a different type.", "21"], - [0, 0, 0, "Unexpected any. Specify a different type.", "22"], - [0, 0, 0, "Unexpected any. Specify a different type.", "23"] - ], "public/app/plugins/datasource/opentsdb/specs/datasource.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], - "public/app/plugins/datasource/opentsdb/specs/query_ctrl.test.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] - ], - "public/app/plugins/datasource/opentsdb/types.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], "public/app/plugins/datasource/postgres/datasource.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], diff --git a/.drone.star b/.drone.star index 10719f988f5..9d2a65ebc63 100644 --- a/.drone.star +++ b/.drone.star @@ -7,7 +7,8 @@ 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', 'release_pipelines', 'publish_image_pipelines', 'publish_artifacts_pipelines', 'publish_npm_pipelines', 'publish_packages_pipeline', 'artifacts_page_pipeline') +load('scripts/drone/events/release.star', 'release_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') @@ -15,7 +16,7 @@ load('scripts/drone/vault.star', 'secrets') def main(ctx): edition = 'oss' return pr_pipelines(edition=edition) + main_pipelines(edition=edition) + release_pipelines() + \ - publish_image_pipelines('public') + publish_image_pipelines('security') + \ + 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() diff --git a/.drone.yml b/.drone.yml index d93fa58adb8..bb3b01a59d9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -19,7 +19,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -73,7 +73,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -329,7 +329,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -594,7 +594,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -699,7 +699,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -828,7 +828,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -919,7 +919,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1162,7 +1162,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1587,7 +1587,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1693,7 +1693,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -1790,7 +1790,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1922,6 +1922,8 @@ type: docker clone: retries: 3 depends_on: [] +environment: + EDITION: OSS image_pull_secrets: - dockerconfigjson kind: pipeline @@ -1939,7 +1941,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2230,6 +2232,8 @@ volumes: clone: retries: 3 depends_on: [] +environment: + EDITION: OSS image_pull_secrets: - dockerconfigjson kind: pipeline @@ -2247,7 +2251,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2337,6 +2341,8 @@ volumes: clone: retries: 3 depends_on: [] +environment: + EDITION: OSS image_pull_secrets: - dockerconfigjson kind: pipeline @@ -2370,7 +2376,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2452,6 +2458,8 @@ depends_on: - release-oss-build-e2e-publish - release-oss-test - release-oss-integration-tests +environment: + EDITION: OSS image_pull_secrets: - dockerconfigjson kind: pipeline @@ -2468,7 +2476,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -2513,6 +2521,8 @@ volumes: clone: disable: true depends_on: [] +environment: + EDITION: ENTERPRISE image_pull_secrets: - dockerconfigjson kind: pipeline @@ -2526,7 +2536,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2861,6 +2871,8 @@ volumes: clone: disable: true depends_on: [] +environment: + EDITION: ENTERPRISE image_pull_secrets: - dockerconfigjson kind: pipeline @@ -2874,7 +2886,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3010,6 +3022,8 @@ volumes: clone: disable: true depends_on: [] +environment: + EDITION: ENTERPRISE image_pull_secrets: - dockerconfigjson kind: pipeline @@ -3049,7 +3063,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3175,6 +3189,8 @@ depends_on: - release-enterprise-build-e2e-publish - release-enterprise-test - release-enterprise-integration-tests +environment: + EDITION: ENTERPRISE image_pull_secrets: - dockerconfigjson kind: pipeline @@ -3191,7 +3207,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -3269,7 +3285,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3357,7 +3373,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3418,95 +3434,6 @@ depends_on: [] image_pull_secrets: - dockerconfigjson kind: pipeline -name: publish-docker-oss-security -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl -- commands: - - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd - depends_on: [] - environment: - CGO_ENABLED: 0 - image: golang:1.19.1 - name: compile-build-cmd -- commands: - - ./bin/build artifacts docker fetch --edition oss - depends_on: - - compile-build-cmd - environment: - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - GCP_KEY: - from_secret: gcp_key - image: google/cloud-sdk - name: fetch-images-oss - volumes: - - name: docker - path: /var/run/docker.sock -- commands: - - ./bin/grabpl artifacts docker publish --security --dockerhub-repo grafana --base - alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7 --version-tag ${TAG} - depends_on: - - fetch-images-oss - environment: - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - GCP_KEY: - from_secret: gcp_key - image: google/cloud-sdk - name: publish-images-grafana - volumes: - - name: docker - path: /var/run/docker.sock -- commands: - - ./bin/grabpl artifacts docker publish --security --dockerhub-repo grafana-oss - --base alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7 --version-tag - ${TAG} - depends_on: - - fetch-images-oss - environment: - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USER: - from_secret: docker_username - GCP_KEY: - from_secret: gcp_key - image: google/cloud-sdk - name: publish-images-grafana-oss - volumes: - - name: docker - path: /var/run/docker.sock -trigger: - event: - - promote - target: - - security -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -image_pull_secrets: -- dockerconfigjson -kind: pipeline name: publish-docker-enterprise-security node: type: no-parallel @@ -3517,7 +3444,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3589,7 +3516,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3631,7 +3558,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3673,7 +3600,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3735,10 +3662,17 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl +- commands: + - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd + depends_on: [] + environment: + CGO_ENABLED: 0 + image: golang:1.19.1 + name: compile-build-cmd - commands: - ./bin/grabpl publish packages --edition oss --gcp-key /tmp/gcpkey.json ${DRONE_TAG} depends_on: @@ -3757,7 +3691,7 @@ steps: image: grafana/grafana-ci-deploy:1.3.3 name: publish-packages-oss - commands: - - ./bin/grabpl publish grafana-com --edition oss ${DRONE_TAG} + - ./bin/build publish grafana-com --edition oss ${DRONE_TAG} depends_on: - publish-packages-oss environment: @@ -3817,10 +3751,17 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl +- commands: + - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd + depends_on: [] + environment: + CGO_ENABLED: 0 + image: golang:1.19.1 + name: compile-build-cmd - commands: - ./bin/grabpl publish packages --edition enterprise --gcp-key /tmp/gcpkey.json ${DRONE_TAG} @@ -3840,7 +3781,7 @@ steps: image: grafana/grafana-ci-deploy:1.3.3 name: publish-packages-enterprise - commands: - - ./bin/grabpl publish grafana-com --edition enterprise ${DRONE_TAG} + - ./bin/build publish grafana-com --edition enterprise ${DRONE_TAG} depends_on: - publish-packages-enterprise environment: @@ -3897,7 +3838,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3923,6 +3864,8 @@ volumes: clone: retries: 3 depends_on: [] +environment: + EDITION: OSS image_pull_secrets: - dockerconfigjson kind: pipeline @@ -3940,7 +3883,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4200,6 +4143,8 @@ volumes: clone: retries: 3 depends_on: [] +environment: + EDITION: OSS image_pull_secrets: - dockerconfigjson kind: pipeline @@ -4217,7 +4162,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4301,6 +4246,8 @@ volumes: clone: retries: 3 depends_on: [] +environment: + EDITION: OSS image_pull_secrets: - dockerconfigjson kind: pipeline @@ -4334,7 +4281,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4410,6 +4357,8 @@ depends_on: - release-branch-oss-build-e2e-publish - release-branch-oss-test - release-branch-oss-integration-tests +environment: + EDITION: OSS image_pull_secrets: - dockerconfigjson kind: pipeline @@ -4426,7 +4375,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -4461,6 +4410,8 @@ volumes: clone: disable: true depends_on: [] +environment: + EDITION: ENTERPRISE image_pull_secrets: - dockerconfigjson kind: pipeline @@ -4474,7 +4425,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4798,6 +4749,8 @@ volumes: clone: disable: true depends_on: [] +environment: + EDITION: ENTERPRISE image_pull_secrets: - dockerconfigjson kind: pipeline @@ -4811,7 +4764,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4938,6 +4891,8 @@ volumes: clone: disable: true depends_on: [] +environment: + EDITION: ENTERPRISE image_pull_secrets: - dockerconfigjson kind: pipeline @@ -4977,7 +4932,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -5094,6 +5049,8 @@ depends_on: - release-branch-enterprise-build-e2e-publish - release-branch-enterprise-test - release-branch-enterprise-integration-tests +environment: + EDITION: ENTERPRISE image_pull_secrets: - dockerconfigjson kind: pipeline @@ -5110,7 +5067,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.11/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -5293,15 +5250,16 @@ platform: os: linux steps: - commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.10/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl + - go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd + depends_on: [] + environment: + CGO_ENABLED: 0 + image: golang:1.19.1 + name: compile-build-cmd - commands: - - ./bin/grabpl publish grafana-com --edition oss + - ./bin/build publish grafana-com --edition oss depends_on: - - grabpl + - compile-build-cmd environment: GCP_KEY: from_secret: gcp_key @@ -5381,6 +5339,6 @@ kind: secret name: packages_secret_access_key --- kind: signature -hmac: 774fd382b75b0860cc64326952818257df858a0f59144ad9ed978c984b94fd0e +hmac: 5ff426194c616a934af92cb113aebbb6cebf06d6317e62a693f4f8d7faa3b4cd ... diff --git a/.eslintrc b/.eslintrc index 3d3cae6a0fd..1b384e06201 100644 --- a/.eslintrc +++ b/.eslintrc @@ -67,7 +67,7 @@ // we should fix them one by one and mark them as errors // once they're all fixed, we can remove them all and instead extend the strict preset // with "extends": ["plugin:jsx-a11y/strict"] - "jsx-a11y/alt-text": "off", + "jsx-a11y/alt-text": "error", "jsx-a11y/anchor-has-content": "error", "jsx-a11y/anchor-is-valid": "off", "jsx-a11y/aria-activedescendant-has-tabindex": "error", @@ -79,7 +79,7 @@ "jsx-a11y/click-events-have-key-events": "off", "jsx-a11y/heading-has-content": "error", "jsx-a11y/html-has-lang": "error", - "jsx-a11y/iframe-has-title": "off", + "jsx-a11y/iframe-has-title": "error", "jsx-a11y/img-redundant-alt": "error", "jsx-a11y/interactive-supports-focus": [ "off", @@ -101,7 +101,12 @@ "jsx-a11y/media-has-caption": "error", "jsx-a11y/mouse-events-have-key-events": "off", "jsx-a11y/no-access-key": "error", - "jsx-a11y/no-autofocus": "off", + "jsx-a11y/no-autofocus": [ + "error", + { + "ignoreNonDOM": true + } + ], "jsx-a11y/no-distracting-elements": "error", "jsx-a11y/no-interactive-element-to-noninteractive-role": "error", "jsx-a11y/no-noninteractive-element-interactions": [ diff --git a/.github/workflows/sbom-report.yml b/.github/workflows/sbom-report.yml new file mode 100644 index 00000000000..bae173f6ef6 --- /dev/null +++ b/.github/workflows/sbom-report.yml @@ -0,0 +1,20 @@ +name: syft-sbom-ci + +on: + release: + types: [created] + +jobs: + syft-sbom: + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Anchore SBOM Action + uses: anchore/sbom-action@v0.12.0 + with: + artifact-name: ${{ github.event.repository.name }}-spdx.json + diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index 268d5078697..f523fc3085f 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -35,9 +35,9 @@ jobs: token: ${{ secrets.GH_BOT_ACCESS_TOKEN }} metricsWriteAPIKey: ${{ secrets.GRAFANA_MISC_STATS_API_KEY }} - name: Run update changelog (workflow invoked) - if: ${{ inputs.version_call != '' }} + if: ${{ inputs.version != '' }} uses: ./actions/update-changelog with: - version_call: ${{ inputs.version_call }} + version_call: ${{ inputs.version }} token: ${{ secrets.token }} metricsWriteAPIKey: ${{ secrets.metricsWriteAPIKey }} diff --git a/.gitignore b/.gitignore index cf9fe79b44d..50e6fb06da5 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ pkg/cmd/grafana-server/__debug_bin !.yarn/patches/*.patch # Ignoring frontend packages specifics +/packages/grafana-ui/.yarn/.cache /packages/**/dist /packages/**/compiled /packages/**/.rpt2_cache diff --git a/.yarn/patches/storybook-dark-mode-npm-1.1.2-ecc4605688.patch b/.yarn/patches/storybook-dark-mode-npm-1.1.2-ecc4605688.patch new file mode 100644 index 00000000000..981e943f101 Binary files /dev/null and b/.yarn/patches/storybook-dark-mode-npm-1.1.2-ecc4605688.patch differ diff --git a/.yarnrc.yml b/.yarnrc.yml index ed357e268d6..fe724e9a7bf 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -3,41 +3,39 @@ enableTelemetry: false nodeLinker: pnp packageExtensions: - "@mdx-js/loader@1.6.22": + '@storybook/addon-docs@6.5.12': peerDependencies: - react: 17.0.1 - "@npmcli/run-script@4.1.3": + '@babel/core': ^7.0.0 + '@storybook/manager-webpack5': 6.5.12 + 'webpack': 5.74.0 + '@storybook/addon-essentials@6.5.12': + peerDependencies: + '@storybook/components': 6.5.12 + '@storybook/core-events': 6.5.12 + '@storybook/manager-webpack5': 6.5.12 + '@storybook/theming': 6.5.12 + '@storybook/core-server@6.5.12': + peerDependencies: + '@babel/core': ^7.0.0 + '@storybook/core@6.5.12': + peerDependencies: + '@babel/core': ^7.0.0 + '@storybook/manager-webpack5': 6.5.12 + '@storybook/csf-tools@6.5.12': + peerDependencies: + '@babel/core': ^7.0.0 + '@storybook/mdx2-csf@0.0.3': dependencies: - which: ^2.0.2 - "@storybook/addon-docs@6.4.21": + '@babel/types': ^7.14.8 + '@storybook/react@6.5.12': peerDependencies: - "@storybook/manager-webpack5": 6.4.21 - "@storybook/addon-essentials@6.4.21": - peerDependencies: - "@storybook/components": 6.4.21 - "@storybook/core-events": 6.4.21 - "@storybook/manager-webpack5": 6.4.21 - "@storybook/theming": 6.4.21 - "@storybook/core-server@6.4.21": - peerDependencies: - "@babel/core": ^7.0.0 - "@storybook/core@6.4.21": - peerDependencies: - "@babel/core": ^7.0.0 - "@storybook/manager-webpack5": 6.4.21 - "@storybook/csf-tools@6.4.21": - peerDependencies: - "@babel/core": ^7.0.0 - "@storybook/react@6.4.21": - peerDependencies: - "@storybook/manager-webpack5": 6.4.21 + '@storybook/manager-webpack5': 6.5.12 + '@storybook/addon-knobs@6.4.0': + dependencies: + '@storybook/client-api': '*' doctrine@3.0.0: dependencies: assert: 2.0.0 - moveable@0.30.0: - dependencies: - "@daybrush/utils": 1.7.0 - framework-utils: ^1.1.0 rc-time-picker@3.7.3: peerDependencies: react: 17.0.1 @@ -49,33 +47,22 @@ packageExtensions: react-compat-css-styled@1.0.8: dependencies: react-simple-compat: 1.2.2 - react-compat-moveable@0.18.0: - dependencies: - "@egjs/agent": ^2.2.1 - "@egjs/children-differ": ^1.0.1 - "@scena/matrix": 1.1.1 - css-to-mat: ^1.0.3 - gesto: ^1.9.0 - overlap-area: ^1.0.0 - react-simple-compat: 1.2.2 - peerDependencies: - framework-utils: ^1.1.0 react-docgen-typescript-loader@3.7.2: peerDependencies: webpack: 4.41.5 react-icons@2.2.7: peerDependencies: - prop-types: "*" + prop-types: '*' react-resizable@3.0.4: peerDependencies: react-dom: 17.0.1 plugins: - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs - spec: "@yarnpkg/plugin-typescript" + spec: '@yarnpkg/plugin-typescript' - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs - spec: "@yarnpkg/plugin-interactive-tools" + spec: '@yarnpkg/plugin-interactive-tools' - path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs - spec: "https://mskelton.dev/yarn-outdated/v2" + spec: 'https://mskelton.dev/yarn-outdated/v2' yarnPath: .yarn/releases/yarn-3.2.3.cjs diff --git a/CHANGELOG.md b/CHANGELOG.md index b26d7fe7d9a..2423d04a481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -191,6 +191,36 @@ Issue [#55364](https://github.com/grafana/grafana/issues/55364) - **CustomScrollbar:** Add optional scroll indicators to `CustomScrollbar`. [#54705](https://github.com/grafana/grafana/pull/54705), [@ashharrison90](https://github.com/ashharrison90) - **Build:** Introduce ESM and Treeshaking to NPM package builds. [#51517](https://github.com/grafana/grafana/pull/51517), [@jackw](https://github.com/jackw) + + +# 9.1.7 (2022-10-04) + +### Features and enhancements + +- **Chore:** Upgrade Go version to 1.19.1 (backport). [#55733](https://github.com/grafana/grafana/pull/55733), [@sakjur](https://github.com/sakjur) +- **CloudWatch:** Add missing AWS/Prometheus metrics. [#54990](https://github.com/grafana/grafana/pull/54990), [@jangaraj](https://github.com/jangaraj) +- **Explore:** Add feature tracking events. [#54514](https://github.com/grafana/grafana/pull/54514), [@L-M-K-B](https://github.com/L-M-K-B) +- **Graphite:** Add error information to graphite queries tracing. [#55249](https://github.com/grafana/grafana/pull/55249), [@jesusvazquez](https://github.com/jesusvazquez) +- **Prometheus:** Restore FromAlert header. [#55255](https://github.com/grafana/grafana/pull/55255), [@kylebrandt](https://github.com/kylebrandt) +- **SAML:** Account for all orgs in org_mapping (#3855). (Enterprise) +- **Search:** Add search index configuration options. [#55525](https://github.com/grafana/grafana/pull/55525), [@ArturWierzbicki](https://github.com/ArturWierzbicki) +- **Thresholds:** Add option for dashed line style. [#55875](https://github.com/grafana/grafana/pull/55875), [@leeoniya](https://github.com/leeoniya) + +### Bug fixes + +- **Alerting:** Fix default query's data source when no default datasource specified. [#55435](https://github.com/grafana/grafana/pull/55435), [@konrad147](https://github.com/konrad147) +- **Alerting:** Fix mathexp.NoData cannot be reduced. [#55347](https://github.com/grafana/grafana/pull/55347), [@grobinson-grafana](https://github.com/grobinson-grafana) +- **Alerting:** Skip unsupported file types on provisioning. [#55573](https://github.com/grafana/grafana/pull/55573), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) +- **AzureMonitor:** Ensure resourceURI template variable is migrated. [#56095](https://github.com/grafana/grafana/pull/56095), [@aangelisc](https://github.com/aangelisc) +- **Dashboard:** Fix plugin dashboard save as button. [#55197](https://github.com/grafana/grafana/pull/55197), [@lpskdl](https://github.com/lpskdl) +- **Docs:** Fix decimals: auto docs for panel edit. [#55477](https://github.com/grafana/grafana/pull/55477), [@joshhunt](https://github.com/joshhunt) +- **Fix:** RBAC handle `error no resolver` found. [#55676](https://github.com/grafana/grafana/pull/55676), [@gamab](https://github.com/gamab) +- **Fix:** RBAC handle `error no resolver` found. (Enterprise) +- **LibraryPanelSearch:** Refactor and fix hyphen issue. [#55314](https://github.com/grafana/grafana/pull/55314), [@kaydelaney](https://github.com/kaydelaney) +- **Live:** Fix live streaming with `live-service-web-worker` feature flag enabled. [#55528](https://github.com/grafana/grafana/pull/55528), [@ArturWierzbicki](https://github.com/ArturWierzbicki) +- **QueryField:** Fix wrong cursor position on autocomplete. [#55576](https://github.com/grafana/grafana/pull/55576), [@svennergr](https://github.com/svennergr) + + diff --git a/conf/defaults.ini b/conf/defaults.ini index fc6452d21fe..e5c19f5e4f7 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1316,3 +1316,13 @@ full_reindex_interval = 5m # Defines the frequency of partial index updates based on recent changes such as dashboard updates. # This is a temporary settings that might be removed in the future. index_update_interval = 10s + + +# Move an app plugin referenced by its id (including all its pages) to a specific navigation section +# Dependencies: needs the `topnav` feature to be enabled +# Format: =
+[navigation.app_sections] + +# Move a specific app plugin page (referenced by its `path` field) to a specific navigation section +# Format: =
+[navigation.app_standalone_pages] diff --git a/conf/sample.ini b/conf/sample.ini index ac660f1d066..7a5dc6a38c5 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -1228,3 +1228,14 @@ # Enable or disable loading other base map layers ;enable_custom_baselayers = true + +# Move an app plugin referenced by its id (including all its pages) to a specific navigation section +# Dependencies: needs the `topnav` feature to be enabled +[navigation.app_sections] +# The following will move an app plugin with the id of `my-app-id` under the `starred` section +# my-app-id = admin + +# Move a specific app plugin page (referenced by its `path` field) to a specific navigation section +[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 \ No newline at end of file diff --git a/contribute/style-guides/themes.md b/contribute/style-guides/themes.md index 03694fd7b09..f6a0eea866b 100644 --- a/contribute/style-guides/themes.md +++ b/contribute/style-guides/themes.md @@ -25,16 +25,16 @@ import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; import { css } from '@emotion/css'; -const getComponentStyles = (theme: GrafanaTheme2) => css` - padding: ${theme.spacing.md}; -`; - -const Foo: FC = () => { - const styles = useStyles2(getComponentsStyles); +function Foo(props: FooProps) { + const styles = useStyles2(getStyles); // Use styles with className -}; +} ``` +const getStyles = (theme: GrafanaTheme2) => css({ +padding: theme.spacing(1,2) +}); + #### Get the theme object ```tsx @@ -93,7 +93,7 @@ Example use cases: | -------------------------- | ------------------------------------------------------------ | | theme.colors.border.weak | Primary border for panels and panes and other subtle borders | | theme.colors.border.medium | For stronger borders like inputs | -| theme.colors.border.strong | For even stronger border like hover highighted border | +| theme.colors.border.strong | For even stronger border like hover highlighted border | ### Actions diff --git a/devenv/datasources.yaml b/devenv/datasources.yaml index 073cc0c7ad8..6c44788edb6 100644 --- a/devenv/datasources.yaml +++ b/devenv/datasources.yaml @@ -242,6 +242,7 @@ datasources: label: "Logs to metrics" description: "Related metrics stored in Prometheus" config: + type: query target: expr: "{ job=\"test\" }" field: "labels" diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-by-value-color-schemes.json b/devenv/dev-dashboards/panel-timeseries/timeseries-by-value-color-schemes.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng-by-value-color-schemes.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-by-value-color-schemes.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-gradient-area.json b/devenv/dev-dashboards/panel-timeseries/timeseries-gradient-area.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng-gradient-area.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-gradient-area.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-hue-gradients.json b/devenv/dev-dashboards/panel-timeseries/timeseries-hue-gradients.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng-hue-gradients.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-hue-gradients.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-nulls.json b/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng-nulls.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json diff --git a/devenv/dev-dashboards/panel-timeseries/timeseries-out-of-rage.json b/devenv/dev-dashboards/panel-timeseries/timeseries-out-of-rage.json new file mode 100644 index 00000000000..1ca036ac71e --- /dev/null +++ b/devenv/dev-dashboards/panel-timeseries/timeseries-out-of-rage.json @@ -0,0 +1,513 @@ +{ + "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": 1435, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "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": 10, + "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": 9, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 3, + "maxDataPoints": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.3.0-pre", + "targets": [ + { + "csvContent": "Time,Value,Name\n2022-09-01T05:00:00Z,100,Before\n2022-09-01T06:00:00Z,100,Middle", + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "B", + "scenarioId": "csv_content" + } + ], + "title": "Before + Middle", + "type": "timeseries" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "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": 10, + "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": 9, + "w": 8, + "x": 8, + "y": 0 + }, + "id": 2, + "maxDataPoints": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.3.0-pre", + "targets": [ + { + "csvContent": "Time,Value,Name\n2022-09-01T05:00:00Z,100,Before\n2022-09-01T07:00:00Z,100,After\n", + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "B", + "scenarioId": "csv_content" + } + ], + "title": "Before + After", + "type": "timeseries" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "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": 10, + "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": 9, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 4, + "maxDataPoints": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.3.0-pre", + "targets": [ + { + "csvContent": "Time,Value,Name\n2022-09-01T06:00:00Z,100,Middle\n2022-09-01T07:00:00Z,100,After\n", + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "B", + "scenarioId": "csv_content" + } + ], + "title": "Middle + After", + "type": "timeseries" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "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": 10, + "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": 9, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 5, + "maxDataPoints": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.3.0-pre", + "targets": [ + { + "csvContent": "Time,Value,Name\n2022-09-01T04:00:00Z,100,Before1\n2022-09-01T05:00:00Z,100,Before2\n", + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "B", + "scenarioId": "csv_content" + } + ], + "title": "Two points before (show zoom button)", + "type": "timeseries" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "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": 10, + "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": 9, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 6, + "maxDataPoints": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.3.0-pre", + "targets": [ + { + "csvContent": "Time,Value,Name\n2022-09-01T07:00:00Z,100,After1\n2022-09-01T08:00:00Z,100,After2\n", + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "B", + "scenarioId": "csv_content" + } + ], + "title": "Two points after (show zoom button)", + "type": "timeseries" + } + ], + "refresh": false, + "schemaVersion": 37, + "style": "dark", + "tags": [ + "gdev", + "panel-tests", + "graph-ng" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Panel Tests - Timeseries - Out of range", + "uid": "pqnrfd4Vz", + "version": 1, + "weekStart": "" + } diff --git a/devenv/dev-dashboards/panel-graph/graph-shared-tooltip-cursor-position.json b/devenv/dev-dashboards/panel-timeseries/timeseries-shared-tooltip-cursor-position.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-shared-tooltip-cursor-position.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-shared-tooltip-cursor-position.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-soft-limits.json b/devenv/dev-dashboards/panel-timeseries/timeseries-soft-limits.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng-soft-limits.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-soft-limits.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-stacking.json b/devenv/dev-dashboards/panel-timeseries/timeseries-stacking.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng-stacking.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-stacking.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-stacking2.json b/devenv/dev-dashboards/panel-timeseries/timeseries-stacking2.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng-stacking2.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-stacking2.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-thresholds.json b/devenv/dev-dashboards/panel-timeseries/timeseries-thresholds.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng-thresholds.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-thresholds.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-time.json b/devenv/dev-dashboards/panel-timeseries/timeseries-time.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng-time.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-time.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-y-ticks-zero-decimals.json b/devenv/dev-dashboards/panel-timeseries/timeseries-y-ticks-zero-decimals.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng-y-ticks-zero-decimals.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-y-ticks-zero-decimals.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-yaxis-ticks.json b/devenv/dev-dashboards/panel-timeseries/timeseries-yaxis-ticks.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng-yaxis-ticks.json rename to devenv/dev-dashboards/panel-timeseries/timeseries-yaxis-ticks.json diff --git a/devenv/dev-dashboards/panel-graph/graph-ng.json b/devenv/dev-dashboards/panel-timeseries/timeseries.json similarity index 100% rename from devenv/dev-dashboards/panel-graph/graph-ng.json rename to devenv/dev-dashboards/panel-timeseries/timeseries.json diff --git a/docs/sources/administration/team-management/index.md b/docs/sources/administration/team-management/index.md index 8f83a8e8aaf..9f4759ef5c8 100644 --- a/docs/sources/administration/team-management/index.md +++ b/docs/sources/administration/team-management/index.md @@ -17,6 +17,8 @@ A user can be a Member or an Administrator for a given team. Members of a team i For more information about teams, refer to [Teams and permissions]({{< relref "../roles-and-permissions/#teams-and-permissions" >}}). +For information about how to optimize your teams, refer to [How to best organize your teams and resources in Grafana](https://grafana.com/blog/2022/03/14/how-to-best-organize-your-teams-and-resources-in-grafana/). + ## Create a team A team is a group of users within an organization that have common dashboard and data source permission needs. Use teams to help make user-permission management more efficient. diff --git a/docs/sources/alerting/migrating-alerts/migrating-legacy-alerts.md b/docs/sources/alerting/migrating-alerts/migrating-legacy-alerts.md index 19df966b282..e7b6fbbd54a 100644 --- a/docs/sources/alerting/migrating-alerts/migrating-legacy-alerts.md +++ b/docs/sources/alerting/migrating-alerts/migrating-legacy-alerts.md @@ -31,6 +31,8 @@ longer supported. We refer to these as [Differences]({{< relref "#differences" > 5. Unlike legacy dashboard alerts where images in notifications are enabled per contact point, images in notifications for Grafana Alerting must be enabled in the Grafana configuration, either in the configuration file or environment variables, and are enabled for either all or no contact points. Please refer to the [documentation for images in notifications]({{< relref "../images-in-notifications" >}}). +6. Grafana Alerting does not support pausing the evaluation of alert rules. After migration, all paused alert rules will become active, which may cause unexpected notifications to be sent. + ## Limitations 1. Since `Hipchat` and `Sensu` notification channels are no longer supported, legacy alerts associated with these channels are not automatically migrated to Grafana Alerting. Assign the legacy alerts to a supported notification channel so that you continue to receive notifications for those alerts. diff --git a/docs/sources/dashboards/build-dashboards/best-practices/index.md b/docs/sources/dashboards/build-dashboards/best-practices/index.md index ae04ef4dd37..c2587b98b2b 100644 --- a/docs/sources/dashboards/build-dashboards/best-practices/index.md +++ b/docs/sources/dashboards/build-dashboards/best-practices/index.md @@ -15,7 +15,9 @@ weight: 100 # Grafana dashboard best practices -This section provides information about best practices for intermediate Grafana administrators and users. Click on each of the links before for more information. +This section provides information about best practices for intermediate Grafana administrators and users about how to build and maintain Grafana dashboards. + +For more information about the different kinds of dashboards you can create, refer to [Grafana dashboards: A complete guide to all the different types you can build](https://grafana.com/blog/2022/06/06/grafana-dashboards-a-complete-guide-to-all-the-different-types-you-can-build/?pg=webinar-getting-started-with-grafana-dashboard-design-amer&plcmt=related-content-1). ## Common observability strategies diff --git a/docs/sources/dashboards/create-reports/index.md b/docs/sources/dashboards/create-reports/index.md index 93d61cc4f05..382292fb765 100644 --- a/docs/sources/dashboards/create-reports/index.md +++ b/docs/sources/dashboards/create-reports/index.md @@ -28,6 +28,8 @@ Reporting enables you to automatically generate PDFs from any of your dashboards Any changes you make to a dashboard used in a report are reflected the next time the report is sent. For example, if you change the time range in the dashboard, then the time range in the report also changes. +For information about recent improvements to the reporting UI, refer to [Grafana reporting: How we improved the UX in Grafana](https://grafana.com/blog/2022/06/29/grafana-reporting-how-we-improved-the-ux-in-grafana/). + ## Requirements - SMTP must be configured for reports to be sent. Refer to [SMTP]({{< relref "../../setup-grafana/configure-grafana/#smtp" >}}) in [Configuration]({{< relref "../../setup-grafana/configure-grafana/" >}}) for more information. diff --git a/docs/sources/dashboards/variables/add-template-variables/index.md b/docs/sources/dashboards/variables/add-template-variables/index.md index e04a7b94ef1..d53ffb6270c 100644 --- a/docs/sources/dashboards/variables/add-template-variables/index.md +++ b/docs/sources/dashboards/variables/add-template-variables/index.md @@ -113,6 +113,8 @@ For example, if you have server names or region names that never change, then yo _Text box_ variables display a free text input field with an optional default value. This is the most flexible variable, because you can enter any value. Use this type of variable if you have metrics with high cardinality or if you want to update multiple panels in a dashboard at the same time. +For more information about cardinality, refer to [What are cardinality spikes and why do they matter?](https://grafana.com/blog/2022/02/15/what-are-cardinality-spikes-and-why-do-they-matter/) + 1. [Enter general options](#enter-general-options). 1. (Optional) In the **Default value** field, select the default value for the variable. If you do not enter anything in this field, then Grafana displays an empty text box for users to type text into. 1. In **Preview of values**, Grafana displays a list of the current variable values. Review them to ensure they match what you expect. diff --git a/docs/sources/datasources/mssql.md b/docs/sources/datasources/mssql.md index 0350a0841af..b57b2766549 100644 --- a/docs/sources/datasources/mssql.md +++ b/docs/sources/datasources/mssql.md @@ -75,18 +75,51 @@ Make sure the user does not get any unwanted privileges from the public role. If you're using an older version of Microsoft SQL Server like 2008 and 2008R2 you may need to disable encryption to be able to connect. If possible, we recommend you to use the latest service pack available for optimal compatibility. -## Query Editor +## Query builder -{{< figure src="/static/img/docs/v51/mssql_query_editor.png" class="docs-image--no-shadow" >}} +{{< figure src="/static/img/docs/v92/mssql_query_builder.png" class="docs-image--no-shadow" >}} -You will find the MSSQL query editor in the metrics tab in Graph, Singlestat or Table panel's edit mode. You enter edit mode by clicking the -panel title, then edit. The editor allows you to define a SQL query to select data to be visualized. +The MS SQL query builder is available when editing a panel using a MS SQL data source. The built query can be run by pressing the `Run query` button in the top right corner of the editor. -1. Select _Format as_ `Time series` (for use in Graph or Singlestat panel's among others) or `Table` (for use in Table panel among others). -1. This is the actual editor where you write your SQL queries. -1. Show help section for MSSQL below the query editor. -1. Show actual executed SQL query. Will be available first after a successful query has been executed. -1. Add an additional query where an additional query editor will be displayed. +### Format + +The response from MS SQL can be formatted as either a table or as a time series. To use the time series format one of the columns must be named `time`. + +### Dataset and Table selection + +In the dataset dropdown, choose the MS SQL database to query. The dropdown is be populated with the databases that the user has access to. +When the dataset is selected, the table dropdown is populated with the tables that are available. + +### Columns and Aggregation functions (SELECT) + +Using the dropdown, select a column to include in the data. You can also specify an optional aggregation function. + +Add further value columns by clicking the plus button and another column dropdown appears. + +### Filter data (WHERE) + +To add a filter, flip the switch at the top of the editor. +Using the first dropdown, select if all the filters need to match (AND) or if only one of the filters needs to match (OR). + +To add more columns to filter on use the plus button. + +### Group By + +To group the results by column, flip the group switch at the top of the editor. You can then choose which column to group the results by. The group by clause can be removed by pressing the X button. + +### Preview + +By flipping the preview switch at the top of the editor, you can get a preview of the SQL query generated by the query builder. + +## Code editor + +{{< figure src="/static/img/docs/v92/sql_code_editor.png" class="docs-image--no-shadow" >}} + +To make advanced queries, switch to the code editor by clicking `code` in the top right corner of the editor. The code editor support autocompletion of tables, columns, SQL keywords, standard sql functions, Grafana template variables and Grafana macros. Columns cannot be completed before a table has been specified. + +You can expand the code editor by pressing the `chevron` pointing downwards in the lower right corner of the code editor. + +`CTRL/CMD + Return` works as a keyboard shortcut to run the query.
diff --git a/docs/sources/datasources/mysql.md b/docs/sources/datasources/mysql.md index c0e582632e0..e6270f59514 100644 --- a/docs/sources/datasources/mysql.md +++ b/docs/sources/datasources/mysql.md @@ -73,60 +73,51 @@ Example: You can use wildcards (`*`) in place of database or table if you want to grant access to more databases and tables. -## Query Editor +## Query builder -> Only available in Grafana v5.4+. +{{< figure src="/static/img/docs/v92/mysql_query_builder.png" class="docs-image--no-shadow" >}} -{{< figure src="/static/img/docs/v54/mysql_query_still.png" class="docs-image--no-shadow" animated-gif="/static/img/docs/v54/mysql_query.gif" >}} +The MySQL query builder is available when editing a panel using a MySQL data source. The built query can be run by pressing the `Run query` button in the top right corner of the editor. -You find the MySQL query editor in the metrics tab in a panel's edit mode. You enter edit mode by clicking the -panel title, then edit. +### Format -The query editor has a link named `Generated SQL` that shows up after a query has been executed, while in panel edit mode. Click on it and it will expand and show the raw interpolated SQL string that was executed. +The response from MySQL can be formatted as either a table or as a time series. To use the time series format one of the columns must be named `time`. -### Select table, time column and metric column (FROM) +### Dataset and Table selection -When you enter edit mode for the first time or add a new query Grafana will try to prefill the query builder with the first table that has a timestamp column and a numeric column. - -In the FROM field, Grafana will suggest tables that are in the configured database. To select a table or view in another database that your database user has access to you can manually enter a fully qualified name (database.table) like `otherDb.metrics`. - -The Time column field refers to the name of the column holding your time values. Selecting a value for the Metric column field is optional. If a value is selected, the Metric column field will be used as the series name. - -The metric column suggestions will only contain columns with a text datatype (text, tinytext, mediumtext, longtext, varchar, char). -If you want to use a column with a different datatype as metric column you may enter the column name with a cast: `CAST(numericColumn as CHAR)`. -You may also enter arbitrary SQL expressions in the metric column field that evaluate to a text datatype like -`CONCAT(column1, " ", CAST(numericColumn as CHAR))`. +In the dataset dropdown, choose the MySQL database to query. The dropdown is be populated with the databases that the user has access to. +When the dataset is selected, the table dropdown is populated with the tables that are available. ### Columns and Aggregation functions (SELECT) -In the `SELECT` row you can specify what columns and functions you want to use. -In the column field you may write arbitrary expressions instead of a column name like `column1 * column2 / column3`. +Using the dropdown, select a column to include in the data. You can also specify an optional aggregation function. -If you use aggregate functions you need to group your resultset. The editor will automatically add a `GROUP BY time` if you add an aggregate function. - -You may add further value columns by clicking the plus button and selecting `Column` from the menu. Multiple value columns will be plotted as separate series in the graph panel. +Add further value columns by clicking the plus button and another column dropdown appears. ### Filter data (WHERE) -To add a filter click the plus icon to the right of the `WHERE` condition. You can remove filters by clicking on -the filter and selecting `Remove`. A filter for the current selected timerange is automatically added to new queries. +To add a filter, flip the switch at the top of the editor. +Using the first dropdown, select if all the filters need to match (AND) or if only one of the filters needs to match (OR). + +To add more columns to filter on use the plus button. ### Group By -To group by time or any other columns click the plus icon at the end of the GROUP BY row. The suggestion dropdown will only show text columns of your currently selected table but you may manually enter any column. -You can remove the group by clicking on the item and then selecting `Remove`. +To group the results by column, flip the group switch at the top of the editor. You can then choose which column to group the results by. The group by clause can be removed by pressing the X button. -If you add any grouping, all selected columns need to have an aggregate function applied. The query builder will automatically add aggregate functions to all columns without aggregate functions when you add groupings. +### Preview -#### Gap Filling +By flipping the preview switch at the top of the editor, you can get a preview of the SQL query generated by the query builder. -Grafana can fill in missing values when you group by time. The time function accepts two arguments. The first argument is the time window that you would like to group by, and the second argument is the value you want Grafana to fill missing items with. +## Code editor -### Text Editor Mode (RAW) +{{< figure src="/static/img/docs/v92/sql_code_editor.png" class="docs-image--no-shadow" >}} -You can switch to the raw query editor mode by clicking the hamburger icon and selecting `Switch editor mode` or by clicking `Edit SQL` below the query. +To make advanced queries, switch to the code editor by clicking `code` in the top right corner of the editor. The code editor support autocompletion of tables, columns, SQL keywords, standard sql functions, Grafana template variables and Grafana macros. Columns cannot be completed before a table has been specified. -> If you use the raw query editor, be sure your query at minimum has `ORDER BY time` and a filter on the returned time range. +You can expand the code editor by pressing the `chevron` pointing downwards in the lower right corner of the code editor. + +`CTRL/CMD + Return` works as a keyboard shortcut to run the query. ## Macros diff --git a/docs/sources/datasources/postgres.md b/docs/sources/datasources/postgres.md index 32b34124563..406e44ab02e 100644 --- a/docs/sources/datasources/postgres.md +++ b/docs/sources/datasources/postgres.md @@ -19,22 +19,22 @@ Grafana ships with a built-in PostgreSQL data source plugin that allows you to q To access PostgreSQL settings, hover your mouse over the **Configuration** (gear) icon, then click **Data Sources**, and then click the PostgreSQL data source. -| Name | Description | -| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Name` | The data source name. This is how you refer to the data source in panels and queries. | -| `Default` | Default data source means that it will be pre-selected for new panels. | -| `Host` | The IP address/hostname and optional port of your PostgreSQL instance. _Do not_ include the database name. The connection string for connecting to Postgres will not be correct and it may cause errors. | -| `Database` | Name of your PostgreSQL database. | -| `User` | Database user's login/username | -| `Password` | Database user's password | -| `SSL Mode` | Determines whether or with what priority a secure SSL TCP/IP connection will be negotiated with the server. When SSL Mode is disabled, SSL Method and Auth Details would not be visible. | -| `SSL Auth Details Method` | Determines whether the SSL Auth details will be configured as a file path or file content. Grafana v7.5+ | -| `SSL Auth Details Value` | File path or file content of SSL root certificate, client certificate and client key | -| `Max open` | The maximum number of open connections to the database, default `unlimited` (Grafana v5.4+). | -| `Max idle` | The maximum number of connections in the idle connection pool, default `2` (Grafana v5.4+). | -| `Max lifetime` | The maximum amount of time in seconds a connection may be reused, default `14400`/4 hours (Grafana v5.4+). | -| `Version` | Determines which functions are available in the query builder (only available in Grafana 5.3+). | -| `TimescaleDB` | A time-series database built as a PostgreSQL extension. When enabled, Grafana uses `time_bucket` in the `$__timeGroup` macro to display TimescaleDB specific aggregate functions in the query builder (only available in Grafana 5.3+). | +| Name | Description | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Name` | The data source name. This is how you refer to the data source in panels and queries. | +| `Default` | Default data source means that it will be pre-selected for new panels. | +| `Host` | The IP address/hostname and optional port of your PostgreSQL instance. _Do not_ include the database name. The connection string for connecting to Postgres will not be correct and it may cause errors. | +| `Database` | Name of your PostgreSQL database. | +| `User` | Database user's login/username | +| `Password` | Database user's password | +| `SSL Mode` | Determines whether or with what priority a secure SSL TCP/IP connection will be negotiated with the server. When SSL Mode is disabled, SSL Method and Auth Details would not be visible. | +| `SSL Auth Details Method` | Determines whether the SSL Auth details will be configured as a file path or file content. Grafana v7.5+ | +| `SSL Auth Details Value` | File path or file content of SSL root certificate, client certificate and client key | +| `Max open` | The maximum number of open connections to the database, default `unlimited` (Grafana v5.4+). | +| `Max idle` | The maximum number of connections in the idle connection pool, default `2` (Grafana v5.4+). | +| `Max lifetime` | The maximum amount of time in seconds a connection may be reused, default `14400`/4 hours (Grafana v5.4+). | +| `Version` | Determines which functions are available in the query builder (only available in Grafana 5.3+). | +| `TimescaleDB` | A time-series database built as a PostgreSQL extension. When enabled, Grafana uses `time_bucket` in the `$__timeGroup` macro to display TimescaleDB specific aggregate functions in the query builder (only available in Grafana 5.3+). For more information, see [TimescaleDB documentation](https://docs.timescale.com/timescaledb/latest/tutorials/grafana/grafana-timescalecloud/#connect-timescaledb-and-grafana). | ### Min time interval @@ -71,69 +71,51 @@ Example: Make sure the user does not get any unwanted privileges from the public role. -## Query editor +## Query builder -{{< figure src="/static/img/docs/v53/postgres_query_still.png" class="docs-image--no-shadow" animated-gif="/static/img/docs/v53/postgres_query.gif" >}} +{{< figure src="/static/img/docs/v92/postgresql_query_builder.png" class="docs-image--no-shadow" >}} -You find the PostgreSQL query editor in the metrics tab in Graph or Singlestat panel's edit mode. You enter edit mode by clicking the -panel title, then edit. +The PostgreSQL query builder is available when editing a panel using a PostgreSQL data source. The built query can be run by pressing the `Run query` button in the top right corner of the editor. -The query editor has a link named `Generated SQL` that shows up after a query has been executed, while in panel edit mode. Click on it and it will expand and show the raw interpolated SQL string that was executed. +### Format -### Select table, time column and metric column (FROM) +The response from PostgreSQL can be formatted as either a table or as a time series. To use the time series format one of the columns must be named `time`. -When you enter edit mode for the first time or add a new query Grafana will try to prefill the query builder with the first table that has a timestamp column and a numeric column. +### Dataset and Table selection -In the FROM field, Grafana will suggest tables that are in the `search_path` of the database user. To select a table or view not in your `search_path` -you can manually enter a fully qualified name (schema.table) like `public.metrics`. +In the dataset dropdown, choose the PostgreSQL database to query. The dropdown is be populated with the databases that the user has access to. +When the dataset is selected, the table dropdown is populated with the tables that are available. -The Time column field refers to the name of the column holding your time values. Selecting a value for the Metric column field is optional. If a value is selected, the Metric column field will be used as the series name. +### Columns and Aggregation functions (SELECT) -The metric column suggestions will only contain columns with a text datatype (char,varchar,text). -If you want to use a column with a different datatype as metric column you may enter the column name with a cast: `ip::text`. -You may also enter arbitrary SQL expressions in the metric column field that evaluate to a text datatype like -`hostname || ' ' || container_name`. +Using the dropdown, select a column to include in the data. You can also specify an optional aggregation function. -### Columns, window, and aggregation functions (SELECT) - -In the `SELECT` row you can specify what columns and functions you want to use. -In the column field you may write arbitrary expressions instead of a column name like `column1 * column2 / column3`. - -The available functions in the query editor depend on the PostgreSQL version you selected when configuring the data source. -If you use aggregate functions you need to group your resultset. The editor will automatically add a `GROUP BY time` if you add an aggregate function. - -The editor tries to simplify and unify this part of the query. For example:
-![](/static/img/docs/v53/postgres_select_editor.png)
- -The above will generate the following PostgreSQL `SELECT` clause: - -```sql -avg(tx_bytes) OVER (ORDER BY "time" ROWS 5 PRECEDING) AS "tx_bytes" -``` - -You may add further value columns by clicking the plus button and selecting `Column` from the menu. Multiple value columns will be plotted as separate series in the graph panel. +Add further value columns by clicking the plus button and another column dropdown appears. ### Filter data (WHERE) -To add a filter click the plus icon to the right of the `WHERE` condition. You can remove filters by clicking on -the filter and selecting `Remove`. A filter for the current selected timerange is automatically added to new queries. +To add a filter, flip the switch at the top of the editor. +Using the first dropdown, select if all the filters need to match (AND) or if only one of the filters needs to match (OR). -### Group by +To add more columns to filter on use the plus button. -To group by time or any other columns click the plus icon at the end of the GROUP BY row. The suggestion dropdown will only show text columns of your currently selected table but you may manually enter any column. -You can remove the group by clicking on the item and then selecting `Remove`. +### Group By -If you add any grouping, all selected columns need to have an aggregate function applied. The query builder will automatically add aggregate functions to all columns without aggregate functions when you add groupings. +To group the results by column, flip the group switch at the top of the editor. You can then choose which column to group the results by. The group by clause can be removed by pressing the X button. -#### Gap filling +### Preview -Grafana can fill in missing values when you group by time. The time function accepts two arguments. The first argument is the time window that you would like to group by, and the second argument is the value you want Grafana to fill missing items with. +By flipping the preview switch at the top of the editor, you can get a preview of the SQL query generated by the query builder. -### Text editor mode (RAW) +## Code editor -You can switch to the raw query editor mode by clicking the hamburger icon and selecting `Switch editor mode` or by clicking `Edit SQL` below the query. +{{< figure src="/static/img/docs/v92/sql_code_editor.png" class="docs-image--no-shadow" >}} -> If you use the raw query editor, be sure your query at minimum has `ORDER BY time` and a filter on the returned time range. +To make advanced queries, switch to the code editor by clicking `code` in the top right corner of the editor. The code editor support autocompletion of tables, columns, SQL keywords, standard sql functions, Grafana template variables and Grafana macros. Columns cannot be completed before a table has been specified. + +You can expand the code editor by pressing the `chevron` pointing downwards in the lower right corner of the code editor. + +`CTRL/CMD + Return` works as a keyboard shortcut to run the query. ## Macros diff --git a/docs/sources/developers/http_api/correlations.md b/docs/sources/developers/http_api/correlations.md index 9d536b35916..bc5655eca01 100644 --- a/docs/sources/developers/http_api/correlations.md +++ b/docs/sources/developers/http_api/correlations.md @@ -34,6 +34,11 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk "targetUID": "PDDA8E780A17E7EF1", "label": "My Label", "description": "Logs to Traces", + "config": { + "type": "query", + "field": "message", + "target": {}, + } } ``` @@ -55,7 +60,12 @@ Content-Type: application/json "label": "My Label", "sourceUID": "uyBf2637k", "targetUID": "PDDA8E780A17E7EF1", - "uid": "50xhMlg9k" + "uid": "50xhMlg9k", + "config": { + "type": "query", + "field": "message", + "target": {}, + } } } ``` @@ -138,7 +148,12 @@ Content-Type: application/json "label": "My Label", "sourceUID": "uyBf2637k", "targetUID": "PDDA8E780A17E7EF1", - "uid": "J6gn7d31L" + "uid": "J6gn7d31L", + "config": { + "type": "query", + "field": "message", + "target": {} + } } } ``` @@ -175,7 +190,12 @@ Content-Type: application/json "label": "My Label", "sourceUID": "uyBf2637k", "targetUID": "PDDA8E780A17E7EF1", - "uid": "J6gn7d31L" + "uid": "J6gn7d31L", + "config": { + "type": "query", + "field": "message", + "target": {}, + } } ``` @@ -211,14 +231,24 @@ Content-Type: application/json "label": "My Label", "sourceUID": "uyBf2637k", "targetUID": "PDDA8E780A17E7EF1", - "uid": "J6gn7d31L" + "uid": "J6gn7d31L", + "config": { + "type": "query", + "field": "message", + "target": {}, + } }, { "description": "Logs to Metrics", "label": "Another Label", "sourceUID": "uyBf2637k", "targetUID": "P15396BDD62B2BE29", - "uid": "uWCpURgVk" + "uid": "uWCpURgVk", + "config": { + "type": "query", + "field": "message", + "target": {}, + } } ] ``` @@ -255,14 +285,24 @@ Content-Type: application/json "label": "My Label", "sourceUID": "uyBf2637k", "targetUID": "PDDA8E780A17E7EF1", - "uid": "J6gn7d31L" + "uid": "J6gn7d31L", + "config": { + "type": "query", + "field": "message", + "target": {}, + } }, { "description": "Loki to Tempo", "label": "Another Label", "sourceUID": "PDDA8E780A17E7EF1", "targetUID": "P15396BDD62B2BE29", - "uid": "uWCpURgVk" + "uid": "uWCpURgVk", + "config": { + "type": "query", + "field": "message", + "target": {}, + } } ] ``` diff --git a/docs/sources/release-notes/_index.md b/docs/sources/release-notes/_index.md index e868bcc5819..e23d93525ac 100644 --- a/docs/sources/release-notes/_index.md +++ b/docs/sources/release-notes/_index.md @@ -10,6 +10,7 @@ weight: 10000 Here you can find detailed release notes that list everything that is included in every release as well as notices about deprecations, breaking changes as well as changes that relate to plugin development. +- [Release notes for 9.1.7]({{< relref "release-notes-9-1-7" >}}) - [Release notes for 9.1.6]({{< relref "release-notes-9-1-6" >}}) - [Release notes for 9.1.5]({{< relref "release-notes-9-1-5" >}}) - [Release notes for 9.1.4]({{< relref "release-notes-9-1-4" >}}) diff --git a/docs/sources/release-notes/release-notes-9-1-7.md b/docs/sources/release-notes/release-notes-9-1-7.md new file mode 100644 index 00000000000..1802941687a --- /dev/null +++ b/docs/sources/release-notes/release-notes-9-1-7.md @@ -0,0 +1,33 @@ ++++ +title = "Release notes for Grafana 9.1.7" +hide_menu = true ++++ + + + +# Release notes for Grafana 9.1.7 + +### Features and enhancements + +- **Chore:** Upgrade Go version to 1.19.1 (backport). [#55733](https://github.com/grafana/grafana/pull/55733), [@sakjur](https://github.com/sakjur) +- **CloudWatch:** Add missing AWS/Prometheus metrics. [#54990](https://github.com/grafana/grafana/pull/54990), [@jangaraj](https://github.com/jangaraj) +- **Explore:** Add feature tracking events. [#54514](https://github.com/grafana/grafana/pull/54514), [@L-M-K-B](https://github.com/L-M-K-B) +- **Graphite:** Add error information to graphite queries tracing. [#55249](https://github.com/grafana/grafana/pull/55249), [@jesusvazquez](https://github.com/jesusvazquez) +- **Prometheus:** Restore FromAlert header. [#55255](https://github.com/grafana/grafana/pull/55255), [@kylebrandt](https://github.com/kylebrandt) +- **SAML:** Account for all orgs in org_mapping (#3855). (Enterprise) +- **Search:** Add search index configuration options. [#55525](https://github.com/grafana/grafana/pull/55525), [@ArturWierzbicki](https://github.com/ArturWierzbicki) +- **Thresholds:** Add option for dashed line style. [#55875](https://github.com/grafana/grafana/pull/55875), [@leeoniya](https://github.com/leeoniya) + +### Bug fixes + +- **Alerting:** Fix default query's data source when no default datasource specified. [#55435](https://github.com/grafana/grafana/pull/55435), [@konrad147](https://github.com/konrad147) +- **Alerting:** Fix mathexp.NoData cannot be reduced. [#55347](https://github.com/grafana/grafana/pull/55347), [@grobinson-grafana](https://github.com/grobinson-grafana) +- **Alerting:** Skip unsupported file types on provisioning. [#55573](https://github.com/grafana/grafana/pull/55573), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) +- **AzureMonitor:** Ensure resourceURI template variable is migrated. [#56095](https://github.com/grafana/grafana/pull/56095), [@aangelisc](https://github.com/aangelisc) +- **Dashboard:** Fix plugin dashboard save as button. [#55197](https://github.com/grafana/grafana/pull/55197), [@lpskdl](https://github.com/lpskdl) +- **Docs:** Fix decimals: auto docs for panel edit. [#55477](https://github.com/grafana/grafana/pull/55477), [@joshhunt](https://github.com/joshhunt) +- **Fix:** RBAC handle `error no resolver` found. [#55676](https://github.com/grafana/grafana/pull/55676), [@gamab](https://github.com/gamab) +- **Fix:** RBAC handle `error no resolver` found. (Enterprise) +- **LibraryPanelSearch:** Refactor and fix hyphen issue. [#55314](https://github.com/grafana/grafana/pull/55314), [@kaydelaney](https://github.com/kaydelaney) +- **Live:** Fix live streaming with `live-service-web-worker` feature flag enabled. [#55528](https://github.com/grafana/grafana/pull/55528), [@ArturWierzbicki](https://github.com/ArturWierzbicki) +- **QueryField:** Fix wrong cursor position on autocomplete. [#55576](https://github.com/grafana/grafana/pull/55576), [@svennergr](https://github.com/svennergr) diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/ldap.md b/docs/sources/setup-grafana/configure-security/configure-authentication/ldap.md index 181ccac8f92..f097746d744 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/ldap.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/ldap.md @@ -190,7 +190,7 @@ org_role = "Viewer" | Setting | Required | Description | Default | | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | | `group_dn` | Yes | LDAP distinguished name (DN) of LDAP group. If you want to match all (or no LDAP groups) then you can use wildcard (`"*"`) | -| `org_role` | Yes | Assign users of `group_dn` the organization role `"Admin"`, `"Editor"` or `"Viewer"` | +| `org_role` | Yes | Assign users of `group_dn` the organization role `Admin`, `Editor`, or `Viewer`. The organization role name is case sensitive. | | `org_id` | No | The Grafana organization database id. Setting this allows for multiple group_dn's to be assigned to the same `org_role` provided the `org_id` differs | `1` (default org id) | | `grafana_admin` | No | When `true` makes user of `group_dn` Grafana server admin. A Grafana server admin has admin access over all organizations and users. Available in Grafana v5.3 and above | `false` | diff --git a/docs/sources/whatsnew/whats-new-in-v9-2.md b/docs/sources/whatsnew/whats-new-in-v9-2.md index c754c394c5e..5c608abb0bb 100644 --- a/docs/sources/whatsnew/whats-new-in-v9-2.md +++ b/docs/sources/whatsnew/whats-new-in-v9-2.md @@ -66,6 +66,14 @@ This change does not update existing alert rules. For details on this change, see [GitHub pull request #55345](https://github.com/grafana/grafana/pull/55345). For more information about alerting states, see the [alerting documentation](https://grafana.com/docs/grafana/latest/alerting/fundamentals/state-and-health/). +## Configure external alertmanagers as data sources + +_Generally available in all editions._ + +Starting with release 9.2, the URL configuration of external alertmanagers from the Admin tab on the Alerting page is deprecated. It will be removed in a future release. + +External alertmanagers should now be configured as data sources using Grafana Configuration from the main Grafana navigation menu. This enables you to manage the contact points and notification policies of external alertmanagers from within Grafana and also encrypts HTTP basic authentication credentials that were previously visible when configuring external alertmanagers by URL. + ## Public dashboards _Available in Experimental in Grafana Open Source, Enterprise, Cloud Pro, and Cloud Advanced._ @@ -128,19 +136,6 @@ We have extended the Join transformation to support both inner and outer JOINs, Also, you can now click on the `x` to clear values in the select fields for the Outer Join and Grouping to Matrix transformations as expected. -## Custom branding previews - -_Available in beta in Grafana Enterprise._ - -Use custom branding to make Grafana _your_ observability tool by adding your own sign-in page, docs and help links, logo, application name, and more. -Previously, you could configure custom branding only in Grafana's configuration files. -In Grafana v9.1, we introduced a UI where you can update and upload content. -In v9.2, you can preview what your customized Grafana will look like on the custom branding page before applying your changes. - -Turn on the custom branding configuration page and API with the `customBranding` [feature toggle](https://grafana.com/docs/grafana/latest/packages_api/data/featuretoggles/), and learn more about what you can do with custom branding in our [documentation](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/configure-custom-branding/). - -{{< figure src="/static/img/docs/enterprise/custom-branding-preview-9-2.png" max-width="750px" caption="Previewing a custom branding configuration" >}} - ## Simplified UI to create template variable queries for Loki data source _Generally available in Grafana Open Source._ diff --git a/e2e/dashboards-suite/dashboard-time-zone.spec.ts b/e2e/dashboards-suite/dashboard-time-zone.spec.ts index 510abeccdb7..125fbc51306 100644 --- a/e2e/dashboards-suite/dashboard-time-zone.spec.ts +++ b/e2e/dashboards-suite/dashboard-time-zone.spec.ts @@ -61,8 +61,8 @@ e2e.scenario({ e2e.components.Select.option().should('be.visible').contains(toTimeZone).click(); // click to go back to the dashboard. - e2e.components.BackButton.backArrow().click({ force: true }).wait(5000); - e2e.components.RefreshPicker.runButtonV2().click(); + e2e.components.BackButton.backArrow().click({ force: true }); + e2e.components.RefreshPicker.runButtonV2().should('be.visible').click(); for (const title of panelsToCheck) { e2e.components.Panels.Panel.containerByTitle(title) @@ -76,7 +76,9 @@ e2e.scenario({ const inUtc = timesInUtc[title]; const inTz = element.text(); const isCorrect = isTimeCorrect(inUtc, inTz, offset); - assert.isTrue(isCorrect, `Panel with title: "${title}"`); + expect(isCorrect, `Expect the panel "${title}" to have the new timezone applied but isn't`).to.be.equal( + true + ); }) ); } diff --git a/go.mod b/go.mod index 46db1203e84..088dfd0161b 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f github.com/aws/aws-sdk-go v1.44.9 github.com/beevik/etree v1.1.0 - github.com/benbjohnson/clock v1.1.0 + github.com/benbjohnson/clock v1.3.0 github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b github.com/centrifugal/centrifuge v0.25.0 github.com/cortexproject/cortex v1.10.1-0.20211014125347-85c378182d0d @@ -56,7 +56,7 @@ require ( 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.0 + github.com/grafana/grafana-azure-sdk-go v1.3.1 github.com/grafana/grafana-plugin-sdk-go v0.139.0 github.com/grafana/thema v0.0.0-20220817114012-ebeee841c104 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 @@ -81,7 +81,7 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 - github.com/prometheus/alertmanager v0.24.0 + github.com/prometheus/alertmanager v0.24.1-0.20221003101219-ae510d09c048 github.com/prometheus/client_golang v1.13.0 github.com/prometheus/client_model v0.2.0 github.com/prometheus/common v0.37.0 @@ -137,8 +137,6 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/FZambia/eagle v0.0.2 // indirect github.com/FZambia/sentinel v1.1.0 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/andybalholm/brotli v1.0.3 github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 // indirect @@ -162,12 +160,12 @@ require ( github.com/go-openapi/analysis v0.21.2 // indirect github.com/go-openapi/errors v0.20.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/loads v0.21.1 github.com/go-openapi/runtime v0.23.1 // indirect - github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/spec v0.20.6 // indirect github.com/go-openapi/swag v0.21.1 // indirect - github.com/go-openapi/validate v0.21.0 // indirect + github.com/go-openapi/validate v0.22.0 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/status v1.1.0 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect @@ -322,7 +320,7 @@ require ( github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect github.com/klauspost/compress v1.15.2 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/labstack/echo/v4 v4.7.2 // indirect + github.com/labstack/echo/v4 v4.9.0 // indirect github.com/labstack/gommon v0.3.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect @@ -370,3 +368,7 @@ replace github.com/microcosm-cc/bluemonday => github.com/microcosm-cc/bluemonday // 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+. replace xorm.io/xorm => github.com/grafana/xorm v0.8.3-0.20220614223926-2fcda7565af6 + +// Use our fork of the upstream alertmanagers. +// This is required in order to get notification delivery errors from the receivers API. +replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.24.0-rc.0.0.20220930143838-d75bdc5543c0 diff --git a/go.sum b/go.sum index 519b3771fb6..0727a2c26eb 100644 --- a/go.sum +++ b/go.sum @@ -259,10 +259,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C6 github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RoaringBitmap/gocroaring v0.4.0/go.mod h1:NieMwz7ZqwU2DD73/vvYwv7r4eWBKuPVSXZIpsaMwCI= github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76/go.mod h1:oM0MHmQ3nDsq609SS36p+oYbRi16+oVvU2Bw4Ipv0SE= @@ -357,7 +355,6 @@ github.com/aws/aws-sdk-go v1.38.3/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zK github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.60/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.40.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.40.37/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.43.11/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= @@ -412,8 +409,9 @@ github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/immutable v0.2.1/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/benbjohnson/tmpl v1.0.0/go.mod h1:igT620JFIi44B6awvU9IsDhR77IXWtFigTLil/RPdps= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -475,11 +473,8 @@ github.com/caio/go-tdigest v3.1.0+incompatible h1:uoVMJ3Q5lXmVLCCqaMGHLBWnbGoN6L github.com/caio/go-tdigest v3.1.0+incompatible/go.mod h1:sHQM/ubZStBUmF1WbB8FAm8q9GjDajLC5T7ydxE3JHI= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.31.6/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= -github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v1.0.0/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= -github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -490,7 +485,6 @@ github.com/centrifugal/centrifuge v0.25.0/go.mod h1:bFcSFalnROq/wcFeRiTG+wIbHsxE github.com/centrifugal/protocol v0.8.10 h1:eezzBIU/4pWyl7a+NUnANYojJBASqbkPZcQh9b8YQRI= github.com/centrifugal/protocol v0.8.10/go.mod h1:dlHBjKakr0r+f1pkfwSMfZ+cnpvidN7pQe1ZrsKfhtE= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -937,7 +931,6 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= @@ -950,7 +943,6 @@ github.com/go-openapi/analysis v0.20.1/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxl github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU= github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= @@ -964,7 +956,6 @@ github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02E github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.17.2/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -972,16 +963,15 @@ github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUe github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.17.2/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= @@ -995,21 +985,17 @@ github.com/go-openapi/loads v0.20.2/go.mod h1:hTVUotJ+UonAMMZsvakEgmWKgtulweO9vY github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0= github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.18.0/go.mod h1:uI6pHuxWYTy94zZxgcwJkUWa9wbIlhteGfloI10GD4U= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= github.com/go-openapi/runtime v0.19.3/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= -github.com/go-openapi/runtime v0.19.26/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M= github.com/go-openapi/runtime v0.19.28/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M= -github.com/go-openapi/runtime v0.19.29/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M= github.com/go-openapi/runtime v0.23.1 h1:/Drg9R96eMmgKJHVWZADz78XbE39/6QiIiB45mc+epo= github.com/go-openapi/runtime v0.23.1/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= @@ -1021,10 +1007,10 @@ github.com/go-openapi/spec v0.19.15/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFu github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ= github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg= -github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= @@ -1041,7 +1027,6 @@ github.com/go-openapi/strfmt v0.21.2 h1:5NDNgadiX1Vhemth/TH4gCGopWSTdDjxl60H3B7f github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.4/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= @@ -1055,7 +1040,6 @@ github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/ github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= @@ -1066,8 +1050,9 @@ github.com/go-openapi/validate v0.19.14/go.mod h1:PdGrHe0rp6MG3A1SrAY/rIHATqzJEE github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI= github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0= github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0= -github.com/go-openapi/validate v0.21.0 h1:+Wqk39yKOhfpLqNLEC0/eViCkzM5FVXVqrvt526+wcI= github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= +github.com/go-openapi/validate v0.22.0 h1:b0QecH6VslW/TxtpKgzpO1SNG7GU2FsaqKdP1E2T50Y= +github.com/go-openapi/validate v0.22.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 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= @@ -1305,7 +1290,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1376,13 +1360,15 @@ github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5J github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/grafana/grafana-aws-sdk v0.11.0 h1:ncPD/UN0wNcKq3kEU90RdvrnK/6R4VW2Lo5dPcGk9t0= github.com/grafana/grafana-aws-sdk v0.11.0/go.mod h1:5Iw3xY7iXJfNaYHrRHMXa/kaB2lWoyntg71PPLGvSs8= -github.com/grafana/grafana-azure-sdk-go v1.3.0 h1:zboQpq/ljBjqHo/6UQNZAUwqGTtnEGRYSEnqIQvLuAo= -github.com/grafana/grafana-azure-sdk-go v1.3.0/go.mod h1:rgrnK9m6CgKlgx4rH3FFP/6dTdyRO6LYC2mVZov35yo= +github.com/grafana/grafana-azure-sdk-go v1.3.1 h1:xTgBmbDxUPj3X9Pl9vgIOgZoDdtxWl0fYDuHrHr79jM= +github.com/grafana/grafana-azure-sdk-go v1.3.1/go.mod h1:rgrnK9m6CgKlgx4rH3FFP/6dTdyRO6LYC2mVZov35yo= 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.139.0 h1:2RQKM2QpSaWTtaGN6sK+R7LO7zykOeTYF0QkAMA7JsI= github.com/grafana/grafana-plugin-sdk-go v0.139.0/go.mod h1:Y+Ps2sesZ62AyCnX+hzrYnyDQYe/ZZl+A8yKLOBm12c= +github.com/grafana/prometheus-alertmanager v0.24.0-rc.0.0.20220930143838-d75bdc5543c0 h1:Ifcxl2wKT+UoJE+d2hsEZjH5FVdF5nML+1dtMliFk78= +github.com/grafana/prometheus-alertmanager v0.24.0-rc.0.0.20220930143838-d75bdc5543c0/go.mod h1:xVHSIhcJ2xBqw8jSf7ZM+9NnTSV68dCLrj7MDiFEse8= github.com/grafana/saml v0.4.9-0.20220727151557-61cd9c9353fc h1:1PY8n+rXuBNr3r1JQhoytWDCpc+pq+BibxV0SZv+Cr4= github.com/grafana/saml v0.4.9-0.20220727151557-61cd9c9353fc/go.mod h1:9Zh6dWPtB3MSzTRt8fIFH60Z351QQ+s7hCU3J/tTlA4= github.com/grafana/thema v0.0.0-20220817114012-ebeee841c104 h1:dYpwFYIChrMfpq3wDa/ZBxAbUGSW5NYmYBeSezhaoao= @@ -1496,7 +1482,6 @@ github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.2.3/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.2.4/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.3.1 h1:MXgUXLqva1QvpVEDQW1IQLG0wivQAtmFlHRQ+1vWZfM= github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= @@ -1624,7 +1609,6 @@ github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/U github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= @@ -1722,13 +1706,14 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= -github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI= github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY= +github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= @@ -1972,7 +1957,6 @@ github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtb github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/oklog/ulid v0.0.0-20170117200651-66bb6560562f/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -2103,19 +2087,6 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/alertmanager v0.18.0/go.mod h1:WcxHBl40VSPuOaqWae6l6HpnEOVRIycEJ7i9iYkadEE= -github.com/prometheus/alertmanager v0.19.0/go.mod h1:Eyp94Yi/T+kdeb2qvq66E3RGuph5T/jm/RBVh4yz1xo= -github.com/prometheus/alertmanager v0.20.0/go.mod h1:9g2i48FAyZW6BtbsnvHtMHQXl2aVtrORKwKVCQ+nbrg= -github.com/prometheus/alertmanager v0.21.0/go.mod h1:h7tJ81NA0VLWvWEayi1QltevFkLF3KxmC/malTcT8Go= -github.com/prometheus/alertmanager v0.21.1-0.20200911160112-1fdff6b3f939/go.mod h1:imXRHOP6QTsE0fFsIsAV/cXimS32m7gVZOiUj11m6Ig= -github.com/prometheus/alertmanager v0.21.1-0.20201106142418-c39b78780054/go.mod h1:imXRHOP6QTsE0fFsIsAV/cXimS32m7gVZOiUj11m6Ig= -github.com/prometheus/alertmanager v0.21.1-0.20210310093010-0f9cab6991e6/go.mod h1:MTqVn+vIupE0dzdgo+sMcNCp37SCAi8vPrvKTTnTz9g= -github.com/prometheus/alertmanager v0.21.1-0.20210422101724-8176f78a70e1/go.mod h1:gsEqwD5BHHW9RNKvCuPOrrTMiP5I+faJUyLXvnivHik= -github.com/prometheus/alertmanager v0.22.2/go.mod h1:rYinOWxFuCnNssc3iOjn2oMTlhLaPcUuqV5yk5JKUAE= -github.com/prometheus/alertmanager v0.23.0/go.mod h1:0MLTrjQI8EuVmvykEhcfr/7X0xmaDAZrqMgxIq3OXHk= -github.com/prometheus/alertmanager v0.23.1-0.20210914172521-e35efbddb66a/go.mod h1:U7pGu+z7A9ZKhK8lq1MvIOp5GdVlZjwOYk+S0h3LSbA= -github.com/prometheus/alertmanager v0.24.0 h1:HBWR3lk4uy3ys+naDZthDdV7yEsxpaNeZuUS+hJgrOw= -github.com/prometheus/alertmanager v0.24.0/go.mod h1:r6fy/D7FRuZh5YbnX6J3MBY0eI4Pb5yPYS7/bPSXXqI= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -2161,24 +2132,20 @@ github.com/prometheus/common v0.8.0/go.mod h1:PC/OgXc+UN7B4ALwvn1yzVZmVwvhXp5Jsb github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.11.1/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.12.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.20.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.21.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.23.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.31.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= -github.com/prometheus/exporter-toolkit v0.5.0/go.mod h1:OCkM4805mmisBhLmVFw858QYi3v0wKdY6/UxrT0pZVg= github.com/prometheus/exporter-toolkit v0.5.1/go.mod h1:OCkM4805mmisBhLmVFw858QYi3v0wKdY6/UxrT0pZVg= github.com/prometheus/exporter-toolkit v0.6.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= github.com/prometheus/exporter-toolkit v0.7.1 h1:c6RXaK8xBVercEeUQ4tRNL8UGWzDHfvj9dseo1FcK1Y= @@ -2205,7 +2172,6 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/prometheus v0.0.0-20180315085919-58e2a31db8de/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s= github.com/prometheus/prometheus v0.0.0-20190818123050-43acd0e2e93f/go.mod h1:rMTlmxGCvukf2KMu3fClMDKLLoJ5hl61MhcJ7xKakf0= github.com/prometheus/prometheus v0.0.0-20200609090129-a6600f564e3c/go.mod h1:S5n0C6tSgdnwWshBUceRx5G1OsjLv/EeZ9t3wIfEtsY= github.com/prometheus/prometheus v1.8.2-0.20200107122003-4708915ac6ef/go.mod h1:7U90zPoLkWjEIQcy/rweQla82OCTUzxVHE51G3OhJbI= @@ -2251,8 +2217,6 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= @@ -2276,7 +2240,6 @@ github.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e/go.mod h1:gi+0 github.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= -github.com/satori/go.uuid v0.0.0-20160603004225-b111a074d5ef/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210223165440-c65ae3540d44 h1:3egqo0Vut6daANFm7tOXdNAa8v5/uLU+sgCJrc88Meo= @@ -2302,11 +2265,9 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX github.com/shirou/gopsutil v3.21.6+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/vfsgen v0.0.0-20180825020608-02ddb050ef6b/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/vfsgen v0.0.0-20200627165143-92b8a710ab6c/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= @@ -2504,7 +2465,6 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg= github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xorcare/pointer v1.1.0 h1:sFwXOhRF8QZ0tyVZrtxWGIoVZNEmRzBCaFWdONPQIUM= @@ -2869,7 +2829,6 @@ golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -3107,7 +3066,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX 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= -golang.org/x/text v0.3.1-0.20180805044716-cb6730876b98/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -3140,7 +3098,6 @@ golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181112210238-4b1f3b6b1646/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190118193359-16909d206f00/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -3207,7 +3164,6 @@ golang.org/x/tools v0.0.0-20200422205258-72e4a01eba43/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200603131246-cc40288be839/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/latest.json b/latest.json index b4c531e6e4c..b133482734e 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "9.1.6", + "stable": "9.1.7", "testing": "9.2.0-beta1" } diff --git a/package.json b/package.json index f9e8e785985..b7b50e8fe57 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "blob-polyfill": "7.0.20220408", "copy-webpack-plugin": "9.0.1", "css-loader": "6.7.1", - "css-minimizer-webpack-plugin": "4.1.0", + "css-minimizer-webpack-plugin": "4.2.0", "cypress": "9.5.1", "enzyme": "3.11.0", "enzyme-to-json": "3.6.2", @@ -206,7 +206,7 @@ "jest-fail-on-console": "2.4.2", "jest-junit": "14.0.0", "jest-matcher-utils": "28.1.3", - "lerna": "5.2.0", + "lerna": "5.5.4", "lint-staged": "13.0.3", "mini-css-extract-plugin": "2.6.1", "msw": "^0.47.3", @@ -225,8 +225,8 @@ "react-test-renderer": "17.0.2", "redux-mock-store": "1.5.4", "rimraf": "3.0.2", - "rudder-sdk-js": "^2.13.0", - "sass": "1.54.0", + "rudder-sdk-js": "2.15.0", + "sass": "1.55.0", "sass-loader": "13.0.2", "sinon": "14.0.0", "style-loader": "3.3.1", @@ -265,6 +265,7 @@ "@grafana/ui": "workspace:*", "@jaegertracing/jaeger-ui-components": "workspace:*", "@kusto/monaco-kusto": "5.2.0", + "@leeoniya/ufuzzy": "0.7.0", "@lezer/common": "1.0.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "1.2.3", @@ -389,7 +390,7 @@ "rst2html": "github:thoward/rst2html#990cb89f2a300cdd9151790be377c4c0840df809", "rxjs": "7.5.6", "sass": "link:./public/sass", - "selecto": "1.19.1", + "selecto": "1.20.2", "semver": "7.3.7", "slate": "0.47.9", "slate-plain-serializer": "0.7.11", @@ -412,9 +413,11 @@ "@rushstack/node-core-library": "3.53.0", "@rushstack/rig-package": "0.3.13", "@rushstack/ts-command-line": "4.12.1", - "@storybook/react/webpack": "5.74.0", + "@storybook/builder-webpack5/webpack": "5.74.0", + "@storybook/manager-webpack5/webpack": "5.74.0", "node-fetch": "2.6.7", - "slate-dev-environment@^0.2.2": "patch:slate-dev-environment@npm:0.2.5#.yarn/patches/slate-dev-environment-npm-0.2.5-9aeb7da7b5.patch" + "slate-dev-environment@^0.2.2": "patch:slate-dev-environment@npm:0.2.5#.yarn/patches/slate-dev-environment-npm-0.2.5-9aeb7da7b5.patch", + "storybook-dark-mode@1.1.2": "patch:storybook-dark-mode@npm%3A1.1.2#./.yarn/patches/storybook-dark-mode-npm-1.1.2-ecc4605688.patch" }, "workspaces": { "packages": [ diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 4e197b6fe70..0c0fdf1058c 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -83,9 +83,9 @@ "react-dom": "17.0.2", "react-test-renderer": "17.0.2", "rimraf": "3.0.2", - "rollup": "2.77.2", + "rollup": "2.79.1", "rollup-plugin-dts": "^4.2.2", - "rollup-plugin-esbuild": "^4.9.1", + "rollup-plugin-esbuild": "4.10.1", "rollup-plugin-node-externals": "^4.1.0", "sinon": "14.0.0", "typescript": "4.8.2" diff --git a/packages/grafana-data/src/dataframe/frameComparisons.ts b/packages/grafana-data/src/dataframe/frameComparisons.ts index f96a7e1660a..2f97d21ea09 100644 --- a/packages/grafana-data/src/dataframe/frameComparisons.ts +++ b/packages/grafana-data/src/dataframe/frameComparisons.ts @@ -73,7 +73,7 @@ export function compareDataFrameStructures(a: DataFrame, b: DataFrame, skipConfi } /** - * Check if all values in two arrays match the compare funciton + * Check if all values in two arrays match the compare function * * @beta */ diff --git a/packages/grafana-data/src/themes/createTypography.ts b/packages/grafana-data/src/themes/createTypography.ts index 9d9776a456f..914818338f0 100644 --- a/packages/grafana-data/src/themes/createTypography.ts +++ b/packages/grafana-data/src/themes/createTypography.ts @@ -115,7 +115,9 @@ export function createTypography(colors: ThemeColors, typographyInput: ThemeTypo h4: buildVariant(fontWeightRegular, 18, 1.235, 0.25), h5: buildVariant(fontWeightRegular, 16, 1.334, 0), h6: buildVariant(fontWeightMedium, 14, 1.6, 0.15), - body: buildVariant(fontWeightRegular, 14, 1.5, 0.15), + // The line-height of 1.5714285714285714 results in a real line-height of 22px for the default font size. Which is an even number and result in more perfect vertical alignment + body: buildVariant(fontWeightRegular, fontSize, 1.5714285714285714, 0.15), + // The line-height of 1.5 results in a real line height of 18px for the font size of 12px bodySmall: buildVariant(fontWeightRegular, 12, 1.5, 0.15), }; diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 943c705cdc5..8fa95143f28 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -16,6 +16,7 @@ export interface FeatureToggles { [name: string]: boolean | undefined; // support any string value + alertingBigTransactions?: boolean; trimDefaults?: boolean; disableEnvelopeEncryption?: boolean; database_metrics?: boolean; @@ -64,7 +65,6 @@ export interface FeatureToggles { dataConnectionsConsole?: boolean; internationalization?: boolean; topnav?: boolean; - customBranding?: boolean; grpcServer?: boolean; traceqlEditor?: boolean; redshiftAsyncQueryDataSupport?: boolean; diff --git a/packages/grafana-data/src/types/logs.ts b/packages/grafana-data/src/types/logs.ts index 553be92b9e0..b6b2531ea2e 100644 --- a/packages/grafana-data/src/types/logs.ts +++ b/packages/grafana-data/src/types/logs.ts @@ -110,6 +110,7 @@ export enum LogsDedupStrategy { signature = 'signature', } +/** @deprecated will be removed in the next major version */ export interface LogsParser { /** * Value-agnostic matcher for a field label. diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 45b1de30667..e6c239a42c3 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -42,9 +42,9 @@ "@types/node": "16.11.45", "esbuild": "0.15.7", "rimraf": "3.0.2", - "rollup": "2.77.2", + "rollup": "2.79.1", "rollup-plugin-dts": "^4.2.2", - "rollup-plugin-esbuild": "^4.9.1", + "rollup-plugin-esbuild": "4.10.1", "rollup-plugin-node-externals": "^4.1.0" }, "dependencies": { diff --git a/packages/grafana-e2e/package.json b/packages/grafana-e2e/package.json index bdbfa91a4e4..c6f445ee4e0 100644 --- a/packages/grafana-e2e/package.json +++ b/packages/grafana-e2e/package.json @@ -51,16 +51,16 @@ "@types/node": "16.11.45", "@types/uuid": "8.3.4", "esbuild": "0.15.7", - "rollup": "2.77.2", + "rollup": "2.79.1", "rollup-plugin-dts": "^4.2.2", - "rollup-plugin-esbuild": "^4.9.1", + "rollup-plugin-esbuild": "4.10.1", "rollup-plugin-node-externals": "^4.1.0", "webpack": "5.74.0" }, "dependencies": { "@babel/core": "7.19.0", "@babel/preset-env": "7.19.0", - "@cypress/webpack-preprocessor": "5.12.0", + "@cypress/webpack-preprocessor": "5.13.1", "@grafana/e2e-selectors": "9.3.0-pre", "@grafana/tsconfig": "^1.2.0-rc1", "@mochajs/json-file-reporter": "^1.2.0", diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index 73f557c6965..0e98e40f54e 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -65,9 +65,9 @@ "react": "17.0.2", "react-dom": "17.0.2", "rimraf": "3.0.2", - "rollup": "2.77.2", + "rollup": "2.79.1", "rollup-plugin-dts": "^4.2.2", - "rollup-plugin-esbuild": "^4.9.1", + "rollup-plugin-esbuild": "4.10.1", "rollup-plugin-node-externals": "^4.1.0", "rollup-plugin-sourcemaps": "0.6.3", "rollup-plugin-terser": "7.0.2", diff --git a/packages/grafana-runtime/src/components/DataSourcePicker.tsx b/packages/grafana-runtime/src/components/DataSourcePicker.tsx index 8e13c6ff9e6..0ceba9231be 100644 --- a/packages/grafana-runtime/src/components/DataSourcePicker.tsx +++ b/packages/grafana-runtime/src/components/DataSourcePicker.tsx @@ -191,7 +191,7 @@ export class DataSourcePicker extends PureComponent { if (o.meta && isUnsignedPluginSignature(o.meta.signature) && o !== value) { return ( - + {o.label} ); diff --git a/packages/grafana-runtime/src/components/PluginPage.tsx b/packages/grafana-runtime/src/components/PluginPage.tsx index 601a0fb56af..23b0de448d6 100644 --- a/packages/grafana-runtime/src/components/PluginPage.tsx +++ b/packages/grafana-runtime/src/components/PluginPage.tsx @@ -3,6 +3,8 @@ import React from 'react'; import { NavModelItem, PageLayoutType } from '@grafana/data'; export interface PluginPageProps { + /** Shown under main heading */ + subTitle?: React.ReactNode; pageNav?: NavModelItem; children: React.ReactNode; layout?: PageLayoutType; diff --git a/packages/grafana-runtime/src/services/__mocks__/dataSourceSrv.ts b/packages/grafana-runtime/src/services/__mocks__/dataSourceSrv.ts index ee35c224792..61995de2b0a 100644 --- a/packages/grafana-runtime/src/services/__mocks__/dataSourceSrv.ts +++ b/packages/grafana-runtime/src/services/__mocks__/dataSourceSrv.ts @@ -4,6 +4,7 @@ const ds1 = { type: 'prometheus', name: 'gdev-prometheus', meta: { + alerting: true, info: { logos: { small: 'http://example.com/logo.png', diff --git a/packages/grafana-runtime/src/services/backendSrv.ts b/packages/grafana-runtime/src/services/backendSrv.ts index 27e743447dc..90df2ad263f 100644 --- a/packages/grafana-runtime/src/services/backendSrv.ts +++ b/packages/grafana-runtime/src/services/backendSrv.ts @@ -143,15 +143,16 @@ export function isFetchError(e: unknown): e is FetchError { * @public */ export interface BackendSrv { - get(url: string, params?: any, requestId?: string): Promise; - delete(url: string, data?: any): Promise; - post(url: string, data?: any): Promise; - patch(url: string, data?: any): Promise; - put(url: string, data?: any): Promise; + get(url: string, params?: any, requestId?: string, options?: Partial): Promise; + delete(url: string, data?: any, options?: Partial): Promise; + post(url: string, data?: any, options?: Partial): Promise; + patch(url: string, data?: any, options?: Partial): Promise; + put(url: string, data?: any, options?: Partial): Promise; /** - * @deprecated Use the fetch function instead. If you prefer to work with a promise - * wrap the Observable returned by fetch with the lastValueFrom function. + * @deprecated Use the `.fetch()` function instead. If you prefer to work with a promise + * wrap the Observable returned by fetch with the lastValueFrom function, or use the get|delete|post|patch|put methods. + * This method is going to be private from Grafana 10. */ request(options: BackendSrvRequest): Promise; diff --git a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts index bb9b75d1282..3c8fe6800a2 100644 --- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts +++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts @@ -1,4 +1,4 @@ -import { merge, Observable, of } from 'rxjs'; +import { lastValueFrom, merge, Observable, of } from 'rxjs'; import { catchError, switchMap } from 'rxjs/operators'; import { @@ -24,6 +24,8 @@ import { getGrafanaLiveSrv, StreamingFrameOptions, StreamingFrameAction, + BackendSrvRequest, + FetchResponse, } from '../services'; import { BackendDataSourceResponse, toDataQueryResponse } from './queryResponse'; @@ -219,29 +221,38 @@ class DataSourceWithBackend< /** * Make a GET request to the datasource resource path */ - async getResource(path: string, params?: any): Promise { - return getBackendSrv().get(`/api/datasources/${this.id}/resources/${path}`, params); + async getResource( + path: string, + params?: BackendSrvRequest['params'], + options?: Partial + ): Promise { + return getBackendSrv().get(`/api/datasources/${this.id}/resources/${path}`, params, options?.requestId, options); } /** * Send a POST request to the datasource resource path */ - async postResource(path: string, body?: any): Promise { - return getBackendSrv().post(`/api/datasources/${this.id}/resources/${path}`, { ...body }); + async postResource( + path: string, + data?: BackendSrvRequest['data'], + options?: Partial + ): Promise { + return getBackendSrv().post(`/api/datasources/${this.id}/resources/${path}`, { ...data }, options); } /** * Run the datasource healthcheck */ async callHealthCheck(): Promise { - return getBackendSrv() - .request({ method: 'GET', url: `/api/datasources/${this.id}/health`, showErrorAlert: false }) - .then((v) => { - return v as HealthCheckResult; + return lastValueFrom( + getBackendSrv().fetch({ + method: 'GET', + url: `/api/datasources/${this.id}/health`, + showErrorAlert: false, }) - .catch((err) => { - return err.data as HealthCheckResult; - }); + ) + .then((v: FetchResponse) => v.data as HealthCheckResult) + .catch((err) => err.data as HealthCheckResult); } /** diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index fe87ef1f45c..745fed83fae 100644 --- a/packages/grafana-schema/package.json +++ b/packages/grafana-schema/package.json @@ -41,9 +41,9 @@ "@swc/helpers": "0.4.3", "esbuild": "0.15.7", "rimraf": "3.0.2", - "rollup": "2.77.2", + "rollup": "2.79.1", "rollup-plugin-dts": "^4.2.2", - "rollup-plugin-esbuild": "^4.9.1", + "rollup-plugin-esbuild": "4.10.1", "rollup-plugin-node-externals": "^4.1.0", "typescript": "4.8.2" }, diff --git a/packages/grafana-schema/src/index.gen.ts b/packages/grafana-schema/src/index.gen.ts index 4ec9a38b62f..6b17d832f7a 100644 --- a/packages/grafana-schema/src/index.gen.ts +++ b/packages/grafana-schema/src/index.gen.ts @@ -76,7 +76,10 @@ export { } from './veneer/dashboard.types'; // Raw generated types from playlist entity type. -export type { Playlist } from './raw/playlist/x/playlist.gen'; +export type { + Playlist, + PlaylistItem +} from './raw/playlist/x/playlist.gen'; // Raw generated default consts from playlist entity type. export { defaultPlaylist } from './raw/playlist/x/playlist.gen'; diff --git a/packages/grafana-schema/src/raw/playlist/x/playlist.gen.ts b/packages/grafana-schema/src/raw/playlist/x/playlist.gen.ts index bfd1c192b1b..ed13e0ef775 100644 --- a/packages/grafana-schema/src/raw/playlist/x/playlist.gen.ts +++ b/packages/grafana-schema/src/raw/playlist/x/playlist.gen.ts @@ -6,11 +6,29 @@ // // Run `make gen-cue` from repository root to regenerate. -export interface Playlist { +export interface PlaylistItem { /** - * Unique playlist identifier for internal use, set by Grafana. + * Title is an unused property -- it will be removed in the future */ - id: number; + title?: string; + /** + * Type of the item. + */ + type: ('dashboard_by_uid' | 'dashboard_by_id' | 'dashboard_by_tag'); + /** + * Value depends on type and describes the playlist item. + * + * - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This + * is not portable as the numerical identifier is non-deterministic between different instances. + * Will be replaced by dashboard_by_uid in the future. (deprecated) + * - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All + * dashboards behind the tag will be added to the playlist. + * - dashboard_by_uid: The value is the dashboard UID + */ + value: string; +} + +export interface Playlist { /** * Interval sets the time between switching views in a playlist. * FIXME: Is this based on a standardized format or what options are available? Can datemath be used? @@ -19,39 +37,7 @@ export interface Playlist { /** * The ordered list of items that the playlist will iterate over. */ - items?: Array<{ - /** - * FIXME: The prefixDropper removes playlist from playlist_id, that doesn't work for us since it'll mean we'll have Id twice. - * ID of the playlist item for internal use by Grafana. Deprecated. - */ - id: number; - /** - * PlaylistID for the playlist containing the item. Deprecated. - */ - playlistid: number; - /** - * Type of the item. - */ - type: ('dashboard_by_uid' | 'dashboard_by_id' | 'dashboard_by_tag'); - /** - * Value depends on type and describes the playlist item. - * - * - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This - * is not portable as the numerical identifier is non-deterministic between different instances. - * Will be replaced by dashboard_by_uid in the future. - * - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All - * dashboards behind the tag will be added to the playlist. - */ - value: string; - /** - * Title is the human-readable identifier for the playlist item. - */ - title: string; - /** - * Order is the position in the list for the item. Deprecated. - */ - order: number; - }>; + items?: Array; /** * Name of the playlist. */ diff --git a/packages/grafana-toolkit/src/cli/index.ts b/packages/grafana-toolkit/src/cli/index.ts index d20ddd835ea..bd0a16d5dc1 100644 --- a/packages/grafana-toolkit/src/cli/index.ts +++ b/packages/grafana-toolkit/src/cli/index.ts @@ -1,8 +1,6 @@ import chalk from 'chalk'; import { program } from 'commander'; -import { changelogTask } from './tasks/changelog'; -import { cherryPickTask } from './tasks/cherrypick'; import { closeMilestoneTask } from './tasks/closeMilestone'; import { componentCreateTask } from './tasks/component.create'; import { nodeVersionCheckerTask } from './tasks/nodeVersionChecker'; @@ -38,30 +36,6 @@ export const run = (includeInternalScripts = false) => { }); }); - program - .command('changelog') - .option('-m, --milestone ', 'Specify milestone') - .description('Builds changelog markdown') - .action(async (cmd) => { - if (!cmd.milestone) { - console.log('Please specify milestone, example: -m '); - return; - } - - await execTask(changelogTask)({ - milestone: cmd.milestone, - silent: true, - }); - }); - - program - .command('cherrypick') - .option('-e, --enterprise', 'Run task for grafana-enterprise') - .description('Helps find commits to cherry pick') - .action(async (cmd) => { - await execTask(cherryPickTask)({ enterprise: !!cmd.enterprise }); - }); - program .command('node-version-check') .description('Verify node version') @@ -112,9 +86,15 @@ export const run = (includeInternalScripts = false) => { program .command('component:create') .description( - 'Scaffold React components. Optionally add test, story and .mdx files. The components are created in the same dir the script is run from.' + '[deprecated] Scaffold React components. Optionally add test, story and .mdx files. The components are created in the same dir the script is run from.' ) .action(async () => { + chalk.yellow.bold( + `⚠️ This command is deprecated and will be removed in v10. No further support will be provided. ⚠️` + ); + console.log( + 'if you were reliant on this command we recommend https://www.npmjs.com/package/react-gen-component' + ); await execTask(componentCreateTask)({}); }); } diff --git a/packages/grafana-toolkit/src/cli/tasks/changelog.ts b/packages/grafana-toolkit/src/cli/tasks/changelog.ts deleted file mode 100644 index 9815045be4b..00000000000 --- a/packages/grafana-toolkit/src/cli/tasks/changelog.ts +++ /dev/null @@ -1,148 +0,0 @@ -import chalk from 'chalk'; -import { difference, sortBy } from 'lodash'; - -import GithubClient from '../utils/githubClient'; -import { useSpinner } from '../utils/useSpinner'; - -import { Task } from './task'; - -interface ChangelogOptions { - milestone: string; -} - -const filterBugs = (item: any) => { - if (item.title.match(/fix|fixes/i)) { - return true; - } - if (item.labels.find((label: any) => label.name === 'type/bug')) { - return true; - } - return false; -}; - -const getPackageChangelog = (packageName: string, issues: any[]) => { - if (issues.length === 0) { - return ''; - } - - let markdown = chalk.bold.yellow(`\n\n/*** ${packageName} changelog ***/\n\n`); - const bugs = sortBy(issues.filter(filterBugs), 'title'); - const notBugs = sortBy(difference(issues, bugs), 'title'); - - if (notBugs.length > 0) { - markdown += '### Features / Enhancements\n'; - for (const item of notBugs) { - markdown += getMarkdownLineForIssue(item); - } - } - - if (bugs.length > 0) { - markdown += '\n### Bug Fixes\n'; - for (const item of bugs) { - markdown += getMarkdownLineForIssue(item); - } - } - - return markdown; -}; - -const changelogTaskRunner = ({ milestone }: ChangelogOptions) => - useSpinner('Generating changelog', async () => { - const githubClient = new GithubClient(); - const client = githubClient.client; - - if (!/^\d+$/.test(milestone)) { - console.log('Use milestone number not title, find number in milestone url'); - return; - } - - let res = await client.get('/issues', { - params: { - state: 'closed', - per_page: 100, - labels: 'add to changelog', - milestone: milestone, - }, - }); - - const data: any[] = res.data; - - while (res.headers.link) { - const links = parseLink(res.headers.link); - if (links.next) { - res = await client.get(links.next); - data.push(...res.data); - } else { - break; - } - } - - const mergedIssues = []; - for (const item of data) { - if (!item.pull_request) { - // it's an issue, not pull request - mergedIssues.push(item); - continue; - } - const isMerged = await client.get(item.pull_request.url + '/merge'); - if (isMerged.status === 204) { - mergedIssues.push(item); - } - } - const issues = sortBy(mergedIssues, 'title'); - - const toolkitIssues = issues.filter((item: any) => - item.labels.find((label: any) => label.name === 'area/grafana/toolkit') - ); - const grafanaUiIssues = issues.filter((item: any) => - item.labels.find((label: any) => label.name === 'area/grafana/ui') - ); - - let markdown = ''; - - markdown += getPackageChangelog('Grafana', issues); - markdown += getPackageChangelog('grafana-toolkit', toolkitIssues); - markdown += getPackageChangelog('grafana-ui', grafanaUiIssues); - - console.log(markdown); - }); - -function getMarkdownLineForIssue(item: any) { - const githubGrafanaUrl = 'https://github.com/grafana/grafana'; - let markdown = ''; - let title: string = item.title.replace(/^([^:]*)/, (_match: any, g1: any) => { - return `**${g1}**`; - }); - title = title.trim(); - if (title[title.length - 1] === '.') { - title = title.slice(0, -1); - } - - if (!item.pull_request) { - markdown += '* ' + title + '.'; - markdown += ` [#${item.number}](${githubGrafanaUrl}/issues/${item.number})`; - } else { - markdown += '* ' + title + '.'; - markdown += ` [#${item.number}](${githubGrafanaUrl}/pull/${item.number})`; - markdown += `, [@${item.user.login}](${item.user.html_url})`; - } - - markdown += '\n'; - - return markdown; -} - -function parseLink(s: any) { - const output: any = {}; - const regex = /<([^>]+)>; rel="([^"]+)"/g; - - let m; - while ((m = regex.exec(s))) { - const [, v, k] = m; - output[k] = v; - } - - return output; -} - -export const changelogTask = new Task('Changelog generator task', changelogTaskRunner); diff --git a/packages/grafana-toolkit/src/cli/tasks/cherrypick.ts b/packages/grafana-toolkit/src/cli/tasks/cherrypick.ts deleted file mode 100644 index 3aa66dd33f2..00000000000 --- a/packages/grafana-toolkit/src/cli/tasks/cherrypick.ts +++ /dev/null @@ -1,96 +0,0 @@ -import GithubClient from '../utils/githubClient'; - -import { Task, TaskRunner } from './task'; - -interface CherryPickOptions { - enterprise: boolean; -} - -// https://github.com/lisposter/github-pagination/blob/master/lib/octopage.js -const pagingParser = (linkStr: string): { prev?: string; next?: string; last?: string; first?: string } => { - return linkStr - .split(',') - .map((rel) => { - //@ts-ignore - return rel.split(';').map((curr, idx) => { - if (idx === 0) { - //@ts-ignore - return /[^_]page=(\d+)/.exec(curr)[1]; - } - if (idx === 1) { - //@ts-ignore - return /rel="(.+)"/.exec(curr)[1]; - } - }); - }) - .reduce(function (obj, curr, i) { - //@ts-ignore - obj[curr[1]] = curr[0]; - return obj; - }, {}); -}; - -const getIssues = async (client: any, page: string) => { - const result = await client.get('/issues', { - params: { - state: 'closed', - per_page: 100, - labels: 'cherry-pick needed', - sort: 'closed', - direction: 'asc', - page, - }, - }); - - let data = result.data; - if (!result.headers.link) { - return data; - } - - const pages = pagingParser(result.headers.link); - - if (pages.next) { - const nextPage = await getIssues(client, pages.next); - data = data.concat(nextPage); - } - return data; -}; - -const cherryPickRunner: TaskRunner = async ({ enterprise }) => { - const githubClient = new GithubClient({ enterprise }); - const client = githubClient.client; - const results = await getIssues(client, '1'); - - // sort by closed date ASC - results.sort((a: any, b: any) => { - return new Date(a.closed_at).getTime() - new Date(b.closed_at).getTime(); - }); - - let commands = ''; - - console.log('--------------------------------------------------------------------'); - console.log('Printing PRs with cherry-pick-needed, in ASC merge date order'); - console.log('--------------------------------------------------------------------'); - - for (const item of results) { - if (!item.milestone) { - console.log(item.number + ' missing milestone!'); - continue; - } - const issueDetails = await client.get(item.pull_request.url); - - if (!issueDetails.data.merged) { - continue; - } - - console.log(`* ${item.title}, (#${item.number}), merge-sha: ${issueDetails.data.merge_commit_sha}`); - commands += `git cherry-pick -x ${issueDetails.data.merge_commit_sha}\n`; - } - - console.log('--------------------------------------------------------------------'); - console.log('Commands (in order of how they should be executed)'); - console.log('--------------------------------------------------------------------'); - console.log(commands); -}; - -export const cherryPickTask = new Task('Cherry pick task', cherryPickRunner); diff --git a/packages/grafana-ui/.storybook/grafana.dark.scss b/packages/grafana-ui/.storybook/grafana.dark.scss new file mode 100644 index 00000000000..6b40cca3a73 --- /dev/null +++ b/packages/grafana-ui/.storybook/grafana.dark.scss @@ -0,0 +1,6 @@ +// reset font file paths so storybook loads them based on +// staticDirs defined in packages/grafana-ui/.storybook/main.ts +$font-file-path: './public/fonts'; +$fa-font-path: $font-file-path; + +@import '../../../public/sass/grafana.dark.scss'; diff --git a/packages/grafana-ui/.storybook/grafana.light.scss b/packages/grafana-ui/.storybook/grafana.light.scss new file mode 100644 index 00000000000..01f0865cef8 --- /dev/null +++ b/packages/grafana-ui/.storybook/grafana.light.scss @@ -0,0 +1,6 @@ +// reset font file paths so storybook loads them based on +// staticDirs defined in packages/grafana-ui/.storybook/main.ts +$font-file-path: './public/fonts'; +$fa-font-path: $font-file-path; + +@import '../../../public/sass/grafana.light.scss'; diff --git a/packages/grafana-ui/.storybook/main.ts b/packages/grafana-ui/.storybook/main.ts index 9b738e8b239..b09a98f1b75 100644 --- a/packages/grafana-ui/.storybook/main.ts +++ b/packages/grafana-ui/.storybook/main.ts @@ -1,40 +1,94 @@ -const path = require('path'); -const { ProvidePlugin } = require('webpack'); -const TerserPlugin = require('terser-webpack-plugin'); -const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); -const FilterWarningsPlugin = require('webpack-filter-warnings-plugin'); +import path from 'path'; +import type { StorybookConfig } from '@storybook/react/types'; +import { getAvailableIcons, IconName } from '../src/types/icon'; +import { getIconSubDir } from '../src/components/Icon/utils'; -const stories = ['../src/**/*.story.{js,jsx,ts,tsx,mdx}']; +const stories = ['../src/**/*.story.@(tsx|mdx)']; if (process.env.NODE_ENV !== 'production') { - stories.push('../src/**/*.story.internal.{js,jsx,ts,tsx,mdx}'); + stories.push('../src/**/*.story.internal.@(tsx|mdx)'); } -module.exports = { - stories: stories, +// We limit icon paths to only the available icons so publishing +// doesn't require uploading 1000s of unused assets. +const iconPaths = getAvailableIcons() + .filter((iconName) => !iconName.includes('fa')) + .map((iconName) => { + const subDir = getIconSubDir(iconName as IconName, 'default'); + return { + from: `../../../public/img/icons/${subDir}/${iconName}.svg`, + to: `/public/img/icons/${subDir}/${iconName}.svg`, + }; + }); + +const mainConfig: StorybookConfig = { + stories, addons: [ + { + // work around docs 6.5.x not resolving correctly with yarn PnP + name: path.dirname(require.resolve('@storybook/addon-docs/package.json')), + options: { + configureJSX: true, + babelOptions: {}, + }, + }, { name: '@storybook/addon-essentials', options: { backgrounds: false, + // work around docs 6.5.x not resolving correctly with yarn PnP + docs: false, }, }, '@storybook/addon-a11y', '@storybook/addon-knobs', + { + name: '@storybook/preset-scss', + options: { + styleLoaderOptions: { + // this is required for theme switching .use() and .unuse() + injectType: 'lazyStyleTag', + }, + cssLoaderOptions: { + url: false, + importLoaders: 2, + }, + }, + }, '@storybook/addon-storysource', 'storybook-dark-mode', + { + // replace babel-loader in manager and preview with esbuild-loader + name: 'storybook-addon-turbo-build', + options: { + optimizationLevel: 3, + }, + }, ], - staticDirs: [ - { from: '../../../public/fonts', to: '/fonts' }, - { from: '../../../public/img', to: '/public/img' }, - { from: '../../../public/lib', to: '/public/lib' }, - ], + core: { + builder: { + name: 'webpack5', + options: { + fsCache: true, + }, + }, + }, + features: { + previewMdx2: true, + }, + framework: '@storybook/react', + logLevel: 'debug', reactOptions: { fastRefresh: true, }, - core: { - builder: 'webpack5', - }, + staticDirs: [ + { from: '../../../public/fonts', to: '/public/fonts' }, + { from: '../../../public/img/grafana_text_logo-dark.svg', to: '/public/img/grafana_text_logo-dark.svg' }, + { from: '../../../public/img/grafana_text_logo-light.svg', to: '/public/img/grafana_text_logo-light.svg' }, + { from: '../../../public/img/fav32.png', to: '/public/img/fav32.png' }, + { from: '../../../public/lib', to: '/public/lib' }, + ...iconPaths, + ], typescript: { check: true, reactDocgen: 'react-docgen-typescript', @@ -46,126 +100,24 @@ module.exports = { savePropValueAsString: true, }, }, - webpackFinal: async (config: any, { configType }: any) => { - const isProductionBuild = configType === 'PRODUCTION'; - - // remove svg from default storybook webpack 5 config so we can use `raw-loader` - config.module.rules = config.module.rules.map((rule: any) => { - if ( - String(rule.test) === - String(/\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/) - ) { - return { - ...rule, - test: /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/, - }; - } - - return rule; + webpackFinal: async (config) => { + // expose jquery as a global so jquery plugins don't break at runtime. + config.module.rules.push({ + test: require.resolve('jquery'), + loader: 'expose-loader', + options: { + exposes: ['$', 'jQuery'], + }, }); - config.module.rules = [ - ...(config.module.rules || []), - { - test: /\.tsx?$/, - use: [ - { - loader: require.resolve('ts-loader'), - options: { - transpileOnly: true, - configFile: path.resolve(__dirname, 'tsconfig.json'), - }, - }, - ], - exclude: /node_modules/, - include: [path.resolve(__dirname, '../../../public/'), path.resolve(__dirname, '../../../packages/')], - }, - { - test: /\.scss$/, - use: [ - { - loader: 'style-loader', - options: { injectType: 'lazyStyleTag' }, - }, - { - loader: 'css-loader', - options: { - url: false, - importLoaders: 2, - }, - }, - { - loader: 'postcss-loader', - options: { - sourceMap: false, - postcssOptions: { - config: path.resolve(__dirname + '../../../../scripts/webpack/postcss.config.js'), - }, - }, - }, - { - loader: 'sass-loader', - options: { - sourceMap: false, - }, - }, - ], - }, - // for pre-caching SVGs as part of the JS bundles - { - test: /\.svg$/, - use: 'raw-loader', - }, - { - test: require.resolve('jquery'), - loader: 'expose-loader', - options: { - exposes: ['$', 'jQuery'], - }, - }, - ]; - - if (isProductionBuild) { - config.optimization = { - nodeEnv: 'production', - moduleIds: 'deterministic', - runtimeChunk: 'single', - splitChunks: { - chunks: 'all', - minChunks: 1, - cacheGroups: { - vendors: { - test: /[\\/]node_modules[\\/].*[jt]sx?$/, - chunks: 'initial', - priority: -10, - reuseExistingChunk: true, - enforce: true, - }, - default: { - priority: -20, - chunks: 'all', - test: /.*[jt]sx?$/, - reuseExistingChunk: true, - }, - }, - }, - minimize: isProductionBuild, - minimizer: isProductionBuild - ? [new TerserPlugin({ parallel: false, exclude: /monaco/ }), new CssMinimizerPlugin()] - : [], - }; - } - - config.resolve.alias['@grafana/ui'] = path.resolve(__dirname, '..'); - - // Silence "export not found" webpack warnings with transpileOnly - // https://github.com/TypeStrong/ts-loader#transpileonly - config.plugins.push( - new FilterWarningsPlugin({ - exclude: /export .* was not found in/, - }) - ); + // use the asset module for SVGS for compatibility with grafana/ui Icon component. + config.module.rules.push({ + test: /(unicons|mono|custom)[\\/].*\.svg$/, + type: 'asset/source', + }); return config; }, }; + +module.exports = mainConfig; diff --git a/packages/grafana-ui/.storybook/manager.ts b/packages/grafana-ui/.storybook/manager.ts new file mode 100644 index 00000000000..96ed58aa799 --- /dev/null +++ b/packages/grafana-ui/.storybook/manager.ts @@ -0,0 +1,9 @@ +import { addons } from '@storybook/addons'; +import { GrafanaDark } from './storybookTheme'; + +addons.setConfig({ + sidebar: { + showRoots: false, + }, + theme: GrafanaDark, +}); diff --git a/packages/grafana-ui/.storybook/preview-head.html b/packages/grafana-ui/.storybook/preview-head.html deleted file mode 100644 index 5bcc8d66948..00000000000 --- a/packages/grafana-ui/.storybook/preview-head.html +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/packages/grafana-ui/.storybook/preview.ts b/packages/grafana-ui/.storybook/preview.ts index d8a51bcd804..1c91f1565fa 100644 --- a/packages/grafana-ui/.storybook/preview.ts +++ b/packages/grafana-ui/.storybook/preview.ts @@ -9,13 +9,12 @@ import '../../../public/vendor/flot/jquery.flot.crosshair'; import '../../../public/vendor/flot/jquery.flot.dashes'; import '../../../public/vendor/flot/jquery.flot.gauge'; import { withTheme } from '../src/utils/storybook/withTheme'; -// @ts-ignore -import lightTheme from '../../../public/sass/grafana.light.scss'; -// @ts-ignore -import darkTheme from '../../../public/sass/grafana.dark.scss'; -import { GrafanaLight, GrafanaDark } from './storybookTheme'; -import addons from '@storybook/addons'; import { ThemedDocsContainer } from '../src/utils/storybook/ThemedDocsContainer'; +// @ts-ignore +import lightTheme from './grafana.light.scss'; +// @ts-ignore +import darkTheme from './grafana.dark.scss'; +import { GrafanaLight, GrafanaDark } from './storybookTheme'; const handleThemeChange = (theme: any) => { if (theme !== 'light') { @@ -27,23 +26,21 @@ const handleThemeChange = (theme: any) => { } }; -addons.setConfig({ - showRoots: false, - theme: GrafanaDark, -}); - export const decorators = [withTheme(handleThemeChange)]; export const parameters = { - docs: { - container: ThemedDocsContainer, - }, + actions: { argTypesRegex: '^on[A-Z].*' }, darkMode: { dark: GrafanaDark, light: GrafanaLight, }, + docs: { + container: ThemedDocsContainer, + }, + knobs: { + disable: true, + }, layout: 'fullscreen', - actions: { argTypesRegex: '^on[A-Z].*' }, options: { showPanel: true, panelPosition: 'right', @@ -56,7 +53,4 @@ export const parameters = { order: ['Docs Overview', ['Intro']], }, }, - knobs: { - disable: true, - }, }; diff --git a/packages/grafana-ui/.storybook/tsconfig.json b/packages/grafana-ui/.storybook/tsconfig.json index a37abf437df..cbcee5966e1 100644 --- a/packages/grafana-ui/.storybook/tsconfig.json +++ b/packages/grafana-ui/.storybook/tsconfig.json @@ -4,7 +4,6 @@ "noUnusedLocals": false, "outDir": "compiled" }, - "exclude": ["../dist/**/*"], "extends": "../tsconfig.json", - "include": ["../src/**/*.ts", "../src/**/*.tsx", "../../../public/app/types/svg.d.ts"] + "include": ["../src/**/*.ts*", "../../../public/app/types/svg.d.ts"] } diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 29dd855df5a..b7277aa52e8 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -25,10 +25,11 @@ "access": "public" }, "files": [ - "dist", + "./dist", + "!./dist/storybook", "./README.md", "./CHANGELOG.md", - "LICENSE_APACHE2" + "./LICENSE_APACHE2" ], "scripts": { "build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts", @@ -49,6 +50,7 @@ "@grafana/data": "9.3.0-pre", "@grafana/e2e-selectors": "9.3.0-pre", "@grafana/schema": "9.3.0-pre", + "@leeoniya/ufuzzy": "0.7.0", "@monaco-editor/react": "4.4.5", "@popperjs/core": "2.11.5", "@react-aria/button": "3.6.1", @@ -81,7 +83,7 @@ "rc-time-picker": "^3.7.3", "react-beautiful-dnd": "13.1.0", "react-calendar": "3.9.0", - "react-colorful": "5.5.1", + "react-colorful": "5.6.1", "react-custom-scrollbars-2": "4.5.0", "react-dropzone": "14.2.2", "react-highlight-words": "0.18.0", @@ -110,21 +112,23 @@ "@grafana/tsconfig": "^1.2.0-rc1", "@mdx-js/react": "1.6.22", "@rollup/plugin-node-resolve": "13.3.0", - "@storybook/addon-a11y": "6.4.21", - "@storybook/addon-actions": "6.4.21", - "@storybook/addon-docs": "6.4.21", - "@storybook/addon-essentials": "6.4.21", + "@storybook/addon-a11y": "6.5.12", + "@storybook/addon-actions": "6.5.12", + "@storybook/addon-docs": "6.5.12", + "@storybook/addon-essentials": "6.5.12", "@storybook/addon-knobs": "6.4.0", - "@storybook/addon-storysource": "6.4.21", - "@storybook/addons": "6.4.21", - "@storybook/api": "6.4.21", - "@storybook/builder-webpack5": "6.4.21", - "@storybook/client-api": "6.4.21", - "@storybook/components": "6.4.21", - "@storybook/core-events": "6.4.21", - "@storybook/manager-webpack5": "6.4.21", - "@storybook/react": "6.4.21", - "@storybook/theming": "6.4.21", + "@storybook/addon-storysource": "6.5.12", + "@storybook/addons": "6.5.12", + "@storybook/api": "6.5.12", + "@storybook/builder-webpack5": "6.5.12", + "@storybook/client-api": "6.5.12", + "@storybook/components": "6.5.12", + "@storybook/core-events": "6.5.12", + "@storybook/manager-webpack5": "6.5.12", + "@storybook/mdx2-csf": "0.0.3", + "@storybook/preset-scss": "1.0.3", + "@storybook/react": "6.5.12", + "@storybook/theming": "6.5.12", "@swc/helpers": "0.4.3", "@testing-library/dom": "8.13.0", "@testing-library/jest-dom": "5.16.4", @@ -163,37 +167,30 @@ "@types/tinycolor2": "1.4.3", "@types/uuid": "8.3.4", "@wojtekmaj/enzyme-adapter-react-17": "0.6.7", - "babel-loader": "8.2.5", "common-tags": "1.8.2", "css-loader": "6.7.1", - "css-minimizer-webpack-plugin": "4.1.0", "csstype": "3.1.0", "enzyme": "3.11.0", "esbuild": "0.15.7", "expose-loader": "4.0.0", "mock-raf": "1.0.1", - "postcss": "8.4.14", - "postcss-loader": "7.0.1", "process": "^0.11.10", - "raw-loader": "4.0.2", "react": "17.0.2", "react-docgen-typescript-loader": "3.7.2", "react-dom": "17.0.2", "react-test-renderer": "17.0.2", "rimraf": "3.0.2", - "rollup": "2.77.2", + "rollup": "2.79.1", "rollup-plugin-dts": "^4.2.2", - "rollup-plugin-esbuild": "^4.9.1", + "rollup-plugin-esbuild": "4.10.1", "rollup-plugin-node-externals": "^4.1.0", "rollup-plugin-svg-import": "^1.6.0", "sass-loader": "13.0.2", - "storybook-dark-mode": "1.1.0", + "storybook-addon-turbo-build": "1.1.0", + "storybook-dark-mode": "1.1.2", "style-loader": "3.3.1", - "terser-webpack-plugin": "5.3.3", - "ts-loader": "8.4.0", "typescript": "4.8.2", - "webpack": "5.74.0", - "webpack-filter-warnings-plugin": "1.2.1" + "webpack": "5.74.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0", diff --git a/packages/grafana-ui/src/components/FilterInput/FilterInput.tsx b/packages/grafana-ui/src/components/FilterInput/FilterInput.tsx index 315a85d7dbf..403df7146c6 100644 --- a/packages/grafana-ui/src/components/FilterInput/FilterInput.tsx +++ b/packages/grafana-ui/src/components/FilterInput/FilterInput.tsx @@ -2,8 +2,10 @@ import React, { HTMLProps } from 'react'; import { escapeStringForRegex, unEscapeStringFromRegex } from '@grafana/data'; -import { Button, Icon, Input } from '..'; import { useCombinedRefs } from '../../utils/useCombinedRefs'; +import { Button } from '../Button'; +import { Icon } from '../Icon/Icon'; +import { Input } from '../Input/Input'; export interface Props extends Omit, 'onChange'> { value: string | undefined; @@ -14,8 +16,8 @@ export interface Props extends Omit, 'onChange'> { export const FilterInput = React.forwardRef( ({ value, width, onChange, escapeRegex = true, ...restProps }, ref) => { - const innerRef = React.useRef(null); - const combinedRef = useCombinedRefs(ref, innerRef) as React.Ref; + const innerRef = React.useRef(null); + const combinedRef = useCombinedRefs(ref, innerRef); const suffix = value !== '' ? ( diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx index ecfa3948e54..c618e71605e 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx @@ -17,7 +17,7 @@ export const SelectOption = (props: ExtendedOptionProps) => { return (
- {data.imgUrl && } + {data.imgUrl && }
{children}
{data.description &&
{data.description}
} diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Select/__snapshots__/SelectOption.test.tsx.snap b/packages/grafana-ui/src/components/Forms/Legacy/Select/__snapshots__/SelectOption.test.tsx.snap index 8146572a82d..157d8ca4818 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Select/__snapshots__/SelectOption.test.tsx.snap +++ b/packages/grafana-ui/src/components/Forms/Legacy/Select/__snapshots__/SelectOption.test.tsx.snap @@ -14,6 +14,7 @@ exports[`SelectOption renders correctly 1`] = ` className="gf-form-select-box__desc-option" > diff --git a/packages/grafana-ui/src/components/Icon/Icon.story.tsx b/packages/grafana-ui/src/components/Icon/Icon.story.tsx index 28d96fc4255..b5576ec105e 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.story.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.story.tsx @@ -72,6 +72,8 @@ export const IconsOverview = () => { className={css` display: flex; flex-direction: column; + height: 100%; + overflow: auto; width: 100%; `} > diff --git a/packages/grafana-ui/src/components/Logs/LogDetails.test.tsx b/packages/grafana-ui/src/components/Logs/LogDetails.test.tsx deleted file mode 100644 index 78a7abf600a..00000000000 --- a/packages/grafana-ui/src/components/Logs/LogDetails.test.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { render, screen, within } from '@testing-library/react'; -import React from 'react'; - -import { Field, GrafanaTheme2, LogLevel, LogRowModel, MutableDataFrame } from '@grafana/data'; - -import { LogDetails, Props } from './LogDetails'; -import { createLogRow } from './__mocks__/logRow'; - -const setup = (propOverrides?: Partial, rowOverrides?: Partial) => { - const props: Props = { - theme: {} as GrafanaTheme2, - showDuplicates: false, - wrapLogMessage: false, - row: createLogRow({ logLevel: LogLevel.error, timeEpochMs: 1546297200000, ...rowOverrides }), - getRows: () => [], - onClickFilterLabel: () => {}, - onClickFilterOutLabel: () => {}, - ...(propOverrides || {}), - }; - - render( - - - - -
- ); -}; - -describe('LogDetails', () => { - describe('when labels are present', () => { - it('should render heading', () => { - setup(undefined, { labels: { key1: 'label1', key2: 'label2' } }); - expect(screen.getAllByLabelText('Log labels')).toHaveLength(1); - }); - it('should render labels', () => { - setup(undefined, { labels: { key1: 'label1', key2: 'label2' } }); - expect(screen.getByRole('cell', { name: 'key1' })).toBeInTheDocument(); - expect(screen.getByRole('cell', { name: 'label1' })).toBeInTheDocument(); - expect(screen.getByRole('cell', { name: 'key2' })).toBeInTheDocument(); - expect(screen.getByRole('cell', { name: 'label2' })).toBeInTheDocument(); - }); - }); - describe('when log row has error', () => { - it('should not render log level border', () => { - // Is this a good test case for RTL?? - setup({ hasError: true }, undefined); - expect(screen.getByLabelText('Log level').classList.toString()).not.toContain('logs-row__level'); - }); - }); - describe('when row entry has parsable fields', () => { - it('should render heading ', () => { - setup(undefined, { entry: 'test=successful' }); - expect(screen.getAllByTitle('Ad-hoc statistics')).toHaveLength(1); - }); - it('should render detected fields', () => { - setup(undefined, { entry: 'test=successful' }); - expect(screen.getByRole('cell', { name: 'test' })).toBeInTheDocument(); - expect(screen.getByRole('cell', { name: 'successful' })).toBeInTheDocument(); - }); - }); - describe('when row entry have parsable fields and labels are present', () => { - it('should render all headings', () => { - setup(undefined, { entry: 'test=successful', labels: { key: 'label' } }); - expect(screen.getAllByLabelText('Log labels')).toHaveLength(1); - expect(screen.getAllByLabelText('Detected fields')).toHaveLength(1); - }); - it('should render all labels and detected fields', () => { - setup(undefined, { entry: 'test=successful', labels: { key: 'label' } }); - expect(screen.getByRole('cell', { name: 'key' })).toBeInTheDocument(); - expect(screen.getByRole('cell', { name: 'label' })).toBeInTheDocument(); - expect(screen.getByRole('cell', { name: 'test' })).toBeInTheDocument(); - expect(screen.getByRole('cell', { name: 'successful' })).toBeInTheDocument(); - }); - }); - describe('when row entry and labels are not present', () => { - it('should render no details available message', () => { - setup(undefined, { entry: '' }); - expect(screen.getByText('No details available')).toBeInTheDocument(); - }); - it('should not render headings', () => { - setup(undefined, { entry: '' }); - expect(screen.queryAllByLabelText('Log labels')).toHaveLength(0); - expect(screen.queryAllByLabelText('Detected fields')).toHaveLength(0); - }); - }); - - it('should render fields from dataframe with links', () => { - const entry = 'traceId=1234 msg="some message"'; - const dataFrame = new MutableDataFrame({ - fields: [ - { name: 'entry', values: [entry] }, - // As we have traceId in message already this will shadow it. - { - name: 'traceId', - values: ['1234'], - config: { links: [{ title: 'link', url: 'localhost:3210/${__value.text}' }] }, - }, - { name: 'userId', values: ['5678'] }, - ], - }); - setup( - { - getFieldLinks: (field: Field, rowIndex: number) => { - if (field.config && field.config.links) { - return field.config.links.map((link) => { - return { - href: link.url.replace('${__value.text}', field.values.get(rowIndex)), - title: link.title, - target: '_blank', - origin: field, - }; - }); - } - return []; - }, - }, - { entry, dataFrame, entryFieldIndex: 0, rowIndex: 0 } - ); - expect(screen.getAllByRole('table')).toHaveLength(2); - const rowDetailsTable = screen.getAllByRole('table')[1]; - const rowDetailRows = within(rowDetailsTable).getAllByRole('row'); - expect(rowDetailRows).toHaveLength(4); // 3 LogDetailsRow + 1 header - const traceIdRow = within(rowDetailsTable).getByRole('cell', { name: 'traceId' }).closest('tr'); - expect(traceIdRow).toBeInTheDocument(); - const link = within(traceIdRow!).getByRole('link', { name: 'link' }); - expect(link).toBeInTheDocument(); - expect(link).toHaveAttribute('href', 'localhost:3210/1234'); - }); -}); diff --git a/packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.tsx b/packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.tsx deleted file mode 100644 index afdc4a49eb4..00000000000 --- a/packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import { FieldType, LogRowModel, MutableDataFrame, DataQueryResponse } from '@grafana/data'; - -import { getRowContexts, LogRowContextProvider, RowContextOptions } from './LogRowContextProvider'; -import { createLogRow } from './__mocks__/logRow'; - -const row = createLogRow({ entry: '4', timeEpochMs: 4 }); - -describe('getRowContexts', () => { - describe('when called with a DataFrame and results are returned', () => { - it('then the result should be in correct format and filtered', async () => { - const firstResult = new MutableDataFrame({ - refId: 'B', - fields: [ - { name: 'ts', type: FieldType.time, values: [3, 2, 1] }, - { name: 'line', type: FieldType.string, values: ['3', '2', '1'], labels: {} }, - { name: 'id', type: FieldType.string, values: ['3', '2', '1'], labels: {} }, - ], - }); - const secondResult = new MutableDataFrame({ - refId: 'B', - fields: [ - { name: 'ts', type: FieldType.time, values: [6, 5, 4] }, - { name: 'line', type: FieldType.string, values: ['6', '5', '4'], labels: {} }, - { name: 'id', type: FieldType.string, values: ['6', '5', '4'], labels: {} }, - ], - }); - let called = false; - const getRowContextMock = (row: LogRowModel, options?: RowContextOptions): Promise => { - if (!called) { - called = true; - return Promise.resolve({ data: [firstResult] }); - } - return Promise.resolve({ data: [secondResult] }); - }; - - const result = await getRowContexts(getRowContextMock, row, 10); - - expect(result).toEqual({ - data: [ - ['3', '2'], - ['6', '5', '4'], - ], - errors: ['', ''], - }); - }); - - it('then the result should be in correct format and filtered without uid', async () => { - const firstResult = new MutableDataFrame({ - refId: 'B', - fields: [ - { name: 'ts', type: FieldType.time, values: [3, 2, 1] }, - { name: 'line', type: FieldType.string, values: ['3', '2', '1'], labels: {} }, - ], - }); - const secondResult = new MutableDataFrame({ - refId: 'B', - fields: [ - { name: 'ts', type: FieldType.time, values: [6, 5, 4] }, - { name: 'line', type: FieldType.string, values: ['6', '5', '4'], labels: {} }, - ], - }); - let called = false; - const getRowContextMock = (row: LogRowModel, options?: RowContextOptions): Promise => { - if (!called) { - called = true; - return Promise.resolve({ data: [firstResult] }); - } - return Promise.resolve({ data: [secondResult] }); - }; - - const result = await getRowContexts(getRowContextMock, row, 10); - - expect(result).toEqual({ - data: [ - ['3', '2', '1'], - ['6', '5'], - ], - errors: ['', ''], - }); - }); - }); - - describe('when called with a DataFrame and errors occur', () => { - it('then the result should be in correct format', async () => { - const firstError = new Error('Error 1'); - const secondError = new Error('Error 2'); - let called = false; - const getRowContextMock = (row: LogRowModel, options?: RowContextOptions): Promise => { - if (!called) { - called = true; - return Promise.reject(firstError); - } - return Promise.reject(secondError); - }; - - const result = await getRowContexts(getRowContextMock, row, 10); - - expect(result).toEqual({ data: [[], []], errors: ['Error 1', 'Error 2'] }); - }); - }); -}); - -describe('LogRowContextProvider', () => { - describe('when requesting longer context', () => { - it('can request more log lines', async () => { - const firstResult = new MutableDataFrame({ - refId: 'B', - fields: [ - { name: 'ts', type: FieldType.time, values: [10, 9, 8, 7, 6, 5] }, - { - name: 'line', - type: FieldType.string, - values: ['10', '9', '8', '7', '6', '5'], - labels: {}, - }, - { - name: 'id', - type: FieldType.string, - values: ['10', '9', '8', '7', '6', '5'], - labels: {}, - }, - ], - }); - - const secondResult = new MutableDataFrame({ - refId: 'B', - fields: [ - { name: 'ts', type: FieldType.time, values: [14, 13, 12] }, - { name: 'line', type: FieldType.string, values: ['14', '13', '12'], labels: {} }, - { name: 'id', type: FieldType.string, values: ['14', '13', '12'], labels: {} }, - ], - }); - - let called = false; - const getRowContextMock = (row: LogRowModel, options?: RowContextOptions): Promise => { - if (!called) { - called = true; - return Promise.resolve({ data: [firstResult] }); - } - return Promise.resolve({ data: [secondResult] }); - }; - let updateLimitCalled = false; - - const mockedChildren = jest.fn((mockState) => { - const { result, errors, hasMoreContextRows, updateLimit, limit } = mockState; - if (!updateLimitCalled && result.before.length === 0) { - expect(result).toEqual({ before: [], after: [] }); - expect(errors).toEqual({ before: undefined, after: undefined }); - expect(hasMoreContextRows).toEqual({ before: true, after: true }); - expect(limit).toBe(10); - return
; - } - if (!updateLimitCalled && result.before.length > 0) { - expect(result).toEqual({ before: ['10', '9', '8', '7', '6', '5'], after: ['14', '13', '12'] }); - expect(errors).toEqual({ before: '', after: '' }); - expect(hasMoreContextRows).toEqual({ before: true, after: true }); - expect(limit).toBe(10); - updateLimit(); - updateLimitCalled = true; - return
; - } - if (updateLimitCalled && result.before.length > 0 && limit > 10) { - expect(limit).toBe(20); - } - return
; - }); - render( - - {mockedChildren} - - ); - await screen.findByTestId('mockChild'); - }); - }); -}); diff --git a/packages/grafana-ui/src/components/Logs/LogRows.test.tsx b/packages/grafana-ui/src/components/Logs/LogRows.test.tsx deleted file mode 100644 index 6753d95765a..00000000000 --- a/packages/grafana-ui/src/components/Logs/LogRows.test.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import { range } from 'lodash'; -import React from 'react'; - -import { LogRowModel, LogsDedupStrategy, LogsSortOrder } from '@grafana/data'; - -import { LogRows, PREVIEW_LIMIT } from './LogRows'; -import { createLogRow } from './__mocks__/logRow'; - -describe('LogRows', () => { - it('renders rows', () => { - const rows: LogRowModel[] = [createLogRow({ uid: '1' }), createLogRow({ uid: '2' }), createLogRow({ uid: '3' })]; - render( - - ); - - expect(screen.queryAllByRole('row')).toHaveLength(3); - expect(screen.queryAllByRole('row').at(0)).toHaveTextContent('log message 1'); - expect(screen.queryAllByRole('row').at(1)).toHaveTextContent('log message 2'); - expect(screen.queryAllByRole('row').at(2)).toHaveTextContent('log message 3'); - }); - - it('renders rows only limited number of rows first', () => { - const rows: LogRowModel[] = [createLogRow({ uid: '1' }), createLogRow({ uid: '2' }), createLogRow({ uid: '3' })]; - jest.useFakeTimers(); - const { rerender } = render( - - ); - - // There is an extra row with the rows that are rendering - expect(screen.queryAllByRole('row')).toHaveLength(2); - expect(screen.queryAllByRole('row').at(0)).toHaveTextContent('log message 1'); - - jest.runAllTimers(); - rerender( - - ); - - expect(screen.queryAllByRole('row')).toHaveLength(3); - expect(screen.queryAllByRole('row').at(0)).toHaveTextContent('log message 1'); - expect(screen.queryAllByRole('row').at(1)).toHaveTextContent('log message 2'); - expect(screen.queryAllByRole('row').at(2)).toHaveTextContent('log message 3'); - - jest.useRealTimers(); - }); - - it('renders deduped rows if supplied', () => { - const rows: LogRowModel[] = [createLogRow({ uid: '1' }), createLogRow({ uid: '2' }), createLogRow({ uid: '3' })]; - const dedupedRows: LogRowModel[] = [createLogRow({ uid: '4' }), createLogRow({ uid: '5' })]; - render( - - ); - expect(screen.queryAllByRole('row')).toHaveLength(2); - expect(screen.queryAllByRole('row').at(0)).toHaveTextContent('log message 4'); - expect(screen.queryAllByRole('row').at(1)).toHaveTextContent('log message 5'); - }); - - it('renders with default preview limit', () => { - // PREVIEW_LIMIT * 2 is there because otherwise we just render all rows - const rows: LogRowModel[] = range(PREVIEW_LIMIT * 2 + 1).map((num) => createLogRow({ uid: num.toString() })); - render( - - ); - - // There is an extra row with the rows that are rendering - expect(screen.queryAllByRole('row')).toHaveLength(101); - }); - - it('renders asc ordered rows if order and function supplied', () => { - const rows: LogRowModel[] = [ - createLogRow({ uid: '1', timeEpochMs: 1 }), - createLogRow({ uid: '3', timeEpochMs: 3 }), - createLogRow({ uid: '2', timeEpochMs: 2 }), - ]; - render( - - ); - - expect(screen.queryAllByRole('row').at(0)).toHaveTextContent('log message 1'); - expect(screen.queryAllByRole('row').at(1)).toHaveTextContent('log message 2'); - expect(screen.queryAllByRole('row').at(2)).toHaveTextContent('log message 3'); - }); - it('renders desc ordered rows if order and function supplied', () => { - const rows: LogRowModel[] = [ - createLogRow({ uid: '1', timeEpochMs: 1 }), - createLogRow({ uid: '3', timeEpochMs: 3 }), - createLogRow({ uid: '2', timeEpochMs: 2 }), - ]; - render( - - ); - - expect(screen.queryAllByRole('row').at(0)).toHaveTextContent('log message 3'); - expect(screen.queryAllByRole('row').at(1)).toHaveTextContent('log message 2'); - expect(screen.queryAllByRole('row').at(2)).toHaveTextContent('log message 1'); - }); -}); diff --git a/packages/grafana-ui/src/components/Logs/__mocks__/logRow.ts b/packages/grafana-ui/src/components/Logs/__mocks__/logRow.ts deleted file mode 100644 index caee86b585d..00000000000 --- a/packages/grafana-ui/src/components/Logs/__mocks__/logRow.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { LogLevel, LogRowModel, MutableDataFrame } from '@grafana/data'; - -export const createLogRow = (overrides?: Partial): LogRowModel => { - const uid = overrides?.uid || '1'; - const timeEpochMs = overrides?.timeEpochMs || 1; - const entry = overrides?.entry || `log message ${uid}`; - - return { - entryFieldIndex: 0, - rowIndex: 0, - dataFrame: new MutableDataFrame(), - uid, - logLevel: LogLevel.info, - entry, - hasAnsi: false, - hasUnescapedContent: false, - labels: {}, - raw: entry, - timeFromNow: '', - timeEpochMs, - timeEpochNs: (timeEpochMs * 1000000).toString(), - timeLocal: '', - timeUtc: '', - searchWords: [], - ...overrides, - }; -}; diff --git a/packages/grafana-ui/src/components/Logs/logParser.test.ts b/packages/grafana-ui/src/components/Logs/logParser.test.ts deleted file mode 100644 index 7c261805832..00000000000 --- a/packages/grafana-ui/src/components/Logs/logParser.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { ArrayVector, FieldType, MutableDataFrame } from '@grafana/data'; - -import { createLogRow } from './__mocks__/logRow'; -import { getAllFields } from './logParser'; - -describe('getAllFields', () => { - it('should filter out field with labels name and other type', () => { - const logRow = createLogRow({ - entryFieldIndex: 10, - dataFrame: new MutableDataFrame({ - refId: 'A', - fields: [ - testStringField, - { - name: 'labels', - type: FieldType.other, - config: {}, - values: new ArrayVector([{ place: 'luna', source: 'data' }]), - }, - ], - }), - }); - - const fields = getAllFields(logRow); - expect(fields.length).toBe(1); - expect(fields.find((field) => field.key === 'labels')).toBe(undefined); - }); - - it('should not filter out field with labels name and string type', () => { - const logRow = createLogRow({ - entryFieldIndex: 10, - dataFrame: new MutableDataFrame({ - refId: 'A', - fields: [ - testStringField, - { - name: 'labels', - type: FieldType.string, - config: {}, - values: new ArrayVector([{ place: 'luna', source: 'data' }]), - }, - ], - }), - }); - const fields = getAllFields(logRow); - expect(fields.length).toBe(2); - expect(fields.find((field) => field.key === 'labels')).not.toBe(undefined); - }); - - it('should filter out field with id name', () => { - const logRow = createLogRow({ - entryFieldIndex: 10, - dataFrame: new MutableDataFrame({ - refId: 'A', - fields: [ - testStringField, - { - name: 'id', - type: FieldType.string, - config: {}, - values: new ArrayVector(['1659620138401000000_8b1f7688_']), - }, - ], - }), - }); - - const fields = getAllFields(logRow); - expect(fields.length).toBe(1); - expect(fields.find((field) => field.key === 'id')).toBe(undefined); - }); - - it('should filter out entry field which is shown as the log message', () => { - const logRow = createLogRow({ - entryFieldIndex: 3, - dataFrame: new MutableDataFrame({ - refId: 'A', - fields: [ - testStringField, - { - name: 'labels', - type: FieldType.other, - config: {}, - values: new ArrayVector([{ place: 'luna', source: 'data' }]), - }, - { - name: 'Time', - type: FieldType.time, - config: {}, - values: new ArrayVector([1659620138401]), - }, - { - name: 'Line', - type: FieldType.string, - config: {}, - values: new ArrayVector([ - '_entry="log text with ANSI \u001b[31mpart of the text\u001b[0m [616951240]" counter=300 float=NaN label=val3 level=info', - ]), - }, - ], - }), - }); - - const fields = getAllFields(logRow); - expect(fields.find((field) => field.key === 'Line')).toBe(undefined); - }); - - it('should filter out field with config hidden field', () => { - const testField = { ...testStringField }; - testField.config = { - custom: { - hidden: true, - }, - }; - const logRow = createLogRow({ - entryFieldIndex: 10, - dataFrame: new MutableDataFrame({ - refId: 'A', - fields: [{ ...testField }], - }), - }); - - const fields = getAllFields(logRow); - expect(fields.length).toBe(0); - expect(fields.find((field) => field.key === testField.name)).toBe(undefined); - }); - - it('should filter out field with null values', () => { - const logRow = createLogRow({ - entryFieldIndex: 10, - dataFrame: new MutableDataFrame({ - refId: 'A', - fields: [{ ...testFieldWithNullValue }], - }), - }); - - const fields = getAllFields(logRow); - expect(fields.length).toBe(0); - expect(fields.find((field) => field.key === testFieldWithNullValue.name)).toBe(undefined); - }); - - it('should not filter out field with string values', () => { - const logRow = createLogRow({ - entryFieldIndex: 10, - dataFrame: new MutableDataFrame({ - refId: 'A', - fields: [{ ...testStringField }], - }), - }); - - const fields = getAllFields(logRow); - expect(fields.length).toBe(1); - expect(fields.find((field) => field.key === testStringField.name)).not.toBe(undefined); - }); -}); - -const testStringField = { - name: 'test_field_string', - type: FieldType.string, - config: {}, - values: new ArrayVector(['abc']), -}; - -const testFieldWithNullValue = { - name: 'test_field_null', - type: FieldType.string, - config: {}, - values: new ArrayVector([null]), -}; diff --git a/packages/grafana-ui/src/components/MatchersUI/utils.ts b/packages/grafana-ui/src/components/MatchersUI/utils.ts index 490a514009a..d3a9c216545 100644 --- a/packages/grafana-ui/src/components/MatchersUI/utils.ts +++ b/packages/grafana-ui/src/components/MatchersUI/utils.ts @@ -29,7 +29,7 @@ export function frameHasName(name: string | undefined, names: FrameFieldsDisplay } /** - * Retuns the distinct names in a set of frames + * Returns the distinct names in a set of frames */ function getFrameFieldsDisplayNames(data: DataFrame[], filter?: (field: Field) => boolean): FrameFieldsDisplayNames { const names: FrameFieldsDisplayNames = { diff --git a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx index 8b65ddc728e..c4c5b58a412 100644 --- a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx +++ b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx @@ -24,7 +24,6 @@ export interface Props { className?: string; isFullscreen?: boolean; 'aria-label'?: string; - buttonOverflowAlignment?: 'left' | 'right'; } /** @alpha */ @@ -43,7 +42,6 @@ export const PageToolbar: FC = React.memo( className, /** main nav-container aria-label **/ 'aria-label': ariaLabel, - buttonOverflowAlignment = 'right', }) => { const styles = useStyles2(getStyles); @@ -134,9 +132,7 @@ export const PageToolbar: FC = React.memo( )}
- - {React.Children.toArray(children).filter(Boolean)} - + {React.Children.toArray(children).filter(Boolean)} ); } diff --git a/packages/grafana-ui/src/components/Segment/SegmentInput.tsx b/packages/grafana-ui/src/components/Segment/SegmentInput.tsx index 5e3e4704be6..acfaef89d9d 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentInput.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentInput.tsx @@ -74,6 +74,8 @@ export function SegmentInput({ = (args) => { ); }; +export const BasicVirtualizedList: Story = (args) => { + const [value, setValue] = useState>(); + + return ( + <> + { + onUpdateExpressionType(selection.value ?? ExpressionQueryType.classic); + setEditMode(false); + }} + onBlur={() => { + setEditMode(false); + }} + options={gelTypes} + value={selectedExpressionType} + width={25} + /> + )} + + + + + + ); +}; + +interface FrameProps extends Pick { + frame: DataFrame; + index: number; +} + +const FrameRow: FC = ({ frame, index, isAlertCondition }) => { + const styles = useStyles2(getStyles); + + const name = getSeriesName(frame) || 'Series ' + index; + const value = getSeriesValue(frame); + + const showFiring = isAlertCondition && value !== 0; + const showNormal = isAlertCondition && value === 0; + + return ( +
+ + + {name} + +
{value}
+ {showFiring && } + {showNormal && } +
+
+ ); +}; + +const TimeseriesRow: FC = ({ frame, index }) => { + const styles = useStyles2(getStyles); + + const hasLabels = frame.fields[1].labels; + const name = hasLabels ? formatLabels(frame.fields[1].labels ?? {}) : 'Series ' + index; + + const timestamps = frame.fields[0].values.toArray(); + + const getTimestampFromIndex = (index: number) => frame.fields[0].values.get(index); + const getValueFromIndex = (index: number) => frame.fields[1].values.get(index); + + return ( +
+ + + {name} + +
+ + + + Timestamp + Value + + + + {timestamps.map((_, index) => ( + + {dateTimeFormat(getTimestampFromIndex(index))} + {getValueFromIndex(index)} + + ))} + + + } + > + Time series data + +
+
+
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + expression: { + wrapper: css` + display: flex; + border: solid 1px ${theme.colors.border.medium}; + + border-radius: ${theme.shape.borderRadius()}; + max-width: 640px; + `, + stack: css` + display: flex; + flex-direction: column; + flex-wrap: nowrap; + gap: 0; + min-width: 0; // this one is important to prevent text overflow + `, + alertCondition: css``, + body: css` + padding: ${theme.spacing(1)}; + flex: 1; + `, + refId: css` + font-weight: ${theme.typography.fontWeightBold}; + color: ${theme.colors.primary.text}; + `, + results: css` + border-top: solid 1px ${theme.colors.border.medium}; + `, + noResults: css` + display: flex; + align-items: center; + justify-content: center; + `, + resultsRow: css` + padding: ${theme.spacing(0.75)} ${theme.spacing(1)}; + + &:nth-child(odd) { + background-color: ${theme.colors.background.secondary}; + } + + &:hover { + background-color: ${theme.colors.background.canvas}; + } + `, + resultValue: css` + color: ${theme.colors.text.maxContrast}; + text-align: right; + `, + resultLabel: css` + flex: 1; + `, + noData: css` + display: flex; + align-items: center; + justify-content: center; + padding: ${theme.spacing()}; + `, + }, + mutedText: css` + color: ${theme.colors.text.secondary}; + font-size: 0.9em; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + `, + header: { + wrapper: css` + background: ${theme.colors.background.secondary}; + padding: ${theme.spacing(0.5)} ${theme.spacing(1)}; + border-bottom: solid 1px ${theme.colors.border.medium}; + `, + }, + footer: css` + background: ${theme.colors.background.secondary}; + padding: ${theme.spacing(1)}; + border-top: solid 1px ${theme.colors.border.medium}; + `, + draggableIcon: css` + cursor: grab; + `, + mutedIcon: css` + color: ${theme.colors.text.secondary}; + `, + editable: css` + padding: ${theme.spacing(0.5)} ${theme.spacing(1)}; + border: solid 1px ${theme.colors.border.weak}; + border-radius: ${theme.shape.borderRadius()}; + + display: flex; + flex-direction: row; + align-items: center; + gap: ${theme.spacing(1)}; + + cursor: pointer; + `, + timeseriesTableWrapper: css` + max-height: 500px; + max-width: 300px; + + overflow-y: scroll; + + padding: 0 !important; // not sure why but style override doesn't work otherwise :( (Gilles) + `, + timeseriesTable: css` + table-layout: auto; + + width: 100%; + height: 100%; + + td, + th { + padding: ${theme.spacing(1)}; + } + + td { + background: ${theme.colors.background.primary}; + } + + th { + background: ${theme.colors.background.secondary}; + } + + tr { + border-bottom: 1px solid ${theme.colors.border.medium}; + + &:last-of-type { + border-bottom: none; + } + } + `, +}); diff --git a/public/app/features/alerting/unified/components/expressions/util.ts b/public/app/features/alerting/unified/components/expressions/util.ts new file mode 100644 index 00000000000..61a6b097dd8 --- /dev/null +++ b/public/app/features/alerting/unified/components/expressions/util.ts @@ -0,0 +1,31 @@ +import { DataFrame, Labels, roundDecimals } from '@grafana/data'; + +const getSeriesName = (frame: DataFrame): string => { + return frame.name ?? formatLabels(frame.fields[0].labels ?? {}); +}; + +const getSeriesValue = (frame: DataFrame) => { + const value = frame.fields[0].values.get(0); + + if (Number.isFinite(value)) { + return roundDecimals(value, 5); + } + + return value; +}; + +const formatLabels = (labels: Labels): string => { + return Object.entries(labels) + .map(([key, value]) => key + '=' + value) + .join(', '); +}; + +const isEmptySeries = (series: DataFrame[]): boolean => { + const isEmpty = series.every((serie) => + serie.fields.every((field) => field.values.toArray().every((value) => value == null)) + ); + + return isEmpty; +}; + +export { getSeriesName, getSeriesValue, formatLabels, isEmptySeries }; diff --git a/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.test.tsx b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.test.tsx new file mode 100644 index 00000000000..9950ba64363 --- /dev/null +++ b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.test.tsx @@ -0,0 +1,56 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { logInfo } from '@grafana/runtime'; +import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; + +import { LogMessages } from '../../Analytics'; + +import { NewRuleFromPanelButton } from './NewRuleFromPanelButton'; + +jest.mock('app/types', () => { + const original = jest.requireActual('app/types'); + return { + ...original, + useSelector: jest.fn(), + }; +}); + +jest.mock('react-router-dom', () => ({ + useLocation: () => ({ + pathname: 'localhost:3000/example/path', + }), +})); + +jest.mock('@grafana/runtime', () => { + const original = jest.requireActual('@grafana/runtime'); + return { + ...original, + logInfo: jest.fn(), + }; +}); + +jest.mock('react-use', () => ({ + useAsync: () => ({ loading: false, value: {} }), +})); + +describe('Analytics', () => { + it('Sends log info when creating an alert rule from a panel', async () => { + const panel = new PanelModel({ + id: 123, + }); + const dashboard = new DashboardModel({ + id: 1, + }); + render(); + + const button = screen.getByText('Create alert rule from this panel'); + + button.addEventListener('click', (event) => event.preventDefault(), false); + + await userEvent.click(button); + + expect(logInfo).toHaveBeenCalledWith(LogMessages.alertRuleFromPanel); + }); +}); diff --git a/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx index 20d394bbb65..51ace2c47d6 100644 --- a/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx +++ b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx @@ -3,10 +3,12 @@ import { useLocation } from 'react-router-dom'; import { useAsync } from 'react-use'; import { urlUtil } from '@grafana/data'; +import { logInfo } from '@grafana/runtime'; import { Alert, Button, LinkButton } from '@grafana/ui'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { useSelector } from 'app/types'; +import { LogMessages } from '../../Analytics'; import { panelToRuleFormValues } from '../../utils/rule-form'; interface Props { @@ -46,7 +48,13 @@ export const NewRuleFromPanelButton: FC = ({ dashboard, panel, className }); return ( - + logInfo(LogMessages.alertRuleFromPanel)} + href={ruleFormUrl} + className={className} + data-testid="create-alert-rule-button" + > Create alert rule from this panel ); diff --git a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx index 7c8a0d91469..6fa8e77b6bf 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx @@ -4,13 +4,15 @@ import { FormProvider, useForm, UseFormWatch } from 'react-hook-form'; import { Link } from 'react-router-dom'; import { GrafanaTheme2 } from '@grafana/data'; -import { Button, ConfirmModal, CustomScrollbar, PageToolbar, Spinner, useStyles2 } from '@grafana/ui'; +import { logInfo } from '@grafana/runtime'; +import { Button, ConfirmModal, CustomScrollbar, Spinner, useStyles2, HorizontalGroup } from '@grafana/ui'; import { useAppNotification } from 'app/core/copy/appNotification'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { useDispatch } from 'app/types'; import { RuleWithLocation } from 'app/types/unified-alerting'; +import { LogMessages } from '../../Analytics'; import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { deleteRuleAction, saveRuleFormAction } from '../../state/actions'; import { RuleFormType, RuleFormValues } from '../../types/rule-form'; @@ -23,7 +25,7 @@ import { DetailsStep } from './DetailsStep'; import { GrafanaEvaluationBehavior } from './GrafanaEvaluationBehavior'; import { NotificationsStep } from './NotificationsStep'; import { RuleInspector } from './RuleInspector'; -import { QueryAndAlertConditionStep } from './query-and-alert-condition/QueryAndAlertConditionStep'; +import { QueryAndExpressionsStep } from './query-and-alert-condition/QueryAndExpressionsStep'; type Props = { existing?: RuleWithLocation; @@ -46,6 +48,7 @@ export const AlertRuleForm: FC = ({ existing }) => { return { ...getDefaultFormValues(), queries: getDefaultQueries(), + condition: 'C', ...(queryParams['defaults'] ? JSON.parse(queryParams['defaults'] as string) : {}), type: RuleFormType.grafana, }; @@ -108,9 +111,15 @@ export const AlertRuleForm: FC = ({ existing }) => { return (
e.preventDefault()} className={styles.form}> - + - @@ -147,11 +156,11 @@ export const AlertRuleForm: FC = ({ existing }) => { {submitState.loading && } Save and exit - +
- + {showStep2 && ( <> {type === RuleFormType.grafana ? : } @@ -204,9 +213,9 @@ const getStyles = (theme: GrafanaTheme2) => { background: ${theme.colors.background.primary}; border: 1px solid ${theme.colors.border.weak}; border-radius: ${theme.shape.borderRadius()}; - margin: ${theme.spacing(0, 2, 2)}; overflow: hidden; flex: 1; + margin-top: ${theme.spacing(1)}; `, flexRow: css` display: flex; diff --git a/public/app/features/alerting/unified/components/rule-editor/ConditionField.test.tsx b/public/app/features/alerting/unified/components/rule-editor/ConditionField.test.tsx deleted file mode 100644 index 440d155f385..00000000000 --- a/public/app/features/alerting/unified/components/rule-editor/ConditionField.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React, { FC } from 'react'; -import { FormProvider, useForm, UseFormProps } from 'react-hook-form'; - -import { ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource'; - -import { RuleFormValues } from '../../types/rule-form'; - -import { ConditionField } from './ConditionField'; - -const FormProviderWrapper: FC = ({ children, ...props }) => { - const methods = useForm({ ...props }); - return {children}; -}; - -describe('ConditionField', () => { - it('should render the correct condition when editing existing rule', () => { - const existingRule = { - name: 'ConditionsTest', - condition: 'B', - queries: [ - { refId: 'A' }, - { refId: 'B', datasourceUid: ExpressionDatasourceUID }, - { refId: 'C', datasourceUid: ExpressionDatasourceUID }, - ], - } as RuleFormValues; - - const form = ( - - - - ); - - render(form); - expect(screen.getByLabelText(/^A/)).not.toBeChecked(); - expect(screen.getByLabelText(/^B/)).toBeChecked(); - expect(screen.getByLabelText(/^C/)).not.toBeChecked(); - }); -}); diff --git a/public/app/features/alerting/unified/components/rule-editor/ConditionField.tsx b/public/app/features/alerting/unified/components/rule-editor/ConditionField.tsx deleted file mode 100644 index a07c9ba8285..00000000000 --- a/public/app/features/alerting/unified/components/rule-editor/ConditionField.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { css } from '@emotion/css'; -import { last } from 'lodash'; -import React, { FC, useEffect, useMemo } from 'react'; -import { useFormContext } from 'react-hook-form'; - -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { Alert, Card, Field, InputControl, RadioButtonList, useStyles2 } from '@grafana/ui'; -import { ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource'; - -import { RuleFormValues } from '../../types/rule-form'; - -interface Props { - existing?: boolean; -} - -export const ConditionField: FC = ({ existing = false }) => { - const { - watch, - setValue, - formState: { errors }, - } = useFormContext(); - - const queries = watch('queries'); - const condition = watch('condition'); - - const expressions = useMemo(() => { - return queries.filter((query) => query.datasourceUid === ExpressionDatasourceUID); - }, [queries]); - - const options = useMemo( - () => - queries - .filter((q) => !!q.refId) - .map>((q) => ({ - value: q.refId, - label: `${q.refId} - ${expressions.includes(q) ? 'expression' : 'query'}`, - })), - [queries, expressions] - ); - - // automatically use the last expression when new expressions have been added - useEffect(() => { - const lastExpression = last(expressions); - if (lastExpression && !existing) { - setValue('condition', lastExpression.refId, { shouldValidate: true }); - } - }, [expressions, setValue, existing]); - - // reset condition if option no longer exists or if it is unset, but there are options available - useEffect(() => { - const lastExpression = last(expressions); - const conditionExists = options.find(({ value }) => value === condition); - - if (condition && !conditionExists) { - setValue('condition', lastExpression?.refId ?? null); - } else if (!condition && lastExpression) { - setValue('condition', lastExpression.refId, { shouldValidate: true }); - } - }, [condition, expressions, options, setValue]); - - const styles = useStyles2(getStyles); - - return options.length ? ( - - Set alert condition - Select one of your queries or expressions set above that contains your alert condition. - - - ( - - )} - rules={{ - required: { - value: true, - message: 'Please select the condition to alert on', - }, - }} - /> - - - - ) : ( - - Create at least one query or expression to be alerted on - - ); -}; - -const getStyles = (theme: GrafanaTheme2) => ({ - container: css` - max-width: ${theme.breakpoints.values.sm}px; - `, -}); diff --git a/public/app/features/alerting/unified/components/rule-editor/ExpressionsEditor.tsx b/public/app/features/alerting/unified/components/rule-editor/ExpressionsEditor.tsx new file mode 100644 index 00000000000..a1ecc460869 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/ExpressionsEditor.tsx @@ -0,0 +1,68 @@ +import React, { FC, useMemo } from 'react'; + +import { PanelData } from '@grafana/data'; +import { Stack } from '@grafana/ui'; +import { isExpressionQuery } from 'app/features/expressions/guards'; +import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; +import { AlertQuery } from 'app/types/unified-alerting-dto'; + +import { Expression } from '../expressions/Expression'; + +import { errorFromSeries, warningFromSeries } from './util'; + +interface Props { + condition: string | null; + onSetCondition: (refId: string) => void; + panelData: Record; + queries: AlertQuery[]; + onRemoveExpression: (refId: string) => void; + onUpdateRefId: (oldRefId: string, newRefId: string) => void; + onUpdateExpressionType: (refId: string, type: ExpressionQueryType) => void; + onUpdateQueryExpression: (query: ExpressionQuery) => void; +} + +export const ExpressionsEditor: FC = ({ + condition, + onSetCondition, + queries, + panelData, + onUpdateRefId, + onRemoveExpression, + onUpdateExpressionType, + onUpdateQueryExpression, +}) => { + const expressionQueries = useMemo(() => { + return queries.reduce((acc: ExpressionQuery[], query) => { + return isExpressionQuery(query.model) ? acc.concat(query.model) : acc; + }, []); + }, [queries]); + + return ( + + {expressionQueries.map((query) => { + const data = panelData[query.refId]; + + const isAlertCondition = condition === query.refId; + const error = isAlertCondition && data ? errorFromSeries(data.series) : undefined; + const warning = isAlertCondition && data ? warningFromSeries(data.series) : undefined; + + return ( + + ); + })} + + ); +}; diff --git a/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx b/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx index c8ff4d087dd..cf59072f1aa 100644 --- a/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx @@ -16,7 +16,6 @@ import { CollapseToggle } from '../CollapseToggle'; import { EvaluationIntervalLimitExceeded } from '../InvalidIntervalWarning'; import { GrafanaAlertStatePicker } from './GrafanaAlertStatePicker'; -import { PreviewRule } from './PreviewRule'; import { RuleEditorSection } from './RuleEditorSection'; const MIN_TIME_RANGE_STEP_S = 10; // 10 seconds @@ -161,7 +160,6 @@ export const GrafanaEvaluationBehavior: FC = () => { )} - ); }; diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryEditor.test.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryEditor.test.tsx deleted file mode 100644 index 7036b1ed10a..00000000000 --- a/public/app/features/alerting/unified/components/rule-editor/QueryEditor.test.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { render } from '@testing-library/react'; -import React from 'react'; -import { byLabelText, byTestId, byText } from 'testing-library-selector'; - -import { getDefaultRelativeTimeRange } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; -import { setDataSourceSrv } from '@grafana/runtime'; - -import { MockDataSourceApi } from '../../../../../../test/mocks/datasource_srv'; -import { ExpressionDatasourceUID, instanceSettings } from '../../../../expressions/ExpressionDatasource'; -import { mockDataSource, MockDataSourceSrv } from '../../mocks'; -import { getDefaultQueries } from '../../utils/rule-form'; - -import { QueryEditor } from './QueryEditor'; - -const ui = { - queryNames: byTestId('query-name-div'), - dataSourcePicker: byLabelText(selectors.components.DataSourcePicker.container), - noDataSourcesWarning: byText('You appear to have no compatible data sources'), -}; - -const onChangeMock = jest.fn(); -describe('Query Editor', () => { - it('should maintain the original query time range when duplicating it', () => { - const query = { - refId: 'A', - queryType: '', - datasourceUid: '', - model: { refId: 'A', hide: false }, - relativeTimeRange: { from: 100, to: 0 }, - }; - const queryEditor = new QueryEditor({ - onChange: onChangeMock, - value: [query], - }); - - queryEditor.onDuplicateQuery(query); - - expect(onChangeMock).toHaveBeenCalledWith([ - query, - { ...query, ...{ refId: 'B', model: { refId: 'B', hide: false } } }, - ]); - }); - - it('should use the default query time range if none is set when duplicating a query', () => { - const query = { - refId: 'A', - queryType: '', - datasourceUid: '', - model: { refId: 'A', hide: false }, - }; - const queryEditor = new QueryEditor({ - onChange: onChangeMock, - value: [query], - }); - - queryEditor.onDuplicateQuery(query); - - const defaultRange = getDefaultRelativeTimeRange(); - - expect(onChangeMock).toHaveBeenCalledWith([ - query, - { ...query, ...{ refId: 'B', relativeTimeRange: defaultRange, model: { refId: 'B', hide: false } } }, - ]); - }); - - it('should select first data source supporting alerting when there is no default data source', async () => { - const dsServer = new MockDataSourceSrv({ - influx: mockDataSource({ name: 'influx' }, { alerting: true }), - postgres: mockDataSource({ name: 'postgres' }, { alerting: true }), - [ExpressionDatasourceUID]: instanceSettings, - }); - dsServer.get = () => Promise.resolve(new MockDataSourceApi()); - - setDataSourceSrv(dsServer); - - const defaultQueries = getDefaultQueries(); - - render( null} value={defaultQueries} />); - - const queryRef = await ui.queryNames.findAll(); - const select = await ui.dataSourcePicker.find(); - - expect(queryRef).toHaveLength(2); - expect(queryRef[0]).toHaveTextContent('A'); - expect(queryRef[1]).toHaveTextContent('B'); - expect(select).toHaveTextContent('influx'); // Alphabetical order - expect(ui.noDataSourcesWarning.query()).not.toBeInTheDocument(); - }); - - it('should select the default data source when specified', async () => { - const dsServer = new MockDataSourceSrv({ - influx: mockDataSource({ name: 'influx' }, { alerting: true }), - postgres: mockDataSource({ name: 'postgres', isDefault: true }, { alerting: true }), - [ExpressionDatasourceUID]: instanceSettings, - }); - dsServer.get = () => Promise.resolve(new MockDataSourceApi()); - - setDataSourceSrv(dsServer); - - const defaultQueries = getDefaultQueries(); - - render( null} value={defaultQueries} />); - - const queryRef = await ui.queryNames.findAll(); - const select = await ui.dataSourcePicker.find(); - - expect(queryRef).toHaveLength(2); - expect(select).toHaveTextContent('postgres'); // Default data source - }); -}); diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryEditor.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryEditor.tsx index 243309d58b9..d41a10472b4 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryEditor.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryEditor.tsx @@ -1,227 +1,52 @@ import { css } from '@emotion/css'; -import React, { PureComponent } from 'react'; +import React, { FC } from 'react'; -import { - DataQuery, - getDefaultRelativeTimeRange, - GrafanaTheme2, - LoadingState, - PanelData, - RelativeTimeRange, -} from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; -import { config } from '@grafana/runtime'; -import { Button, HorizontalGroup, stylesFactory, Tooltip } from '@grafana/ui'; -import { getNextRefIdChar } from 'app/core/utils/query'; -import { - dataSource as expressionDatasource, - ExpressionDatasourceUID, -} from 'app/features/expressions/ExpressionDatasource'; -import { isExpressionQuery } from 'app/features/expressions/guards'; -import { ExpressionQueryType } from 'app/features/expressions/types'; -import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; +import { GrafanaTheme2, PanelData } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; import { AlertQuery } from 'app/types/unified-alerting-dto'; -import { AlertingQueryRunner } from '../../state/AlertingQueryRunner'; -import { getDefaultOrFirstCompatibleDataSource } from '../../utils/datasource'; - import { QueryRows } from './QueryRows'; interface Props { - value?: AlertQuery[]; - onChange: (queries: AlertQuery[]) => void; + panelData: Record; + queries: AlertQuery[]; + onRunQueries: () => void; + onChangeQueries: (queries: AlertQuery[]) => void; + onDuplicateQuery: (query: AlertQuery) => void; + condition: string | null; + onSetCondition: (refId: string) => void; } -interface State { - panelDataByRefId: Record; -} +export const QueryEditor: FC = ({ + queries, + panelData, + onRunQueries, + onChangeQueries, + onDuplicateQuery, + condition, + onSetCondition, +}) => { + const styles = useStyles2(getStyles); -export class QueryEditor extends PureComponent { - private runner: AlertingQueryRunner; - private queries: AlertQuery[]; - - constructor(props: Props) { - super(props); - this.state = { panelDataByRefId: {} }; - this.runner = new AlertingQueryRunner(); - this.queries = props.value ?? []; - } - - componentDidMount() { - this.runner.get().subscribe((data) => { - this.setState({ panelDataByRefId: data }); - }); - } - - componentWillUnmount() { - this.runner.destroy(); - } - - onRunQueries = () => { - const { queries } = this; - this.runner.run(queries); - }; - - onCancelQueries = () => { - this.runner.cancel(); - }; - - onChangeQueries = (queries: AlertQuery[]) => { - this.queries = queries; - this.props.onChange(queries); - }; - - onDuplicateQuery = (query: AlertQuery) => { - const { queries } = this; - this.onChangeQueries(addQuery(queries, query)); - }; - - onNewAlertingQuery = () => { - const { queries } = this; - const datasource = getDefaultOrFirstCompatibleDataSource(); - - if (!datasource) { - return; - } - - this.onChangeQueries( - addQuery(queries, { - datasourceUid: datasource.uid, - model: { - refId: '', - datasource: { - type: datasource.type, - uid: datasource.uid, - }, - }, - }) - ); - }; - - onNewExpressionQuery = () => { - const { queries } = this; - - const lastQuery = queries.at(-1); - const defaultParams = lastQuery ? [lastQuery.refId] : []; - - this.onChangeQueries( - addQuery(queries, { - datasourceUid: ExpressionDatasourceUID, - model: expressionDatasource.newQuery({ - type: ExpressionQueryType.classic, - conditions: [{ ...defaultCondition, query: { params: defaultParams } }], - expression: lastQuery?.refId, - }), - }) - ); - }; - - isRunning() { - const data = Object.values(this.state.panelDataByRefId).find((d) => Boolean(d)); - return data?.state === LoadingState.Loading; - } - - renderRunQueryButton() { - const isRunning = this.isRunning(); - - if (isRunning) { - return ( - - ); - } - - return ( - - ); - } - - render() { - const { value = [] } = this.props; - const { panelDataByRefId } = this.state; - const styles = getStyles(config.theme2); - - const noCompatibleDataSources = getDefaultOrFirstCompatibleDataSource() === undefined; - - return ( -
- - - - - - {config.expressionsEnabled && ( - - )} - {this.renderRunQueryButton()} - -
- ); - } -} - -const addQuery = ( - queries: AlertQuery[], - queryToAdd: Pick -): AlertQuery[] => { - const refId = getNextRefIdChar(queries); - - const query: AlertQuery = { - ...queryToAdd, - refId, - queryType: '', - model: { - ...queryToAdd.model, - hide: false, - refId, - }, - relativeTimeRange: queryToAdd.relativeTimeRange || defaultTimeRange(queryToAdd.model), - }; - - return [...queries, query]; + return ( +
+ +
+ ); }; -const defaultTimeRange = (model: DataQuery): RelativeTimeRange | undefined => { - if (isExpressionQuery(model)) { - return; - } - - return getDefaultRelativeTimeRange(); -}; - -const getStyles = stylesFactory((theme: GrafanaTheme2) => { - return { - container: css` - background-color: ${theme.colors.background.primary}; - height: 100%; - max-width: ${theme.breakpoints.values.xxl}px; - `, - runWrapper: css` - margin-top: ${theme.spacing(1)}; - `, - editorWrapper: css` - border: 1px solid ${theme.colors.border.medium}; - border-radius: ${theme.shape.borderRadius()}; - `, - }; +const getStyles = (theme: GrafanaTheme2) => ({ + container: css` + background-color: ${theme.colors.background.primary}; + height: 100%; + max-width: ${theme.breakpoints.values.xxl}px; + `, }); diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx index 77b1feb17cf..d0b424efcb4 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx @@ -19,36 +19,29 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; import { EmptyQueryWrapper, QueryWrapper } from './QueryWrapper'; -import { queriesWithUpdatedReferences } from './util'; +import { errorFromSeries } from './util'; interface Props { // The query configuration queries: AlertQuery[]; data: Record; + onRunQueries: () => void; // Query editing onQueriesChange: (queries: AlertQuery[]) => void; onDuplicateQuery: (query: AlertQuery) => void; - onRunQueries: () => void; + condition: string | null; + onSetCondition: (refId: string) => void; } -interface State { - dataPerQuery: Record; -} - -export class QueryRows extends PureComponent { +export class QueryRows extends PureComponent { constructor(props: Props) { super(props); - - this.state = { dataPerQuery: {} }; } onRemoveQuery = (query: DataQuery) => { - this.props.onQueriesChange( - this.props.queries.filter((item) => { - return item.model.refId !== query.refId; - }) - ); + const { queries, onQueriesChange } = this.props; + onQueriesChange(queries.filter((q) => q.refId !== query.refId)); }; onChangeTimeRange = (timeRange: RelativeTimeRange, index: number) => { @@ -119,12 +112,8 @@ export class QueryRows extends PureComponent { onChangeQuery = (query: DataQuery, index: number) => { const { queries, onQueriesChange } = this.props; - // find what queries still have a reference to the old name - const previousRefId = queries[index].refId; - const newRefId = query.refId; - onQueriesChange( - queriesWithUpdatedReferences(queries, previousRefId, newRefId).map((item, itemIndex) => { + queries.map((item, itemIndex) => { if (itemIndex !== index) { return item; } @@ -162,13 +151,6 @@ export class QueryRows extends PureComponent { onQueriesChange(update); }; - onDuplicateQuery = (query: DataQuery, source: AlertQuery): void => { - this.props.onDuplicateQuery({ - ...source, - model: query, - }); - }; - getDataSourceSettings = (query: AlertQuery): DataSourceInstanceSettings | undefined => { return getDataSourceSrv().getInstanceSettings(query.datasourceUid); }; @@ -218,7 +200,7 @@ export class QueryRows extends PureComponent { }; render() { - const { onDuplicateQuery, onRunQueries, queries } = this.props; + const { queries } = this.props; const thresholdByRefId = this.getThresholdsForQueries(queries); return ( @@ -234,6 +216,9 @@ export class QueryRows extends PureComponent { }; const dsSettings = this.getDataSourceSettings(query); + const isAlertCondition = this.props.condition === query.refId; + const error = isAlertCondition ? errorFromSeries(data.series) : undefined; + if (!dsSettings) { return ( { key={query.refId} dsSettings={dsSettings} data={data} + error={error} query={query} onChangeQuery={this.onChangeQuery} onRemoveQuery={this.onRemoveQuery} queries={queries} onChangeDataSource={this.onChangeDataSource} - onDuplicateQuery={onDuplicateQuery} - onRunQueries={onRunQueries} + onDuplicateQuery={this.props.onDuplicateQuery} onChangeTimeRange={this.onChangeTimeRange} thresholds={thresholdByRefId[query.refId]} onChangeThreshold={this.onChangeThreshold} + onRunQueries={this.props.onRunQueries} + condition={this.props.condition} + onSetCondition={this.props.onSetCondition} /> ); })} diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx index 0538ea7a3bd..0fa08b0bbad 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx @@ -13,18 +13,20 @@ import { RelativeTimeRange, ThresholdsConfig, } from '@grafana/data'; -import { RelativeTimeRangePicker, useStyles2, Tooltip, Icon } from '@grafana/ui'; +import { RelativeTimeRangePicker, useStyles2, Tooltip, Icon, Stack } from '@grafana/ui'; import { isExpressionQuery } from 'app/features/expressions/guards'; import { QueryEditorRow } from 'app/features/query/components/QueryEditorRow'; import { AlertQuery } from 'app/types/unified-alerting-dto'; import { TABLE, TIMESERIES } from '../../utils/constants'; import { SupportedPanelPlugins } from '../PanelPluginsButtonGroup'; +import { AlertConditionIndicator } from '../expressions/AlertConditionIndicator'; import { VizWrapper } from './VizWrapper'; interface Props { data: PanelData; + error?: Error; query: AlertQuery; queries: AlertQuery[]; dsSettings: DataSourceInstanceSettings; @@ -37,10 +39,13 @@ interface Props { index: number; thresholds: ThresholdsConfig; onChangeThreshold: (thresholds: ThresholdsConfig, index: number) => void; + condition: string | null; + onSetCondition: (refId: string) => void; } export const QueryWrapper: FC = ({ data, + error, dsSettings, index, onChangeDataSource, @@ -53,6 +58,8 @@ export const QueryWrapper: FC = ({ queries, thresholds, onChangeThreshold, + condition, + onSetCondition, }) => { const styles = useStyles2(getStyles); const isExpression = isExpressionQuery(query.model); @@ -84,12 +91,13 @@ export const QueryWrapper: FC = ({ ); } - function HeaderExtras({ query, index }: { query: AlertQuery; index: number }) { + // TODO add a warning label here too when the data looks like time series data and is used as an alert condition + function HeaderExtras({ query, error, index }: { query: AlertQuery; error?: Error; index: number }) { if (isExpressionQuery(query.model)) { return null; } else { return ( - <> + {onChangeTimeRange && ( = ({ onChange={(range) => onChangeTimeRange(range, index)} /> )} - + onSetCondition(query.refId)} + enabled={condition === query.refId} + error={error} + /> + ); } } @@ -118,7 +131,7 @@ export const QueryWrapper: FC = ({ onAddQuery={() => onDuplicateQuery(cloneDeep(query))} onRunQuery={onRunQueries} queries={queries} - renderHeaderExtras={() => } + renderHeaderExtras={() => } app={CoreApp.UnifiedAlerting} visualization={ data.state !== LoadingState.NotStarted ? ( @@ -152,7 +165,6 @@ const getStyles = (theme: GrafanaTheme2) => ({ dsTooltip: css` display: flex; align-items: center; - margin-right: ${theme.spacing(2)}; &:hover { opacity: 0.85; cursor: pointer; diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/Query.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/Query.tsx deleted file mode 100644 index 5b1ebd874df..00000000000 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/Query.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { FC } from 'react'; -import { useFormContext } from 'react-hook-form'; - -import { Field, InputControl } from '@grafana/ui'; - -import { RuleFormType, RuleFormValues } from '../../../types/rule-form'; -import { ExpressionEditor } from '../ExpressionEditor'; -import { QueryEditor } from '../QueryEditor'; - -export const Query: FC = () => { - const { - control, - watch, - formState: { errors }, - } = useFormContext(); - - const [type, dataSourceName] = watch(['type', 'dataSourceName']); - - const isGrafanaManagedType = type === RuleFormType.grafana; - const isCloudAlertRuleType = type === RuleFormType.cloudAlerting; - const isRecordingRuleType = type === RuleFormType.cloudRecording; - - const showCloudExpressionEditor = (isRecordingRuleType || isCloudAlertRuleType) && dataSourceName; - - return ( -
- {/* This is the PromQL Editor for Cloud rules and recording rules */} - {showCloudExpressionEditor && ( - - { - return ; - }} - control={control} - rules={{ - required: { value: true, message: 'A valid expression is required' }, - }} - /> - - )} - - {/* This is the editor for Grafana managed rules */} - {isGrafanaManagedType && ( - - } - control={control} - rules={{ - validate: (queries) => Array.isArray(queries) && !!queries.length, - }} - /> - - )} -
- ); -}; diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndAlertConditionStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndAlertConditionStep.tsx deleted file mode 100644 index c4031f8762d..00000000000 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndAlertConditionStep.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { FC } from 'react'; -import { useFormContext } from 'react-hook-form'; - -import { RuleFormType, RuleFormValues } from '../../../types/rule-form'; -import { ConditionField } from '../ConditionField'; -import { RuleEditorSection } from '../RuleEditorSection'; - -import { AlertType } from './AlertType'; -import { Query } from './Query'; - -interface Props { - editingExistingRule: boolean; -} - -export const QueryAndAlertConditionStep: FC = ({ editingExistingRule }) => { - const { watch } = useFormContext(); - - const type = watch('type'); - const isGrafanaManagedType = type === RuleFormType.grafana; - - return ( - - - {type && } - {isGrafanaManagedType && } - - ); -}; diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx new file mode 100644 index 00000000000..3e99c6f88fd --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx @@ -0,0 +1,253 @@ +import React, { FC, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; +import { useFormContext } from 'react-hook-form'; + +import { LoadingState, PanelData } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { config } from '@grafana/runtime'; +import { Alert, Button, Field, InputControl, Stack, Tooltip } from '@grafana/ui'; +import { isExpressionQuery } from 'app/features/expressions/guards'; +import { AlertQuery } from 'app/types/unified-alerting-dto'; + +import { AlertingQueryRunner } from '../../../state/AlertingQueryRunner'; +import { RuleFormType, RuleFormValues } from '../../../types/rule-form'; +import { getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource'; +import { ExpressionEditor } from '../ExpressionEditor'; +import { ExpressionsEditor } from '../ExpressionsEditor'; +import { QueryEditor } from '../QueryEditor'; +import { RuleEditorSection } from '../RuleEditorSection'; +import { refIdExists } from '../util'; + +import { AlertType } from './AlertType'; +import { + duplicateQuery, + addNewDataQuery, + addNewExpression, + queriesAndExpressionsReducer, + removeExpression, + rewireExpressions, + setDataQueries, + updateExpression, + updateExpressionRefId, + updateExpressionType, +} from './reducer'; + +interface Props { + editingExistingRule: boolean; +} + +export const QueryAndExpressionsStep: FC = ({ editingExistingRule }) => { + const runner = useRef(new AlertingQueryRunner()); + const { + setValue, + getValues, + watch, + formState: { errors }, + control, + } = useFormContext(); + const [panelData, setPanelData] = useState>({}); + + const initialState = { + queries: getValues('queries'), + panelData: {}, + }; + const [{ queries }, dispatch] = useReducer(queriesAndExpressionsReducer, initialState); + + const [type, condition, dataSourceName] = watch(['type', 'condition', 'dataSourceName']); + + const isGrafanaManagedType = type === RuleFormType.grafana; + const isCloudAlertRuleType = type === RuleFormType.cloudAlerting; + const isRecordingRuleType = type === RuleFormType.cloudRecording; + + const showCloudExpressionEditor = (isRecordingRuleType || isCloudAlertRuleType) && dataSourceName; + + const cancelQueries = useCallback(() => { + runner.current.cancel(); + }, []); + + const runQueries = useCallback(() => { + runner.current.run(queries); + }, [queries]); + + // whenever we update the queries we have to update the form too + useEffect(() => { + setValue('queries', queries, { shouldValidate: false }); + }, [queries, runQueries, setValue]); + + // set up the AlertQueryRunner + useEffect(() => { + const currentRunner = runner.current; + + runner.current.get().subscribe((data) => { + setPanelData(data); + }); + + return () => currentRunner.destroy(); + }, []); + + const noCompatibleDataSources = getDefaultOrFirstCompatibleDataSource() === undefined; + + const isDataLoading = useMemo(() => { + return Object.values(panelData).some((d) => d.state === LoadingState.Loading); + }, [panelData]); + + // data queries only + const dataQueries = useMemo(() => { + return queries.filter((query) => !isExpressionQuery(query.model)); + }, [queries]); + + const emptyQueries = queries.length === 0; + + const onUpdateRefId = useCallback( + (oldRefId: string, newRefId: string) => { + const newRefIdExists = refIdExists(queries, newRefId); + // TODO we should set an error and explain what went wrong instead of just refusing to update + if (newRefIdExists) { + return; + } + + dispatch(updateExpressionRefId({ oldRefId, newRefId })); + + // update condition too if refId was updated + if (condition === oldRefId) { + setValue('condition', newRefId); + } + }, + [condition, queries, setValue] + ); + + const onChangeQueries = useCallback( + (updatedQueries: AlertQuery[]) => { + dispatch(setDataQueries(updatedQueries)); + + // check if we need to rewire expressions + updatedQueries.forEach((query, index) => { + const oldRefId = queries[index].refId; + const newRefId = query.refId; + + if (oldRefId !== newRefId) { + dispatch(rewireExpressions({ oldRefId, newRefId })); + } + }); + }, + [queries] + ); + + const onDuplicateQuery = useCallback((query: AlertQuery) => { + dispatch(duplicateQuery(query)); + }, []); + + // update the condition if it's been removed + useEffect(() => { + if (!refIdExists(queries, condition)) { + const lastRefId = queries.at(-1)?.refId ?? null; + setValue('condition', lastRefId); + } + }, [condition, queries, setValue]); + + return ( + + + + {/* This is the PromQL Editor for Cloud rules and recording rules */} + {showCloudExpressionEditor && ( + + { + return ; + }} + control={control} + rules={{ + required: { value: true, message: 'A valid expression is required' }, + }} + /> + + )} + + {/* This is the editor for Grafana managed rules */} + {isGrafanaManagedType && ( + + {/* Data Queries */} + { + setValue('condition', refId); + }} + /> + {/* Expression Queries */} + { + setValue('condition', refId); + }} + onRemoveExpression={(refId) => { + dispatch(removeExpression(refId)); + }} + onUpdateRefId={onUpdateRefId} + onUpdateExpressionType={(refId, type) => { + dispatch(updateExpressionType({ refId, type })); + }} + onUpdateQueryExpression={(model) => { + dispatch(updateExpression(model)); + }} + /> + {/* action buttons */} + + + + + + {config.expressionsEnabled && ( + + )} + + {isDataLoading && ( + + )} + {!isDataLoading && ( + + )} + + + {/* No Queries */} + {emptyQueries && ( + + Create at least one query or expression to be alerted on + + )} + + )} + + ); +}; diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/reducer.test.tsx.snap b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/reducer.test.tsx.snap new file mode 100644 index 00000000000..b8678183f40 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/__snapshots__/reducer.test.tsx.snap @@ -0,0 +1,382 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Query and expressions reducer should add a new expression 1`] = ` +Object { + "queries": Array [ + Object { + "datasourceUid": "abc123", + "model": Object { + "refId": "A", + }, + "queryType": "query", + "refId": "A", + }, + Object { + "datasourceUid": "-100", + "model": Object { + "conditions": Array [ + Object { + "evaluator": Object { + "params": Array [ + 0, + 0, + ], + "type": "gt", + }, + "operator": Object { + "type": "and", + }, + "query": Object { + "params": Array [], + }, + "reducer": Object { + "params": Array [], + "type": "avg", + }, + "type": "query", + }, + ], + "datasource": Object { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "", + "hide": false, + "refId": "B", + "type": "math", + }, + "queryType": "", + "refId": "B", + "relativeTimeRange": undefined, + }, + ], +} +`; + +exports[`Query and expressions reducer should add query 1`] = ` +Object { + "queries": Array [ + Object { + "datasourceUid": "abc123", + "model": Object { + "refId": "A", + }, + "queryType": "query", + "refId": "A", + }, + Object { + "datasourceUid": "c8eceabb-0275-4108-8f03-8f74faf4bf6d", + "model": Object { + "datasource": Object { + "type": "prometheus", + "uid": "c8eceabb-0275-4108-8f03-8f74faf4bf6d", + }, + "hide": false, + "refId": "B", + }, + "queryType": "", + "refId": "B", + "relativeTimeRange": Object { + "from": 600, + "to": 0, + }, + }, + ], +} +`; + +exports[`Query and expressions reducer should duplicate query 1`] = ` +Object { + "queries": Array [ + Object { + "datasourceUid": "abc123", + "model": Object { + "refId": "A", + }, + "queryType": "query", + "refId": "A", + }, + Object { + "datasourceUid": "abc123", + "model": Object { + "hide": false, + "refId": "B", + }, + "queryType": "", + "refId": "B", + "relativeTimeRange": Object { + "from": 600, + "to": 0, + }, + }, + ], +} +`; + +exports[`Query and expressions reducer should remove an expression or alert query 1`] = ` +Object { + "queries": Array [ + Object { + "datasourceUid": "abc123", + "model": Object { + "refId": "A", + }, + "queryType": "query", + "refId": "A", + }, + ], +} +`; + +exports[`Query and expressions reducer should rewire expressions 1`] = ` +Object { + "queries": Array [ + Object { + "datasourceUid": "abc123", + "model": Object { + "refId": "A", + }, + "queryType": "query", + "refId": "A", + }, + Object { + "datasourceUid": "-100", + "model": Object { + "conditions": Array [ + Object { + "evaluator": Object { + "params": Array [ + 0, + 0, + ], + "type": "gt", + }, + "operator": Object { + "type": "and", + }, + "query": Object { + "params": Array [ + "C", + ], + }, + "reducer": Object { + "params": Array [], + "type": "avg", + }, + "type": "query", + }, + ], + "datasource": Object { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "", + "refId": "B", + "type": "classic_conditions", + }, + "queryType": "", + "refId": "B", + }, + ], +} +`; + +exports[`Query and expressions reducer should set data queries 1`] = ` +Object { + "queries": Array [ + Object { + "datasourceUid": "-100", + "model": Object { + "conditions": Array [ + Object { + "evaluator": Object { + "params": Array [ + 0, + 0, + ], + "type": "gt", + }, + "operator": Object { + "type": "and", + }, + "query": Object { + "params": Array [ + "A", + ], + }, + "reducer": Object { + "params": Array [], + "type": "avg", + }, + "type": "query", + }, + ], + "datasource": Object { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "", + "refId": "B", + "type": "classic_conditions", + }, + "queryType": "", + "refId": "B", + }, + ], +} +`; + +exports[`Query and expressions reducer should update an expression 1`] = ` +Object { + "queries": Array [ + Object { + "datasourceUid": "-100", + "model": Object { + "conditions": Array [ + Object { + "evaluator": Object { + "params": Array [ + 0, + 0, + ], + "type": "gt", + }, + "operator": Object { + "type": "and", + }, + "query": Object { + "params": Array [ + "A", + ], + }, + "reducer": Object { + "params": Array [], + "type": "avg", + }, + "type": "query", + }, + ], + "datasource": Object { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "", + "refId": "B", + "type": "math", + }, + "queryType": "", + "refId": "B", + }, + ], +} +`; + +exports[`Query and expressions reducer should update an expression refId and rewire expressions 1`] = ` +Object { + "queries": Array [ + Object { + "datasourceUid": "abc123", + "model": Object { + "refId": "C", + }, + "queryType": "query", + "refId": "C", + }, + Object { + "datasourceUid": "-100", + "model": Object { + "conditions": Array [ + Object { + "evaluator": Object { + "params": Array [ + 0, + 0, + ], + "type": "gt", + }, + "operator": Object { + "type": "and", + }, + "query": Object { + "params": Array [ + "C", + ], + }, + "reducer": Object { + "params": Array [], + "type": "avg", + }, + "type": "query", + }, + ], + "datasource": Object { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "", + "refId": "B", + "type": "classic_conditions", + }, + "queryType": "", + "refId": "B", + }, + ], +} +`; + +exports[`Query and expressions reducer should update expression type 1`] = ` +Object { + "queries": Array [ + Object { + "datasourceUid": "abc123", + "model": Object { + "refId": "A", + }, + "queryType": "query", + "refId": "A", + }, + Object { + "datasourceUid": "-100", + "model": Object { + "conditions": Array [ + Object { + "evaluator": Object { + "params": Array [ + 0, + 0, + ], + "type": "gt", + }, + "operator": Object { + "type": "and", + }, + "query": Object { + "params": Array [], + }, + "reducer": Object { + "params": Array [], + "type": "avg", + }, + "type": "query", + }, + ], + "datasource": Object { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "", + "refId": "B", + "type": "reduce", + }, + "queryType": "", + "refId": "B", + }, + ], +} +`; diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx new file mode 100644 index 00000000000..835e8d24d41 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.test.tsx @@ -0,0 +1,213 @@ +import { getDefaultRelativeTimeRange } from '@grafana/data'; +import { getDataSourceSrv } from '@grafana/runtime/src/services/__mocks__/dataSourceSrv'; +import { + dataSource as expressionDatasource, + ExpressionDatasourceUID, +} from 'app/features/expressions/ExpressionDatasource'; +import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; +import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; +import { AlertQuery } from 'app/types/unified-alerting-dto'; + +import { + addNewDataQuery, + addNewExpression, + duplicateQuery, + queriesAndExpressionsReducer, + QueriesAndExpressionsState, + removeExpression, + rewireExpressions, + setDataQueries, + updateExpression, + updateExpressionRefId, + updateExpressionType, +} from './reducer'; + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + getDataSourceSrv: getDataSourceSrv, +})); + +const alertQuery: AlertQuery = { + refId: 'A', + queryType: 'query', + datasourceUid: 'abc123', + model: { + refId: 'A', + }, +}; + +const expressionQuery: AlertQuery = { + datasourceUid: ExpressionDatasourceUID, + model: expressionDatasource.newQuery({ + type: ExpressionQueryType.classic, + conditions: [{ ...defaultCondition, query: { params: ['A'] } }], + expression: '', + refId: 'B', + }), + refId: 'B', + queryType: '', +}; + +describe('Query and expressions reducer', () => { + it('should return initial state', () => { + expect(queriesAndExpressionsReducer(undefined, { type: undefined })).toEqual({ + queries: [], + }); + }); + + it('should duplicate query', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery], + }; + + const newState = queriesAndExpressionsReducer(initialState, duplicateQuery(alertQuery)); + const newQuery = newState.queries.at(-1); + expect(newState).toMatchSnapshot(); + expect(newQuery).toHaveProperty('relativeTimeRange', getDefaultRelativeTimeRange()); + }); + + it('should duplicate query and copy time range', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery], + }; + + const customTimeRange = { + from: -200, + to: 800, + }; + + const query: AlertQuery = { + ...initialState.queries[0], + relativeTimeRange: customTimeRange, + }; + + const previousState: QueriesAndExpressionsState = { + queries: [query], + }; + + const newState = queriesAndExpressionsReducer(previousState, duplicateQuery(query)); + const newQuery = newState.queries.at(-1); + expect(newQuery).toHaveProperty('relativeTimeRange', customTimeRange); + }); + + it('should add query', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery], + }; + + const newState = queriesAndExpressionsReducer(initialState, addNewDataQuery()); + expect(newState.queries).toHaveLength(2); + expect(newState).toMatchSnapshot(); + }); + + it('should set data queries', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery, expressionQuery], + }; + + const newState = queriesAndExpressionsReducer(initialState, setDataQueries([])); + expect(newState.queries).toHaveLength(1); + expect(newState).toMatchSnapshot(); + }); + + it('should add a new expression', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery], + }; + + const newState = queriesAndExpressionsReducer(initialState, addNewExpression()); + expect(newState.queries).toHaveLength(2); + expect(newState).toMatchSnapshot(); + }); + + it('should remove an expression or alert query', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery, expressionQuery], + }; + + let stateWithoutB = queriesAndExpressionsReducer(initialState, removeExpression('B')); + expect(stateWithoutB.queries).toHaveLength(1); + expect(stateWithoutB).toMatchSnapshot(); + + let stateWithoutAOrB = queriesAndExpressionsReducer(stateWithoutB, removeExpression('A')); + expect(stateWithoutAOrB.queries).toHaveLength(0); + }); + + it('should update an expression', () => { + const newExpression: ExpressionQuery = { + ...expressionQuery.model, + type: ExpressionQueryType.math, + }; + + const initialState: QueriesAndExpressionsState = { + queries: [expressionQuery], + }; + + const newState = queriesAndExpressionsReducer(initialState, updateExpression(newExpression)); + expect(newState).toMatchSnapshot(); + }); + + it('should update an expression refId and rewire expressions', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery, expressionQuery], + }; + + const newState = queriesAndExpressionsReducer( + initialState, + updateExpressionRefId({ + oldRefId: 'A', + newRefId: 'C', + }) + ); + + expect(newState).toMatchSnapshot(); + }); + + it('should not update an expression when the refId exists', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery, expressionQuery], + }; + + const newState = queriesAndExpressionsReducer( + initialState, + updateExpressionRefId({ + oldRefId: 'A', + newRefId: 'B', + }) + ); + + expect(newState).toEqual(initialState); + }); + + it('should rewire expressions', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery, expressionQuery], + }; + + const newState = queriesAndExpressionsReducer( + initialState, + rewireExpressions({ + oldRefId: 'A', + newRefId: 'C', + }) + ); + + expect(newState).toMatchSnapshot(); + }); + + it('should update expression type', () => { + const initialState: QueriesAndExpressionsState = { + queries: [alertQuery, expressionQuery], + }; + + const newState = queriesAndExpressionsReducer( + initialState, + updateExpressionType({ + refId: 'B', + type: ExpressionQueryType.reduce, + }) + ); + + expect(newState).toMatchSnapshot(); + }); +}); diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts new file mode 100644 index 00000000000..d5533ffdf7b --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/reducer.ts @@ -0,0 +1,163 @@ +import { createAction, createReducer } from '@reduxjs/toolkit'; + +import { DataQuery, RelativeTimeRange, getDefaultRelativeTimeRange } from '@grafana/data'; +import { getNextRefIdChar } from 'app/core/utils/query'; +import { + dataSource as expressionDatasource, + ExpressionDatasourceUID, +} from 'app/features/expressions/ExpressionDatasource'; +import { isExpressionQuery } from 'app/features/expressions/guards'; +import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; +import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; +import { AlertQuery } from 'app/types/unified-alerting-dto'; + +import { getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource'; +import { queriesWithUpdatedReferences, refIdExists } from '../util'; + +export interface QueriesAndExpressionsState { + queries: AlertQuery[]; +} + +const initialState: QueriesAndExpressionsState = { + queries: [], +}; + +export const duplicateQuery = createAction('duplicateQuery'); +export const addNewDataQuery = createAction('addNewDataQuery'); +export const setDataQueries = createAction('setDataQueries'); + +export const addNewExpression = createAction('addNewExpression'); +export const removeExpression = createAction('removeExpression'); +export const updateExpression = createAction('updateExpression'); +export const updateExpressionRefId = createAction<{ oldRefId: string; newRefId: string }>('updateExpressionRefId'); +export const rewireExpressions = createAction<{ oldRefId: string; newRefId: string }>('rewireExpressions'); +export const updateExpressionType = createAction<{ refId: string; type: ExpressionQueryType }>('updateExpressionType'); + +export const queriesAndExpressionsReducer = createReducer(initialState, (builder) => { + // data queries actions + builder + .addCase(duplicateQuery, (state, { payload }) => { + state.queries = addQuery(state.queries, payload); + }) + .addCase(addNewDataQuery, (state) => { + const datasource = getDefaultOrFirstCompatibleDataSource(); + if (!datasource) { + return; + } + + state.queries = addQuery(state.queries, { + datasourceUid: datasource.uid, + model: { + refId: '', + datasource: { + type: datasource.type, + uid: datasource.uid, + }, + }, + }); + }) + .addCase(setDataQueries, (state, { payload }) => { + const expressionQueries = state.queries.filter((query) => isExpressionQuery(query.model)); + state.queries = [...payload, ...expressionQueries]; + }); + + // expressions actions + builder + .addCase(addNewExpression, (state) => { + state.queries = addQuery(state.queries, { + datasourceUid: ExpressionDatasourceUID, + model: expressionDatasource.newQuery({ + type: ExpressionQueryType.math, + conditions: [{ ...defaultCondition, query: { params: [] } }], + expression: '', + }), + }); + }) + .addCase(removeExpression, (state, { payload }) => { + state.queries = state.queries.filter((query) => query.refId !== payload); + }) + .addCase(updateExpression, (state, { payload }) => { + state.queries = state.queries.map((query) => { + return query.refId === payload.refId + ? { + ...query, + model: payload, + } + : query; + }); + }) + .addCase(updateExpressionRefId, (state, { payload }) => { + const { newRefId, oldRefId } = payload; + + // if the new refId already exists we just refuse to update the state + const newRefIdExists = refIdExists(state.queries, newRefId); + if (newRefIdExists) { + return; + } + + const updatedQueries = queriesWithUpdatedReferences(state.queries, oldRefId, newRefId); + state.queries = updatedQueries.map((query) => { + if (query.refId === oldRefId) { + return { + ...query, + refId: newRefId, + model: { + ...query.model, + refId: newRefId, + }, + }; + } + + return query; + }); + }) + .addCase(rewireExpressions, (state, { payload }) => { + state.queries = queriesWithUpdatedReferences(state.queries, payload.oldRefId, payload.newRefId); + }) + .addCase(updateExpressionType, (state, action) => { + state.queries = state.queries.map((query) => { + return query.refId === action.payload.refId + ? { + ...query, + model: { + ...expressionDatasource.newQuery({ + type: action.payload.type, + conditions: [{ ...defaultCondition, query: { params: [] } }], + expression: '', + }), + refId: action.payload.refId, + }, + } + : query; + }); + }); +}); + +const addQuery = ( + queries: AlertQuery[], + queryToAdd: Pick +): AlertQuery[] => { + const refId = getNextRefIdChar(queries); + + const query: AlertQuery = { + ...queryToAdd, + refId, + queryType: '', + model: { + ...queryToAdd.model, + hide: false, + refId, + }, + relativeTimeRange: queryToAdd.relativeTimeRange ?? defaultTimeRange(queryToAdd.model), + }; + + return [...queries, query]; +}; + +const defaultTimeRange = (model: DataQuery): RelativeTimeRange | undefined => { + if (isExpressionQuery(model)) { + return; + } + + return getDefaultRelativeTimeRange(); +}; diff --git a/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx b/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx index 258617c1730..163af5695e8 100644 --- a/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx @@ -32,7 +32,7 @@ const RuleType: FC = (props) => { return ( onClick(value)} disabled={disabled}> - + {name} {description} diff --git a/public/app/features/alerting/unified/components/rule-editor/util.ts b/public/app/features/alerting/unified/components/rule-editor/util.ts index b623941723f..dd5ee24759f 100644 --- a/public/app/features/alerting/unified/components/rule-editor/util.ts +++ b/public/app/features/alerting/unified/components/rule-editor/util.ts @@ -1,5 +1,7 @@ import { ValidateResult } from 'react-hook-form'; +import { DataFrame } from '@grafana/data'; +import { isTimeSeries } from '@grafana/data/src/dataframe/utils'; import { isExpressionQuery } from 'app/features/expressions/guards'; import { AlertQuery } from 'app/types/unified-alerting-dto'; @@ -67,6 +69,10 @@ export function updateMathExpressionRefs(expression: string, previousRefId: stri return expression.replace(oldExpression, newExpression); } +export function refIdExists(queries: AlertQuery[], refId: string | null): boolean { + return queries.find((query) => query.refId === refId) !== undefined; +} + // some gateways (like Istio) will decode "/" and "\" characters – this will cause 404 errors for any API call // that includes these values in the URL (ie. /my/path%2fto/resource -> /my/path/to/resource) // @@ -79,3 +85,25 @@ export function checkForPathSeparator(value: string): ValidateResult { return true; } + +export function errorFromSeries(series: DataFrame[]): Error | undefined { + if (series.length === 0) { + return; + } + + const isTimeSeriesResults = isTimeSeries(series); + + let error; + if (isTimeSeriesResults) { + error = new Error('You cannot use time series data as an alert condition, consider adding a reduce expression.'); + } + + return error; +} + +export function warningFromSeries(series: DataFrame[]): Error | undefined { + const notices = series[0]?.meta?.notices ?? []; + const warning = notices.find((notice) => notice.severity === 'warning')?.text; + + return warning ? new Error(warning) : undefined; +} diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewerLayout.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewerLayout.tsx index 1306b7566f4..529abe2ce96 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewerLayout.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewerLayout.tsx @@ -1,9 +1,8 @@ import { css } from '@emotion/css'; import React from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; -import { locationService } from '@grafana/runtime'; -import { PageToolbar, useStyles2 } from '@grafana/ui'; +import { GrafanaTheme2, NavModelItem } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; type Props = { @@ -12,14 +11,21 @@ type Props = { wrapInContent?: boolean; }; +const defaultPageNav: Partial = { + icon: 'bell', + id: 'alert-rule-view', + breadcrumbs: [{ title: 'Alert rules', url: 'alerting/list' }], +}; + export function RuleViewerLayout(props: Props): JSX.Element | null { const { wrapInContent = true, children, title } = props; const styles = useStyles2(getPageStyles); return ( - - locationService.push('/alerting/list')} /> -
{wrapInContent ? : children}
+ + +
{wrapInContent ? : children}
+
); } @@ -37,7 +43,6 @@ export function RuleViewerLayoutContent({ children, padding = 2 }: ContentProps) const getPageStyles = (theme: GrafanaTheme2) => { return { content: css` - margin: ${theme.spacing(0, 2, 2)}; max-width: ${theme.breakpoints.values.xxl}px; `, }; diff --git a/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx b/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx index 375b07f3aad..510da6f2f1e 100644 --- a/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx +++ b/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx @@ -7,8 +7,11 @@ import { alertStateToReadable, alertStateToState } from '../../utils/rules'; import { StateTag } from '../StateTag'; interface Props { state: PromAlertingRuleState | GrafanaAlertState | GrafanaAlertStateWithReason | AlertState; + size?: 'md' | 'sm'; } -export const AlertStateTag: FC = ({ state }) => ( - {alertStateToReadable(state)} +export const AlertStateTag: FC = ({ state, size = 'md' }) => ( + + {alertStateToReadable(state)} + ); diff --git a/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx b/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx index b90632070f1..03ed69d6cf4 100644 --- a/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx +++ b/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx @@ -1,8 +1,10 @@ import React, { FC } from 'react'; +import { logInfo } from '@grafana/runtime'; import { CallToActionCard } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { LogMessages } from '../../Analytics'; import { useRulesAccess } from '../../utils/accessControlHooks'; export const NoRulesSplash: FC = () => { @@ -19,6 +21,7 @@ export const NoRulesSplash: FC = () => { proTipLink="https://grafana.com/docs/" proTipLinkTitle="Learn more" proTipTarget="_blank" + onClick={() => logInfo(LogMessages.alertRuleFromScratch)} /> ); } diff --git a/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx b/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx new file mode 100644 index 00000000000..4f7b1877828 --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx @@ -0,0 +1,40 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { logInfo } from '@grafana/runtime'; + +import { LogMessages } from '../../Analytics'; + +import RulesFilter from './RulesFilter'; + +jest.mock('@grafana/runtime', () => { + const original = jest.requireActual('@grafana/runtime'); + return { + ...original, + logInfo: jest.fn(), + DataSourcePicker: () => <>, + }; +}); + +jest.mock('react-router-dom', () => ({ + useLocation: () => ({ + pathname: 'localhost:3000/example/path', + }), +})); + +jest.mock('../../utils/misc', () => ({ + getFiltersFromUrlParams: jest.fn(() => ({ dataSource: {}, alertState: {}, queryString: '', ruleType: '' })), +})); + +describe('Analytics', () => { + it('Sends log info when clicking alert state filters', async () => { + render(); + + const button = screen.getByText('Pending'); + + await userEvent.click(button); + + expect(logInfo).toHaveBeenCalledWith(LogMessages.clickingAlertStateFilters); + }); +}); diff --git a/public/app/features/alerting/unified/components/rules/RulesFilter.tsx b/public/app/features/alerting/unified/components/rules/RulesFilter.tsx index 5b2d767c042..7a85d505bc4 100644 --- a/public/app/features/alerting/unified/components/rules/RulesFilter.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesFilter.tsx @@ -3,11 +3,12 @@ import { debounce } from 'lodash'; import React, { FormEvent, useState } from 'react'; import { DataSourceInstanceSettings, GrafanaTheme, SelectableValue } from '@grafana/data'; -import { DataSourcePicker } from '@grafana/runtime'; +import { DataSourcePicker, logInfo } from '@grafana/runtime'; import { Button, Field, Icon, Input, Label, RadioButtonGroup, Stack, Tooltip, useStyles } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto'; +import { LogMessages } from '../../Analytics'; import { getFiltersFromUrlParams } from '../../utils/misc'; import { alertStateToReadable } from '../../utils/rules'; @@ -69,6 +70,7 @@ const RulesFilter = () => { }, 600); const handleAlertStateChange = (value: string) => { + logInfo(LogMessages.clickingAlertStateFilters); setQueryParams({ alertState: value }); }; diff --git a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx index 52fa9d54bdc..2c37eddfd47 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx @@ -164,6 +164,7 @@ export const SilencesEditor: FC = ({ silence, alertManagerSourceName }) = 700, [clearErrors, duration, endsAt, prevDuration, setValue, startsAt] ); + const userLogged = Boolean(config.bootData.user.isSignedIn && config.bootData.user.name); return ( @@ -206,6 +207,20 @@ export const SilencesEditor: FC = ({ silence, alertManagerSourceName }) = placeholder="Details about the silence" /> + {!userLogged && ( + + + + )}
diff --git a/public/app/features/alerting/unified/hooks/useSilenceNavData.test.tsx b/public/app/features/alerting/unified/hooks/useSilenceNavData.test.tsx new file mode 100644 index 00000000000..5240b06a999 --- /dev/null +++ b/public/app/features/alerting/unified/hooks/useSilenceNavData.test.tsx @@ -0,0 +1,47 @@ +import { render } from '@testing-library/react'; +import React from 'react'; +import { useRouteMatch } from 'react-router-dom'; + +import { useSilenceNavData } from './useSilenceNavData'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useRouteMatch: jest.fn(), +})); + +const setup = () => { + let result: ReturnType; + function TestComponent() { + result = useSilenceNavData(); + return null; + } + + render(); + + return { result }; +}; +describe('useSilenceNavData', () => { + it('should return correct nav data when route is "/alerting/silence/new"', () => { + (useRouteMatch as jest.Mock).mockReturnValue({ isExact: true, path: '/alerting/silence/new' }); + const { result } = setup(); + + expect(result).toEqual({ + icon: 'bell-slash', + breadcrumbs: [{ title: 'Silences', url: 'alerting/silences' }], + id: 'silence-new', + text: 'Add silence', + }); + }); + + it('should return correct nav data when route is "/alerting/silence/:id/edit"', () => { + (useRouteMatch as jest.Mock).mockReturnValue({ isExact: true, path: '/alerting/silence/:id/edit' }); + const { result } = setup(); + + expect(result).toEqual({ + icon: 'bell-slash', + breadcrumbs: [{ title: 'Silences', url: 'alerting/silences' }], + id: 'silence-edit', + text: 'Edit silence', + }); + }); +}); diff --git a/public/app/features/alerting/unified/hooks/useSilenceNavData.ts b/public/app/features/alerting/unified/hooks/useSilenceNavData.ts new file mode 100644 index 00000000000..9757d385f3b --- /dev/null +++ b/public/app/features/alerting/unified/hooks/useSilenceNavData.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from 'react'; +import { useRouteMatch } from 'react-router-dom'; + +import { NavModelItem } from '@grafana/data'; + +const defaultPageNav: Partial = { + icon: 'bell-slash', + breadcrumbs: [{ title: 'Silences', url: 'alerting/silences' }], +}; + +export function useSilenceNavData() { + const { isExact, path } = useRouteMatch(); + const [pageNav, setPageNav] = useState | undefined>(); + + useEffect(() => { + if (path === '/alerting/silence/new') { + setPageNav({ + ...defaultPageNav, + id: 'silence-new', + text: 'Add silence', + }); + } else if (path === '/alerting/silence/:id/edit') { + setPageNav({ + ...defaultPageNav, + id: 'silence-edit', + text: 'Edit silence', + }); + } + }, [path, isExact]); + + return pageNav; +} diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index 3043cfede1e..8d3d59a98a2 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -1,7 +1,7 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { isEmpty } from 'lodash'; -import { locationService } from '@grafana/runtime'; +import { locationService, logInfo } from '@grafana/runtime'; import { AlertmanagerAlert, AlertManagerCortexConfig, @@ -32,6 +32,7 @@ import { } from 'app/types/unified-alerting-dto'; import { backendSrv } from '../../../../core/services/backend_srv'; +import { LogMessages } from '../Analytics'; import { addAlertManagers, createOrUpdateSilence, @@ -422,6 +423,9 @@ export const saveRuleFormAction = createAsyncThunk( } else { throw new Error('Unexpected rule form type'); } + + logInfo(LogMessages.successSavingAlertRule); + if (redirectOnSave) { locationService.push(redirectOnSave); } else { diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts index cdd336af723..4e830e3a608 100644 --- a/public/app/features/alerting/unified/utils/rule-form.ts +++ b/public/app/features/alerting/unified/utils/rule-form.ts @@ -198,7 +198,7 @@ export const getDefaultQueries = (): AlertQuery[] => { const dataSource = getDefaultOrFirstCompatibleDataSource(); if (!dataSource) { - return [getDefaultExpression('A')]; + return [...getDefaultExpressions('A', 'B')]; } const relativeTimeRange = getDefaultRelativeTimeRange(); @@ -213,15 +213,18 @@ export const getDefaultQueries = (): AlertQuery[] => { hide: false, }, }, - getDefaultExpression('B'), + ...getDefaultExpressions('B', 'C'), ]; }; -const getDefaultExpression = (refId: string): AlertQuery => { - const model: ExpressionQuery = { - refId, +const getDefaultExpressions = (...refIds: [string, string]): AlertQuery[] => { + const refOne = refIds[0]; + const refTwo = refIds[1]; + + const reduceExpression: ExpressionQuery = { + refId: refIds[0], hide: false, - type: ExpressionQueryType.classic, + type: ExpressionQueryType.reduce, datasource: { uid: ExpressionDatasourceUID, type: ExpressionDatasourceRef.type, @@ -230,14 +233,14 @@ const getDefaultExpression = (refId: string): AlertQuery => { { type: 'query', evaluator: { - params: [3], + params: [], type: EvalFunction.IsAbove, }, operator: { type: 'and', }, query: { - params: ['A'], + params: [refOne], }, reducer: { params: [], @@ -245,15 +248,54 @@ const getDefaultExpression = (refId: string): AlertQuery => { }, }, ], + reducer: 'last', expression: 'A', }; - return { - refId, - datasourceUid: ExpressionDatasourceUID, - queryType: '', - model, + const thresholdExpression: ExpressionQuery = { + refId: refTwo, + hide: false, + type: ExpressionQueryType.threshold, + datasource: { + uid: ExpressionDatasourceUID, + type: ExpressionDatasourceRef.type, + }, + conditions: [ + { + type: 'query', + evaluator: { + params: [0], + type: EvalFunction.IsAbove, + }, + operator: { + type: 'and', + }, + query: { + params: [refTwo], + }, + reducer: { + params: [], + type: 'last', + }, + }, + ], + expression: refOne, }; + + return [ + { + refId: refOne, + datasourceUid: ExpressionDatasourceUID, + queryType: '', + model: reduceExpression, + }, + { + refId: refTwo, + datasourceUid: ExpressionDatasourceUID, + queryType: '', + model: thresholdExpression, + }, + ]; }; const dataQueriesToGrafanaQueries = async ( @@ -338,7 +380,14 @@ export const panelToRuleFormValues = async ( } if (!queries.find((query) => query.datasourceUid === ExpressionDatasourceUID)) { - queries.push(getDefaultExpression(getNextRefIdChar(queries.map((query) => query.model)))); + const [reduceExpression, _thresholdExpression] = getDefaultExpressions(getNextRefIdChar(queries), '-'); + queries.push(reduceExpression); + + const [_reduceExpression, thresholdExpression] = getDefaultExpressions( + reduceExpression.refId, + getNextRefIdChar(queries) + ); + queries.push(thresholdExpression); } const { folderId, folderTitle } = dashboard.meta; diff --git a/public/app/features/canvas/elements/metricValue.tsx b/public/app/features/canvas/elements/metricValue.tsx index fe424f705df..893d0bbd408 100644 --- a/public/app/features/canvas/elements/metricValue.tsx +++ b/public/app/features/canvas/elements/metricValue.tsx @@ -13,15 +13,14 @@ import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensi import { CanvasElementItem, CanvasElementProps, defaultBgColor, defaultTextColor } from '../element'; import { ElementState } from '../runtime/element'; - -import { Align, TextBoxConfig, TextBoxData, VAlign } from './textBox'; +import { Align, TextConfig, TextData, VAlign } from '../types'; // eslint-disable-next-line const dummyFieldSettings: StandardEditorsRegistryItem = { settings: {}, } as StandardEditorsRegistryItem; -const MetricValueDisplay = (props: CanvasElementProps) => { +const MetricValueDisplay = (props: CanvasElementProps) => { const { data, isSelected } = props; const styles = useStyles2(getStyles(data)); @@ -40,7 +39,7 @@ const MetricValueDisplay = (props: CanvasElementProps) => { +const MetricValueEdit = (props: CanvasElementProps) => { let { data, config } = props; const context = usePanelContext(); let panelData: DataFrame[]; @@ -89,7 +88,7 @@ const MetricValueEdit = (props: CanvasElementProps) ); }; -const getStyles = (data: TextBoxData | undefined) => (theme: GrafanaTheme2) => ({ +const getStyles = (data: TextData | undefined) => (theme: GrafanaTheme2) => ({ container: css` position: absolute; height: 100%; @@ -112,7 +111,7 @@ const getStyles = (data: TextBoxData | undefined) => (theme: GrafanaTheme2) => ( `, }); -export const metricValueItem: CanvasElementItem = { +export const metricValueItem: CanvasElementItem = { id: 'metric-value', name: 'Metric Value', description: 'Display a field value', @@ -148,8 +147,8 @@ export const metricValueItem: CanvasElementItem = { }, }), - prepareData: (ctx: DimensionContext, cfg: TextBoxConfig) => { - const data: TextBoxData = { + prepareData: (ctx: DimensionContext, cfg: TextConfig) => { + const data: TextData = { text: cfg.text ? ctx.getText(cfg.text).value() : '', align: cfg.align ?? Align.Center, valign: cfg.valign ?? VAlign.Middle, diff --git a/public/app/features/canvas/elements/textBox.tsx b/public/app/features/canvas/elements/rectangle.tsx similarity index 76% rename from public/app/features/canvas/elements/textBox.tsx rename to public/app/features/canvas/elements/rectangle.tsx index 852e94f2201..8e75e27667c 100644 --- a/public/app/features/canvas/elements/textBox.tsx +++ b/public/app/features/canvas/elements/rectangle.tsx @@ -7,39 +7,11 @@ import { config } from 'app/core/config'; import { DimensionContext } from 'app/features/dimensions/context'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; -import { ColorDimensionConfig, TextDimensionConfig } from 'app/features/dimensions/types'; import { CanvasElementItem, CanvasElementProps, defaultBgColor, defaultTextColor } from '../element'; +import { Align, TextConfig, TextData, VAlign } from '../types'; -export enum Align { - Left = 'left', - Center = 'center', - Right = 'right', -} - -export enum VAlign { - Top = 'top', - Middle = 'middle', - Bottom = 'bottom', -} - -export interface TextBoxData { - text?: string; - color?: string; - size?: number; // 0 or missing will "auto size" - align: Align; - valign: VAlign; -} - -export interface TextBoxConfig { - text?: TextDimensionConfig; - color?: ColorDimensionConfig; - size?: number; // 0 or missing will "auto size" - align: Align; - valign: VAlign; -} - -class TextBoxDisplay extends PureComponent> { +class RectangleDisplay extends PureComponent> { render() { const { data } = this.props; const styles = getStyles(config.theme2, data); @@ -65,12 +37,12 @@ const getStyles = stylesFactory((theme: GrafanaTheme2, data) => ({ color: ${data?.color}; `, })); -export const textBoxItem: CanvasElementItem = { - id: 'text-box', - name: 'Text', - description: 'Text box', +export const rectangleItem: CanvasElementItem = { + id: 'rectangle', + name: 'Rectangle', + description: 'Rectangle', - display: TextBoxDisplay, + display: RectangleDisplay, defaultSize: { width: 240, @@ -94,8 +66,8 @@ export const textBoxItem: CanvasElementItem = { }), // Called when data changes - prepareData: (ctx: DimensionContext, cfg: TextBoxConfig) => { - const data: TextBoxData = { + prepareData: (ctx: DimensionContext, cfg: TextConfig) => { + const data: TextData = { text: cfg.text ? ctx.getText(cfg.text).value() : '', align: cfg.align ?? Align.Center, valign: cfg.valign ?? VAlign.Middle, @@ -111,7 +83,7 @@ export const textBoxItem: CanvasElementItem = { // Heatmap overlay options registerOptionsUI: (builder) => { - const category = ['Text box']; + const category = ['Rectangle']; builder .addCustomEditor({ category, diff --git a/public/app/features/canvas/elements/text.tsx b/public/app/features/canvas/elements/text.tsx new file mode 100644 index 00000000000..408e89120b7 --- /dev/null +++ b/public/app/features/canvas/elements/text.tsx @@ -0,0 +1,210 @@ +import { css } from '@emotion/css'; +import React, { useCallback } from 'react'; +import { useObservable } from 'react-use'; +import { of } from 'rxjs'; + +import { DataFrame, GrafanaTheme2 } from '@grafana/data'; +import { Input, usePanelContext, useStyles2 } from '@grafana/ui'; +import { DimensionContext } from 'app/features/dimensions/context'; +import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; +import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; + +import { CanvasElementItem, CanvasElementProps, defaultTextColor } from '../element'; +import { ElementState } from '../runtime/element'; +import { Align, TextConfig, TextData, VAlign } from '../types'; + +const TextDisplay = (props: CanvasElementProps) => { + const { data, isSelected } = props; + const styles = useStyles2(getStyles(data)); + + const context = usePanelContext(); + const scene = context.instanceState?.scene; + + const isEditMode = useObservable(scene?.editModeEnabled ?? of(false)); + + if (isEditMode && isSelected) { + return ; + } + return ( +
+ {data?.text ? data.text : 'Double click to set text'} +
+ ); +}; + +const TextEdit = (props: CanvasElementProps) => { + let { data, config } = props; + const context = usePanelContext(); + let panelData: DataFrame[]; + panelData = context.instanceState?.scene?.data.series; + + const onTextChange = (event: React.SyntheticEvent) => { + const { value: textValue } = event.currentTarget; + saveText(textValue); + }; + + const onKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + event.preventDefault(); + const scene = context.instanceState?.scene; + if (scene) { + scene.editModeEnabled.next(false); + } + } + }; + + const saveText = useCallback( + (textValue: string) => { + let selectedElement: ElementState; + selectedElement = context.instanceState?.selected[0]; + if (selectedElement) { + const options = selectedElement.options; + selectedElement.onChange({ + ...options, + config: { + ...options.config, + text: { ...selectedElement.options.config.text, fixed: textValue }, + }, + }); + + // Force a re-render (update scene data after config update) + const scene = context.instanceState?.scene; + if (scene) { + scene.updateData(scene.data); + } + } + }, + [context.instanceState?.scene, context.instanceState?.selected] + ); + + const styles = useStyles2(getStyles(data)); + return ( +
+ {panelData && } +
+ ); +}; + +const getStyles = (data: TextData | undefined) => (theme: GrafanaTheme2) => ({ + container: css` + position: absolute; + height: 100%; + width: 100%; + display: table; + `, + inlineEditorContainer: css` + height: 100%; + width: 100%; + display: flex; + align-items: center; + padding: 10px; + `, + span: css` + display: table-cell; + vertical-align: ${data?.valign}; + text-align: ${data?.align}; + font-size: ${data?.size}px; + color: ${data?.color}; + `, +}); + +export const textItem: CanvasElementItem = { + id: 'text', + name: 'Text', + description: 'Display text', + + display: TextDisplay, + + hasEditMode: true, + + defaultSize: { + width: 100, + height: 50, + }, + + getNewOptions: (options) => ({ + ...options, + config: { + align: Align.Center, + valign: VAlign.Middle, + color: { + fixed: defaultTextColor, + }, + size: 16, + }, + placement: { + top: 100, + left: 100, + }, + }), + + prepareData: (ctx: DimensionContext, cfg: TextConfig) => { + const data: TextData = { + text: cfg.text ? ctx.getText(cfg.text).value() : '', + align: cfg.align ?? Align.Center, + valign: cfg.valign ?? VAlign.Middle, + size: cfg.size, + }; + + if (cfg.color) { + data.color = ctx.getColor(cfg.color).value(); + } + + return data; + }, + + registerOptionsUI: (builder) => { + const category = ['Text']; + builder + .addCustomEditor({ + category, + id: 'textSelector', + path: 'config.text', + name: 'Text', + editor: TextDimensionEditor, + }) + .addCustomEditor({ + category, + id: 'config.color', + path: 'config.color', + name: 'Text color', + editor: ColorDimensionEditor, + settings: {}, + defaultValue: {}, + }) + .addRadio({ + category, + path: 'config.align', + name: 'Align text', + settings: { + options: [ + { value: Align.Left, label: 'Left' }, + { value: Align.Center, label: 'Center' }, + { value: Align.Right, label: 'Right' }, + ], + }, + defaultValue: Align.Left, + }) + .addRadio({ + category, + path: 'config.valign', + name: 'Vertical align', + settings: { + options: [ + { value: VAlign.Top, label: 'Top' }, + { value: VAlign.Middle, label: 'Middle' }, + { value: VAlign.Bottom, label: 'Bottom' }, + ], + }, + defaultValue: VAlign.Middle, + }) + .addNumberInput({ + category, + path: 'config.size', + name: 'Text size', + settings: { + placeholder: 'Auto', + }, + }); + }, +}; diff --git a/public/app/features/canvas/registry.ts b/public/app/features/canvas/registry.ts index 8ed20d197b6..b65c26bd4e0 100644 --- a/public/app/features/canvas/registry.ts +++ b/public/app/features/canvas/registry.ts @@ -7,7 +7,8 @@ import { droneSideItem } from './elements/droneSide'; import { droneTopItem } from './elements/droneTop'; import { iconItem } from './elements/icon'; import { metricValueItem } from './elements/metricValue'; -import { textBoxItem } from './elements/textBox'; +import { rectangleItem } from './elements/rectangle'; +import { textItem } from './elements/text'; import { windTurbineItem } from './elements/windTurbine'; export const DEFAULT_CANVAS_ELEMENT_CONFIG: CanvasElementOptions = { @@ -19,7 +20,8 @@ export const DEFAULT_CANVAS_ELEMENT_CONFIG: CanvasElementOptions = { export const defaultElementItems = [ metricValueItem, // default for now - textBoxItem, + textItem, + rectangleItem, iconItem, ]; diff --git a/public/app/features/canvas/types.ts b/public/app/features/canvas/types.ts index 24185c5448d..5e2af18d89e 100644 --- a/public/app/features/canvas/types.ts +++ b/public/app/features/canvas/types.ts @@ -1,4 +1,4 @@ -import { ColorDimensionConfig, ResourceDimensionConfig } from 'app/features/dimensions/types'; +import { ColorDimensionConfig, ResourceDimensionConfig, TextDimensionConfig } from 'app/features/dimensions/types'; export interface Placement { top?: number; @@ -58,3 +58,31 @@ export enum QuickPlacement { HorizontalCenter = 'hcenter', VerticalCenter = 'vcenter', } + +export enum Align { + Left = 'left', + Center = 'center', + Right = 'right', +} + +export enum VAlign { + Top = 'top', + Middle = 'middle', + Bottom = 'bottom', +} + +export interface TextData { + text?: string; + color?: string; + size?: number; // 0 or missing will "auto size" + align: Align; + valign: VAlign; +} + +export interface TextConfig { + text?: TextDimensionConfig; + color?: ColorDimensionConfig; + size?: number; // 0 or missing will "auto size" + align: Align; + valign: VAlign; +} diff --git a/public/app/features/data-connections/DataConnectionsPage.test.tsx b/public/app/features/connections/ConnectionsPage.test.tsx similarity index 94% rename from public/app/features/data-connections/DataConnectionsPage.test.tsx rename to public/app/features/connections/ConnectionsPage.test.tsx index c23ab82a0aa..7e1c33fa469 100644 --- a/public/app/features/data-connections/DataConnectionsPage.test.tsx +++ b/public/app/features/connections/ConnectionsPage.test.tsx @@ -8,7 +8,7 @@ import { getMockDataSources } from 'app/features/datasources/__mocks__'; import * as api from 'app/features/datasources/api'; import { configureStore } from 'app/store/configureStore'; -import DataConnectionsPage from './DataConnectionsPage'; +import ConnectionsPage from './ConnectionsPage'; import { navIndex } from './__mocks__/store.navIndex.mock'; import { ROUTE_BASE_ID, ROUTES } from './constants'; @@ -22,13 +22,13 @@ const renderPage = (path = `/${ROUTE_BASE_ID}`): RenderResult => { return render( - + ); }; -describe('Data Connections Page', () => { +describe('Connections Page', () => { const mockDatasources = getMockDataSources(3); beforeEach(() => { diff --git a/public/app/features/data-connections/DataConnectionsPage.tsx b/public/app/features/connections/ConnectionsPage.tsx similarity index 97% rename from public/app/features/data-connections/DataConnectionsPage.tsx rename to public/app/features/connections/ConnectionsPage.tsx index d8ed4a78f51..410e4eebf28 100644 --- a/public/app/features/data-connections/DataConnectionsPage.tsx +++ b/public/app/features/connections/ConnectionsPage.tsx @@ -12,7 +12,7 @@ import { CloudIntegrations } from './tabs/CloudIntegrations'; import { DataSourcesEdit } from './tabs/DataSourcesEdit'; import { Plugins } from './tabs/Plugins'; -export default function DataConnectionsPage() { +export default function ConnectionsPage() { const navModel = useNavModel(); return ( diff --git a/public/app/features/data-connections/__mocks__/store.navIndex.mock.ts b/public/app/features/connections/__mocks__/store.navIndex.mock.ts similarity index 95% rename from public/app/features/data-connections/__mocks__/store.navIndex.mock.ts rename to public/app/features/connections/__mocks__/store.navIndex.mock.ts index ca393c9f96e..7ce642f2493 100644 --- a/public/app/features/data-connections/__mocks__/store.navIndex.mock.ts +++ b/public/app/features/connections/__mocks__/store.navIndex.mock.ts @@ -1210,224 +1210,224 @@ export const navIndex: NavIndex = { ], }, }, - 'data-connections': { - id: 'data-connections', - text: 'Data Connections', + connections: { + id: 'connections', + text: 'Connections', section: NavSection.Core, icon: 'link', - url: '/data-connections', + url: '/connections', sortWeight: -1500, children: [ { - id: 'data-connections-datasources', + id: 'connections-datasources', text: 'Data sources', description: 'Add and configure data sources', icon: 'database', - url: '/data-connections/datasources', + url: '/connections/datasources', active: false, }, { - id: 'data-connections-plugins', + id: 'connections-plugins', text: 'Plugins', description: 'Manage plugins', icon: 'plug', - url: '/data-connections/plugins', + url: '/connections/plugins', active: false, }, { - id: 'data-connections-cloud-integrations', + id: 'connections-cloud-integrations', text: 'Cloud integrations', description: 'Manage your cloud integrations', icon: 'bolt', - url: '/data-connections/cloud-integrations', + url: '/connections/cloud-integrations', active: true, }, { - id: 'data-connections-recorded-queries', + id: 'connections-recorded-queries', text: 'Recorded queries', description: 'Manage your recorded queries', icon: 'record-audio', - url: '/data-connections/recorded-queries', + url: '/connections/recorded-queries', active: false, }, ], }, - 'data-connections-datasources': { - id: 'data-connections-datasources', + 'connections-datasources': { + id: 'connections-datasources', text: 'Data sources', description: 'Add and configure data sources', icon: 'database', - url: '/data-connections/datasources', + url: '/connections/datasources', parentItem: { - id: 'data-connections', - text: 'Data Connections', + id: 'connections', + text: 'Connections', section: NavSection.Core, icon: 'link', - url: '/data-connections', + url: '/connections', sortWeight: -1500, children: [ { - id: 'data-connections-datasources', + id: 'connections-datasources', text: 'Data sources', description: 'Add and configure data sources', icon: 'database', - url: '/data-connections/datasources', + url: '/connections/datasources', }, { - id: 'data-connections-plugins', + id: 'connections-plugins', text: 'Plugins', description: 'Manage plugins', icon: 'plug', - url: '/data-connections/plugins', + url: '/connections/plugins', }, { - id: 'data-connections-cloud-integrations', + id: 'connections-cloud-integrations', text: 'Cloud integrations', description: 'Manage your cloud integrations', icon: 'bolt', - url: '/data-connections/cloud-integrations', + url: '/connections/cloud-integrations', }, { - id: 'data-connections-recorded-queries', + id: 'connections-recorded-queries', text: 'Recorded queries', description: 'Manage your recorded queries', icon: 'record-audio', - url: '/data-connections/recorded-queries', + url: '/connections/recorded-queries', }, ], }, }, - 'data-connections-plugins': { - id: 'data-connections-plugins', + 'connections-plugins': { + id: 'connections-plugins', text: 'Plugins', description: 'Manage plugins', icon: 'plug', - url: '/data-connections/plugins', + url: '/connections/plugins', parentItem: { - id: 'data-connections', - text: 'Data Connections', + id: 'connections', + text: 'Connections', section: NavSection.Core, icon: 'link', - url: '/data-connections', + url: '/connections', sortWeight: -1500, children: [ { - id: 'data-connections-datasources', + id: 'connections-datasources', text: 'Data sources', description: 'Add and configure data sources', icon: 'database', - url: '/data-connections/datasources', + url: '/connections/datasources', }, { - id: 'data-connections-plugins', + id: 'connections-plugins', text: 'Plugins', description: 'Manage plugins', icon: 'plug', - url: '/data-connections/plugins', + url: '/connections/plugins', }, { - id: 'data-connections-cloud-integrations', + id: 'connections-cloud-integrations', text: 'Cloud integrations', description: 'Manage your cloud integrations', icon: 'bolt', - url: '/data-connections/cloud-integrations', + url: '/connections/cloud-integrations', }, { - id: 'data-connections-recorded-queries', + id: 'connections-recorded-queries', text: 'Recorded queries', description: 'Manage your recorded queries', icon: 'record-audio', - url: '/data-connections/recorded-queries', + url: '/connections/recorded-queries', }, ], }, }, - 'data-connections-cloud-integrations': { - id: 'data-connections-cloud-integrations', + 'connections-cloud-integrations': { + id: 'connections-cloud-integrations', text: 'Cloud integrations', description: 'Manage your cloud integrations', icon: 'bolt', - url: '/data-connections/cloud-integrations', + url: '/connections/cloud-integrations', parentItem: { - id: 'data-connections', - text: 'Data Connections', + id: 'connections', + text: 'Connections', section: NavSection.Core, icon: 'link', - url: '/data-connections', + url: '/connections', sortWeight: -1500, children: [ { - id: 'data-connections-datasources', + id: 'connections-datasources', text: 'Data sources', description: 'Add and configure data sources', icon: 'database', - url: '/data-connections/datasources', + url: '/connections/datasources', }, { - id: 'data-connections-plugins', + id: 'connections-plugins', text: 'Plugins', description: 'Manage plugins', icon: 'plug', - url: '/data-connections/plugins', + url: '/connections/plugins', }, { - id: 'data-connections-cloud-integrations', + id: 'connections-cloud-integrations', text: 'Cloud integrations', description: 'Manage your cloud integrations', icon: 'bolt', - url: '/data-connections/cloud-integrations', + url: '/connections/cloud-integrations', }, { - id: 'data-connections-recorded-queries', + id: 'connections-recorded-queries', text: 'Recorded queries', description: 'Manage your recorded queries', icon: 'record-audio', - url: '/data-connections/recorded-queries', + url: '/connections/recorded-queries', }, ], }, }, - 'data-connections-recorded-queries': { - id: 'data-connections-recorded-queries', + 'connections-recorded-queries': { + id: 'connections-recorded-queries', text: 'Recorded queries', description: 'Manage your recorded queries', icon: 'record-audio', - url: '/data-connections/recorded-queries', + url: '/connections/recorded-queries', parentItem: { - id: 'data-connections', - text: 'Data Connections', + id: 'connections', + text: 'Connections', section: NavSection.Core, icon: 'link', - url: '/data-connections', + url: '/connections', sortWeight: -1500, children: [ { - id: 'data-connections-datasources', + id: 'connections-datasources', text: 'Data sources', description: 'Add and configure data sources', icon: 'database', - url: '/data-connections/datasources', + url: '/connections/datasources', }, { - id: 'data-connections-plugins', + id: 'connections-plugins', text: 'Plugins', description: 'Manage plugins', icon: 'plug', - url: '/data-connections/plugins', + url: '/connections/plugins', }, { - id: 'data-connections-cloud-integrations', + id: 'connections-cloud-integrations', text: 'Cloud integrations', description: 'Manage your cloud integrations', icon: 'bolt', - url: '/data-connections/cloud-integrations', + url: '/connections/cloud-integrations', }, { - id: 'data-connections-recorded-queries', + id: 'connections-recorded-queries', text: 'Recorded queries', description: 'Manage your recorded queries', icon: 'record-audio', - url: '/data-connections/recorded-queries', + url: '/connections/recorded-queries', }, ], }, diff --git a/public/app/features/connections/constants.ts b/public/app/features/connections/constants.ts new file mode 100644 index 00000000000..e609bd6d929 --- /dev/null +++ b/public/app/features/connections/constants.ts @@ -0,0 +1,14 @@ +// 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'; + +export const ROUTES = { + DataSources: `/${ROUTE_BASE_ID}/datasources`, + DataSourcesNew: `/${ROUTE_BASE_ID}/datasources/new`, + DataSourcesEdit: `/${ROUTE_BASE_ID}/datasources/edit/:uid`, + DataSourcesDashboards: `/${ROUTE_BASE_ID}/datasources/edit/:uid/dashboards`, + Plugins: `/${ROUTE_BASE_ID}/plugins`, + CloudIntegrations: `/${ROUTE_BASE_ID}/cloud-integrations`, +} as const; diff --git a/public/app/features/data-connections/hooks/useNavModel.ts b/public/app/features/connections/hooks/useNavModel.ts similarity index 91% rename from public/app/features/data-connections/hooks/useNavModel.ts rename to public/app/features/connections/hooks/useNavModel.ts index f3aa6be0a9d..3b85563fafb 100644 --- a/public/app/features/data-connections/hooks/useNavModel.ts +++ b/public/app/features/connections/hooks/useNavModel.ts @@ -14,7 +14,7 @@ export const useNavModel = () => { const node = navIndex[ROUTE_BASE_ID]; const main = node; const isDefaultRoute = (item: NavModelItem) => - currentPath === `/${ROUTE_BASE_ID}` && item.id === 'data-connections-datasources'; + currentPath === `/${ROUTE_BASE_ID}` && item.id === 'connections-datasources'; const isItemActive = (item: NavModelItem) => currentPath.startsWith(item.url || ''); main.children = main.children?.map((item) => ({ diff --git a/public/app/features/data-connections/routes.tsx b/public/app/features/connections/routes.tsx similarity index 90% rename from public/app/features/data-connections/routes.tsx rename to public/app/features/connections/routes.tsx index 5366ec53caa..db117d9a784 100644 --- a/public/app/features/data-connections/routes.tsx +++ b/public/app/features/connections/routes.tsx @@ -11,7 +11,7 @@ export function getRoutes(): RouteDescriptor[] { path: `/${ROUTE_BASE_ID}`, exact: false, component: SafeDynamicImport( - () => import(/* webpackChunkName: "DataConnectionsPage"*/ 'app/features/data-connections/DataConnectionsPage') + () => import(/* webpackChunkName: "DataConnectionsPage"*/ 'app/features/connections/ConnectionsPage') ), }, ]; diff --git a/public/app/features/data-connections/tabs/CloudIntegrations/CloudIntegrations.tsx b/public/app/features/connections/tabs/CloudIntegrations/CloudIntegrations.tsx similarity index 100% rename from public/app/features/data-connections/tabs/CloudIntegrations/CloudIntegrations.tsx rename to public/app/features/connections/tabs/CloudIntegrations/CloudIntegrations.tsx diff --git a/public/app/features/data-connections/tabs/CloudIntegrations/index.tsx b/public/app/features/connections/tabs/CloudIntegrations/index.tsx similarity index 100% rename from public/app/features/data-connections/tabs/CloudIntegrations/index.tsx rename to public/app/features/connections/tabs/CloudIntegrations/index.tsx diff --git a/public/app/features/data-connections/tabs/DataSourcesEdit/DataSourcesEdit.tsx b/public/app/features/connections/tabs/DataSourcesEdit/DataSourcesEdit.tsx similarity index 100% rename from public/app/features/data-connections/tabs/DataSourcesEdit/DataSourcesEdit.tsx rename to public/app/features/connections/tabs/DataSourcesEdit/DataSourcesEdit.tsx diff --git a/public/app/features/data-connections/tabs/DataSourcesEdit/index.tsx b/public/app/features/connections/tabs/DataSourcesEdit/index.tsx similarity index 100% rename from public/app/features/data-connections/tabs/DataSourcesEdit/index.tsx rename to public/app/features/connections/tabs/DataSourcesEdit/index.tsx diff --git a/public/app/features/data-connections/tabs/Plugins/Plugins.tsx b/public/app/features/connections/tabs/Plugins/Plugins.tsx similarity index 100% rename from public/app/features/data-connections/tabs/Plugins/Plugins.tsx rename to public/app/features/connections/tabs/Plugins/Plugins.tsx diff --git a/public/app/features/data-connections/tabs/Plugins/index.tsx b/public/app/features/connections/tabs/Plugins/index.tsx similarity index 100% rename from public/app/features/data-connections/tabs/Plugins/index.tsx rename to public/app/features/connections/tabs/Plugins/index.tsx diff --git a/public/app/features/correlations/CorrelationsPage.tsx b/public/app/features/correlations/CorrelationsPage.tsx index a8a17e2046c..d1b8824c13d 100644 --- a/public/app/features/correlations/CorrelationsPage.tsx +++ b/public/app/features/correlations/CorrelationsPage.tsx @@ -184,7 +184,7 @@ const DataSourceCell = memo( return ( - + {value.name} ); diff --git a/public/app/features/dashboard/components/DashboardSettings/PreviewSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/PreviewSettings.tsx index 407c2f96783..2d4f4dffc12 100644 --- a/public/app/features/dashboard/components/DashboardSettings/PreviewSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/PreviewSettings.tsx @@ -70,10 +70,10 @@ export class PreviewSettings extends PureComponent { - + Preview of dashboard in dark theme - + Preview of dashboard in light theme diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx index 1ee6994232b..2ab45f6268d 100644 --- a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx @@ -57,7 +57,7 @@ describe('VersionSettings', () => { // Need to use delay: null here to work with fakeTimers // see https://github.com/testing-library/user-event/issues/833 user = userEvent.setup({ delay: null }); - jest.resetAllMocks(); + jest.clearAllMocks(); jest.useFakeTimers(); }); @@ -103,7 +103,7 @@ describe('VersionSettings', () => { historySrv.getHistoryList.mockResolvedValue(versions.slice(0, VERSIONS_FETCH_LIMIT - 5)); setup(); - expect(screen.queryByRole('button', { name: /show more versions|/i })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: /show more versions/i })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: /compare versions/i })).not.toBeInTheDocument(); await waitFor(() => expect(screen.getByRole('table')).toBeInTheDocument()); diff --git a/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx b/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx index 1698000c553..f0a95b690d0 100644 --- a/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx +++ b/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx @@ -202,6 +202,7 @@ export function HelpWizard({ panel, plugin, onClose }: Props) { {({ height }) => ( <>