diff --git a/.betterer.results b/.betterer.results index df376f544fa..1772909d8f6 100644 --- a/.betterer.results +++ b/.betterer.results @@ -491,6 +491,9 @@ exports[`better eslint`] = { "packages/grafana-runtime/src/services/pluginExtensions/usePluginExtensions.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "packages/grafana-runtime/src/services/pluginExtensions/usePluginFunctions.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "packages/grafana-runtime/src/utils/DataSourceWithBackend.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -1324,8 +1327,10 @@ exports[`better eslint`] = { [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.", "4"] + [0, 0, 0, "Do not use any type assertions.", "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"] ], "public/app/core/utils/richHistory.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -5541,7 +5546,8 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], "public/app/features/plugins/admin/components/PluginDetailsPanel.tsx:5381": [ - [0, 0, 0, "\'@grafana/runtime/src/components/PluginPage\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] + [0, 0, 0, "\'@grafana/runtime/src/components/PluginPage\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], "public/app/features/plugins/admin/components/PluginDetailsSignature.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -5645,6 +5651,9 @@ exports[`better eslint`] = { [0, 0, 0, "\'@grafana/runtime/src/services/pluginExtensions/getPluginExtensions\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], + "public/app/features/plugins/extensions/usePluginFunctions.tsx:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "public/app/features/plugins/extensions/usePluginLinks.tsx:5381": [ [0, 0, 0, "\'@grafana/runtime/src/services/pluginExtensions/getPluginExtensions\' import is restricted from being used by a pattern. Import from the public export instead.", "0"] ], @@ -5752,10 +5761,8 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "8"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "9"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "10"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "11"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "12"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "9"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "10"] ], "public/app/features/provisioning/FileHistoryPage.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], @@ -5782,8 +5789,7 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] ], "public/app/features/provisioning/RepositoryHealth.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -5793,12 +5799,14 @@ exports[`better eslint`] = { ], "public/app/features/provisioning/RepositoryListPage.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "8"] ], "public/app/features/provisioning/RepositoryOverview.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ae2abf11a9e..28582099745 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -244,6 +244,7 @@ /devenv/dev-dashboards/extensions/ @grafana/plugins-platform-frontend /devenv/docker/blocks/alert_webhook_listener/ @grafana/alerting-backend +/devenv/docker/blocks/stateful_webhook/ @grafana/alerting-backend /devenv/docker/blocks/caddy_tls/ @grafana/alerting-backend /devenv/docker/blocks/clickhouse/ @grafana/partner-datasources /devenv/docker/blocks/collectd/ @grafana/observability-metrics @@ -383,6 +384,11 @@ /crowdin.yml @grafana/grafana-frontend-platform /public/locales/ @grafana/grafana-frontend-platform +/public/locales/de-DE @grafanabot +/public/locales/es-ES @grafanabot +/public/locales/fr-FR @grafanabot +/public/locales/pt-BR @grafanabot +/public/locales/zh-Hans @grafanabot /public/app/core/internationalization/ @grafana/grafana-frontend-platform /e2e/ @grafana/grafana-frontend-platform /e2e/cloud-plugins-suite/ @grafana/partner-datasources diff --git a/.github/workflows/i18n-crowdin-download.yml b/.github/workflows/i18n-crowdin-download.yml index cba14abe43b..e0c3c50b9bb 100644 --- a/.github/workflows/i18n-crowdin-download.yml +++ b/.github/workflows/i18n-crowdin-download.yml @@ -3,7 +3,7 @@ name: Crowdin Download Action on: workflow_dispatch: schedule: - - cron: "0 * * * *" + - cron: "0 0 * * *" jobs: download-sources-from-crowdin: @@ -12,6 +12,7 @@ jobs: permissions: contents: write # needed to commit changes into the PR pull-requests: write # needed to update PR description, labels, etc + id-token: write # needed to get vault secrets steps: - name: Generate token @@ -41,17 +42,11 @@ jobs: pull_request_body: | :robot: Automatic download of translations from Crowdin. - Steps for merging: - 1. A quick sanity check of the changes and approve. Things to look out for: - - No changes in the English file. The source of truth is in the main branch, NOT in Crowdin. - - Translations maybe be removed if the English phrase was removed, but there should not be many of these - - Anything else that looks 'funky'. Ask if you're not sure. - 2. Approve & (Auto-)merge. :tada: + This runs once per day and will merge automatically if all the required checks pass. - If there's a conflict, close the pull request and **delete the branch**. A GH action will recreate the pull request. - Remember, the longer this pull request is open, the more likely it is that it'll get conflicts. + If there's a conflict, close the pull request and **delete the branch**. + You can then either wait for the schedule to trigger a new PR, or rerun the action manually. pull_request_labels: 'area/frontend, area/internationalization, no-changelog, no-backport' - pull_request_reviewers: 'grafana-frontend-platform' pull_request_base_branch_name: 'main' base_url: 'https://grafana.api.crowdin.com' config: 'crowdin.yml' @@ -119,3 +114,45 @@ jobs: with: pr: ${{ steps.crowdin-download.outputs.pull_request_number }} token: ${{ steps.generate_token.outputs.token }} + + - name: Get vault secrets + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + # Secrets placed in ci/repo/grafana/grafana/grafana-pr-approver + repo_secrets: | + GRAFANA_PR_APPROVER_APP_ID=grafana-pr-approver:app-id + GRAFANA_PR_APPROVER_APP_PEM=grafana-pr-approver:private-key + + - name: Generate approver token + if: steps.crowdin-download.outputs.pull_request_url + id: generate_approver_token + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 + with: + app_id: ${{ env.GRAFANA_PR_APPROVER_APP_ID }} + private_key: ${{ env.GRAFANA_PR_APPROVER_APP_PEM }} + + - name: Approve and automerge PR + if: steps.crowdin-download.outputs.pull_request_url + shell: bash + # Only approve if: + # - the PR does not modify files other than json files under the public/locales/ directory + # - the PR does not modify the en-US locale + run: | + filesChanged=$(gh pr diff --name-only ${{ steps.crowdin-download.outputs.pull_request_url }}) + + if [[ $(echo $filesChanged | grep -v 'public/locales/[a-zA-Z\-]*/grafana.json' | wc -l) -ne 0 ]]; then + echo "Non-i18n changes detected, not approving" + exit 1 + fi + + if [[ $(echo $filesChanged | grep "public/locales/en-US" | wc -l) -ne 0 ]]; then + echo "public/locales/en-US changes detected, not approving" + exit 1 + fi + + echo "Approving and enabling automerge" + gh pr review ${{ steps.crowdin-download.outputs.pull_request_url }} --approve + gh pr merge --auto --squash ${{ steps.crowdin-download.outputs.pull_request_url }} + env: + GITHUB_TOKEN: ${{ steps.generate_approver_token.outputs.token }} diff --git a/.github/workflows/publish-technical-documentation-release.yml b/.github/workflows/publish-technical-documentation-release.yml index bd77c286018..57d779660c5 100644 --- a/.github/workflows/publish-technical-documentation-release.yml +++ b/.github/workflows/publish-technical-documentation-release.yml @@ -3,7 +3,7 @@ name: publish-technical-documentation-release on: push: branches: - - release-v[0-9]+.[0-9]+.[0-9]+ + - release-[0-9]+.[0-9]+.[0-9]+ tags: - v[0-9]+.[0-9]+.[0-9]+ paths: diff --git a/Makefile b/Makefile index 2721c2830b8..11cee0a4255 100644 --- a/Makefile +++ b/Makefile @@ -271,6 +271,14 @@ test-go-integration-alertmanager: ## Run integration tests for the remote alertm AM_URL=http://localhost:8080 AM_TENANT_ID=test \ $(GO) test $(GO_RACE_FLAG) -count=1 -run "^TestIntegrationRemoteAlertmanager" -covermode=atomic -timeout=5m ./pkg/services/ngalert/... +.PHONY: test-go-integration-grafana-alertmanager +test-go-integration-grafana-alertmanager: ## Run integration tests for the grafana alertmanager + @echo "test grafana alertmanager integration tests" + @export GRAFANA_VERSION=11.5.0-81938; \ + $(GO) run tools/setup_grafana_alertmanager_integration_test_images.go; \ + $(GO) clean -testcache; \ + $(GO) test $(GO_RACE_FLAG) -count=1 -run "^TestAlertmanagerIntegration" -covermode=atomic -timeout=10m ./pkg/tests/alertmanager/... + .PHONY: test-go-integration-postgres test-go-integration-postgres: devenv-postgres ## Run integration tests for postgres backend with flags. @echo "test backend integration postgres tests" diff --git a/devenv/bulk-dashboards/bulkdash.jsonnet b/devenv/bulk-dashboards/bulkdash.jsonnet index 1a77d8abd70..05e396df2d6 100644 --- a/devenv/bulk-dashboards/bulkdash.jsonnet +++ b/devenv/bulk-dashboards/bulkdash.jsonnet @@ -1118,18 +1118,6 @@ "1d" ], "status": "Stable", - "time_options": [ - "5m", - "15m", - "1h", - "2h", - " 6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ], "type": "timepicker" }, "timezone": "browser", diff --git a/devenv/dev-dashboards-without-uid/panel_tests_graph.json b/devenv/dev-dashboards-without-uid/panel_tests_graph.json index b5d50f4f7b7..6bba1d9ae02 100644 --- a/devenv/dev-dashboards-without-uid/panel_tests_graph.json +++ b/devenv/dev-dashboards-without-uid/panel_tests_graph.json @@ -1639,8 +1639,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "browser", "title": "Panel Tests - Graph", diff --git a/devenv/dev-dashboards-without-uid/panel_tests_graph_time_regions.json b/devenv/dev-dashboards-without-uid/panel_tests_graph_time_regions.json index 3ff76d12df2..98d49958aaf 100644 --- a/devenv/dev-dashboards-without-uid/panel_tests_graph_time_regions.json +++ b/devenv/dev-dashboards-without-uid/panel_tests_graph_time_regions.json @@ -490,17 +490,6 @@ "1h", "2h", "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" ] }, "timezone": "browser", diff --git a/devenv/dev-dashboards-without-uid/panel_tests_polystat.json b/devenv/dev-dashboards-without-uid/panel_tests_polystat.json index 951bb780017..25b1f7154e5 100644 --- a/devenv/dev-dashboards-without-uid/panel_tests_polystat.json +++ b/devenv/dev-dashboards-without-uid/panel_tests_polystat.json @@ -3408,8 +3408,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Panel Tests - Polystat", diff --git a/devenv/dev-dashboards/datasource-influxdb/influxdb-templated.json b/devenv/dev-dashboards/datasource-influxdb/influxdb-templated.json index 97719e82251..f46ccc2042f 100644 --- a/devenv/dev-dashboards/datasource-influxdb/influxdb-templated.json +++ b/devenv/dev-dashboards/datasource-influxdb/influxdb-templated.json @@ -312,7 +312,6 @@ "now": true, "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], "status": "Stable", - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"], "type": "timepicker" }, "timezone": "browser", diff --git a/devenv/dev-dashboards/datasource-mssql/mssql_fakedata.json b/devenv/dev-dashboards/datasource-mssql/mssql_fakedata.json index 19c5e1d0718..8fda3e8e714 100644 --- a/devenv/dev-dashboards/datasource-mssql/mssql_fakedata.json +++ b/devenv/dev-dashboards/datasource-mssql/mssql_fakedata.json @@ -537,8 +537,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Datasource tests - MSSQL", diff --git a/devenv/dev-dashboards/datasource-mssql/mssql_unittest.json b/devenv/dev-dashboards/datasource-mssql/mssql_unittest.json index 0137001067c..40f9d55e5ca 100644 --- a/devenv/dev-dashboards/datasource-mssql/mssql_unittest.json +++ b/devenv/dev-dashboards/datasource-mssql/mssql_unittest.json @@ -2831,8 +2831,7 @@ "to": "2018-03-15T13:55:01.000Z" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Datasource tests - MSSQL (unit test)", diff --git a/devenv/dev-dashboards/datasource-mysql/mysql_fakedata.json b/devenv/dev-dashboards/datasource-mysql/mysql_fakedata.json index 96e4688f7bd..cd1a24fb5bd 100644 --- a/devenv/dev-dashboards/datasource-mysql/mysql_fakedata.json +++ b/devenv/dev-dashboards/datasource-mysql/mysql_fakedata.json @@ -541,8 +541,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Datasource tests - MySQL", diff --git a/devenv/dev-dashboards/datasource-mysql/mysql_unittest.json b/devenv/dev-dashboards/datasource-mysql/mysql_unittest.json index b68c9db97ec..2f2a42c1175 100644 --- a/devenv/dev-dashboards/datasource-mysql/mysql_unittest.json +++ b/devenv/dev-dashboards/datasource-mysql/mysql_unittest.json @@ -2643,8 +2643,7 @@ "to": "2018-03-15T13:55:01.000Z" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Datasource tests - MySQL (unittest)", diff --git a/devenv/dev-dashboards/datasource-postgres/postgres_fakedata.json b/devenv/dev-dashboards/datasource-postgres/postgres_fakedata.json index d7d9514e639..750b3284517 100644 --- a/devenv/dev-dashboards/datasource-postgres/postgres_fakedata.json +++ b/devenv/dev-dashboards/datasource-postgres/postgres_fakedata.json @@ -577,8 +577,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Datasource tests - Postgres", diff --git a/devenv/dev-dashboards/datasource-postgres/postgres_unittest.json b/devenv/dev-dashboards/datasource-postgres/postgres_unittest.json index a114ed1b7ef..acec0d08a44 100644 --- a/devenv/dev-dashboards/datasource-postgres/postgres_unittest.json +++ b/devenv/dev-dashboards/datasource-postgres/postgres_unittest.json @@ -2621,8 +2621,7 @@ "to": "2018-03-15T13:55:01.000Z" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Datasource tests - Postgres (unittest)", diff --git a/devenv/dev-dashboards/datasource-testdata/demo1.json b/devenv/dev-dashboards/datasource-testdata/demo1.json index abe39ffeb55..6d8034f81e6 100644 --- a/devenv/dev-dashboards/datasource-testdata/demo1.json +++ b/devenv/dev-dashboards/datasource-testdata/demo1.json @@ -1092,7 +1092,6 @@ "now": true, "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], "status": "Stable", - "time_options": ["5m", "15m", "1h", "2h", " 6h", "12h", "24h", "2d", "7d", "30d"], "type": "timepicker" }, "timezone": "browser", diff --git a/devenv/dev-dashboards/datasource-testdata/new_features_in_v62.json b/devenv/dev-dashboards/datasource-testdata/new_features_in_v62.json index 4b9535ecbbd..a002a208f85 100644 --- a/devenv/dev-dashboards/datasource-testdata/new_features_in_v62.json +++ b/devenv/dev-dashboards/datasource-testdata/new_features_in_v62.json @@ -1326,8 +1326,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "New Features in v6.2", diff --git a/devenv/dev-dashboards/home.json b/devenv/dev-dashboards/home.json index 9c3d65d4add..840d32919ea 100644 --- a/devenv/dev-dashboards/home.json +++ b/devenv/dev-dashboards/home.json @@ -240,17 +240,6 @@ "1h", "2h", "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" ] }, "timezone": "", diff --git a/devenv/dev-dashboards/panel-bargauge/bar_gauge_demo.json b/devenv/dev-dashboards/panel-bargauge/bar_gauge_demo.json index a708467d7be..eda6ccfe996 100644 --- a/devenv/dev-dashboards/panel-bargauge/bar_gauge_demo.json +++ b/devenv/dev-dashboards/panel-bargauge/bar_gauge_demo.json @@ -654,8 +654,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["2s", "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["2s", "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Bar Gauge Demo", diff --git a/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json b/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json index 29a6929cf8f..3dfa360c740 100644 --- a/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json +++ b/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json @@ -1423,17 +1423,6 @@ "1h", "2h", "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" ] }, "timezone": "", diff --git a/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge2.json b/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge2.json index 0f36c203cd0..06fc26e382d 100644 --- a/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge2.json +++ b/devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge2.json @@ -519,8 +519,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Panel Tests - Bar Gauge 2", diff --git a/devenv/dev-dashboards/panel-common/lazy_loading.json b/devenv/dev-dashboards/panel-common/lazy_loading.json index 859eede4b4f..960c466124c 100644 --- a/devenv/dev-dashboards/panel-common/lazy_loading.json +++ b/devenv/dev-dashboards/panel-common/lazy_loading.json @@ -2202,8 +2202,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Lazy Loading", diff --git a/devenv/dev-dashboards/panel-common/panels_without_title.json b/devenv/dev-dashboards/panel-common/panels_without_title.json index 44bd210e73b..8f62c9cca5e 100644 --- a/devenv/dev-dashboards/panel-common/panels_without_title.json +++ b/devenv/dev-dashboards/panel-common/panels_without_title.json @@ -893,8 +893,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Panel Tests - With & Without title", diff --git a/devenv/dev-dashboards/panel-gauge/gauge-multi-series.json b/devenv/dev-dashboards/panel-gauge/gauge-multi-series.json index 09b72e5c030..f7ff80edc12 100644 --- a/devenv/dev-dashboards/panel-gauge/gauge-multi-series.json +++ b/devenv/dev-dashboards/panel-gauge/gauge-multi-series.json @@ -254,8 +254,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Panel Tests - Gauge Multi Series", diff --git a/devenv/dev-dashboards/panel-gauge/gauge_tests.json b/devenv/dev-dashboards/panel-gauge/gauge_tests.json index 458f53dbc08..309a255fcc7 100644 --- a/devenv/dev-dashboards/panel-gauge/gauge_tests.json +++ b/devenv/dev-dashboards/panel-gauge/gauge_tests.json @@ -1319,8 +1319,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Panel Tests - Gauge", diff --git a/devenv/dev-dashboards/panel-graph/graph-gradient-area-fills.json b/devenv/dev-dashboards/panel-graph/graph-gradient-area-fills.json index 01e9e8c2f43..c2d27efd469 100644 --- a/devenv/dev-dashboards/panel-graph/graph-gradient-area-fills.json +++ b/devenv/dev-dashboards/panel-graph/graph-gradient-area-fills.json @@ -372,8 +372,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Panel Tests - Graph - Gradient Area Fills", diff --git a/devenv/dev-dashboards/panel-graph/graph-time-regions.json b/devenv/dev-dashboards/panel-graph/graph-time-regions.json index 2031788ae3f..a4e03148536 100644 --- a/devenv/dev-dashboards/panel-graph/graph-time-regions.json +++ b/devenv/dev-dashboards/panel-graph/graph-time-regions.json @@ -569,8 +569,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "browser", "title": "Panel Tests - Graph Time Regions", diff --git a/devenv/dev-dashboards/panel-graph/graph_tests.json b/devenv/dev-dashboards/panel-graph/graph_tests.json index 4d01c2cb534..bd1fc95d3d6 100644 --- a/devenv/dev-dashboards/panel-graph/graph_tests.json +++ b/devenv/dev-dashboards/panel-graph/graph_tests.json @@ -1639,8 +1639,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "browser", "title": "Panel Tests - Graph", diff --git a/devenv/dev-dashboards/panel-polystat/polystat_test.json b/devenv/dev-dashboards/panel-polystat/polystat_test.json index 6be355ebd99..faa84463019 100644 --- a/devenv/dev-dashboards/panel-polystat/polystat_test.json +++ b/devenv/dev-dashboards/panel-polystat/polystat_test.json @@ -3408,8 +3408,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Panel Tests - Polystat", diff --git a/devenv/dev-dashboards/panel-table/table_tests.json b/devenv/dev-dashboards/panel-table/table_tests.json index 8582ef068d7..b8ca436e110 100644 --- a/devenv/dev-dashboards/panel-table/table_tests.json +++ b/devenv/dev-dashboards/panel-table/table_tests.json @@ -440,8 +440,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "browser", "title": "Panel Tests - Table", diff --git a/devenv/dev-dashboards/panel-timeseries/timeseries-gradient-area.json b/devenv/dev-dashboards/panel-timeseries/timeseries-gradient-area.json index a7389cc6ed6..8f3267a62d7 100644 --- a/devenv/dev-dashboards/panel-timeseries/timeseries-gradient-area.json +++ b/devenv/dev-dashboards/panel-timeseries/timeseries-gradient-area.json @@ -562,8 +562,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Panel Tests - Graph NG - Gradient Area Fills", diff --git a/devenv/dev-dashboards/scenarios/slow_queries_and_annotations.json b/devenv/dev-dashboards/scenarios/slow_queries_and_annotations.json index a7cc41acd8f..8966137b55e 100644 --- a/devenv/dev-dashboards/scenarios/slow_queries_and_annotations.json +++ b/devenv/dev-dashboards/scenarios/slow_queries_and_annotations.json @@ -1132,8 +1132,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Panel tests - Slow Queries & Annotations", diff --git a/devenv/dev-dashboards/scenarios/time_zone_support.json b/devenv/dev-dashboards/scenarios/time_zone_support.json index feb317f917e..cc1f81ee221 100644 --- a/devenv/dev-dashboards/scenarios/time_zone_support.json +++ b/devenv/dev-dashboards/scenarios/time_zone_support.json @@ -684,8 +684,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "utc", "title": "Panel Tests - Time zone support", diff --git a/devenv/docker/blocks/prometheus_random_data/Dockerfile b/devenv/docker/blocks/prometheus_random_data/Dockerfile index c4ebc8cb0ec..41f90677409 100644 --- a/devenv/docker/blocks/prometheus_random_data/Dockerfile +++ b/devenv/docker/blocks/prometheus_random_data/Dockerfile @@ -7,7 +7,7 @@ RUN CGO_ENABLED=0 GOOS=linux go install -tags netgo -ldflags '-w' github.com/pro # Final image. FROM scratch -LABEL maintainer "The Prometheus Authors " +LABEL maintainer="The Prometheus Authors " COPY --from=builder /go/bin/random . EXPOSE 8080 ENTRYPOINT ["/random"] diff --git a/devenv/docker/blocks/stateful_webhook/Dockerfile b/devenv/docker/blocks/stateful_webhook/Dockerfile new file mode 100644 index 00000000000..03b50db2135 --- /dev/null +++ b/devenv/docker/blocks/stateful_webhook/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.23.5 + +ADD main.go /go/src/webhook/main.go + +WORKDIR /go/src/webhook + +RUN mkdir /tmp/logs +RUN go build -o /bin main.go + +ENV PORT=8080 + +ENTRYPOINT [ "/bin/main" ] diff --git a/devenv/docker/blocks/stateful_webhook/docker-compose.yaml b/devenv/docker/blocks/stateful_webhook/docker-compose.yaml new file mode 100644 index 00000000000..7217516c4e9 --- /dev/null +++ b/devenv/docker/blocks/stateful_webhook/docker-compose.yaml @@ -0,0 +1,5 @@ + stateful_webhook: + build: + context: docker/blocks/stateful_webhook + ports: + - "8080:8080" diff --git a/devenv/docker/blocks/stateful_webhook/main.go b/devenv/docker/blocks/stateful_webhook/main.go new file mode 100644 index 00000000000..926cf4e593e --- /dev/null +++ b/devenv/docker/blocks/stateful_webhook/main.go @@ -0,0 +1,149 @@ +package main + +import ( + "encoding/json" + "io" + "log" + "net/http" + "strings" + "sync" + "time" +) + +type Event struct { + Status string `json:"status"` + TimeNow time.Time `json:"timeNow"` + StartsAt time.Time `json:"startsAt"` + Node string `json:"node"` + DeltaLastSeconds float64 `json:"deltaLastSeconds"` + DeltaStartSeconds float64 `json:"deltaStartSeconds"` +} + +type Notification struct { + Alerts []Alert `json:"alerts"` + CommonAnnotations map[string]string `json:"commonAnnotations"` + CommonLabels map[string]string `json:"commonLabels"` + ExternalURL string `json:"externalURL"` + GroupKey string `json:"groupKey"` + GroupLabels map[string]string `json:"groupLabels"` + Message string `json:"message"` + OrgID int `json:"orgId"` + Receiver string `json:"receiver"` + State string `json:"state"` + Status string `json:"status"` + Title string `json:"title"` + TruncatedAlerts int `json:"truncatedAlerts"` + Version string `json:"version"` +} + +type Alert struct { + Annotations map[string]string `json:"annotations"` + DashboardURL string `json:"dashboardURL"` + StartsAt time.Time `json:"startsAt"` + EndsAt time.Time `json:"endsAt"` + Fingerprint string `json:"fingerprint"` + GeneratorURL string `json:"generatorURL"` + Labels map[string]string `json:"labels"` + PanelURL string `json:"panelURL"` + SilenceURL string `json:"silenceURL"` + Status string `json:"status"` + ValueString string `json:"valueString"` + Values map[string]any `json:"values"` +} + +type NotificationHandler struct { + startedAt time.Time + stats map[string]int + hist []Event + m sync.Mutex +} + +func NewNotificationHandler() *NotificationHandler { + return &NotificationHandler{ + startedAt: time.Now(), + stats: make(map[string]int), + hist: make([]Event, 0), + } +} + +func (ah *NotificationHandler) Notify(w http.ResponseWriter, r *http.Request) { + b, err := io.ReadAll(r.Body) + if err != nil { + log.Println(err) + w.WriteHeader(http.StatusBadRequest) + return + } + n := Notification{} + if err := json.Unmarshal(b, &n); err != nil { + log.Println(err) + w.WriteHeader(http.StatusBadRequest) + return + } + log.Printf("got notification from: %s. a: %v", r.RemoteAddr, n) + + ah.m.Lock() + defer ah.m.Unlock() + + addr := r.RemoteAddr + if split := strings.Split(r.RemoteAddr, ":"); len(split) > 0 { + addr = split[0] + } + + a := n.Alerts[0] + + timeNow := time.Now() + + ah.stats[n.Status]++ + + var d time.Duration + if len(ah.hist) > 0 { + last := ah.hist[len(ah.hist)-1] + d = timeNow.Sub(last.TimeNow) + } + + ah.hist = append(ah.hist, Event{ + Status: n.Status, + StartsAt: a.StartsAt, + TimeNow: timeNow, + Node: addr, + DeltaLastSeconds: d.Seconds(), + DeltaStartSeconds: timeNow.Sub(ah.startedAt).Seconds(), + }) +} + +func (ah *NotificationHandler) GetNotifications(w http.ResponseWriter, _ *http.Request) { + ah.m.Lock() + defer ah.m.Unlock() + w.Header().Set("Content-Type", "application/json") + + res, err := json.MarshalIndent(map[string]any{"stats": ah.stats, "history": ah.hist}, "", "\t") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + //nolint:errcheck + w.Write([]byte(`{"error":"failed to marshal alerts"}`)) + log.Printf("failed to marshal alerts: %v\n", err) + return + } + + log.Printf("requested current state\n%v\n", string(res)) + + _, err = w.Write(res) + if err != nil { + log.Printf("failed to write response: %v\n", err) + } +} + +func main() { + ah := NewNotificationHandler() + + http.HandleFunc("/ready", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + http.HandleFunc("/notify", ah.Notify) + http.HandleFunc("/notifications", ah.GetNotifications) + + log.Println("Listening") + //nolint:errcheck + http.ListenAndServe("0.0.0.0:8080", nil) +} diff --git a/devenv/docker/ha-test-unified-alerting/grafana/provisioning/dashboards/alerts/overview.json b/devenv/docker/ha-test-unified-alerting/grafana/provisioning/dashboards/alerts/overview.json index b4946cfb6b3..0357c3d10f1 100644 --- a/devenv/docker/ha-test-unified-alerting/grafana/provisioning/dashboards/alerts/overview.json +++ b/devenv/docker/ha-test-unified-alerting/grafana/provisioning/dashboards/alerts/overview.json @@ -151,17 +151,6 @@ "1h", "2h", "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" ] }, "timezone": "", diff --git a/devenv/docker/ha-test-unified-alerting/grafana/provisioning/dashboards/mysql/overview.json b/devenv/docker/ha-test-unified-alerting/grafana/provisioning/dashboards/mysql/overview.json index 7643250ec28..2bf789366cc 100644 --- a/devenv/docker/ha-test-unified-alerting/grafana/provisioning/dashboards/mysql/overview.json +++ b/devenv/docker/ha-test-unified-alerting/grafana/provisioning/dashboards/mysql/overview.json @@ -5376,17 +5376,6 @@ "1d" ], "status": "Stable", - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ], "type": "timepicker" }, "timezone": "browser", diff --git a/devenv/docker/ha_test/grafana/provisioning/dashboards/alerts/overview.json b/devenv/docker/ha_test/grafana/provisioning/dashboards/alerts/overview.json index b4946cfb6b3..0357c3d10f1 100644 --- a/devenv/docker/ha_test/grafana/provisioning/dashboards/alerts/overview.json +++ b/devenv/docker/ha_test/grafana/provisioning/dashboards/alerts/overview.json @@ -151,17 +151,6 @@ "1h", "2h", "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" ] }, "timezone": "", diff --git a/devenv/docker/ha_test/grafana/provisioning/dashboards/mysql/overview.json b/devenv/docker/ha_test/grafana/provisioning/dashboards/mysql/overview.json index 7643250ec28..2bf789366cc 100644 --- a/devenv/docker/ha_test/grafana/provisioning/dashboards/mysql/overview.json +++ b/devenv/docker/ha_test/grafana/provisioning/dashboards/mysql/overview.json @@ -5376,17 +5376,6 @@ "1d" ], "status": "Stable", - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ], "type": "timepicker" }, "timezone": "browser", diff --git a/docs/sources/administration/roles-and-permissions/access-control/plan-rbac-rollout-strategy/index.md b/docs/sources/administration/roles-and-permissions/access-control/plan-rbac-rollout-strategy/index.md index 09c2fc4c3d4..e04c2fcecf2 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/plan-rbac-rollout-strategy/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/plan-rbac-rollout-strategy/index.md @@ -369,9 +369,11 @@ Here are two ways to achieve this: # Update the role curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' -H 'Content-Type: application/json' \ - -X PUT-d @/tmp/basic_viewer.json '/api/access-control/roles/basic_viewer' + -X PUT -d @/tmp/basic_viewer.json '/api/access-control/roles/basic_viewer' ``` + The token that is used in this request is the [service account token](ref:service-accounts). + - Or use the `role > from` list and `permission > state` option of your provisioning file: ```yaml @@ -394,6 +396,20 @@ Here are two ways to achieve this: state: 'present' ``` + If your goal is to remove an access to an app you should remove it from the role and update it. For example: + + ```bash + # Fetch the role, modify it to remove permissions to kentik-connect-app and increment role version + curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' \ + -X GET '/api/access-control/roles/basic_viewer' | \ + jq 'del(.created)| del(.updated) | del(.permissions[].created) | del(.permissions[].updated) | .version += 1' | \ + jq 'del(.permissions[] | select (.action == "plugins.app:access" and .scope == "plugins:id:kentik-connect-app"))' + + # Update the role + curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' -H 'Content-Type: application/json' \ + -X PUT -d @/tmp/basic_viewer.json '/api/access-control/roles/basic_viewer' + ``` + ### Manage user permissions through teams In the scenario where you want users to grant access by the team they belong to, we recommend to set users role to `No Basic Role` and let the team assignment assign the role instead. diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/_index.md b/docs/sources/alerting/configure-notifications/manage-contact-points/_index.md index 160d76360dc..215ed1926dc 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/_index.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/_index.md @@ -140,13 +140,13 @@ Each contact point integration has its own configuration options and setup proce - [Discord](ref:discord) - [Email](ref:email) - [Google Chat](ref:gchat) -- [Grafana Oncall](ref:oncall) +- [Grafana OnCall](ref:oncall) - Kafka REST Proxy - Line - [Microsoft Teams](ref:teams) - [MQTT](ref:mqtt) - [Opsgenie](ref:opsgenie) -- [Pagerduty](ref:pagerduty) +- [PagerDuty](ref:pagerduty) - Pushover - Sensu Go - [Slack](ref:slack) diff --git a/docs/sources/dashboards/build-dashboards/view-dashboard-json-model/index.md b/docs/sources/dashboards/build-dashboards/view-dashboard-json-model/index.md index 44b20434a0d..0fff2d65d9f 100644 --- a/docs/sources/dashboards/build-dashboards/view-dashboard-json-model/index.md +++ b/docs/sources/dashboards/build-dashboards/view-dashboard-json-model/index.md @@ -138,12 +138,12 @@ The grid has a negative gravity that moves panels up if there is empty space abo "nowDelay": "", "quick_ranges": [ { - "display": "Last 6 hours" + "display": "Last 6 hours", "from": "now-6h", "to": "now" }, { - "display": "Last 7 days" + "display": "Last 7 days", "from": "now-7d", "to": "now" } diff --git a/docs/sources/datasources/tempo/_index.md b/docs/sources/datasources/tempo/_index.md index 8b2123b5cd4..836db6cac14 100644 --- a/docs/sources/datasources/tempo/_index.md +++ b/docs/sources/datasources/tempo/_index.md @@ -71,7 +71,14 @@ refs: # Tempo data source -Grafana ships with built-in support for [Tempo](https://grafana.com/docs/tempo/latest/), a high-volume, minimal-dependency trace storage, open source tracing solution from Grafana Labs. This topic explains configuration and queries specific to the Tempo data source. +Grafana ships with built-in support for [Tempo](https://grafana.com/docs/tempo//), a high-volume, minimal-dependency trace storage, open source tracing solution from Grafana Labs. + +To learn more about traces, refer to [Introduction to tracing](https://grafana.com/docs/tempo//introduction/). + +To use traces, you need you have an application or service that is instrumented to emit traces. +Refer to the [Instrument for tracing](https://grafana.com/docs/tempo//getting-started/instrumentation/) for more information. + +## Add a data source For instructions on how to add a data source to Grafana, refer to the [administration documentation](ref:data-source-management). Only users with the organization administrator role can add data sources. @@ -81,6 +88,8 @@ This video explains how to add data sources, including Loki, Tempo, and Mimir, t {{< youtube id="cqHO0oYW6Ic" start="298" >}} -Once you've added the data source, you can [configure it]({{< relref "./configure-tempo-data-source/" >}}) so that your Grafana instance's users can create queries in its [query editor]({{< relref "./query-editor/" >}}) when they [build dashboards](ref:build-dashboards) and use [Explore](ref:explore). +## Learn more + +After you've added the data source, you can [configure it](./configure-tempo-data-source/) so that your Grafana instance's users can create queries in its [query editor](./query-editor/) when they [build dashboards](ref:build-dashboards) and use [Explore](ref:explore). {{< section withDescriptions="true">}} diff --git a/docs/sources/datasources/tempo/configure-tempo-data-source.md b/docs/sources/datasources/tempo/configure-tempo-data-source.md index 24844b82ef3..39d3cdf751a 100644 --- a/docs/sources/datasources/tempo/configure-tempo-data-source.md +++ b/docs/sources/datasources/tempo/configure-tempo-data-source.md @@ -131,7 +131,7 @@ To use streaming, you need to: - Run Tempo version 2.2 or later, or Grafana Enterprise Traces (GET) version 2.2 or later, or use Grafana Cloud Traces. - Tempo must have `stream_over_http_enabled: true` for streaming to work. - For more information, refer to [Tempo GRPC API](https://grafana.com/docs/tempo//api_docs/#tempo-grpc-api). + For more information, refer to [Tempo gRPC API](https://grafana.com/docs/tempo//api_docs/#tempo-grpc-api). - For self-managed Tempo or GET instances: If your Tempo or GET instance is behind a load balancer or proxy that doesn't supporting gRPC or HTTP2, streaming may not work and should be deactivated. diff --git a/docs/sources/datasources/tempo/query-editor/_index.md b/docs/sources/datasources/tempo/query-editor/_index.md index b561a54bdcd..b6aa5c0f49b 100644 --- a/docs/sources/datasources/tempo/query-editor/_index.md +++ b/docs/sources/datasources/tempo/query-editor/_index.md @@ -105,7 +105,7 @@ Refer to the [Search using the TraceQL query builder documentation]({{< relref " The **TraceQL** query editor lets you search by trace ID and write TraceQL queries using autocomplete. -Refer to the [TraceQL query editor documentation]({{< relref "./traceql-editor" >}}) to learn more about constructing queries using a code-editor-like experience. +Refer to the [TraceQL query editor documentation](./traceql-editor) to learn more about constructing queries using a code-editor-like experience. ![The TraceQL query editor](/media/docs/grafana/data-sources/tempo/query-editor/tempo-ds-query-traceql-v11.png) @@ -113,8 +113,8 @@ You can also search for a trace ID by entering it into the query field. ### Service graph view -Grafana’s **Service Graph** view uses metrics to display span request rates, error rates, and durations, as well as service graphs. -Once the requirements are set up, this preconfigured view is immediately available. +The **Service Graph** view uses metrics to display span request rates, error rates, and durations, as well as service graphs. +After the requirements are set up, this preconfigured view is immediately available. Using the service graph view, you can: diff --git a/docs/sources/datasources/tempo/query-editor/traceql-editor.md b/docs/sources/datasources/tempo/query-editor/traceql-editor.md index 7752a290229..cc5b38beaaa 100644 --- a/docs/sources/datasources/tempo/query-editor/traceql-editor.md +++ b/docs/sources/datasources/tempo/query-editor/traceql-editor.md @@ -12,7 +12,7 @@ labels: - oss menuTitle: Write TraceQL queries title: Write TraceQL queries with the editor -weight: 300 +weight: 400 refs: explore: - pattern: /docs/grafana/ @@ -41,4 +41,4 @@ refs: [//]: # 'Shared content for the TraceQL query editor' [//]: # 'This content is located in /docs/sources/shared/datasources/tempo-editor-traceql.md' -{{< docs/shared source="grafana" lookup="datasources/tempo-editor-traceql.md" version="" >}} +{{< docs/shared source="grafana" lookup="datasources/tempo-editor-traceql.md" version="" >}} diff --git a/docs/sources/datasources/tempo/query-editor/traceql-search.md b/docs/sources/datasources/tempo/query-editor/traceql-search.md index b885f1239d8..84ac0b396c4 100644 --- a/docs/sources/datasources/tempo/query-editor/traceql-search.md +++ b/docs/sources/datasources/tempo/query-editor/traceql-search.md @@ -56,4 +56,4 @@ To enable the TraceQL query builder in self-hosted Grafana through version 10.1, [//]: # 'Shared content for the Search - TraceQL query builder' -{{< docs/shared source="grafana" lookup="datasources/tempo-search-traceql.md" leveloffset="+1" version="" >}} +{{< docs/shared source="grafana" lookup="datasources/tempo-search-traceql.md" leveloffset="+1" version="" >}} diff --git a/docs/sources/datasources/tempo/query-editor/traceql-structure.md b/docs/sources/datasources/tempo/query-editor/traceql-structure.md new file mode 100644 index 00000000000..b67d57cedf3 --- /dev/null +++ b/docs/sources/datasources/tempo/query-editor/traceql-structure.md @@ -0,0 +1,21 @@ +--- +description: Learn how to create TraceQL queries are structured. +keywords: + - queries + - TraceQL +labels: + products: + - cloud + - enterprise + - oss +menuTitle: TraceQL query structure +title: TraceQL query structure +weight: 200 +--- + +# TraceQL query structure + +[//]: # 'Shared content for best practices for traces' +[//]: # 'This content is located in /tempo/docs/sources/shared/trace-structure.md' + +{{< docs/shared source="tempo" lookup="traceql-query-structure.md" version="" >}} diff --git a/docs/sources/datasources/tempo/service-graph.md b/docs/sources/datasources/tempo/service-graph.md index 0e6c1f8b657..d1644d41b73 100644 --- a/docs/sources/datasources/tempo/service-graph.md +++ b/docs/sources/datasources/tempo/service-graph.md @@ -72,8 +72,8 @@ You use the Service Graph to detect performance issues; track increases in error ## Display the Service Graph -1. [Configure Grafana Alloy](https://grafana.com/docs/tempo/latest/configuration/grafana-alloy/) or [Tempo or GET](https://grafana.com/docs/tempo/latest/metrics-generator/service_graphs/#tempo) to generate Service Graph data. -1. Link a Prometheus data source in the Tempo data source's [Service Graph]({{< relref "./configure-tempo-data-source#configure-service-graph" >}}) settings. +1. [Configure Grafana Alloy](https://grafana.com/docs/tempo//configuration/grafana-alloy/) or [Tempo or GET](https://grafana.com/docs/tempo//metrics-generator/service_graphs/#tempo) to generate Service Graph data. +1. Link a Prometheus data source in the Tempo data source's [Service Graph](./configure-tempo-data-source#configure-service-graph) settings. 1. Navigate to [Explore](ref:explore). 1. Select the Tempo data source. 1. Select the **Service Graph** query type. @@ -113,10 +113,10 @@ To open the Service Graph view: 1. Run the query. 1. _(Optional)_ Filter your results. -{{% admonition type="note" %}} +{{< admonition type="note" >}} Grafana uses the `traces_spanmetrics_calls_total` metric to display the name, rate, and error rate columns, and `traces_spanmetrics_latency_bucket` to display the duration column. These metrics must exist in your Prometheus data source. -{{% /admonition %}} +{{< /admonition >}} To open a query in Prometheus with the span name of that row automatically set in the query, click a row in the **rate**, **error rate**, or **duration** columns. diff --git a/docs/sources/datasources/tempo/traces-in-grafana/_index.md b/docs/sources/datasources/tempo/traces-in-grafana/_index.md new file mode 100644 index 00000000000..c3f622bb57d --- /dev/null +++ b/docs/sources/datasources/tempo/traces-in-grafana/_index.md @@ -0,0 +1,23 @@ +--- +description: Learn about how you can use tracing data in Grafana Cloud to query data, + generate metrics, and link your tracing data with logs, metrics, and profiles. +keywords: + - Grafana + - traces + - tracing +title: Use traces in Grafana +weight: 275 +--- + +# Use traces in Grafana + +Using traces, you can search for traces, generate metrics from spans, and link your tracing data with logs, metrics, and profiles. + +This page provides a summary of how you can use tracing data in Grafana. + +For general documentation on querying data sources in Grafana, refer to [Query and transform data](https://grafana.com/docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/). + +[//]: # 'Shared content for best practices for traces' +[//]: # 'This content is located in /tempo/docs/sources/shared/tempo-in-grafana.md' + +{{< docs/shared source="tempo" lookup="tempo-in-grafana.md" version="" >}} diff --git a/docs/sources/datasources/tempo/json-trace-file.md b/docs/sources/datasources/tempo/traces-in-grafana/json-trace-file.md similarity index 82% rename from docs/sources/datasources/tempo/json-trace-file.md rename to docs/sources/datasources/tempo/traces-in-grafana/json-trace-file.md index 0387eab883a..77b45011a2f 100644 --- a/docs/sources/datasources/tempo/json-trace-file.md +++ b/docs/sources/datasources/tempo/traces-in-grafana/json-trace-file.md @@ -12,7 +12,9 @@ labels: - oss menuTitle: Upload JSON trace file title: Upload a JSON trace file -weight: 400 +weight: 900 +aliases: + - ../json-trace-file/ # /docs/grafana/latest/datasources/tempo/json-trace-file --- # Upload a JSON trace file @@ -20,7 +22,15 @@ weight: 400 You can upload a JSON file that contains a single trace and visualize it. If the file has multiple traces, Grafana visualizes the first trace. -**To download a trace or Service Graph through the inspector:** +To upload a trace file: + +1. Select **Explore** in Grafana. +1. Select **Import trace** in the right corner. +1. Upload your JSON trace file. + +## Download a trace or service graph + +To download a trace or Service Graph through the [Inspector panel](https://grafana.com/docs/grafana//explore/explore-inspector/): 1. Open the inspector. 1. Navigate to the **Data** tab. diff --git a/docs/sources/datasources/tempo/link-trace-id.md b/docs/sources/datasources/tempo/traces-in-grafana/link-trace-id.md similarity index 87% rename from docs/sources/datasources/tempo/link-trace-id.md rename to docs/sources/datasources/tempo/traces-in-grafana/link-trace-id.md index d53496e3784..07c7f6f2bec 100644 --- a/docs/sources/datasources/tempo/link-trace-id.md +++ b/docs/sources/datasources/tempo/traces-in-grafana/link-trace-id.md @@ -12,7 +12,10 @@ labels: - oss menuTitle: Link to a trace ID title: Link to a trace ID -weight: 700 +weight: 800 +aliases: + - ../link-trace-id/ # /docs/grafana/latest/datasources/tempo/link-trace-id/ + refs: configure-grafana-feature-toggles: - pattern: /docs/grafana/ @@ -69,10 +72,10 @@ You can link to Tempo traces from logs or metrics. You can link to Tempo traces from logs in Loki, Elasticsearch, Splunk, and other logs data sources by configuring an internal link. -To configure this feature, see the [Derived fields]({{< relref "../loki#configure-derived-fields" >}}) section of the Loki data source docs or the [Data links]({{< relref "../elasticsearch#data-links" >}}) section of the Elasticsearch or Splunk data source docs. +To configure this feature, refer to the [Derived fields](../../loki#configure-derived-fields) section of the Loki data source docs or the [Data links](../../elasticsearch#data-links) section of the Elasticsearch or Splunk data source docs. ## Link to a trace ID from metrics You can link to Tempo traces from metrics in Prometheus data sources by configuring an exemplar. -To configure this feature, see the [introduction to exemplars](ref:exemplars) documentation. +To configure this feature, refer to the [Exemplars](ref:exemplars) documentation. diff --git a/docs/sources/explore/correlations-editor-in-explore.md b/docs/sources/explore/correlations-editor-in-explore.md index 50053226158..dd0a0ce1936 100644 --- a/docs/sources/explore/correlations-editor-in-explore.md +++ b/docs/sources/explore/correlations-editor-in-explore.md @@ -3,6 +3,7 @@ labels: products: - enterprise - oss + - cloud title: Correlations Editor in Explore weight: 20 --- @@ -13,22 +14,22 @@ weight: 20 The Explore editor is available in 10.1 and later versions. In the editor, transformations is available in Grafana 10.3 and later versions. {{% /admonition %}} -Correlations allow users to build a link between any two data sources. For more information about correlations in general, please see the [correlations]({{< relref "../administration/correlations" >}}) topic in the administration page. +Correlations allow users to build a link between any two data sources. For more information about correlations in general, please see the [correlations](/docs/grafana//administration/correlations/) topic in the administration page. ## Create a correlation 1. In Grafana, navigate to the Explore page. -1. Select a data source that you would like to be [the source data source]({{< relref "../administration/correlations/correlation-configuration#source-data-source-and-result-field" >}}) for a new correlation. -1. Run a query producing data in [a supported visualization]({{< relref "../administration/correlations#correlations" >}}). -1. Click **+ Add** in the top toolbar and select **Add correlation** (you can also select **Correlations Editor** from the [Command Palette]({{< relref "../search#command-palette" >}})). +1. Select a data source that you would like to be [the source data source](/docs/grafana//administration/correlations/correlation-configuration/#source-data-source-and-result-field) for a new correlation. +1. Run a query producing data in [a supported visualization](/docs/grafana//administration/correlations/#correlations). +1. Click **+ Add** in the top toolbar and select **Add correlation** (you can also select **Correlations Editor** from the [Command Palette](/docs/grafana//search/#command-palette)). 1. Explore is now in Correlations Editor mode indicated by a blue border and top bar. You can exit Correlations Editor by clicking **Exit** in the top bar. 1. You can now create the following new correlations for the visualization with links that are attached to the data that you can use to build a new query: - Logs: links are displayed next to field values inside log details for each log row - Table: every table cell is a link 1. Click on a link to add a new correlation. - Links are associated with a field that is used as a [result field of a correlation]({{< relref "../administration/correlations/correlation-configuration" >}}). -1. In the split view that opens, use the right pane to set up [the target query source of the correlation]({{< relref "../administration/correlations/correlation-configuration#target-query" >}}). -1. Build a target query using [variables syntax]({{< relref "../dashboards/variables/variable-syntax" >}}) with variables from the list provided at the top of the pane. The list contains sample values from the selected data row. + Links are associated with a field that is used as a [result field of a correlation](/docs/grafana//administration/correlations/correlation-configuration/). +1. In the split view that opens, use the right pane to set up [the target query source of the correlation](/docs/grafana//administration/correlations/correlation-configuration/#target-query). +1. Build a target query using [variables syntax](/docs/grafana//dashboards/variables/variable-syntax/) with variables from the list provided at the top of the pane. The list contains sample values from the selected data row. 1. Provide a label and description (optional). A label will be used as the name of the link inside the visualization and can contain variables. 1. Provide transformations (optional; see below for details). @@ -37,7 +38,7 @@ Correlations allow users to build a link between any two data sources. For more ## Transformations -Transformations allow you to extract values that exist in a field with other data. For example, using a transformation, you can extract one portion of a log line to use in a correlation. For more details on transformations in correlations, see [Correlations]({{< relref "../administration/correlations/correlation-configuration/#correlation-transformations" >}}). +Transformations allow you to extract values that exist in a field with other data. For example, using a transformation, you can extract one portion of a log line to use in a correlation. For more details on transformations in correlations, see [Correlations](/docs/grafana//explore/correlations-editor-in-explore/#transformations). After clicking one of the generated links in the editor mode, you can add transformations by clicking **Add transformation** in the Transformations dropdown menu. @@ -47,7 +48,7 @@ You can use a transformation in your correlation with the following steps: Select the portion of the field that you want to use for the transformation. For example, a log line. Once selected, the value of this field will be used to assist you in building the transformation. 1. Select the type of the transformation. - See [correlations]({{< relref "../administration/correlations/correlation-configuration/#correlation-transformations" >}}) for the options and relevant settings. + See [correlations](/docs/grafana//explore/correlations-editor-in-explore/#transformations) for the options and relevant settings. 1. Based on your selection, you might see one or more variables populate, or you might need to provide more specifications in options that are displayed. 1. Select **Add transformation to correlation** to add the specified variables to the list of available variables. @@ -57,7 +58,7 @@ For regular expressions in this dialog box, the `mapValue` referred to in other ## Correlations examples -The following examples show how to create correlations using the Correlations Editor in Explore. If you'd like to follow these examples, make sure to set up a [test data source]({{< relref "../datasources/testdata#testdata-data-source" >}}). +The following examples show how to create correlations using the Correlations Editor in Explore. If you'd like to follow these examples, make sure to set up a [test data source](/docs/grafana//datasources/testdata/#testdata-data-source). ### Create a text to graph correlation @@ -65,7 +66,7 @@ This example shows how to create a correlation using Correlations Editor in Expl Correlations allow you to use results of one query to run a new query in any data source. In this example, you will run a query that renders tabular data. The data will be used to run a different query that yields a graph result. -To follow this example, make sure you have set up [a test data source]({{< relref "../datasources/testdata#testdata-data-source" >}}). +To follow this example, make sure you have set up [a test data source](/docs/grafana//datasources/testdata/#testdata-data-source). 1. In Grafana, navigate to **Explore**. 1. Select the **test data source** from the dropdown menu at the top left of the page. @@ -100,7 +101,7 @@ You can apply the same steps to any data source. Correlations allow you to creat In this example, you will create a correlation to demonstrate how to use transformations to extract values from the log line and another field. -To follow this example, make sure you have set up [a test data source]({{< relref "../datasources/testdata#testdata-data-source" >}}). +To follow this example, make sure you have set up [a test data source](/docs/grafana//datasources/testdata/#testdata-data-source). 1. In Grafana, navigate to **Explore**. 1. Select the **test data source** from the dropdown menu at the top left of the page. diff --git a/docs/sources/explore/trace-integration.md b/docs/sources/explore/trace-integration.md index f9b0779b12d..532bb3ca8f2 100644 --- a/docs/sources/explore/trace-integration.md +++ b/docs/sources/explore/trace-integration.md @@ -157,7 +157,8 @@ The node graph requires data to be returned from the data source in a specific f ## Service graph -A service graph visualizes span metrics, including rates, error rates, and durations (RED), along with service relationships. Once the requirements are configured, this pre-configured view is immediately available. +A service graph visualizes rates, error rates, and durations (RED), along with service relationships. +After the requirements are configured, this pre-configured view is immediately available. For additional information refer to the following documentation: diff --git a/docs/sources/introduction/grafana-enterprise.md b/docs/sources/introduction/grafana-enterprise.md index 28a647254ef..e9317e632a4 100644 --- a/docs/sources/introduction/grafana-enterprise.md +++ b/docs/sources/introduction/grafana-enterprise.md @@ -33,7 +33,7 @@ Grafana Enterprise includes integrations with more ways to authenticate your use Supported auth providers: -- [Auth Proxy]({{< relref "../setup-grafana/configure-security/configure-authentication/auth-proxy#team-sync-enterprise-only" >}}) +- [Auth Proxy](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/auth-proxy#team-sync-enterprise-only) - [Azure AD](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/azuread#group-sync-enterprise-only) - [Generic OAuth integration](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/generic-oauth#configure-group-synchronization) - [GitHub OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/github#configure-group-synchronization) diff --git a/docs/sources/panels-visualizations/configure-data-links/index.md b/docs/sources/panels-visualizations/configure-data-links/index.md index 31bf6df9b67..0245bc75eb5 100644 --- a/docs/sources/panels-visualizations/configure-data-links/index.md +++ b/docs/sources/panels-visualizations/configure-data-links/index.md @@ -25,9 +25,9 @@ weight: 80 refs: global-variables: - pattern: /docs/grafana/ - destination: /docs/grafana//dashboards/variables/add-template-variables/#**from-and-**to + destination: /docs/grafana//dashboards/variables/add-template-variables/#__from-and-__to - pattern: /docs/grafana-cloud/ - destination: /docs/grafana-cloud/visualizations/dashboards/variables/add-template-variables/#**from-and-**to + destination: /docs/grafana-cloud/visualizations/dashboards/variables/add-template-variables/#__from-and-__to heatmap: - pattern: /docs/grafana/ destination: /docs/grafana//panels-visualizations/visualizations/heatmap/ @@ -253,13 +253,17 @@ If you want to add all of the current dashboard's variables to the URL, then use 1. Hover over any part of the panel to display the menu icon in the upper-right corner. 1. Click the menu icon and select **Edit** to open the panel editor. 1. In the panel edit pane, scroll down to the **Data links** section and expand it. -1. Click **Add link**. -1. In the dialog box that opens, enter a **Title**. This is a human-readable label for the link, which will be displayed in the UI. -1. Enter the **URL** or variable to which you want to link. +1. Click **+ Add link**. +1. In the dialog box that opens, enter a **Title**. This is a human-readable label for the link displayed in the UI. This is a required field. +1. Enter the **URL** or variable to which you want to link. This is a required field. To add a data link variable, click in the **URL** field and enter `$` or press Ctrl+Space or Cmd+Space to see a list of available variables. 1. If you want the link to open in a new tab, toggle the **Open in a new tab** switch. +1. If you want the data link to open with a single click, toggle the **One click** switch. + + Only one data link can have **One click** enabled at a time. **One click** is only supported for some visualizations. + 1. Click **Save** to save changes and close the dialog box. 1. Click **Save dashboard**. 1. Click **Back to dashboard** and then **Exit edit**. diff --git a/docs/sources/panels-visualizations/visualizations/bar-chart/index.md b/docs/sources/panels-visualizations/visualizations/bar-chart/index.md index 3d7f982d554..c9aaf77688b 100644 --- a/docs/sources/panels-visualizations/visualizations/bar-chart/index.md +++ b/docs/sources/panels-visualizations/visualizations/bar-chart/index.md @@ -201,7 +201,7 @@ You can set standard min/max options to define hard limits of the Y-axis. For mo ### Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-2.md" source="grafana" version="" >}} ### Value mappings diff --git a/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md b/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md index f4059970634..cc70b2bbb20 100644 --- a/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md +++ b/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md @@ -213,7 +213,7 @@ This option only applies when bar size is set to manual. ## Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-1.md" source="grafana" version="" >}} ## Value mappings diff --git a/docs/sources/panels-visualizations/visualizations/candlestick/index.md b/docs/sources/panels-visualizations/visualizations/candlestick/index.md index ee51937afc9..c673509a446 100644 --- a/docs/sources/panels-visualizations/visualizations/candlestick/index.md +++ b/docs/sources/panels-visualizations/visualizations/candlestick/index.md @@ -138,7 +138,7 @@ The candlestick visualization is based on the time series visualization. It can ## Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-2.md" source="grafana" version="" >}} ## Thresholds diff --git a/docs/sources/panels-visualizations/visualizations/canvas/index.md b/docs/sources/panels-visualizations/visualizations/canvas/index.md index b31b8018902..3313fb3b225 100644 --- a/docs/sources/panels-visualizations/visualizations/canvas/index.md +++ b/docs/sources/panels-visualizations/visualizations/canvas/index.md @@ -330,14 +330,14 @@ Use the following options to control the border of the canvas: The following options allow you to control the appearance of the element you've selected. To access an element so that you can edit it, expand the **Layer** section and select the desired element. -| Option | Description | -| ------------------------------------------- | ----------------------------------------------------------------------------------------------- | -| [Element type](#element-type) | Change the selected element type. | -| [Element](#element) | Control the appearance of text on the element. This section is named based on the element type. | -| [Layout](#layout) | Control the placement of elements on the canvas. | -| [Background (element)](#background-element) | Set the background of the element. | -| [Border (element)](#border-element) | Set the border of the element. | -| [Data links](#data-links) | Configure data links for elements. | +| Option | Description | +| ------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| [Element type](#element-type) | Change the selected element type. | +| [Element](#element) | Control the appearance of text on the element. This section is named based on the element type. | +| [Layout](#layout) | Control the placement of elements on the canvas. | +| [Background (element)](#background-element) | Set the background of the element. | +| [Border (element)](#border-element) | Set the border of the element. | +| [Data links and actions](#data-links-and-actions) | Configure data links and actions for elements. | #### Element type @@ -405,40 +405,66 @@ Use the following options to set the border of the element: - **Color** - Set the border color. This option is only displayed when the border width is greater than zero. - **Radius** - Add rounded corners to the element border and control the degree of curve. -#### Data links +#### Data links and actions -Canvases support [data links](ref:data-links) for all elements except drone and button elements. You can add a data link by following these steps: +Canvases support [data links](ref:data-links) and actions for all elements. -1. Enable inline editing. -1. Click the element you to which you want to add the data link. -1. In either the inline editor or panel editor, expand the **Selected element** editor. -1. Scroll down to the **Data links** section and expand it. -1. Click **Add link**. -1. In the dialog box that opens, enter a **Title**. This is a human-readable label for the link, which will be displayed in the UI. -1. Enter the **URL** or variable to which you want to link. +If you add multiple data links or actions to an element, you can control the order in which they appear in the element tooltip. +To do this, click and drag the link or action to the desired position. - To add a data link variable, click in the **URL** field and enter `$` or press Ctrl+Space or Cmd+Space to see a list of available variables. +The following tasks describe how to configure data links and actions. -1. If you want the link to open in a new tab, toggle the **Open in a new tab** switch. -1. Click **Save** to save changes and close the dialog box. -1. Disable inline editing. - -If you add multiple data links, you can control the order in which they appear in the visualization. To do this, click and drag the data link to the desired position. - -##### One-click data link - -You can configure a canvas data link to open with a single click on the element. To enable this feature, follow these steps: +{{< tabs >}} +{{< tab-content name="Add data links" >}} +To add a data link, follow these steps: 1. Enable inline editing. 1. Click the element to which you want to add the data link. 1. In either the inline editor or panel editor, expand the **Selected element** editor. -1. Scroll down to the **Data links** section and expand it. -1. In the **One-click** section, choose **Link**. +1. Scroll down to the **Data links and actions** section and expand it. +1. Click **+ Add link**. +1. In the dialog box that opens, enter a **Title**. + + This is a human-readable label for the link displayed in the UI. This is a required field. + +1. Enter the **URL** or variable to which you want to link. + + To add a data link variable, click in the **URL** field and enter `$` or press Ctrl+Space or Cmd+Space to see a list of available variables. This is a required field. + +1. If you want the link to open in a new tab, toggle the **Open in a new tab** switch. +1. If you want the data link to open with a single click on the element, toggle the **One click** switch. + + Only one data link or action can have **One click** enabled at a time. + +1. Click **Save** to save changes and close the dialog box. 1. Disable inline editing. + {{< /tab-content >}} + {{< tab-content name="Add actions" >}} + To add an action, by follow these steps: -The first data link in the list will be configured as your one-click data link. If you want to change the one-click data link, simply drag the desired data link to the top of the list. +1. Enable inline editing. +1. Click the element to which you want to add the data link. +1. In either the inline editor or panel editor, expand the **Selected element** editor. +1. Scroll down to the **Data links and actions** section and expand it. +1. Click **+ Add action**. +1. In the dialog box that opens, set the action options: -{{< video-embed src="/media/docs/grafana/panels-visualizations/canvas-one-click-datalink-.mp4" >}} + | Option | Description | + | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Title | A human-readable label for the action that's displayed in the UI. | + | Confirmation message | A descriptive prompt to confirm or cancel the action. | + | One click | If you want the action to be triggered by a single click on the element, toggle the switch.

Only one data link or action can have **One click** enabled at a time. | + | Method | Select from **POST**, **PUT**, or **GET**. | + | URL | The request URL or variable to which you want to link.

To add a variable, click in the **URL** field and enter `$` or press Ctrl+Space or Cmd+Space to see a list of available variables. | + | Query parameters | **Key** and **Value** pairs. Click the **+** icon to add as many key/value pairs as you need. | + | Headers | Comprised of **Key** and **Value** pairs and a **Content-Type**.

Click the **+** icon to add as many key/value pairs as you need. | + | Content-Type | Select from the following: **application/json**, **text/plain**, **application/XML**, and **application/x-www-form-urlencoded**. | + | Body | The body of the request. | + +1. Click **Save** to save changes and close the dialog box. +1. Disable inline editing. + {{< /tab-content >}} + {{< /tabs >}} ### Selected connection options diff --git a/docs/sources/panels-visualizations/visualizations/gauge/index.md b/docs/sources/panels-visualizations/visualizations/gauge/index.md index bfcd1c89549..fb6b224a12e 100644 --- a/docs/sources/panels-visualizations/visualizations/gauge/index.md +++ b/docs/sources/panels-visualizations/visualizations/gauge/index.md @@ -173,7 +173,7 @@ Adjust the sizes of the gauge text. ### Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-1.md" source="grafana" version="" >}} ### Value mappings diff --git a/docs/sources/panels-visualizations/visualizations/pie-chart/index.md b/docs/sources/panels-visualizations/visualizations/pie-chart/index.md index bb1d2cd02aa..83efab500f9 100644 --- a/docs/sources/panels-visualizations/visualizations/pie-chart/index.md +++ b/docs/sources/panels-visualizations/visualizations/pie-chart/index.md @@ -176,7 +176,7 @@ Use these settings to define how the legend appears in your visualization. For m ### Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-1.md" source="grafana" version="" >}} ### Value mappings diff --git a/docs/sources/panels-visualizations/visualizations/stat/index.md b/docs/sources/panels-visualizations/visualizations/stat/index.md index eb989ae6d4e..8eca8f63b29 100644 --- a/docs/sources/panels-visualizations/visualizations/stat/index.md +++ b/docs/sources/panels-visualizations/visualizations/stat/index.md @@ -174,7 +174,7 @@ Adjust the sizes of the gauge text. ### Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-1.md" source="grafana" version="" >}} ### Value mappings diff --git a/docs/sources/panels-visualizations/visualizations/state-timeline/index.md b/docs/sources/panels-visualizations/visualizations/state-timeline/index.md index a6916eb1f83..cc861506203 100644 --- a/docs/sources/panels-visualizations/visualizations/state-timeline/index.md +++ b/docs/sources/panels-visualizations/visualizations/state-timeline/index.md @@ -159,7 +159,7 @@ The **Page size** option lets you paginate the state timeline visualization to l ### Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-2.md" source="grafana" version="" >}} ### Value mappings diff --git a/docs/sources/panels-visualizations/visualizations/status-history/index.md b/docs/sources/panels-visualizations/visualizations/status-history/index.md index 357461f3382..998ff72e251 100644 --- a/docs/sources/panels-visualizations/visualizations/status-history/index.md +++ b/docs/sources/panels-visualizations/visualizations/status-history/index.md @@ -145,7 +145,7 @@ Controls the opacity of state regions. ## Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-2.md" source="grafana" version="" >}} ## Value mappings diff --git a/docs/sources/panels-visualizations/visualizations/table/index.md b/docs/sources/panels-visualizations/visualizations/table/index.md index 42635aa733f..c53a0f7adff 100644 --- a/docs/sources/panels-visualizations/visualizations/table/index.md +++ b/docs/sources/panels-visualizations/visualizations/table/index.md @@ -364,7 +364,7 @@ If you want to apply this setting to only some fields instead of all fields, you ### Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-1.md" source="grafana" version="" >}} ### Value mappings diff --git a/docs/sources/panels-visualizations/visualizations/time-series/index.md b/docs/sources/panels-visualizations/visualizations/time-series/index.md index e54509d5aeb..2031d0db76a 100644 --- a/docs/sources/panels-visualizations/visualizations/time-series/index.md +++ b/docs/sources/panels-visualizations/visualizations/time-series/index.md @@ -364,7 +364,7 @@ Set the position of the bar relative to a data point. In the examples below, **S ### Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-2.md" source="grafana" version="" >}} ### Value mappings diff --git a/docs/sources/panels-visualizations/visualizations/trend/index.md b/docs/sources/panels-visualizations/visualizations/trend/index.md index 7c8900b0f4f..49032640568 100644 --- a/docs/sources/panels-visualizations/visualizations/trend/index.md +++ b/docs/sources/panels-visualizations/visualizations/trend/index.md @@ -62,7 +62,7 @@ For example, you could represent engine power and torque versus speed where spee ## Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-2.md" source="grafana" version="" >}} ## Thresholds diff --git a/docs/sources/panels-visualizations/visualizations/xy-chart/index.md b/docs/sources/panels-visualizations/visualizations/xy-chart/index.md index 1e3f4d341dd..8e25e2e2dfa 100644 --- a/docs/sources/panels-visualizations/visualizations/xy-chart/index.md +++ b/docs/sources/panels-visualizations/visualizations/xy-chart/index.md @@ -310,7 +310,7 @@ To learn more, refer to [Configure standard options](ref:configure-standard-opti ## Data links -{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/datalink-options-2.md" source="grafana" version="" >}} ## Field overrides diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 4805b42a1a7..e0b265a30d1 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -1821,7 +1821,7 @@ The timeout string is a possibly signed sequence of decimal numbers, followed by #### `max_attempts` -Sets a maximum number of times Grafana attempts to evaluate an alert rule before giving up on that evaluation. The default value is `1`. +Sets a maximum number of times Grafana attempts to evaluate an alert rule before giving up on that evaluation. The default value is `3`. #### `min_interval` diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md index 1be5e2d993e..0a192c80baf 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md @@ -113,8 +113,26 @@ Sign in to Grafana and navigate to **Administration > Authentication > Configure 1. If you wish to [map user information from SAML assertions]({{< relref "../saml#assertion-mapping" >}}), complete the **Assertion attributes mappings** section. - You also need to configure the **Groups attribute** field if you want to use group synchronization. Group sync allows you to automatically map users to Grafana teams or role-based access control roles based on their SAML group membership. - To learn more about how to configure group synchronization, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation. +If Azure is the Identity Provider over SAML there are caveats for the assertion attribute mappings. Due to how Azure interprets these attributes the full URL will need to be entered in the corresponding fields within the UI, which should match the URLs from the metadata XML. There are differences depending on whether it's a Role or Group claim vs other assertions which Microsoft has [documented](https://learn.microsoft.com/en-us/entra/identity-platform/reference-claims-customization#table-2-saml-restricted-claim-set). + +Group and Role: + +``` +http://schemas.microsoft.com/ws/2008/06/identity/claims/role +http://schemas.microsoft.com/ws/2008/06/identity/claims/groups +http://schemas.microsoft.com/identity/claims/displayname +``` + +Other Assertions: + +``` +http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress +``` + +![image](https://github.com/user-attachments/assets/23910ab8-20ec-4dfd-8ef6-7dbaec51ac90) + +You also need to configure the **Groups attribute** field if you want to use group synchronization. Group sync allows you to automatically map users to Grafana teams or role-based access control roles based on their SAML group membership. +To learn more about how to configure group synchronization, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation. 1. If you want to automatically assign users' roles based on their SAML roles, complete the **Role mapping** section. diff --git a/docs/sources/setup-grafana/configure-security/configure-team-sync.md b/docs/sources/setup-grafana/configure-security/configure-team-sync.md index f5d50cff311..89665525574 100644 --- a/docs/sources/setup-grafana/configure-security/configure-team-sync.md +++ b/docs/sources/setup-grafana/configure-security/configure-team-sync.md @@ -27,16 +27,15 @@ This mechanism allows Grafana to remove an existing synchronized user from a tea ## Supported providers -- [Auth Proxy]({{< relref "./configure-authentication/auth-proxy#team-sync-enterprise-only" >}}) -- [Azure AD](https://grafana.com/docs/grafana//configure-authentication/azuread#group-sync-enterprise-only) -- [Azure AD](https://grafana.com/docs/grafana//configure-security/configure-authentication/azuread#group-sync-enterprise-only) -- [Generic OAuth integration](https://grafana.com/docs/grafana//configure-security/configure-authentication/generic-oauth#configure-group-synchronization) -- [GitHub OAuth](https://grafana.com/docs/grafana//configure-security/configure-authentication/github#configure-group-synchronization) -- [GitLab OAuth](https://grafana.com/docs/grafana//configure-security/configure-authentication/gitlab#configure-group-synchronization) -- [Google OAuth](https://grafana.com/docs/grafana//configure-security/configure-authentication/google#configure-group-synchronization) -- [LDAP](https://grafana.com/docs/grafana//configure-security/configure-authentication/enhanced-ldap#ldap-group-synchronization) -- [Okta](https://grafana.com/docs/grafana//configure-security/configure-authentication/okta#configure-group-synchronization-enterprise-only) -- [SAML](https://grafana.com/docs/grafana//configure-security/configure-authentication/saml#configure-group-synchronization) +- [Auth Proxy](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/auth-proxy/#team-sync-enterprise-only) +- [Azure AD](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/azuread#group-sync-enterprise-only) +- [Generic OAuth integration](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/generic-oauth#configure-group-synchronization) +- [GitHub OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/github#configure-group-synchronization) +- [GitLab OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/gitlab#configure-group-synchronization) +- [Google OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/google#configure-group-synchronization) +- [LDAP](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/enhanced-ldap#ldap-group-synchronization) +- [Okta](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/okta#configure-group-synchronization-enterprise-only) +- [SAML](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/saml#configure-group-synchronization) ## Synchronize a Grafana team with an external group diff --git a/docs/sources/shared/visualizations/datalink-options-1.md b/docs/sources/shared/visualizations/datalink-options-1.md new file mode 100644 index 00000000000..debfdcccbf9 --- /dev/null +++ b/docs/sources/shared/visualizations/datalink-options-1.md @@ -0,0 +1,17 @@ +--- +title: Data link options +comments: | + This file is used in the following visualizations: bar gauge, gauge, pie chart, stat, table +--- + +Data links allow you to link to other panels, dashboards, and external resources while maintaining the context of the source panel. +You can create links that include the series name or even the value under the cursor. +To learn more, refer to [Configure data links](../../configure-data-links/). + +For each data link, set the following options: + +- **Title** +- **URL** +- **Open in new tab** + +Data links for this visualization don't include the **One click** switch, however, if there's only one data link configured, that data link has single-click functionality. If multiple data links are configured, then clicking the visualization opens a menu that displays all the data links. diff --git a/docs/sources/shared/visualizations/datalink-options-2.md b/docs/sources/shared/visualizations/datalink-options-2.md new file mode 100644 index 00000000000..ec8502a1ae5 --- /dev/null +++ b/docs/sources/shared/visualizations/datalink-options-2.md @@ -0,0 +1,16 @@ +--- +title: Data link options +comments: | + This file is used in the following visualizations: bar chart, candlestick, state timeline, status history, time series, trend, xy chart +--- + +Data links allow you to link to other panels, dashboards, and external resources while maintaining the context of the source panel. You can create links that include the series name or even the value under the cursor. + +For each data link, set the following options: + +- **Title** +- **URL** +- **Open in new tab** +- **One click** - Opens the data link with a single click. Only one data link can have **One click** enabled at a time. + +To learn more, refer to [Configure data links](../../configure-data-links/). diff --git a/docs/sources/shared/visualizations/datalink-options.md b/docs/sources/shared/visualizations/datalink-options.md index e3d77551cf5..6e567cfd464 100644 --- a/docs/sources/shared/visualizations/datalink-options.md +++ b/docs/sources/shared/visualizations/datalink-options.md @@ -1,7 +1,7 @@ --- title: Data link options comments: | - This file is used in the following visualizations: bar chart, bar gauge, candlestick, gauge, geomap, heatmap, histogram, stat, state timeline, status history, table, time series, trend, xy chart + This file is used in the following visualizations: geomap, heatmap, histogram --- Data links allow you to link to other panels, dashboards, and external resources while maintaining the context of the source panel. You can create links that include the series name or even the value under the cursor. diff --git a/docs/sources/upgrade-guide/when-to-upgrade/index.md b/docs/sources/upgrade-guide/when-to-upgrade/index.md index 052107861c4..31d6385ef9f 100644 --- a/docs/sources/upgrade-guide/when-to-upgrade/index.md +++ b/docs/sources/upgrade-guide/when-to-upgrade/index.md @@ -99,6 +99,7 @@ Here is an overview of projected version support through 2024: | 11.2 | August 2024 | May 2025 | | 11.3 | October 2024 | July 2025 | | 11.4 | December 2024 | September 2025 | +| 11.5 | January 2025 | October 2025 | {{< admonition type="note" >}} Grafana 9.5.x was the last supported minor for the 9.0 major release and is no longer supported as of July 2024. diff --git a/e2e/old-arch/various-suite/solo-route.spec.ts b/e2e/old-arch/various-suite/solo-route.spec.ts index 9717baf41bd..415257ead7c 100644 --- a/e2e/old-arch/various-suite/solo-route.spec.ts +++ b/e2e/old-arch/various-suite/solo-route.spec.ts @@ -25,7 +25,7 @@ describe('Solo Route', () => { it('Can view solo repeated panel in scenes', () => { // open Panel Tests - Graph NG e2e.pages.SoloPanel.visit( - 'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-16-clone-0/grid-item-2/panel-2-clone-0&__feature.dashboardSceneSolo=true' + 'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-0&__feature.dashboardSceneSolo=true' ); e2e.components.Panels.Panel.title('server=A').should('exist'); @@ -38,7 +38,7 @@ describe('Solo Route', () => { 'Repeating-rows-uid/repeating-rows?orgId=1&var-server=A&var-server=B&var-server=D&var-pod=1&var-pod=2&var-pod=3&panelId=panel-16-clone-1/grid-item-2/panel-2-clone-1&__feature.dashboardSceneSolo=true' ); - e2e.components.Panels.Panel.title('server = A, pod = Rob').should('exist'); + e2e.components.Panels.Panel.title('server = B, pod = Rob').should('exist'); cy.contains('uplot-main-div').should('not.exist'); }); }); diff --git a/e2e/various-suite/solo-route.spec.ts b/e2e/various-suite/solo-route.spec.ts index 9717baf41bd..c7f90f8cd36 100644 --- a/e2e/various-suite/solo-route.spec.ts +++ b/e2e/various-suite/solo-route.spec.ts @@ -22,15 +22,15 @@ describe('Solo Route', () => { cy.contains('uplot-main-div').should('not.exist'); }); - it('Can view solo repeated panel in scenes', () => { + /*it('Can view solo repeated panel in scenes', () => { // open Panel Tests - Graph NG e2e.pages.SoloPanel.visit( - 'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-16-clone-0/grid-item-2/panel-2-clone-0&__feature.dashboardSceneSolo=true' + 'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-0&__feature.dashboardSceneSolo=true' ); e2e.components.Panels.Panel.title('server=A').should('exist'); cy.contains('uplot-main-div').should('not.exist'); - }); + });*/ it('Can view solo in repeated row and panel in scenes', () => { // open Panel Tests - Graph NG @@ -38,7 +38,7 @@ describe('Solo Route', () => { 'Repeating-rows-uid/repeating-rows?orgId=1&var-server=A&var-server=B&var-server=D&var-pod=1&var-pod=2&var-pod=3&panelId=panel-16-clone-1/grid-item-2/panel-2-clone-1&__feature.dashboardSceneSolo=true' ); - e2e.components.Panels.Panel.title('server = A, pod = Rob').should('exist'); + e2e.components.Panels.Panel.title('server = B, pod = Rob').should('exist'); cy.contains('uplot-main-div').should('not.exist'); }); }); diff --git a/go.mod b/go.mod index d758105feae..e2845126aa1 100644 --- a/go.mod +++ b/go.mod @@ -111,7 +111,7 @@ require ( github.com/influxdata/influxql v1.4.0 // @grafana/partner-datasources github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // @grafana/grafana-app-platform-squad github.com/jmespath-community/go-jmespath v1.1.1 // @grafana/identity-access-team - github.com/jmespath/go-jmespath v0.4.0 // indirect; // @grafana/grafana-backend-group + github.com/jmespath/go-jmespath v0.4.0 // // @grafana/grafana-backend-group github.com/jmoiron/sqlx v1.3.5 // @grafana/grafana-backend-group github.com/json-iterator/go v1.1.12 // @grafana/grafana-backend-group github.com/lib/pq v1.10.9 // @grafana/grafana-backend-group @@ -221,6 +221,8 @@ require ( github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20250121113133-e747350fee2d // @grafana/grafana-search-and-storage ) +require github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend + require ( cel.dev/expr v0.19.0 // indirect cloud.google.com/go v0.116.0 // indirect diff --git a/go.sum b/go.sum index f00f8876d26..dea3dbcf891 100644 --- a/go.sum +++ b/go.sum @@ -1310,6 +1310,8 @@ github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= github.com/go-zookeeper/zk v1.0.4 h1:DPzxraQx7OrPyXq2phlGlNSIyWEsAox0RJmjTseMV6I= github.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= +github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= +github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -1544,6 +1546,8 @@ github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 h1:jxJJ5z0GxqhWFbQU github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447/go.mod h1:IxsY6mns6Q5sAnWcrptrgUrSglTZJXH/kXr9nbpb/9I= github.com/grafana/gomemcache v0.0.0-20240805133030-fdaf6a95408e h1:UlEET0InuoFautfaFp8lDrNF7rPHYXuBMrzwWx9XqFY= github.com/grafana/gomemcache v0.0.0-20240805133030-fdaf6a95408e/go.mod h1:IGRj8oOoxwJbHBYl1+OhS9UjQR0dv6SQOep7HqmtyFU= +github.com/grafana/grafana-api-golang-client v0.27.0 h1:zIwMXcbCB4n588i3O2N6HfNcQogCNTd/vPkEXTr7zX8= +github.com/grafana/grafana-api-golang-client v0.27.0/go.mod h1:uNLZEmgKtTjHBtCQMwNn3qsx2mpMb8zU+7T4Xv3NR9Y= github.com/grafana/grafana-app-sdk v0.31.0 h1:/mFCcx+YqG8cWAi9hePDJQxIdtXDClDIDRgZwHkksFk= github.com/grafana/grafana-app-sdk v0.31.0/go.mod h1:Xw00NL7qpRLo5r3Gn48Bl1Xn2n4eUDI5pYf/wMufKWs= github.com/grafana/grafana-app-sdk/logging v0.30.0 h1:K/P/bm7Cp7Di4tqIJ3EQz2+842JozQGRaz62r95ApME= diff --git a/go.work.sum b/go.work.sum index 070810818ab..89477e05e8a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1456,6 +1456,7 @@ github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDs github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= +github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= diff --git a/kinds/dashboard/dashboard_kind.cue b/kinds/dashboard/dashboard_kind.cue index da8be7ef3d5..bbf3cb24321 100644 --- a/kinds/dashboard/dashboard_kind.cue +++ b/kinds/dashboard/dashboard_kind.cue @@ -74,7 +74,7 @@ lineage: schemas: [{ // Version of the JSON schema, incremented each time a Grafana update brings // changes to said schema. - schemaVersion: uint16 | *39 + schemaVersion: uint16 | *41 // Version of the dashboard, incremented each time the dashboard is updated. version?: uint32 @@ -473,8 +473,6 @@ lineage: schemas: [{ hidden?: bool | *false // Interval options available in the refresh picker dropdown. refresh_intervals?: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] - // Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard. - time_options?: [...string] | *["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] // Quick ranges for time picker. quick_ranges?: [...#TimeOption] // Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values. diff --git a/package.json b/package.json index 189fc00835e..7e889ddedde 100644 --- a/package.json +++ b/package.json @@ -56,8 +56,7 @@ "ci:test-frontend": "yarn run test:ci", "i18n:stats": "node ./scripts/cli/reportI18nStats.mjs", "betterer": "betterer --tsconfig ./scripts/cli/tsconfig.json", - "betterer:merge": "betterer merge --tsconfig ./scripts/cli/tsconfig.json", - "betterer:stats": "node ./scripts/cli/reportBettererStats.mjs", + "betterer:stats": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/reportBettererStats.ts", "betterer:issues": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/generateBettererIssues.ts", "plugin:build": "nx run-many -t build --projects='tag:scope:plugin'", "plugin:build:commit": "nx run-many -t build:commit --projects='tag:scope:plugin'", @@ -276,8 +275,8 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "5.42.0", - "@grafana/scenes-react": "5.42.0", + "@grafana/scenes": "6.0.2", + "@grafana/scenes-react": "6.0.2", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", @@ -381,8 +380,8 @@ "react-redux": "9.2.0", "react-resizable": "3.0.5", "react-responsive-carousel": "^3.2.23", - "react-router": "5.3.3", - "react-router-dom": "5.3.3", + "react-router": "5.3.4", + "react-router-dom": "5.3.4", "react-router-dom-v5-compat": "^6.26.1", "react-select": "5.10.0", "react-split-pane": "0.1.92", diff --git a/packages/grafana-data/src/field/overrides/processors.ts b/packages/grafana-data/src/field/overrides/processors.ts index 8c8542fd6ff..e80a73de866 100644 --- a/packages/grafana-data/src/field/overrides/processors.ts +++ b/packages/grafana-data/src/field/overrides/processors.ts @@ -50,7 +50,9 @@ export interface SliderFieldConfigSettings { ariaLabelForHandle?: string; } -export interface DataLinksFieldConfigSettings {} +export interface DataLinksFieldConfigSettings { + showOneClick?: boolean; +} export const dataLinksOverrideProcessor = ( value: any, diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index 76195d7d8c0..6aa49febd08 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -549,6 +549,7 @@ export { type PluginExtensionLink, type PluginExtensionComponent, type PluginExtensionConfig, + type PluginExtensionFunction, type PluginExtensionLinkConfig, type PluginExtensionComponentConfig, type PluginExtensionEventHelpers, @@ -559,6 +560,7 @@ export { type PluginExtensionExposedComponentConfig, type PluginExtensionAddedComponentConfig, type PluginExtensionAddedLinkConfig, + type PluginExtensionAddedFunctionConfig, } from './types/pluginExtensions'; export { type ScopeDashboardBindingSpec, diff --git a/packages/grafana-data/src/themes/createComponents.ts b/packages/grafana-data/src/themes/createComponents.ts index 70dbcd67cf5..e810a94b86e 100644 --- a/packages/grafana-data/src/themes/createComponents.ts +++ b/packages/grafana-data/src/themes/createComponents.ts @@ -80,7 +80,7 @@ export function createComponents(colors: ThemeColors, shadows: ThemeShadows): Th input, panel, dropdown: { - background: input.background, + background: colors.background.elevated, }, tooltip: { background: colors.background.elevated, diff --git a/packages/grafana-data/src/types/app.ts b/packages/grafana-data/src/types/app.ts index 3b33452bd03..23e52f913b8 100644 --- a/packages/grafana-data/src/types/app.ts +++ b/packages/grafana-data/src/types/app.ts @@ -9,6 +9,7 @@ import { PluginExtensionExposedComponentConfig, PluginExtensionAddedComponentConfig, PluginExtensionAddedLinkConfig, + PluginExtensionAddedFunctionConfig, } from './pluginExtensions'; /** @@ -60,6 +61,7 @@ export class AppPlugin extends GrafanaPlugin>; @@ -113,6 +115,10 @@ export class AppPlugin extends GrafanaPlugin(linkConfig: PluginExtensionAddedLinkConfig) { this._addedLinkConfigs.push(linkConfig as PluginExtensionAddedLinkConfig); @@ -125,6 +131,12 @@ export class AppPlugin extends GrafanaPlugin(addedFunctionConfig: PluginExtensionAddedFunctionConfig) { + this._addedFunctionConfigs.push(addedFunctionConfig); + + return this; + } + exposeComponent(componentConfig: PluginExtensionExposedComponentConfig) { this._exposedComponentConfigs.push(componentConfig as PluginExtensionExposedComponentConfig); diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index d96f22190bd..f2283c5956e 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -144,6 +144,7 @@ export enum FieldConfigProperty { Thresholds = 'thresholds', Mappings = 'mappings', Links = 'links', + Actions = 'actions', Color = 'color', Filterable = 'filterable', } diff --git a/packages/grafana-data/src/types/plugin.ts b/packages/grafana-data/src/types/plugin.ts index ebb3da684f6..8e1be814c96 100644 --- a/packages/grafana-data/src/types/plugin.ts +++ b/packages/grafana-data/src/types/plugin.ts @@ -130,6 +130,8 @@ export interface PluginExtensions { // The component extensions that the plugin registers addedComponents: ExtensionInfo[]; + addedFunctions: ExtensionInfo[]; + // The link extensions that the plugin registers addedLinks: ExtensionInfo[]; diff --git a/packages/grafana-data/src/types/pluginExtensions.ts b/packages/grafana-data/src/types/pluginExtensions.ts index fcbbecf5f41..5b10f80f143 100644 --- a/packages/grafana-data/src/types/pluginExtensions.ts +++ b/packages/grafana-data/src/types/pluginExtensions.ts @@ -14,6 +14,7 @@ import { RawTimeRange, TimeZone } from './time'; export enum PluginExtensionTypes { link = 'link', component = 'component', + function = 'function', } type PluginExtensionBase = { @@ -36,7 +37,12 @@ export type PluginExtensionComponent = PluginExtensionBase & { component: React.ComponentType; }; -export type PluginExtension = PluginExtensionLink | PluginExtensionComponent; +export type PluginExtensionFunction void> = PluginExtensionBase & { + type: PluginExtensionTypes.function; + fn: Signature; +}; + +export type PluginExtension = PluginExtensionLink | PluginExtensionComponent | PluginExtensionFunction; // Objects used for registering extensions (in app plugins) // -------------------------------------------------------- @@ -74,6 +80,17 @@ export type PluginExtensionAddedComponentConfig = PluginExtensionCon */ component: React.ComponentType; }; +export type PluginExtensionAddedFunctionConfig = PluginExtensionConfigBase & { + /** + * The target extension points where the component will be added + */ + targets: string | string[]; + + /** + * The function to be executed + */ + fn: Signature; +}; export type PluginAddedLinksConfigureFunc = (context: Readonly | undefined) => | Partial<{ diff --git a/packages/grafana-prometheus/src/dashboards/grafana_stats.json b/packages/grafana-prometheus/src/dashboards/grafana_stats.json index 0131aa9bc40..292f93394f3 100644 --- a/packages/grafana-prometheus/src/dashboards/grafana_stats.json +++ b/packages/grafana-prometheus/src/dashboards/grafana_stats.json @@ -1178,8 +1178,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Grafana metrics", diff --git a/packages/grafana-prometheus/src/dashboards/prometheus_2_stats.json b/packages/grafana-prometheus/src/dashboards/prometheus_2_stats.json index 034663fe6f4..5a6fbdf8518 100644 --- a/packages/grafana-prometheus/src/dashboards/prometheus_2_stats.json +++ b/packages/grafana-prometheus/src/dashboards/prometheus_2_stats.json @@ -1394,8 +1394,7 @@ }, "timepicker": { "now": true, - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "browser", "title": "Prometheus 2.0 Stats", diff --git a/packages/grafana-prometheus/src/dashboards/prometheus_stats.json b/packages/grafana-prometheus/src/dashboards/prometheus_stats.json index 8a2764c5cb7..42ea6e7a4d5 100644 --- a/packages/grafana-prometheus/src/dashboards/prometheus_stats.json +++ b/packages/grafana-prometheus/src/dashboards/prometheus_stats.json @@ -825,8 +825,7 @@ }, "timepicker": { "now": true, - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "browser", "title": "Prometheus Stats", diff --git a/packages/grafana-prometheus/src/querybuilder/shared/OperationInfoButton.tsx b/packages/grafana-prometheus/src/querybuilder/shared/OperationInfoButton.tsx index 69f26d84865..b576188a581 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/OperationInfoButton.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/OperationInfoButton.tsx @@ -94,8 +94,8 @@ const getStyles = (theme: GrafanaTheme2) => { return { docBox: css({ overflow: 'hidden', - background: theme.colors.background.primary, - border: `1px solid ${theme.colors.border.strong}`, + background: theme.colors.background.elevated, + border: `1px solid ${theme.colors.border.weak}`, boxShadow: theme.shadows.z3, maxWidth: '600px', padding: theme.spacing(1), diff --git a/packages/grafana-runtime/src/services/index.ts b/packages/grafana-runtime/src/services/index.ts index 3e02e0d2863..5e8892c5cc7 100644 --- a/packages/grafana-runtime/src/services/index.ts +++ b/packages/grafana-runtime/src/services/index.ts @@ -22,6 +22,8 @@ export { type UsePluginExtensions, type UsePluginExtensionsResult, type UsePluginComponentResult, + type UsePluginFunctionsOptions, + type UsePluginFunctionsResult, } from './pluginExtensions/getPluginExtensions'; export { setPluginExtensionsHook, @@ -33,6 +35,7 @@ export { export { setPluginComponentHook, usePluginComponent } from './pluginExtensions/usePluginComponent'; export { setPluginComponentsHook, usePluginComponents } from './pluginExtensions/usePluginComponents'; export { setPluginLinksHook, usePluginLinks } from './pluginExtensions/usePluginLinks'; +export { setPluginFunctionsHook, usePluginFunctions } from './pluginExtensions/usePluginFunctions'; export { isPluginExtensionLink, isPluginExtensionComponent } from './pluginExtensions/utils'; export { setCurrentUser } from './user'; diff --git a/packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts b/packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts index be151b05858..2f70132d5fe 100644 --- a/packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts +++ b/packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts @@ -1,4 +1,9 @@ -import type { PluginExtension, PluginExtensionLink, PluginExtensionComponent } from '@grafana/data'; +import type { + PluginExtension, + PluginExtensionLink, + PluginExtensionComponent, + PluginExtensionFunction, +} from '@grafana/data'; import { isPluginExtensionComponent, isPluginExtensionLink } from './utils'; @@ -52,6 +57,16 @@ export type UsePluginLinksResult = { links: PluginExtensionLink[]; }; +export type UsePluginFunctionsOptions = { + extensionPointId: string; + limitPerPlugin?: number; +}; + +export type UsePluginFunctionsResult = { + isLoading: boolean; + functions: Array>; +}; + let singleton: GetPluginExtensions | undefined; export function setPluginExtensionGetter(instance: GetPluginExtensions): void { diff --git a/packages/grafana-runtime/src/services/pluginExtensions/usePluginFunctions.ts b/packages/grafana-runtime/src/services/pluginExtensions/usePluginFunctions.ts new file mode 100644 index 00000000000..1eb86b70e14 --- /dev/null +++ b/packages/grafana-runtime/src/services/pluginExtensions/usePluginFunctions.ts @@ -0,0 +1,20 @@ +import { UsePluginFunctionsOptions, UsePluginFunctionsResult } from './getPluginExtensions'; + +export type UsePluginFunctions = (options: UsePluginFunctionsOptions) => UsePluginFunctionsResult; + +let singleton: UsePluginFunctions | undefined; + +export function setPluginFunctionsHook(hook: UsePluginFunctions): void { + // We allow overriding the registry in tests + if (singleton && process.env.NODE_ENV !== 'test') { + throw new Error('setUsePluginFunctionsHook() function should only be called once, when Grafana is starting.'); + } + singleton = hook; +} + +export function usePluginFunctions(options: UsePluginFunctionsOptions): UsePluginFunctionsResult { + if (!singleton) { + throw new Error('usePluginFunctions(options) can only be used after the Grafana instance has started.'); + } + return singleton(options) as UsePluginFunctionsResult; +} diff --git a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts index e200106086a..fbaa9c8f09b 100644 --- a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts +++ b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts @@ -681,17 +681,12 @@ export interface TimePickerConfig { * Interval options available in the refresh picker dropdown. */ refresh_intervals?: Array; - /** - * Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard. - */ - time_options?: Array; } export const defaultTimePickerConfig: Partial = { hidden: false, quick_ranges: [], refresh_intervals: ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'], - time_options: ['5m', '15m', '1h', '6h', '12h', '24h', '2d', '7d', '30d'], }; /** @@ -1205,7 +1200,7 @@ export const defaultDashboard: Partial = { graphTooltip: DashboardCursorSync.Off, links: [], panels: [], - schemaVersion: 39, + schemaVersion: 41, tags: [], timezone: 'browser', }; diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index 4b983ddf7ae..f9763bb219b 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -6,10 +6,7 @@ import ( DashboardV2Spec: { // Title of dashboard. - title: string - - // Description of dashboard. - description?: string + annotations: [...AnnotationQueryKind] // Configuration of dashboard cursor sync behavior. // "Off" for no shared crosshair or tooltip (default). @@ -17,6 +14,19 @@ DashboardV2Spec: { // "Tooltip" for shared crosshair AND shared tooltip. cursorSync: DashboardCursorSync + // Description of dashboard. + description?: string + + // Whether a dashboard is editable or not. + editable?: bool | *true + + elements: [ElementReference.name]: Element + + layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind + + // Links with references to other dashboards or external websites. + links: [...DashboardLink] + // When set to true, the dashboard will redraw panels at an interval matching the pixel width. // This will keep data "moving left" regardless of the query refresh rate. This setting helps // avoid dashboards presenting stale live data. @@ -25,30 +35,20 @@ DashboardV2Spec: { // When set to true, the dashboard will load all panels in the dashboard when it's loaded. preload: bool - // Whether a dashboard is editable or not. - editable?: bool | *true - - // Links with references to other dashboards or external websites. - links: [...DashboardLink] + // Plugins only. The version of the dashboard installed together with the plugin. + // This is used to determine if the dashboard should be updated when the plugin is updated. + revision?: uint16 // Tags associated with dashboard. tags: [...string] timeSettings: TimeSettingsSpec + // Title of dashboard. + title: string + // Configured template variables. variables: [...VariableKind] - - elements: [ElementReference.name]: Element - - annotations: [...AnnotationQueryKind] - - layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind - - - // Plugins only. The version of the dashboard installed together with the plugin. - // This is used to determine if the dashboard should be updated when the plugin is updated. - revision?: uint16 } // Supported dashboard elements @@ -447,6 +447,12 @@ QueryGroupKind: { spec: QueryGroupSpec } +TimeRangeOption: { + display: string | *"Last 6 hours" + from: string | *"now-6h" + to: string | *"now" +} + // Time configuration // It defines the default time config for the time picker, the refresh picker for the specific dashboard. TimeSettingsSpec: { @@ -463,7 +469,7 @@ TimeSettingsSpec: { // Interval options available in the refresh picker dropdown. autoRefreshIntervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] // v1: timepicker.refresh_intervals // Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard. - quickRanges: [...string] | *["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] // v1: timepicker.time_options , not exposed in the UI + quickRanges?: [...TimeRangeOption] // v1: timepicker.quick_ranges , not exposed in the UI // Whether timepicker is visible or not. hideTimepicker: bool // v1: timepicker.hidden // Day when the week starts. Expressed by the name of the day in lowercase, e.g. "monday". diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts index 40b091f29dd..c12f1cc73f7 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts @@ -15,7 +15,6 @@ export const handyTestingSchema: DashboardV2Spec = { from: 'now-1h', hideTimepicker: false, nowDelay: '1m', - quickRanges: [], timezone: 'UTC', to: 'now', weekStart: 'monday', diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts index 83c733c6f60..1ad02256fe8 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts @@ -5,49 +5,50 @@ import * as common from '@grafana/schema'; export interface DashboardV2Spec { // Title of dashboard. - title: string; - // Description of dashboard. - description?: string; + annotations: AnnotationQueryKind[]; // Configuration of dashboard cursor sync behavior. // "Off" for no shared crosshair or tooltip (default). // "Crosshair" for shared crosshair. // "Tooltip" for shared crosshair AND shared tooltip. cursorSync: DashboardCursorSync; + // Description of dashboard. + description?: string; + // Whether a dashboard is editable or not. + editable?: boolean; + elements: Record; + layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind; + // Links with references to other dashboards or external websites. + links: DashboardLink[]; // When set to true, the dashboard will redraw panels at an interval matching the pixel width. // This will keep data "moving left" regardless of the query refresh rate. This setting helps // avoid dashboards presenting stale live data. liveNow?: boolean; // When set to true, the dashboard will load all panels in the dashboard when it's loaded. preload: boolean; - // Whether a dashboard is editable or not. - editable?: boolean; - // Links with references to other dashboards or external websites. - links: DashboardLink[]; - // Tags associated with dashboard. - tags: string[]; - timeSettings: TimeSettingsSpec; - // Configured template variables. - variables: VariableKind[]; - elements: Record; - annotations: AnnotationQueryKind[]; - layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind; // Plugins only. The version of the dashboard installed together with the plugin. // This is used to determine if the dashboard should be updated when the plugin is updated. revision?: number; + // Tags associated with dashboard. + tags: string[]; + timeSettings: TimeSettingsSpec; + // Title of dashboard. + title: string; + // Configured template variables. + variables: VariableKind[]; } export const defaultDashboardV2Spec = (): DashboardV2Spec => ({ - title: "", + annotations: [], cursorSync: "Off", - preload: false, editable: true, + elements: {}, + layout: defaultGridLayoutKind(), links: [], + preload: false, tags: [], timeSettings: defaultTimeSettingsSpec(), + title: "", variables: [], - elements: {}, - annotations: [], - layout: defaultGridLayoutKind(), }); // Supported dashboard elements @@ -620,6 +621,18 @@ export const defaultQueryGroupKind = (): QueryGroupKind => ({ spec: defaultQueryGroupSpec(), }); +export interface TimeRangeOption { + display: string; + from: string; + to: string; +} + +export const defaultTimeRangeOption = (): TimeRangeOption => ({ + display: "Last 6 hours", + from: "now-6h", + to: "now", +}); + // Time configuration // It defines the default time config for the time picker, the refresh picker for the specific dashboard. export interface TimeSettingsSpec { @@ -638,8 +651,8 @@ export interface TimeSettingsSpec { // v1: timepicker.refresh_intervals autoRefreshIntervals: string[]; // Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard. - // v1: timepicker.time_options , not exposed in the UI - quickRanges: string[]; + // v1: timepicker.quick_ranges , not exposed in the UI + quickRanges?: TimeRangeOption[]; // Whether timepicker is visible or not. // v1: timepicker.hidden hideTimepicker: boolean; @@ -668,17 +681,6 @@ export const defaultTimeSettingsSpec = (): TimeSettingsSpec => ({ "1h", "2h", "1d", -], - quickRanges: [ -"5m", -"15m", -"1h", -"6h", -"12h", -"24h", -"2d", -"7d", -"30d", ], hideTimepicker: false, fiscalYearStartMonth: 0, diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index cee844a1b5d..eff1830e872 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -97,7 +97,7 @@ "react-i18next": "^15.0.0", "react-inlinesvg": "4.1.5", "react-loading-skeleton": "3.5.0", - "react-router-dom": "5.3.3", + "react-router-dom": "5.3.4", "react-router-dom-v5-compat": "^6.26.1", "react-select": "5.10.0", "react-table": "7.8.0", diff --git a/packages/grafana-ui/src/components/Cascader/styles.ts b/packages/grafana-ui/src/components/Cascader/styles.ts index 537bb8c4a7e..cef38b62dcc 100644 --- a/packages/grafana-ui/src/components/Cascader/styles.ts +++ b/packages/grafana-ui/src/components/Cascader/styles.ts @@ -72,8 +72,8 @@ export const getCascaderStyles = (theme: GrafanaTheme2) => ({ '.rc-cascader': { '&-menus': { overflow: 'hidden', - background: theme.colors.background.canvas, - border: `1px solid ${theme.colors.border.weak}`, + background: theme.colors.background.elevated, + border: `none`, borderRadius: theme.shape.radius.default, boxShadow: theme.shadows.z3, whiteSpace: 'nowrap', @@ -128,7 +128,7 @@ export const getCascaderStyles = (theme: GrafanaTheme2) => ({ height: '192px', listStyle: 'none', margin: 0, - padding: 0, + padding: theme.spacing(0.5), borderRight: `1px solid ${theme.colors.border.weak}`, overflow: 'auto', @@ -140,6 +140,7 @@ export const getCascaderStyles = (theme: GrafanaTheme2) => ({ height: theme.spacing(4), lineHeight: theme.spacing(4), padding: theme.spacing(0, 4, 0, 2), + borderRadius: theme.shape.radius.default, cursor: 'pointer', whiteSpace: 'nowrap', overflow: 'hidden', diff --git a/packages/grafana-ui/src/components/Combobox/Combobox.tsx b/packages/grafana-ui/src/components/Combobox/Combobox.tsx index 471bb22fe5d..62f231975e3 100644 --- a/packages/grafana-ui/src/components/Combobox/Combobox.tsx +++ b/packages/grafana-ui/src/components/Combobox/Combobox.tsx @@ -407,7 +407,7 @@ export const Combobox = (props: ComboboxProps) => })} > {isOpen && ( - + {!asyncError && (

    {rowVirtualizer.getVirtualItems().map((virtualRow) => { @@ -416,6 +416,7 @@ export const Combobox = (props: ComboboxProps) => key={`${items[virtualRow.index].value}-${virtualRow.index}`} data-index={virtualRow.index} className={cx( + styles.optionBasic, styles.option, selectedItem && items[virtualRow.index].value === selectedItem.value && styles.optionSelected, highlightedIndex === virtualRow.index && styles.optionFocused diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx index bee10de6783..5be7bc09098 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx @@ -156,8 +156,8 @@ describe('MultiCombobox', () => { await user.type(input, 'D'); await user.keyboard('{arrowdown}{enter}'); expect(onChange).toHaveBeenCalledWith([ - { value: 'a' }, - { value: 'c' }, + { label: 'A', value: 'a' }, + { label: 'C', value: 'c' }, { label: 'D', value: 'D', description: 'Use custom value' }, ]); }); @@ -235,6 +235,19 @@ describe('MultiCombobox', () => { await user.click(await screen.findByRole('option', { name: 'All' })); expect(onChange).toHaveBeenCalledWith([]); }); + + it('should keep label names on selected items when searching', async () => { + const options = [ + { label: 'A', value: 'a' }, + { label: 'B', value: 'b' }, + { label: 'C', value: 'c' }, + ]; + render(); + const input = screen.getByRole('combobox'); + await user.click(input); + await user.type(input, 'b'); + expect(screen.getByText('A')).toBeInTheDocument(); + }); }); describe('async', () => { diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx index 7b41ca2ee53..7d18074910a 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx @@ -79,8 +79,8 @@ export const MultiCombobox = (props: MultiComboboxPro return []; } - return getSelectedItemsFromValue(value, baseOptions); - }, [value, baseOptions]); + return getSelectedItemsFromValue(value, typeof props.options !== 'function' ? props.options : baseOptions); + }, [value, props.options, baseOptions]); const { measureRef, counterMeasureRef, suffixMeasureRef, shownItems } = useMeasureMulti( selectedItems, @@ -344,7 +344,7 @@ export const MultiCombobox = (props: MultiComboboxPro {...getMenuProps({ ref: floatingRef })} > {isOpen && ( - +
      {rowVirtualizer.getVirtualItems().map((virtualRow) => { const startingNewGroup = isNewGroup(options[virtualRow.index], options[virtualRow.index - 1]); diff --git a/packages/grafana-ui/src/components/Combobox/getComboboxStyles.ts b/packages/grafana-ui/src/components/Combobox/getComboboxStyles.ts index f0ea95cacb2..6c788b15cbd 100644 --- a/packages/grafana-ui/src/components/Combobox/getComboboxStyles.ts +++ b/packages/grafana-ui/src/components/Combobox/getComboboxStyles.ts @@ -46,6 +46,7 @@ export const getComboboxStyles = (theme: GrafanaTheme2) => { option: css({ padding: MENU_ITEM_PADDING, cursor: 'pointer', + borderRadius: theme.shape.radius.default, width: '100%', '&:hover': { background: theme.colors.action.hover, diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx index 99b15ea1697..2b5a6ee7fe1 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx @@ -9,7 +9,12 @@ type DataLinksInlineEditorProps = Omit, getSuggestions: () => VariableSuggestion[]; }; -export const DataLinksInlineEditor = ({ links, getSuggestions, showOneClick, ...rest }: DataLinksInlineEditorProps) => ( +export const DataLinksInlineEditor = ({ + links, + getSuggestions, + showOneClick = false, + ...rest +}: DataLinksInlineEditorProps) => ( type="link" items={links} {...rest}> {(item, index, onSave, onCancel) => ( )} diff --git a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx index 312d6e76029..8c50da47478 100644 --- a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -21,6 +21,8 @@ interface Props { onError?: (error: Error) => void; /** Callback error state is cleared due to recover props change */ onRecover?: () => void; + /** Default error logger - Faro by default */ + errorLogger?: (error: Error) => void; } interface State { @@ -35,7 +37,12 @@ export class ErrorBoundary extends PureComponent { }; componentDidCatch(error: Error, errorInfo: ErrorInfo) { - faro?.api?.pushError(error); + const logger = this.props.errorLogger ?? faro?.api?.pushError; + + if (logger) { + logger(error); + } + this.setState({ error, errorInfo }); if (this.props.onError) { @@ -89,6 +96,8 @@ export interface ErrorBoundaryAlertProps { /** Will re-render children after error if recover values changes */ dependencies?: unknown[]; + /** Default error logger - Faro by default */ + errorLogger?: (error: Error) => void; } export class ErrorBoundaryAlert extends PureComponent { @@ -98,10 +107,10 @@ export class ErrorBoundaryAlert extends PureComponent { }; render() { - const { title, children, style, dependencies } = this.props; + const { title, children, style, dependencies, errorLogger } = this.props; return ( - + {({ error, errorInfo }) => { if (!errorInfo) { return children; diff --git a/packages/grafana-ui/src/components/Select/SelectMenu.tsx b/packages/grafana-ui/src/components/Select/SelectMenu.tsx index b557b2e9a65..e19c8d87546 100644 --- a/packages/grafana-ui/src/components/Select/SelectMenu.tsx +++ b/packages/grafana-ui/src/components/Select/SelectMenu.tsx @@ -54,7 +54,7 @@ export const SelectMenu = ({ style={{ maxHeight }} aria-label="Select options menu" > - + {toggleAllOptions && ( { whiteSpace: 'nowrap', cursor: 'pointer', borderLeft: '2px solid transparent', + borderRadius: theme.shape.radius.default, '&:hover': { background: theme.colors.action.hover, diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx index c3c4a34eb64..5b79c42711f 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx @@ -1,11 +1,13 @@ import { css } from '@emotion/css'; +import { useMemo } from 'react'; -import { ActionModel, Field, GrafanaTheme2, LinkModel } from '@grafana/data'; +import { ActionModel, Field, GrafanaTheme2, LinkModel, ThemeSpacingTokens } from '@grafana/data'; import { Button, DataLinkButton, Icon, Stack } from '..'; import { useStyles2 } from '../../themes'; import { Trans } from '../../utils/i18n'; import { ActionButton } from '../Actions/ActionButton'; +import { ResponsiveProp } from '../Layout/utils/responsiveness'; interface VizTooltipFooterProps { dataLinks: Array>; @@ -15,50 +17,75 @@ interface VizTooltipFooterProps { export const ADD_ANNOTATION_ID = 'add-annotation-button'; -const renderDataLinks = (dataLinks: LinkModel[], styles: ReturnType) => { - const oneClickLink = dataLinks.find((link) => link.oneClick === true); +type RenderOneClickTrans = (title: string) => React.ReactNode; +type RenderItem = ( + item: T, + idx: number, + styles: ReturnType +) => React.ReactNode; + +function makeRenderLinksOrActions( + renderOneClickTrans: RenderOneClickTrans, + renderItem: RenderItem, + itemGap?: ResponsiveProp +) { + const renderLinksOrActions = (items: T[], styles: ReturnType) => { + if (items.length === 0) { + return; + } + + const oneClickItem = items.find((item) => item.oneClick === true); + + if (oneClickItem != null) { + return ( +
      + + + + {renderOneClickTrans(oneClickItem.title)} + + +
      + ); + } - if (oneClickLink != null) { return ( - - - - - Click to open {{ linkTitle: oneClickLink.title }} - - - +
      + + {items.map((item, i) => renderItem(item, i, styles))} + +
      ); - } + }; - return ( - - {dataLinks.map((link, i) => ( - - ))} - - ); -}; + return renderLinksOrActions; +} -const renderActions = (actions: ActionModel[]) => { - return ( - - {actions.map((action, i) => ( - - ))} - - ); -}; +const renderDataLinks = makeRenderLinksOrActions( + (title) => ( + Click to open {{ linkTitle: title }} + ), + (item, i, styles) => ( + + ), + 0.5 +); + +const renderActions = makeRenderLinksOrActions( + (title) => Click to {{ actionTitle: title }}, + (item, i, styles) => +); export const VizTooltipFooter = ({ dataLinks, actions = [], annotate }: VizTooltipFooterProps) => { const styles = useStyles2(getStyles); - const hasOneClickLink = dataLinks.some((link) => link.oneClick === true); + const hasOneClickLink = useMemo(() => dataLinks.some((link) => link.oneClick === true), [dataLinks]); + const hasOneClickAction = useMemo(() => actions.some((action) => action.oneClick === true), [actions]); return (
      - {dataLinks.length > 0 &&
      {renderDataLinks(dataLinks, styles)}
      } - {!hasOneClickLink && actions.length > 0 &&
      {renderActions(actions)}
      } - {!hasOneClickLink && annotate != null && ( + {!hasOneClickAction && renderDataLinks(dataLinks, styles)} + {!hasOneClickLink && renderActions(actions, styles)} + {!hasOneClickLink && !hasOneClickAction && annotate != null && (
      + + + + )} + + {reportAbuseModalOpen && ( + Report a plugin concern} + isOpen + onDismiss={() => setReportAbuseModalOpen(false)} + > + + + + This feature is for reporting malicious or harmful behaviour within plugins. For plugin concerns, email + us at:{' '} + + integrations@grafana.com + + + + Note: For general plugin issues like bugs or feature requests, please contact the plugin author using + the provided links.{' '} + - - Contact Grafana Labs - - + + + + + )} - + ); } diff --git a/public/app/features/plugins/admin/components/VersionList.tsx b/public/app/features/plugins/admin/components/VersionList.tsx index aaf98fd183b..2e79b0d43c1 100644 --- a/public/app/features/plugins/admin/components/VersionList.tsx +++ b/public/app/features/plugins/admin/components/VersionList.tsx @@ -96,7 +96,7 @@ export const VersionList = ({ pluginId, versions = [], installedVersion, disable {/* Last updated */} - {dateTimeFormatTimeAgo(version.createdAt)} + {dateTimeFormatTimeAgo(version.updatedAt || version.createdAt)} {/* Dependency */} {version.grafanaDependency || 'N/A'} diff --git a/public/app/features/plugins/admin/helpers.test.ts b/public/app/features/plugins/admin/helpers.test.ts index b47c5bf774b..b1b109f2ca5 100644 --- a/public/app/features/plugins/admin/helpers.test.ts +++ b/public/app/features/plugins/admin/helpers.test.ts @@ -217,6 +217,7 @@ describe('Plugins/Helpers', () => { updatedAt: '2021-05-18T14:53:01.000Z', isFullyInstalled: false, angularDetected: false, + url: 'https://github.com/alexanderzobnin/grafana-zabbix', }); }); @@ -354,6 +355,7 @@ describe('Plugins/Helpers', () => { installedVersion: '4.2.2', isFullyInstalled: true, angularDetected: false, + url: 'https://github.com/alexanderzobnin/grafana-zabbix', }); }); diff --git a/public/app/features/plugins/admin/helpers.ts b/public/app/features/plugins/admin/helpers.ts index 172a32e088f..2f7814cc729 100644 --- a/public/app/features/plugins/admin/helpers.ts +++ b/public/app/features/plugins/admin/helpers.ts @@ -121,6 +121,8 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C signatureType, versionSignatureType, versionSignedByOrgName, + url, + raiseAnIssueUrl, } = plugin; const isDisabled = !!error || isDisabledSecretsPlugin(typeCode); @@ -158,6 +160,8 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C angularDetected, isFullyInstalled: isDisabled, latestVersion: plugin.version, + url, + raiseAnIssueUrl, }; } @@ -174,6 +178,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat hasUpdate, accessControl, angularDetected, + raiseAnIssueUrl, } = plugin; const isDisabled = !!error || isDisabledSecretsPlugin(type); @@ -208,6 +213,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat isFullyInstalled: true, iam: plugin.iam, latestVersion: plugin.latestVersion, + raiseAnIssueUrl, }; } @@ -271,6 +277,8 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e isFullyInstalled: Boolean(local) || isDisabled, iam: local?.iam, latestVersion: local?.latestVersion || remote?.version || '', + url: remote?.url || '', + raiseAnIssueUrl: remote?.raiseAnIssueUrl || local?.raiseAnIssueUrl, }; } diff --git a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx index e9c50269d15..d9f7c2e1375 100644 --- a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx +++ b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx @@ -42,7 +42,8 @@ export const usePluginDetailsTabs = ( const navModelChildren = useMemo(() => { const canConfigurePlugins = plugin && contextSrv.hasPermissionInMetadata(AccessControlAction.PluginsWrite, plugin); const navModelChildren: NavModelItem[] = []; - if (isPublished) { + // currently the versions available of core plugins are not consistent + if (isPublished && !plugin?.isCore) { navModelChildren.push({ text: PluginTabLabels.VERSIONS, id: PluginTabIds.VERSIONS, @@ -51,7 +52,8 @@ export const usePluginDetailsTabs = ( active: PluginTabIds.VERSIONS === currentPageId, }); } - if (isPublished && plugin?.details?.changelog) { + // currently there is not changelog available for core plugins + if (isPublished && plugin?.details?.changelog && !plugin.isCore) { navModelChildren.push({ text: PluginTabLabels.CHANGELOG, id: PluginTabIds.CHANGELOG, diff --git a/public/app/features/plugins/admin/hooks/usePluginInfo.tsx b/public/app/features/plugins/admin/hooks/usePluginInfo.tsx index 1bb1334e1ef..2c9124f1860 100644 --- a/public/app/features/plugins/admin/hooks/usePluginInfo.tsx +++ b/public/app/features/plugins/admin/hooks/usePluginInfo.tsx @@ -53,7 +53,10 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => { latestVersionValue = latestVersion; } - addInfo('latestVersion', latestVersionValue); + // latest versions of core plugins are not consistent + if (!plugin.isCore) { + addInfo('latestVersion', latestVersionValue); + } } if (Boolean(plugin.orgName)) { diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index 10e2636b2ef..f3d5783ae3c 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -64,6 +64,8 @@ export interface CatalogPlugin extends WithAccessControlMetadata { isUpdatingFromInstance?: boolean; iam?: IdentityAccessManagement; isProvisioned?: boolean; + url?: string; + raiseAnIssueUrl?: string; } export interface CatalogPluginDetails { @@ -79,6 +81,8 @@ export interface CatalogPluginDetails { iam?: IdentityAccessManagement; changelog?: string; lastCommitDate?: string; + licenseUrl?: string; + documentationUrl?: string; signatureType?: PluginSignatureType; signature?: PluginSignatureStatus; } @@ -143,6 +147,9 @@ export type RemotePlugin = { versionStatus: string; angularDetected?: boolean; lastCommitDate?: string; + licenseUrl?: string; + documentationUrl?: string; + raiseAnIssueUrl?: string; }; // The available status codes on GCOM are available here: @@ -190,6 +197,7 @@ export type LocalPlugin = WithAccessControlMetadata & { dependencies: PluginDependencies; angularDetected: boolean; iam?: IdentityAccessManagement; + raiseAnIssueUrl?: string; }; interface IdentityAccessManagement { @@ -216,6 +224,7 @@ export interface Build { export interface Version { version: string; createdAt: string; + updatedAt?: string; isCompatible: boolean; grafanaDependency: string | null; angularDetected?: boolean; diff --git a/public/app/features/plugins/components/AppRootPage.test.tsx b/public/app/features/plugins/components/AppRootPage.test.tsx index 84736aed8de..c9e5ec616be 100644 --- a/public/app/features/plugins/components/AppRootPage.test.tsx +++ b/public/app/features/plugins/components/AppRootPage.test.tsx @@ -12,6 +12,7 @@ import { Echo } from 'app/core/services/echo/Echo'; import { ExtensionRegistriesProvider } from '../extensions/ExtensionRegistriesContext'; import { AddedComponentsRegistry } from '../extensions/registry/AddedComponentsRegistry'; +import { AddedFunctionsRegistry } from '../extensions/registry/AddedFunctionsRegistry'; import { AddedLinksRegistry } from '../extensions/registry/AddedLinksRegistry'; import { ExposedComponentsRegistry } from '../extensions/registry/ExposedComponentsRegistry'; import { getPluginSettings } from '../pluginSettings'; @@ -93,6 +94,7 @@ function renderUnderRouter(page = '') { addedComponentsRegistry: new AddedComponentsRegistry(), exposedComponentsRegistry: new ExposedComponentsRegistry(), addedLinksRegistry: new AddedLinksRegistry(), + addedFunctionsRegistry: new AddedFunctionsRegistry(), }; const pagePath = page ? `/${page}` : ''; const route = { diff --git a/public/app/features/plugins/components/AppRootPage.tsx b/public/app/features/plugins/components/AppRootPage.tsx index f0d6bd5329e..95a280027df 100644 --- a/public/app/features/plugins/components/AppRootPage.tsx +++ b/public/app/features/plugins/components/AppRootPage.tsx @@ -29,6 +29,7 @@ import { useAddedLinksRegistry, useAddedComponentsRegistry, useExposedComponentsRegistry, + useAddedFunctionsRegistry, } from '../extensions/ExtensionRegistriesContext'; import { getPluginSettings } from '../pluginSettings'; import { importAppPlugin } from '../plugin_loader'; @@ -60,6 +61,7 @@ export function AppRootPage({ pluginId, pluginNavSection }: Props) { const addedLinksRegistry = useAddedLinksRegistry(); const addedComponentsRegistry = useAddedComponentsRegistry(); const exposedComponentsRegistry = useExposedComponentsRegistry(); + const addedFunctionsRegistry = useAddedFunctionsRegistry(); const location = useLocation(); const [state, dispatch] = useReducer(stateSlice.reducer, initialState); const currentUrl = config.appSubUrl + location.pathname + location.search; @@ -104,6 +106,7 @@ export function AppRootPage({ pluginId, pluginNavSection }: Props) { addedLinksRegistry: addedLinksRegistry.readOnly(), addedComponentsRegistry: addedComponentsRegistry.readOnly(), exposedComponentsRegistry: exposedComponentsRegistry.readOnly(), + addedFunctionsRegistry: addedFunctionsRegistry.readOnly(), }} > (undefined); export const AddedComponentsRegistryContext = createContext(undefined); +export const AddedFunctionsRegistryContext = createContext(undefined); export const ExposedComponentsRegistryContext = createContext(undefined); export function useAddedLinksRegistry(): AddedLinksRegistry { @@ -31,6 +33,14 @@ export function useAddedComponentsRegistry(): AddedComponentsRegistry { return context; } +export function useAddedFunctionsRegistry(): AddedFunctionsRegistry { + const context = useContext(AddedFunctionsRegistryContext); + if (!context) { + throw new Error('No `AddedFunctionsRegistry` found.'); + } + return context; +} + export function useExposedComponentsRegistry(): ExposedComponentsRegistry { const context = useContext(ExposedComponentsRegistryContext); if (!context) { @@ -46,9 +56,11 @@ export const ExtensionRegistriesProvider = ({ return ( - - {children} - + + + {children} + + ); diff --git a/public/app/features/plugins/extensions/errors.ts b/public/app/features/plugins/extensions/errors.ts index 39c65df0f8e..cb9d22e7b46 100644 --- a/public/app/features/plugins/extensions/errors.ts +++ b/public/app/features/plugins/extensions/errors.ts @@ -8,6 +8,8 @@ export const TITLE_MISSING = 'Title is missing.'; export const DESCRIPTION_MISSING = 'Description is missing.'; +export const INVALID_EXTENSION_FUNCTION = 'The "fn" argument is invalid, it should be a function.'; + export const INVALID_CONFIGURE_FUNCTION = 'The "configure" function is invalid. It should be a function.'; export const INVALID_PATH_OR_ON_CLICK = 'Either "path" or "onClick" is required.'; @@ -33,6 +35,9 @@ export const TITLE_NOT_MATCHING_META_INFO = 'The "title" doesn\'t match the titl export const ADDED_LINK_META_INFO_MISSING = 'The extension was not recorded in the plugin.json. Added link extensions must be listed in the section "extensions.addedLinks[]". Currently, this is only required in development but will be enforced also in production builds in the future.'; +export const ADDED_FUNCTION_META_INFO_MISSING = + 'The extension was not recorded in the plugin.json. Added function extensions must be listed in the section "extensions.addedFunction[]". Currently, this is only required in development but will be enforced also in production builds in the future.'; + export const DESCRIPTION_NOT_MATCHING_META_INFO = 'The "description" doesn\'t match the description recorded in plugin.json.'; diff --git a/public/app/features/plugins/extensions/getPluginExtensions.ts b/public/app/features/plugins/extensions/getPluginExtensions.ts index a47292c8497..30f5865b259 100644 --- a/public/app/features/plugins/extensions/getPluginExtensions.ts +++ b/public/app/features/plugins/extensions/getPluginExtensions.ts @@ -1,8 +1,8 @@ import { isString } from 'lodash'; import { - type PluginExtension, PluginExtensionTypes, + type PluginExtension, type PluginExtensionLink, type PluginExtensionComponent, } from '@grafana/data'; diff --git a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts index e82c48dcee0..5a18e222431 100644 --- a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts +++ b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts @@ -52,6 +52,7 @@ describe('AddedComponentsRegistry', () => { extensions: { addedLinks: [], addedComponents: [], + addedFunctions: [], exposedComponents: [], extensionPoints: [], }, diff --git a/public/app/features/plugins/extensions/registry/AddedFunctionsRegistry.test.ts b/public/app/features/plugins/extensions/registry/AddedFunctionsRegistry.test.ts new file mode 100644 index 00000000000..ae0addc52c7 --- /dev/null +++ b/public/app/features/plugins/extensions/registry/AddedFunctionsRegistry.test.ts @@ -0,0 +1,677 @@ +import { firstValueFrom } from 'rxjs'; + +import { PluginLoadingStrategy } from '@grafana/data'; +import { config } from '@grafana/runtime'; + +import { log } from '../logs/log'; +import { resetLogMock } from '../logs/testUtils'; +import { isGrafanaDevMode } from '../utils'; + +import { AddedFunctionsRegistry } from './AddedFunctionsRegistry'; +import { MSG_CANNOT_REGISTER_READ_ONLY } from './Registry'; + +jest.mock('../utils', () => ({ + ...jest.requireActual('../utils'), + + // Manually set the dev mode to false + // (to make sure that by default we are testing a production scneario) + isGrafanaDevMode: jest.fn().mockReturnValue(false), +})); + +jest.mock('../logs/log', () => { + const { createLogMock } = jest.requireActual('../logs/testUtils'); + const original = jest.requireActual('../logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + +describe('addedFunctionsRegistry', () => { + const originalApps = config.apps; + const pluginId = 'grafana-basic-app'; + const appPluginConfig = { + id: pluginId, + path: '', + version: '', + preload: false, + angular: { + detected: false, + hideDeprecation: false, + }, + loadingStrategy: PluginLoadingStrategy.fetch, + dependencies: { + grafanaVersion: '8.0.0', + plugins: [], + extensions: { + exposedComponents: [], + }, + }, + extensions: { + addedFunctions: [], + addedLinks: [], + addedComponents: [], + exposedComponents: [], + extensionPoints: [], + }, + }; + + beforeEach(() => { + resetLogMock(log); + jest.mocked(isGrafanaDevMode).mockReturnValue(false); + config.apps = { + [pluginId]: appPluginConfig, + }; + }); + + afterEach(() => { + config.apps = originalApps; + }); + + it('should return empty registry when no extensions registered', async () => { + const addedFunctionsRegistry = new AddedFunctionsRegistry(); + const observable = addedFunctionsRegistry.asObservable(); + const registry = await firstValueFrom(observable); + expect(registry).toEqual({}); + }); + + it('should be possible to register function extensions in the registry', async () => { + const addedFunctionsRegistry = new AddedFunctionsRegistry(); + + addedFunctionsRegistry.register({ + pluginId, + configs: [ + { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn(), + }, + { + title: 'Function 2', + description: 'Function 2 description', + targets: 'plugins/myorg-basic-app/start', + fn: jest.fn(), + }, + ], + }); + + const registry = await addedFunctionsRegistry.getState(); + + expect(registry).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId, + title: 'Function 1', + description: 'Function 1 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + ], + 'plugins/myorg-basic-app/start': [ + { + pluginId: pluginId, + title: 'Function 2', + description: 'Function 2 description', + extensionPointId: 'plugins/myorg-basic-app/start', + fn: expect.any(Function), + }, + ], + }); + }); + it('should be possible to asynchronously register function extensions for the same placement (different plugins)', async () => { + const pluginId1 = 'grafana-basic-app'; + const pluginId2 = 'grafana-basic-app2'; + const reactiveRegistry = new AddedFunctionsRegistry(); + + // Register extensions for the first plugin + reactiveRegistry.register({ + pluginId: pluginId1, + configs: [ + { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry1 = await reactiveRegistry.getState(); + + expect(registry1).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId1, + title: 'Function 1', + description: 'Function 1 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + ], + }); + + // Register extensions for the second plugin to a different placement + reactiveRegistry.register({ + pluginId: pluginId2, + configs: [ + { + title: 'Function 2', + description: 'Function 2 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry2 = await reactiveRegistry.getState(); + + expect(registry2).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId1, + title: 'Function 1', + description: 'Function 1 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + { + pluginId: pluginId2, + title: 'Function 2', + description: 'Function 2 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + ], + }); + }); + + it('should be possible to asynchronously register function extensions for a different placement (different plugin)', async () => { + const pluginId1 = 'grafana-basic-app'; + const pluginId2 = 'grafana-basic-app2'; + const reactiveRegistry = new AddedFunctionsRegistry(); + + // Register extensions for the first plugin + reactiveRegistry.register({ + pluginId: pluginId1, + configs: [ + { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry1 = await reactiveRegistry.getState(); + + expect(registry1).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId1, + + title: 'Function 1', + description: 'Function 1 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + ], + }); + + // Register extensions for the second plugin to a different placement + reactiveRegistry.register({ + pluginId: pluginId2, + configs: [ + { + title: 'Function 2', + description: 'Function 2 description', + targets: 'plugins/myorg-basic-app/start', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry2 = await reactiveRegistry.getState(); + + expect(registry2).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId1, + title: 'Function 1', + description: 'Function 1 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + ], + 'plugins/myorg-basic-app/start': [ + { + pluginId: pluginId2, + title: 'Function 2', + description: 'Function 2 description', + extensionPointId: 'plugins/myorg-basic-app/start', + fn: expect.any(Function), + }, + ], + }); + }); + + it('should be possible to asynchronously register function extensions for the same placement (same plugin)', async () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new AddedFunctionsRegistry(); + + // Register extensions for the first extension point + reactiveRegistry.register({ + pluginId: pluginId, + configs: [ + { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + // Register extensions to a different extension point + reactiveRegistry.register({ + pluginId: pluginId, + configs: [ + { + title: 'Function 2', + description: 'Function 2 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry2 = await reactiveRegistry.getState(); + + expect(registry2).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId, + + title: 'Function 1', + description: 'Function 1 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + { + pluginId: pluginId, + + title: 'Function 2', + description: 'Function 2 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + ], + }); + }); + + it('should be possible to asynchronously register function extensions for a different placement (same plugin)', async () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new AddedFunctionsRegistry(); + + // Register extensions for the first extension point + reactiveRegistry.register({ + pluginId: pluginId, + configs: [ + { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + // Register extensions to a different extension point + reactiveRegistry.register({ + pluginId: pluginId, + configs: [ + { + title: 'Function 2', + description: 'Function 2 description', + targets: 'plugins/myorg-basic-app/start', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry2 = await reactiveRegistry.getState(); + + expect(registry2).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId, + + title: 'Function 1', + description: 'Function 1 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + ], + 'plugins/myorg-basic-app/start': [ + { + pluginId: pluginId, + + title: 'Function 2', + description: 'Function 2 description', + extensionPointId: 'plugins/myorg-basic-app/start', + fn: expect.any(Function), + }, + ], + }); + }); + + it('should notify subscribers when the registry changes', async () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new AddedFunctionsRegistry(); + const observable = reactiveRegistry.asObservable(); + const subscribeCallback = jest.fn(); + + observable.subscribe(subscribeCallback); + + // Register extensions for the first plugin + reactiveRegistry.register({ + pluginId: pluginId, + configs: [ + { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + expect(subscribeCallback).toHaveBeenCalledTimes(2); + + // Register extensions for the first plugin + reactiveRegistry.register({ + pluginId: 'another-plugin', + configs: [ + { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + expect(subscribeCallback).toHaveBeenCalledTimes(3); + + const registry = subscribeCallback.mock.calls[2][0]; + + expect(registry).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId, + title: 'Function 1', + description: 'Function 1 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + { + pluginId: 'another-plugin', + title: 'Function 1', + description: 'Function 1 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + ], + }); + }); + + it('should give the last version of the registry for new subscribers', async () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new AddedFunctionsRegistry(); + const observable = reactiveRegistry.asObservable(); + const subscribeCallback = jest.fn(); + + reactiveRegistry.register({ + pluginId: pluginId, + configs: [ + { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + observable.subscribe(subscribeCallback); + expect(subscribeCallback).toHaveBeenCalledTimes(1); + + const registry = subscribeCallback.mock.calls[0][0]; + + expect(registry).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId, + title: 'Function 1', + description: 'Function 1 description', + extensionPointId: 'grafana/dashboard/panel/menu', + fn: expect.any(Function), + }, + ], + }); + }); + + it('should not register a function extension if it has an invalid fn function', () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new AddedFunctionsRegistry(); + const observable = reactiveRegistry.asObservable(); + const subscribeCallback = jest.fn(); + + reactiveRegistry.register({ + pluginId: pluginId, + configs: [ + { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + //@ts-ignore + fn: '...', + }, + ], + }); + + expect(log.error).toHaveBeenCalled(); + + observable.subscribe(subscribeCallback); + expect(subscribeCallback).toHaveBeenCalledTimes(1); + + const registry = subscribeCallback.mock.calls[0][0]; + expect(registry).toEqual({}); + }); + + it('should not register a function extension if it has invalid properties (empty title)', () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new AddedFunctionsRegistry(); + const observable = reactiveRegistry.asObservable(); + const subscribeCallback = jest.fn(); + + reactiveRegistry.register({ + pluginId: pluginId, + configs: [ + { + title: '', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + expect(log.error).toHaveBeenCalled(); + + observable.subscribe(subscribeCallback); + expect(subscribeCallback).toHaveBeenCalledTimes(1); + + const registry = subscribeCallback.mock.calls[0][0]; + expect(registry).toEqual({}); + }); + + it('should not be possible to register a function on a read-only registry', async () => { + const pluginId = 'grafana-basic-app'; + const registry = new AddedFunctionsRegistry(); + const readOnlyRegistry = registry.readOnly(); + + expect(() => { + readOnlyRegistry.register({ + pluginId, + configs: [ + { + title: 'Function 2', + description: 'Function 2 description', + targets: 'plugins/myorg-basic-app/start', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + }).toThrow(MSG_CANNOT_REGISTER_READ_ONLY); + + const currentState = await readOnlyRegistry.getState(); + expect(Object.keys(currentState)).toHaveLength(0); + }); + + it('should pass down fresh registrations to the read-only version of the registry', async () => { + const pluginId = 'grafana-basic-app'; + const registry = new AddedFunctionsRegistry(); + const readOnlyRegistry = registry.readOnly(); + const subscribeCallback = jest.fn(); + let readOnlyState; + + // Should have no extensions registered in the beginning + readOnlyState = await readOnlyRegistry.getState(); + expect(Object.keys(readOnlyState)).toHaveLength(0); + + readOnlyRegistry.asObservable().subscribe(subscribeCallback); + + // Register an extension to the original (writable) registry + registry.register({ + pluginId, + configs: [ + { + title: 'Function 2', + description: 'Function 2 description', + targets: 'plugins/myorg-basic-app/start', + fn: jest.fn().mockReturnValue({}), + }, + ], + }); + + // The read-only registry should have received the new extension + readOnlyState = await readOnlyRegistry.getState(); + expect(Object.keys(readOnlyState)).toHaveLength(1); + + expect(subscribeCallback).toHaveBeenCalledTimes(2); + expect(Object.keys(subscribeCallback.mock.calls[1][0])).toEqual(['plugins/myorg-basic-app/start']); + }); + + it('should not register a function added by a plugin in dev-mode if the meta-info is missing from the plugin.json', async () => { + // Enabling dev mode + jest.mocked(isGrafanaDevMode).mockReturnValue(true); + + const registry = new AddedFunctionsRegistry(); + const fnConfig = { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }; + + // Make sure that the meta-info is empty + config.apps[pluginId].extensions.addedFunctions = []; + + registry.register({ + pluginId, + configs: [fnConfig], + }); + + const currentState = await registry.getState(); + + expect(Object.keys(currentState)).toHaveLength(0); + expect(log.error).toHaveBeenCalled(); + }); + + it('should register a function added by core Grafana in dev-mode even if the meta-info is missing', async () => { + // Enabling dev mode + jest.mocked(isGrafanaDevMode).mockReturnValue(true); + + const registry = new AddedFunctionsRegistry(); + const fnConfig = { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }; + + registry.register({ + pluginId: 'grafana', + configs: [fnConfig], + }); + + const currentState = await registry.getState(); + + expect(Object.keys(currentState)).toHaveLength(1); + expect(log.error).not.toHaveBeenCalled(); + }); + + it('should register a function added by a plugin in production mode even if the meta-info is missing', async () => { + // Production mode + jest.mocked(isGrafanaDevMode).mockReturnValue(false); + + const registry = new AddedFunctionsRegistry(); + const fnConfig = { + title: 'Function 1', + description: 'Function 1 description', + targets: 'grafana/dashboard/panel/menu', + fn: jest.fn().mockReturnValue({}), + }; + + // Make sure that the meta-info is empty + config.apps[pluginId].extensions.addedFunctions = []; + + registry.register({ + pluginId, + configs: [fnConfig], + }); + + const currentState = await registry.getState(); + + expect(Object.keys(currentState)).toHaveLength(1); + expect(log.error).not.toHaveBeenCalled(); + }); + + it('should register a function added by a plugin in dev-mode if the meta-info is present', async () => { + // Enabling dev mode + jest.mocked(isGrafanaDevMode).mockReturnValue(true); + + const registry = new AddedFunctionsRegistry(); + const fnConfig = { + title: 'Function 1', + description: 'Function 1 description', + targets: ['grafana/dashboard/panel/menu'], + fn: jest.fn().mockReturnValue({}), + }; + + // Make sure that the meta-info is empty + config.apps[pluginId].extensions.addedFunctions = [fnConfig]; + + registry.register({ + pluginId, + configs: [fnConfig], + }); + + const currentState = await registry.getState(); + + expect(Object.keys(currentState)).toHaveLength(1); + expect(log.error).not.toHaveBeenCalled(); + }); +}); diff --git a/public/app/features/plugins/extensions/registry/AddedFunctionsRegistry.ts b/public/app/features/plugins/extensions/registry/AddedFunctionsRegistry.ts new file mode 100644 index 00000000000..d23fe5e78b2 --- /dev/null +++ b/public/app/features/plugins/extensions/registry/AddedFunctionsRegistry.ts @@ -0,0 +1,87 @@ +import { isFunction } from 'lodash'; +import { ReplaySubject } from 'rxjs'; + +import { PluginExtensionAddedFunctionConfig } from '@grafana/data'; + +import * as errors from '../errors'; +import { isGrafanaDevMode } from '../utils'; +import { isAddedFunctionMetaInfoMissing } from '../validators'; + +import { PluginExtensionConfigs, Registry, RegistryType } from './Registry'; + +const logPrefix = 'Could not register function extension. Reason:'; + +export type AddedFunctionsRegistryItem = { + pluginId: string; + title: string; + fn: unknown; + description?: string; +}; + +export class AddedFunctionsRegistry extends Registry { + constructor( + options: { + registrySubject?: ReplaySubject>; + initialState?: RegistryType; + } = {} + ) { + super(options); + } + + mapToRegistry( + registry: RegistryType, + item: PluginExtensionConfigs + ): RegistryType { + const { pluginId, configs } = item; + for (const config of configs) { + const configLog = this.logger.child({ + title: config.title, + pluginId, + }); + + if (!config.title) { + configLog.error(`${logPrefix} ${errors.TITLE_MISSING}`); + continue; + } + + if (!isFunction(config.fn)) { + configLog.error(`${logPrefix} ${errors.INVALID_EXTENSION_FUNCTION}`); + continue; + } + + if (pluginId !== 'grafana' && isGrafanaDevMode() && isAddedFunctionMetaInfoMissing(pluginId, config, configLog)) { + continue; + } + + const extensionPointIds = Array.isArray(config.targets) ? config.targets : [config.targets]; + for (const extensionPointId of extensionPointIds) { + const pointIdLog = configLog.child({ extensionPointId }); + + const result = { + pluginId, + fn: config.fn, + description: config.description, + title: config.title, + extensionPointId, + }; + + pointIdLog.debug('Added function extension successfully registered'); + + if (!(extensionPointId in registry)) { + registry[extensionPointId] = [result]; + } else { + registry[extensionPointId].push(result); + } + } + } + + return registry; + } + + // Returns a read-only version of the registry. + readOnly() { + return new AddedFunctionsRegistry({ + registrySubject: this.registrySubject, + }); + } +} diff --git a/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts b/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts index 4d5ab5c084f..d3586240276 100644 --- a/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts +++ b/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts @@ -51,6 +51,7 @@ describe('AddedLinksRegistry', () => { extensions: { addedLinks: [], addedComponents: [], + addedFunctions: [], exposedComponents: [], extensionPoints: [], }, diff --git a/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.test.ts b/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.test.ts index 0a7036894d1..863c89f7b40 100644 --- a/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.test.ts +++ b/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.test.ts @@ -52,6 +52,7 @@ describe('ExposedComponentsRegistry', () => { extensions: { addedLinks: [], addedComponents: [], + addedFunctions: [], exposedComponents: [], extensionPoints: [], }, diff --git a/public/app/features/plugins/extensions/registry/setup.ts b/public/app/features/plugins/extensions/registry/setup.ts index 6c2fd1e6a5f..91b7badc4eb 100644 --- a/public/app/features/plugins/extensions/registry/setup.ts +++ b/public/app/features/plugins/extensions/registry/setup.ts @@ -1,6 +1,7 @@ import { getCoreExtensionConfigurations } from '../getCoreExtensionConfigurations'; import { AddedComponentsRegistry } from './AddedComponentsRegistry'; +import { AddedFunctionsRegistry } from './AddedFunctionsRegistry'; import { AddedLinksRegistry } from './AddedLinksRegistry'; import { ExposedComponentsRegistry } from './ExposedComponentsRegistry'; import { PluginExtensionRegistries } from './types'; @@ -8,10 +9,12 @@ import { PluginExtensionRegistries } from './types'; export const addedComponentsRegistry = new AddedComponentsRegistry(); export const exposedComponentsRegistry = new ExposedComponentsRegistry(); export const addedLinksRegistry = new AddedLinksRegistry(); +export const addedFunctionsRegistry = new AddedFunctionsRegistry(); export const pluginExtensionRegistries: PluginExtensionRegistries = { addedComponentsRegistry, exposedComponentsRegistry, addedLinksRegistry, + addedFunctionsRegistry, }; // Registering core extensions diff --git a/public/app/features/plugins/extensions/registry/types.ts b/public/app/features/plugins/extensions/registry/types.ts index 115e859b7d9..1927bf31b75 100644 --- a/public/app/features/plugins/extensions/registry/types.ts +++ b/public/app/features/plugins/extensions/registry/types.ts @@ -1,9 +1,11 @@ import { AddedComponentsRegistry } from './AddedComponentsRegistry'; +import { AddedFunctionsRegistry } from './AddedFunctionsRegistry'; import { AddedLinksRegistry } from './AddedLinksRegistry'; import { ExposedComponentsRegistry } from './ExposedComponentsRegistry'; export type PluginExtensionRegistries = { addedComponentsRegistry: AddedComponentsRegistry; exposedComponentsRegistry: ExposedComponentsRegistry; + addedFunctionsRegistry: AddedFunctionsRegistry; addedLinksRegistry: AddedLinksRegistry; }; diff --git a/public/app/features/plugins/extensions/usePluginComponent.test.tsx b/public/app/features/plugins/extensions/usePluginComponent.test.tsx index 6385c022027..b2a3d3d3435 100644 --- a/public/app/features/plugins/extensions/usePluginComponent.test.tsx +++ b/public/app/features/plugins/extensions/usePluginComponent.test.tsx @@ -7,6 +7,7 @@ import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext'; import { log } from './logs/log'; import { resetLogMock } from './logs/testUtils'; import { AddedComponentsRegistry } from './registry/AddedComponentsRegistry'; +import { AddedFunctionsRegistry } from './registry/AddedFunctionsRegistry'; import { AddedLinksRegistry } from './registry/AddedLinksRegistry'; import { ExposedComponentsRegistry } from './registry/ExposedComponentsRegistry'; import { PluginExtensionRegistries } from './registry/types'; @@ -78,6 +79,7 @@ describe('usePluginComponent()', () => { extensions: { addedLinks: [], addedComponents: [], + addedFunctions: [], // This is necessary, so we can register exposed components to the registry during the tests // (Otherwise the registry would reject it in the imitated production mode) exposedComponents: [exposedComponentConfig], @@ -90,6 +92,7 @@ describe('usePluginComponent()', () => { addedComponentsRegistry: new AddedComponentsRegistry(), exposedComponentsRegistry: new ExposedComponentsRegistry(), addedLinksRegistry: new AddedLinksRegistry(), + addedFunctionsRegistry: new AddedFunctionsRegistry(), }; jest.mocked(useLoadAppPlugins).mockReturnValue({ isLoading: false }); jest.mocked(isGrafanaDevMode).mockReturnValue(false); @@ -122,6 +125,7 @@ describe('usePluginComponent()', () => { addedComponents: [], exposedComponents: [], extensionPoints: [], + addedFunctions: [], }, dependencies: { grafanaVersion: '8.0.0', diff --git a/public/app/features/plugins/extensions/usePluginComponents.test.tsx b/public/app/features/plugins/extensions/usePluginComponents.test.tsx index 75adda33f75..fcb729fdd64 100644 --- a/public/app/features/plugins/extensions/usePluginComponents.test.tsx +++ b/public/app/features/plugins/extensions/usePluginComponents.test.tsx @@ -6,6 +6,7 @@ import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext'; import { log } from './logs/log'; import { resetLogMock } from './logs/testUtils'; import { AddedComponentsRegistry } from './registry/AddedComponentsRegistry'; +import { AddedFunctionsRegistry } from './registry/AddedFunctionsRegistry'; import { AddedLinksRegistry } from './registry/AddedLinksRegistry'; import { ExposedComponentsRegistry } from './registry/ExposedComponentsRegistry'; import { PluginExtensionRegistries } from './registry/types'; @@ -60,6 +61,7 @@ describe('usePluginComponents()', () => { addedComponentsRegistry: new AddedComponentsRegistry(), exposedComponentsRegistry: new ExposedComponentsRegistry(), addedLinksRegistry: new AddedLinksRegistry(), + addedFunctionsRegistry: new AddedFunctionsRegistry(), }; jest.mocked(wrapWithPluginContext).mockClear(); @@ -89,6 +91,7 @@ describe('usePluginComponents()', () => { addedComponents: [], exposedComponents: [], extensionPoints: [], + addedFunctions: [], }, dependencies: { grafanaVersion: '8.0.0', diff --git a/public/app/features/plugins/extensions/usePluginExtensions.test.tsx b/public/app/features/plugins/extensions/usePluginExtensions.test.tsx index 739906dd815..479e5cd4c6c 100644 --- a/public/app/features/plugins/extensions/usePluginExtensions.test.tsx +++ b/public/app/features/plugins/extensions/usePluginExtensions.test.tsx @@ -1,6 +1,7 @@ import { act, renderHook } from '@testing-library/react'; import { AddedComponentsRegistry } from './registry/AddedComponentsRegistry'; +import { AddedFunctionsRegistry } from './registry/AddedFunctionsRegistry'; import { AddedLinksRegistry } from './registry/AddedLinksRegistry'; import { ExposedComponentsRegistry } from './registry/ExposedComponentsRegistry'; import { PluginExtensionRegistries } from './registry/types'; @@ -19,6 +20,7 @@ describe('usePluginExtensions()', () => { addedComponentsRegistry: new AddedComponentsRegistry(), addedLinksRegistry: new AddedLinksRegistry(), exposedComponentsRegistry: new ExposedComponentsRegistry(), + addedFunctionsRegistry: new AddedFunctionsRegistry(), }; jest.mocked(useLoadAppPlugins).mockReturnValue({ isLoading: false }); }); diff --git a/public/app/features/plugins/extensions/usePluginFunctions.tsx b/public/app/features/plugins/extensions/usePluginFunctions.tsx new file mode 100644 index 00000000000..68acee76221 --- /dev/null +++ b/public/app/features/plugins/extensions/usePluginFunctions.tsx @@ -0,0 +1,82 @@ +import { useMemo } from 'react'; +import { useObservable } from 'react-use'; + +import { usePluginContext, PluginExtensionFunction, PluginExtensionTypes } from '@grafana/data'; +import { UsePluginFunctionsOptions, UsePluginFunctionsResult } from '@grafana/runtime'; + +import { useAddedFunctionsRegistry } from './ExtensionRegistriesContext'; +import * as errors from './errors'; +import { log } from './logs/log'; +import { useLoadAppPlugins } from './useLoadAppPlugins'; +import { generateExtensionId, getExtensionPointPluginDependencies, isGrafanaDevMode } from './utils'; +import { isExtensionPointIdValid, isExtensionPointMetaInfoMissing } from './validators'; + +// Returns an array of component extensions for the given extension point +export function usePluginFunctions({ + limitPerPlugin, + extensionPointId, +}: UsePluginFunctionsOptions): UsePluginFunctionsResult { + const registry = useAddedFunctionsRegistry(); + const registryState = useObservable(registry.asObservable()); + const pluginContext = usePluginContext(); + const deps = getExtensionPointPluginDependencies(extensionPointId); + const { isLoading: isLoadingAppPlugins } = useLoadAppPlugins(deps); + + return useMemo(() => { + // For backwards compatibility we don't enable restrictions in production or when the hook is used in core Grafana. + const enableRestrictions = isGrafanaDevMode() && pluginContext; + const results: Array> = []; + const extensionsByPlugin: Record = {}; + const pluginId = pluginContext?.meta.id ?? ''; + const pointLog = log.child({ + pluginId, + extensionPointId, + }); + if (enableRestrictions && !isExtensionPointIdValid({ extensionPointId, pluginId })) { + pointLog.error(errors.INVALID_EXTENSION_POINT_ID); + } + + if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) { + pointLog.error(errors.EXTENSION_POINT_META_INFO_MISSING); + return { + isLoading: false, + functions: [], + }; + } + + if (isLoadingAppPlugins) { + return { + isLoading: true, + functions: [], + }; + } + + for (const registryItem of registryState?.[extensionPointId] ?? []) { + const { pluginId } = registryItem; + + // Only limit if the `limitPerPlugin` is set + if (limitPerPlugin && extensionsByPlugin[pluginId] >= limitPerPlugin) { + continue; + } + + if (extensionsByPlugin[pluginId] === undefined) { + extensionsByPlugin[pluginId] = 0; + } + + results.push({ + id: generateExtensionId(pluginId, extensionPointId, registryItem.title), + type: PluginExtensionTypes.function, + title: registryItem.title, + description: registryItem.description ?? '', + pluginId: pluginId, + fn: registryItem.fn as Signature, + }); + extensionsByPlugin[pluginId] += 1; + } + + return { + isLoading: false, + functions: results, + }; + }, [extensionPointId, limitPerPlugin, pluginContext, registryState, isLoadingAppPlugins]); +} diff --git a/public/app/features/plugins/extensions/usePluginLinks.test.tsx b/public/app/features/plugins/extensions/usePluginLinks.test.tsx index 9186ca0ddf6..f9fb623f42a 100644 --- a/public/app/features/plugins/extensions/usePluginLinks.test.tsx +++ b/public/app/features/plugins/extensions/usePluginLinks.test.tsx @@ -6,6 +6,7 @@ import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext'; import { log } from './logs/log'; import { resetLogMock } from './logs/testUtils'; import { AddedComponentsRegistry } from './registry/AddedComponentsRegistry'; +import { AddedFunctionsRegistry } from './registry/AddedFunctionsRegistry'; import { AddedLinksRegistry } from './registry/AddedLinksRegistry'; import { ExposedComponentsRegistry } from './registry/ExposedComponentsRegistry'; import { PluginExtensionRegistries } from './registry/types'; @@ -57,6 +58,7 @@ describe('usePluginLinks()', () => { addedComponentsRegistry: new AddedComponentsRegistry(), exposedComponentsRegistry: new ExposedComponentsRegistry(), addedLinksRegistry: new AddedLinksRegistry(), + addedFunctionsRegistry: new AddedFunctionsRegistry(), }; resetLogMock(log); @@ -85,6 +87,7 @@ describe('usePluginLinks()', () => { addedComponents: [], exposedComponents: [], extensionPoints: [], + addedFunctions: [], }, dependencies: { grafanaVersion: '8.0.0', diff --git a/public/app/features/plugins/extensions/utils.test.tsx b/public/app/features/plugins/extensions/utils.test.tsx index 32ad6215875..0e2d98f44b3 100644 --- a/public/app/features/plugins/extensions/utils.test.tsx +++ b/public/app/features/plugins/extensions/utils.test.tsx @@ -475,6 +475,7 @@ describe('Plugin Extensions / Utils', () => { extensions: { addedLinks: [], addedComponents: [], + addedFunctions: [], exposedComponents: [], extensionPoints: [], }, @@ -553,6 +554,7 @@ describe('Plugin Extensions / Utils', () => { extensions: { addedLinks: [], addedComponents: [], + addedFunctions: [], exposedComponents: [], extensionPoints: [], }, @@ -584,6 +586,7 @@ describe('Plugin Extensions / Utils', () => { addedComponents: [], exposedComponents: [], extensionPoints: [], + addedFunctions: [], }, }, 'myorg-third-app': { @@ -623,6 +626,7 @@ describe('Plugin Extensions / Utils', () => { ], exposedComponents: [], extensionPoints: [], + addedFunctions: [], }, }, }; @@ -679,6 +683,7 @@ describe('Plugin Extensions / Utils', () => { ], exposedComponents: [], extensionPoints: [], + addedFunctions: [], }, dependencies: { ...genereicAppPluginConfig.dependencies, @@ -705,6 +710,7 @@ describe('Plugin Extensions / Utils', () => { }, ], extensionPoints: [], + addedFunctions: [], }, dependencies: { ...genereicAppPluginConfig.dependencies, @@ -726,6 +732,7 @@ describe('Plugin Extensions / Utils', () => { }, ], extensionPoints: [], + addedFunctions: [], }, }, 'myorg-sixth-app': { @@ -763,6 +770,7 @@ describe('Plugin Extensions / Utils', () => { addedComponents: [], exposedComponents: [], extensionPoints: [], + addedFunctions: [], }, }; @@ -791,6 +799,7 @@ describe('Plugin Extensions / Utils', () => { }, ], extensionPoints: [], + addedFunctions: [], }, }, 'myorg-third-app': { @@ -825,6 +834,7 @@ describe('Plugin Extensions / Utils', () => { }, ], extensionPoints: [], + addedFunctions: [], }, dependencies: { ...genereicAppPluginConfig.dependencies, @@ -850,6 +860,7 @@ describe('Plugin Extensions / Utils', () => { }, ], extensionPoints: [], + addedFunctions: [], }, dependencies: { ...genereicAppPluginConfig.dependencies, @@ -871,6 +882,7 @@ describe('Plugin Extensions / Utils', () => { }, ], extensionPoints: [], + addedFunctions: [], }, }, }; @@ -902,6 +914,7 @@ describe('Plugin Extensions / Utils', () => { extensions: { addedLinks: [], addedComponents: [], + addedFunctions: [], exposedComponents: [], extensionPoints: [], }, diff --git a/public/app/features/plugins/extensions/validators.test.tsx b/public/app/features/plugins/extensions/validators.test.tsx index 12b146bbf5c..f083ff738ea 100644 --- a/public/app/features/plugins/extensions/validators.test.tsx +++ b/public/app/features/plugins/extensions/validators.test.tsx @@ -271,6 +271,7 @@ describe('Plugin Extension Validators', () => { addedComponents: [], exposedComponents: [], extensionPoints: [], + addedFunctions: [], }, }; const extensionConfig = { @@ -387,6 +388,7 @@ describe('Plugin Extension Validators', () => { addedComponents: [], exposedComponents: [], extensionPoints: [], + addedFunctions: [], }, }; const extensionConfig = { @@ -503,6 +505,7 @@ describe('Plugin Extension Validators', () => { addedComponents: [], exposedComponents: [], extensionPoints: [], + addedFunctions: [], }, }; const exposedComponentConfig = { @@ -688,6 +691,7 @@ describe('Plugin Extension Validators', () => { addedComponents: [], exposedComponents: [], extensionPoints: [], + addedFunctions: [], }, dependencies: { grafanaVersion: '8.0.0', diff --git a/public/app/features/plugins/extensions/validators.ts b/public/app/features/plugins/extensions/validators.ts index b1dbd5e9af8..cbdaf81e935 100644 --- a/public/app/features/plugins/extensions/validators.ts +++ b/public/app/features/plugins/extensions/validators.ts @@ -5,6 +5,7 @@ import type { PluginContextType, PluginExtensionAddedComponentConfig, PluginExtensionExposedComponentConfig, + PluginExtensionAddedFunctionConfig, } from '@grafana/data'; import { PluginAddedLinksConfigureFunc, PluginExtensionPoints } from '@grafana/data/src/types/pluginExtensions'; import { config, isPluginExtensionLink } from '@grafana/runtime'; @@ -160,6 +161,38 @@ export const isAddedLinkMetaInfoMissing = ( return false; }; +export const isAddedFunctionMetaInfoMissing = ( + pluginId: string, + metaInfo: PluginExtensionAddedFunctionConfig, + log: ExtensionsLog +) => { + const logPrefix = 'Could not register function extension. Reason:'; + const app = config.apps[pluginId]; + const pluginJsonMetaInfo = app ? app.extensions.addedFunctions.find(({ title }) => title === metaInfo.title) : null; + + if (!app) { + log.error(`${logPrefix} ${errors.APP_NOT_FOUND(pluginId)}`); + return true; + } + + if (!pluginJsonMetaInfo) { + log.error(`${logPrefix} ${errors.ADDED_FUNCTION_META_INFO_MISSING}`); + return true; + } + + const targets = Array.isArray(metaInfo.targets) ? metaInfo.targets : [metaInfo.targets]; + if (!targets.every((target) => pluginJsonMetaInfo.targets.includes(target))) { + log.error(`${logPrefix} ${errors.TARGET_NOT_MATCHING_META_INFO}`); + return true; + } + + if (pluginJsonMetaInfo.description !== metaInfo.description) { + log.warning(errors.DESCRIPTION_NOT_MATCHING_META_INFO); + } + + return false; +}; + export const isAddedComponentMetaInfoMissing = ( pluginId: string, metaInfo: PluginExtensionAddedComponentConfig, diff --git a/public/app/features/plugins/importPanelPlugin.ts b/public/app/features/plugins/importPanelPlugin.ts index 45732de7fb1..f6d9c1ac2a6 100644 --- a/public/app/features/plugins/importPanelPlugin.ts +++ b/public/app/features/plugins/importPanelPlugin.ts @@ -82,7 +82,6 @@ function getPanelPlugin(meta: PanelPluginMeta): Promise { if (!plugin.panel && plugin.angularPanelCtrl) { plugin.panel = getAngularPanelReactWrapper(plugin); } - return plugin; }) .catch((err) => { diff --git a/public/app/features/plugins/loader/sharedDependencies.ts b/public/app/features/plugins/loader/sharedDependencies.ts index dbb8ed8692f..7059274c717 100644 --- a/public/app/features/plugins/loader/sharedDependencies.ts +++ b/public/app/features/plugins/loader/sharedDependencies.ts @@ -18,6 +18,7 @@ import { appEvents, contextSrv } from 'app/core/core'; import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv'; import impressionSrv from 'app/core/services/impression_srv'; import TimeSeries from 'app/core/time_series2'; +import { arrayMove } from 'app/core/utils/arrayMove'; import * as flatten from 'app/core/utils/flatten'; import kbn from 'app/core/utils/kbn'; import * as ticks from 'app/core/utils/ticks'; @@ -90,7 +91,8 @@ export const sharedDependenciesMap = { __useDefault: true, }, ...jQueryFlotDeps, - lodash: () => import('lodash').then((module) => ({ ...module, __useDefault: true })), + // add move to lodash for backward compatabilty with plugins + lodash: () => import('lodash').then((module) => ({ ...module, move: arrayMove, __useDefault: true })), moment: () => import('moment').then((module) => ({ ...module, __useDefault: true })), prismjs: () => import('prismjs'), react: () => import('react'), diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index aa20ae5881f..98915c40f6d 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -13,7 +13,12 @@ import { DataQuery } from '@grafana/schema'; import { GenericDataSourcePlugin } from '../datasources/types'; import builtInPlugins from './built_in_plugins'; -import { addedComponentsRegistry, addedLinksRegistry, exposedComponentsRegistry } from './extensions/registry/setup'; +import { + addedComponentsRegistry, + addedFunctionsRegistry, + addedLinksRegistry, + exposedComponentsRegistry, +} from './extensions/registry/setup'; import { getPluginFromCache, registerPluginInCache } from './loader/cache'; // SystemJS has to be imported before the sharedDependenciesMap import { SystemJS } from './loader/systemjs'; @@ -153,7 +158,6 @@ export function importDataSourcePlugin(meta: DataSourcePluginMeta): Promise, @@ -205,6 +209,10 @@ export async function importAppPlugin(meta: PluginMeta): Promise { pluginId, configs: plugin.addedLinkConfigs || [], }); + addedFunctionsRegistry.register({ + pluginId, + configs: plugin.addedFunctionConfigs || [], + }); importedAppPlugins[pluginId] = plugin; diff --git a/public/app/features/provisioning/ConfigForm.tsx b/public/app/features/provisioning/ConfigForm.tsx index 00d8b338ba0..b9a9d061259 100644 --- a/public/app/features/provisioning/ConfigForm.tsx +++ b/public/app/features/provisioning/ConfigForm.tsx @@ -25,7 +25,7 @@ import { useCreateOrUpdateRepository } from './hooks'; import { RepositoryFormData, WorkflowOption } from './types'; import { dataToSpec, specToData } from './utils/data'; -const typeOptions = ['GitHub', 'Local', 'S3'].map((label) => ({ label, value: label.toLowerCase() })); +const typeOptions = ['GitHub', 'Local'].map((label) => ({ label, value: label.toLowerCase() })); const targetOptions = [ { value: 'instance', label: 'Entire instance' }, { value: 'folder', label: 'Managed folder' }, @@ -94,8 +94,13 @@ export function ConfigForm({ data }: ConfigFormProps) { } }, [request.isSuccess, reset, getValues, navigate]); - const onSubmit = (data: RepositoryFormData) => { - const spec = dataToSpec(data); + const onSubmit = (form: RepositoryFormData) => { + const spec = dataToSpec(form); + if (spec.github) { + spec.github.token = form.token || data?.spec?.github?.token; + // If we're still keeping this as GitHub, persist the old token. If we set a new one, it'll be re-encrypted into here. + spec.github.encryptedToken = data?.spec?.github?.encryptedToken; + } submitData(spec); }; @@ -196,16 +201,6 @@ export function ConfigForm({ data }: ConfigFormProps) { )} - {type === 's3' && ( -
      - - - - - - -
      - )}
      @@ -228,7 +223,7 @@ export function ConfigForm({ data }: ConfigFormProps) { /> - +
      diff --git a/public/app/features/provisioning/ExportToRepository.tsx b/public/app/features/provisioning/ExportToRepository.tsx index 81a549c1f2b..562d3594eb8 100644 --- a/public/app/features/provisioning/ExportToRepository.tsx +++ b/public/app/features/provisioning/ExportToRepository.tsx @@ -1,7 +1,6 @@ -import { Controller, useForm } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; import { Box, Button, Field, FieldSet, Input, Stack, Switch, Text } from '@grafana/ui'; -import { FolderPicker } from 'app/core/components/Select/FolderPicker'; import ProgressBar from './ProgressBar'; import { Repository, useCreateRepositoryExportMutation, useListJobQuery, ExportJobOptions } from './api'; @@ -14,7 +13,7 @@ export function ExportToRepository({ repo }: Props) { const [exportRepo, exportQuery] = useCreateRepositoryExportMutation(); const exportName = exportQuery.data?.metadata?.name; - const { register, control, formState, handleSubmit } = useForm({ + const { register, formState, handleSubmit } = useForm({ defaultValues: { history: true, prefix: '', @@ -37,14 +36,6 @@ export function ExportToRepository({ repo }: Props) {
      - - } - /> - - {isGit && ( diff --git a/public/app/features/provisioning/RecentJobs.tsx b/public/app/features/provisioning/RecentJobs.tsx index ceed8ebe607..15266445f75 100644 --- a/public/app/features/provisioning/RecentJobs.tsx +++ b/public/app/features/provisioning/RecentJobs.tsx @@ -193,7 +193,7 @@ export function RecentJobs({ repo }: Props) { key={items?.length} data={items.slice(0, 20)} columns={jobColumns} - getRowId={(item) => item.metadata?.resourceVersion || ''} + getRowId={(item) => `${item.metadata?.name}`} renderExpandedRow={(row) => } /> )} diff --git a/public/app/features/provisioning/RepositoryListPage.tsx b/public/app/features/provisioning/RepositoryListPage.tsx index aa00bea056c..14c9f716a1e 100644 --- a/public/app/features/provisioning/RepositoryListPage.tsx +++ b/public/app/features/provisioning/RepositoryListPage.tsx @@ -11,6 +11,7 @@ import { Stack, TextLink, Text, + Alert, } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; @@ -18,17 +19,23 @@ import { DeleteRepositoryButton } from './DeleteRepositoryButton'; import { SetupWarnings } from './SetupWarnings'; import { StatusBadge } from './StatusBadge'; import { SyncRepository } from './SyncRepository'; -import { Repository, ResourceCount } from './api'; +import { Repository, ResourceCount, useGetFrontendSettingsQuery } from './api'; import { NEW_URL, PROVISIONING_URL } from './constants'; import { useRepositoryList } from './hooks'; export default function RepositoryListPage() { const [items, isLoading] = useRepositoryList({ watch: true }); + const settings = useGetFrontendSettingsQuery(); return ( + {settings.data?.legacyStorage && ( + + Require running the onboarding wizard to convert from legacy to unified + + )} diff --git a/public/app/features/provisioning/SetupWarnings.tsx b/public/app/features/provisioning/SetupWarnings.tsx index 86db36e1a48..18989d2b92a 100644 --- a/public/app/features/provisioning/SetupWarnings.tsx +++ b/public/app/features/provisioning/SetupWarnings.tsx @@ -25,12 +25,6 @@ kubernetesFoldersServiceV2 = true # If you want easy kubectl setup development mode grafanaAPIServerEnsureKubectlAccess = true -[unified_storage.dashboards.dashboard.grafana.app] -dualWriterMode = 5 - -[unified_storage.folders.folder.grafana.app] -dualWriterMode = 5 - # For Github webhook support, you will need something like: [server] root_url = https://supreme-exact-beetle.ngrok-free.app`; diff --git a/public/app/features/provisioning/api/endpoints.gen.ts b/public/app/features/provisioning/api/endpoints.gen.ts index 72f3f7f4a36..5a3cf607fcf 100644 --- a/public/app/features/provisioning/api/endpoints.gen.ts +++ b/public/app/features/provisioning/api/endpoints.gen.ts @@ -687,6 +687,7 @@ export type JobResourceSummary = { /** No action required (useful for sync) */ noop?: number; resource?: string; + total?: number; update?: number; write?: number; }; @@ -736,9 +737,11 @@ export type JobList = { export type GitHubRepositoryConfig = { /** The branch to use in the repository. By default, this is the main branch. */ branch?: string; + /** Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted. */ + encryptedToken?: string; /** Whether we should show dashboard previews for pull requests By default, this is false (i.e. we will not create previews). */ generateDashboardPreviews?: boolean; - /** Token for accessing the repository. */ + /** Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again. */ token?: string; /** The repository URL `https://github.com/example/test`). */ url: string; @@ -748,10 +751,6 @@ export type GitHubRepositoryConfig = { export type LocalRepositoryConfig = { path?: string; }; -export type S3RepositoryConfig = { - bucket?: string; - region?: string; -}; export type SyncOptions = { /** Enabled must be saved as true before any sync job will run */ enabled: boolean; @@ -767,14 +766,12 @@ export type SyncOptions = { export type RepositorySpec = { /** Repository description */ description?: string; - /** The repository on GitHub. Mutually exclusive with local | s3 | github. */ + /** The repository on GitHub. Mutually exclusive with local | github. */ github?: GitHubRepositoryConfig; - /** The repository on the local file system. Mutually exclusive with local | s3 | github. */ + /** The repository on the local file system. Mutually exclusive with local | github. */ local?: LocalRepositoryConfig; /** ReadOnly repository does not allow any write commands */ readOnly: boolean; - /** The repository in an S3 bucket. Mutually exclusive with local | s3 | github. */ - s3?: S3RepositoryConfig; /** Sync settings -- how values are pulled from the repository into grafana */ sync: SyncOptions; /** The repository display name (shown in the UI) */ @@ -783,9 +780,8 @@ export type RepositorySpec = { Possible enum values: - `"github"` - - `"local"` - - `"s3"` */ - type: 'github' | 'local' | 's3'; + - `"local"` */ + type: 'github' | 'local'; }; export type HealthStatus = { /** When the health was checked last time */ @@ -826,6 +822,7 @@ export type SyncStatus = { state: 'error' | 'pending' | 'success' | 'working'; }; export type WebhookStatus = { + encryptedSecret?: string; id?: number; secret?: string; subscribedEvents?: string[]; @@ -1055,9 +1052,8 @@ export type RepositoryView = { Possible enum values: - `"github"` - - `"local"` - - `"s3"` */ - type: 'github' | 'local' | 's3'; + - `"local"` */ + type: 'github' | 'local'; }; export type RepositoryViewList = { /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ @@ -1065,6 +1061,8 @@ export type RepositoryViewList = { items: RepositoryView[]; /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ kind?: string; + /** The backend is using legacy storage FIXME: Not sure where this should be exposed... but we need it somewhere The UI should force the onboarding workflow when this is true */ + legacyStorage?: boolean; }; export type ResourceStats = { /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ diff --git a/public/app/features/search/page/components/columns.tsx b/public/app/features/search/page/components/columns.tsx index 05c4b2e6792..3f982658dd0 100644 --- a/public/app/features/search/page/components/columns.tsx +++ b/public/app/features/search/page/components/columns.tsx @@ -194,17 +194,34 @@ export const generateColumns = ( if (!info && p === 'general') { info = { kind: 'folder', url: '/dashboards', name: 'Dashboards' }; } - return info ? ( - - - - {info.name} - - - ) : ( - {p} - ); + if (info) { + const content = ( + <> + + + + {info.name} + + + ); + + if (info.url) { + return ( + + {content} + + ); + } + + return ( +
      + {content} +
      + ); + } + + return {p}; })}
      )} diff --git a/public/app/features/search/service/unified.test.ts b/public/app/features/search/service/unified.test.ts index e62a126f38f..8841418cd5a 100644 --- a/public/app/features/search/service/unified.test.ts +++ b/public/app/features/search/service/unified.test.ts @@ -115,9 +115,6 @@ describe('Unified Storage Searcher', () => { .mockResolvedValueOnce(mockResults) .mockResolvedValueOnce(mockFolders); - const consoleWarn = jest.fn(); - jest.spyOn(console, 'warn').mockImplementationOnce(consoleWarn); - const query: SearchQuery = { query: 'test', limit: 50, @@ -127,14 +124,15 @@ describe('Unified Storage Searcher', () => { const response = await searcher.search(query); - expect(response.view.length).toBe(1); - expect(response.view.get(0).title).toBe('DB 2'); + expect(response.view.length).toBe(2); + expect(response.view.get(0).title).toBe('DB 1'); + expect(response.view.get(0).folder).toBe('sharedwithme'); + expect(response.view.get(1).title).toBe('DB 2'); const df = response.view.dataFrame; const locationInfo = df.meta?.custom?.locationInfo; expect(locationInfo).toBeDefined(); expect(locationInfo?.folder2.name).toBe('Folder 2'); - expect(consoleWarn).toHaveBeenCalled(); expect(mockSearcher.search).toHaveBeenCalledTimes(3); }); diff --git a/public/app/features/search/service/unified.ts b/public/app/features/search/service/unified.ts index 9d942f668d3..8e270aba37b 100644 --- a/public/app/features/search/service/unified.ts +++ b/public/app/features/search/service/unified.ts @@ -204,15 +204,19 @@ export class UnifiedSearcher implements GrafanaSearcher { if (!hasMissing) { return rsp; } - // we still have results here with folders we can't find - // filter the results since we probably don't have access to that folder + const locationInfo = await this.locationInfo; - const hits = rsp.hits.filter((hit) => { - if (hit.folder === undefined || locationInfo[hit.folder] !== undefined) { - return true; + const hits = rsp.hits.map((hit) => { + if (hit.folder === undefined) { + return { ...hit, location: 'general', folder: 'general' }; } - console.warn('Dropping search hit with missing folder', hit); - return false; + + // this means user has permission to see this dashboard, but not the folder contents + if (locationInfo[hit.folder] === undefined) { + return { ...hit, location: 'sharedwithme', folder: 'sharedwithme' }; + } + + return hit; }); const totalHits = rsp.totalHits - (rsp.hits.length - hits.length); return { ...rsp, hits, totalHits }; @@ -370,6 +374,11 @@ async function loadLocationInfo(): Promise> { name: 'Dashboards', url: '/dashboards', }, // share location info with everyone + sharedwithme: { + kind: 'sharedwithme', + name: 'Shared with me', + url: '', + }, }; for (const hit of rsp.hits) { locationInfo[hit.name] = { diff --git a/public/app/features/search/service/utils.ts b/public/app/features/search/service/utils.ts index 6aeeaefba92..85c0168e480 100644 --- a/public/app/features/search/service/utils.ts +++ b/public/app/features/search/service/utils.ts @@ -49,6 +49,10 @@ export function getIconForKind(kind: string, isOpen?: boolean): IconName { return isOpen ? 'folder-open' : 'folder'; } + if (kind === 'sharedwithme') { + return 'users-alt'; + } + return 'question-circle'; } diff --git a/public/app/features/trails/DataTrail.test.tsx b/public/app/features/trails/DataTrail.test.tsx index 4d0d0af86c4..ecb928383d2 100644 --- a/public/app/features/trails/DataTrail.test.tsx +++ b/public/app/features/trails/DataTrail.test.tsx @@ -44,14 +44,6 @@ describe('DataTrail', () => { let trail: DataTrail; const preTrailUrl = '/'; - function getFilterVar() { - const variable = sceneGraph.lookupVariable(VAR_FILTERS, trail); - if (variable instanceof AdHocFiltersVariable) { - return variable; - } - throw new Error('getFilterVar failed'); - } - function getStepFilterVar(step: number) { const variable = trail.state.history.state.steps[step].trailState.$variables?.getByName(VAR_FILTERS); if (variable instanceof AdHocFiltersVariable) { @@ -226,12 +218,12 @@ describe('DataTrail', () => { }); it('Should have default empty filter', () => { - expect(getFilterVar().state.filters.length).toBe(0); + expect(getFilterVar(trail).state.filters.length).toBe(0); }); describe('And when changing the filter to zone=a', () => { beforeEach(() => { - getFilterVar().setState({ filters: [{ key: 'zone', operator: '=', value: 'a' }] }); + getFilterVar(trail).setState({ filters: [{ key: 'zone', operator: '=', value: 'a' }] }); }); it('should add history step', () => { @@ -247,8 +239,8 @@ describe('DataTrail', () => { }); it('Should have filter be updated to "zone=a"', () => { - expect(getFilterVar().state.filters[0].key).toBe('zone'); - expect(getFilterVar().state.filters[0].value).toBe('a'); + expect(getFilterVar(trail).state.filters[0].key).toBe('zone'); + expect(getFilterVar(trail).state.filters[0].value).toBe('a'); }); it('Previous history step should have empty filter', () => { @@ -274,12 +266,12 @@ describe('DataTrail', () => { }); it('Should have filters set back to empty', () => { - expect(getFilterVar().state.filters.length).toBe(0); + expect(getFilterVar(trail).state.filters.length).toBe(0); }); describe('And when changing the filter to zone=b', () => { beforeEach(() => { - getFilterVar().setState({ filters: [{ key: 'zone', operator: '=', value: 'b' }] }); + getFilterVar(trail).setState({ filters: [{ key: 'zone', operator: '=', value: 'b' }] }); }); it('should add history step', () => { @@ -295,8 +287,8 @@ describe('DataTrail', () => { }); it('Should have filter be updated to "zone=b"', () => { - expect(getFilterVar().state.filters[0].key).toBe('zone'); - expect(getFilterVar().state.filters[0].value).toBe('b'); + expect(getFilterVar(trail).state.filters[0].key).toBe('zone'); + expect(getFilterVar(trail).state.filters[0].value).toBe('b'); }); it('Parent history step 1 should still have empty filter', () => { @@ -327,7 +319,7 @@ describe('DataTrail', () => { }); it('Should have filters set back to empty', () => { - expect(getFilterVar().state.filters.length).toBe(0); + expect(getFilterVar(trail).state.filters.length).toBe(0); }); it('History step 1 should still have empty filter', () => { @@ -417,12 +409,12 @@ describe('DataTrail', () => { describe('And filter is added zone=a', () => { beforeEach(() => { - getFilterVar().setState({ filters: [{ key: 'zone', operator: '=', value: 'a' }] }); + getFilterVar(trail).setState({ filters: [{ key: 'zone', operator: '=', value: 'a' }] }); }); it('Filter of trail should be zone=a', () => { - expect(getFilterVar().state.filters[0].key).toBe('zone'); - expect(getFilterVar().state.filters[0].value).toBe('a'); + expect(getFilterVar(trail).state.filters[0].key).toBe('zone'); + expect(getFilterVar(trail).state.filters[0].value).toBe('a'); }); it('Filter of step 2 should be zone=a', () => { @@ -440,7 +432,7 @@ describe('DataTrail', () => { }); it('Filter of trail should be empty', () => { - expect(getFilterVar().state.filters.length).toBe(0); + expect(getFilterVar(trail).state.filters.length).toBe(0); }); }); }); @@ -518,14 +510,6 @@ describe('DataTrail', () => { throw new Error('getOtelGroupLeftVar failed'); } - function getFilterVar() { - const variable = sceneGraph.lookupVariable(VAR_FILTERS, trail); - if (variable instanceof AdHocFiltersVariable) { - return variable; - } - throw new Error('getFilterVar failed'); - } - beforeEach(() => { trail = new DataTrail({ nonPromotedOtelResources, @@ -540,7 +524,7 @@ describe('DataTrail', () => { it('clicking start button should start with OTel off and showing var filters', () => { trail.setState({ startButtonClicked: true }); const otelResourcesHide = getOtelResourcesVar(trail).state.hide; - const varFiltersHide = getFilterVar().state.hide; + const varFiltersHide = getFilterVar(trail).state.hide; expect(otelResourcesHide).toBe(VariableHide.hideVariable); expect(varFiltersHide).toBe(VariableHide.hideLabel); }); @@ -557,7 +541,7 @@ describe('DataTrail', () => { describe('resetting the OTel experience', () => { it('should display with hideLabel var filters and hide VAR_OTEL_AND_METRIC_FILTERS when resetting otel experience', () => { trail.resetOtelExperience(); - expect(getFilterVar().state.hide).toBe(VariableHide.hideLabel); + expect(getFilterVar(trail).state.hide).toBe(VariableHide.hideLabel); expect(getOtelAndMetricsVar(trail).state.hide).toBe(VariableHide.hideVariable); }); @@ -589,7 +573,7 @@ describe('DataTrail', () => { it('should automatically update the var filters when a promoted resource has been selected from VAR_OTEL_AND_METRICS', () => { getOtelAndMetricsVar(trail).setState({ filters: [{ key: 'promoted', operator: '=', value: 'resource' }] }); - const varFilters = getFilterVar().state.filters[0]; + const varFilters = getFilterVar(trail).state.filters[0]; expect(varFilters.key).toBe('promoted'); expect(varFilters.value).toBe('resource'); }); @@ -600,4 +584,26 @@ describe('DataTrail', () => { }); }); }); + + describe('Label filters', () => { + let trail: DataTrail; + + beforeEach(() => { + trail = new DataTrail({}); + }); + + it('should not escape regex metacharacters in label values', () => { + const filterVar = getFilterVar(trail); + filterVar.setState({ filters: [{ key: 'app', operator: '=~', value: '.*end' }] }); // matches app=frontend, app=backend, etc. + expect(filterVar.getValue()).toBe('app=~".*end"'); + }); + }); }); + +function getFilterVar(trail: DataTrail) { + const variable = sceneGraph.lookupVariable(VAR_FILTERS, trail); + if (variable instanceof AdHocFiltersVariable) { + return variable; + } + throw new Error('getFilterVar failed'); +} diff --git a/public/app/features/trails/DataTrail.tsx b/public/app/features/trails/DataTrail.tsx index fa85dd32773..aef53bad053 100644 --- a/public/app/features/trails/DataTrail.tsx +++ b/public/app/features/trails/DataTrail.tsx @@ -650,10 +650,9 @@ function getVariableSet( addFilterButtonText: 'Select resource attributes', datasource: trailDS, hide: VariableHide.hideVariable, - layout: 'vertical', + layout: 'combobox', defaultKeys: [], applyMode: 'manual', - supportsMultiValueOperators: true, allowCustomValue: true, }), new AdHocFiltersVariable({ @@ -662,13 +661,16 @@ function getVariableSet( datasource: trailDS, // default to use var filters and have otel off hide: VariableHide.hideLabel, - layout: 'vertical', + layout: 'combobox', filters: initialFilters ?? [], baseFilters: getBaseFiltersForMetric(metric), applyMode: 'manual', - // since we only support prometheus datasources, this is always true - supportsMultiValueOperators: true, allowCustomValue: true, + expressionBuilder: (filters: AdHocVariableFilter[]) => { + return [...getBaseFiltersForMetric(metric), ...filters] + .map((filter) => `${filter.key}${filter.operator}"${filter.value}"`) + .join(','); + }, }), ...getVariablesWithOtelJoinQueryConstant(otelJoinQuery ?? ''), new ConstantVariable({ @@ -686,12 +688,10 @@ function getVariableSet( addFilterButtonText: 'Filter', datasource: trailDS, hide: VariableHide.hideVariable, - layout: 'vertical', + layout: 'combobox', filters: initialFilters ?? [], baseFilters: getBaseFiltersForMetric(metric), applyMode: 'manual', - // since we only support prometheus datasources, this is always true - supportsMultiValueOperators: true, allowCustomValue: true, // skipUrlSync: true }), diff --git a/public/app/features/trails/banners/NativeHistogramBanner.test.tsx b/public/app/features/trails/banners/NativeHistogramBanner.test.tsx index d5191037dae..23d4d4f21d1 100644 --- a/public/app/features/trails/banners/NativeHistogramBanner.test.tsx +++ b/public/app/features/trails/banners/NativeHistogramBanner.test.tsx @@ -51,4 +51,11 @@ describe('NativeHistogramBanner', () => { fireEvent.click(histogramButton); expect(mockTrail.publishEvent).toHaveBeenCalledWith(new MetricSelectedEvent('histogram1'), true); }); + + test('Set that the banner has been shown in local storage when a user closes the banner', () => { + render(); + // click the button with aria label "Close alert" + fireEvent.click(screen.getByLabelText('Close alert')); + expect(localStorage.getItem('nativeHistogramBanner')).toBe('true'); + }); }); diff --git a/public/app/features/trails/banners/NativeHistogramBanner.tsx b/public/app/features/trails/banners/NativeHistogramBanner.tsx index 68ca35c0c67..d32375e054e 100644 --- a/public/app/features/trails/banners/NativeHistogramBanner.tsx +++ b/public/app/features/trails/banners/NativeHistogramBanner.tsx @@ -21,7 +21,7 @@ export function NativeHistogramBanner(props: NativeHistogramInfoProps) { const [showHistogramExamples, setShowHistogramExamples] = useState(false); const styles = useStyles2(getStyles, 0); - if (!histogramsLoaded || nativeHistograms.length === 0 || !histogramMessage) { + if (bannerHasBeenShown() || !histogramsLoaded || nativeHistograms.length === 0 || !histogramMessage) { return null; } @@ -32,6 +32,8 @@ export function NativeHistogramBanner(props: NativeHistogramInfoProps) { title={'Native Histogram Support'} severity={'info'} onRemove={() => { + // when a user explicitly closes the banner, save that it has been closed in local storage to not show again + setBannerHasBeenShown(); setHistogramMessage(false); }} className={styles.banner} @@ -275,3 +277,11 @@ function getStyles(theme: GrafanaTheme2, _chromeHeaderHeight: number) { }), }; } + +export function setBannerHasBeenShown() { + localStorage.setItem('nativeHistogramBanner', 'true'); +} + +export function bannerHasBeenShown() { + return localStorage.getItem('nativeHistogramBanner') ?? false; +} diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/dashboards/streaming.json b/public/app/plugins/datasource/grafana-testdata-datasource/dashboards/streaming.json index 8498d79f2f1..b5bd877bcaa 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/dashboards/streaming.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/dashboards/streaming.json @@ -199,8 +199,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Simple Streaming Example", diff --git a/public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json b/public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json index 94e8e685d45..418ba46835f 100644 --- a/public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json +++ b/public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json @@ -154,7 +154,6 @@ "now": true, "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], "status": "Stable", - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"], "type": "timepicker" }, "templating": { diff --git a/public/app/plugins/datasource/graphite/dashboards/metrictank.json b/public/app/plugins/datasource/graphite/dashboards/metrictank.json index 18b2e3939ff..b70ad4d58ec 100644 --- a/public/app/plugins/datasource/graphite/dashboards/metrictank.json +++ b/public/app/plugins/datasource/graphite/dashboards/metrictank.json @@ -4771,7 +4771,6 @@ "now": true, "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], "status": "Stable", - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"], "type": "timepicker" }, "timezone": "utc", diff --git a/public/app/plugins/datasource/prometheus/dashboards/grafana_stats.json b/public/app/plugins/datasource/prometheus/dashboards/grafana_stats.json index a121de7fe5a..d465f31c1f5 100644 --- a/public/app/plugins/datasource/prometheus/dashboards/grafana_stats.json +++ b/public/app/plugins/datasource/prometheus/dashboards/grafana_stats.json @@ -1177,8 +1177,7 @@ "to": "now" }, "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Grafana metrics", diff --git a/public/app/plugins/datasource/prometheus/dashboards/prometheus_2_stats.json b/public/app/plugins/datasource/prometheus/dashboards/prometheus_2_stats.json index 3d4cea64f05..57e2bb5e47d 100644 --- a/public/app/plugins/datasource/prometheus/dashboards/prometheus_2_stats.json +++ b/public/app/plugins/datasource/prometheus/dashboards/prometheus_2_stats.json @@ -1393,8 +1393,7 @@ }, "timepicker": { "now": true, - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "browser", "title": "Prometheus 2.0 Stats", diff --git a/public/app/plugins/datasource/prometheus/dashboards/prometheus_stats.json b/public/app/plugins/datasource/prometheus/dashboards/prometheus_stats.json index 9169006b895..383f75c9011 100644 --- a/public/app/plugins/datasource/prometheus/dashboards/prometheus_stats.json +++ b/public/app/plugins/datasource/prometheus/dashboards/prometheus_stats.json @@ -824,8 +824,7 @@ }, "timepicker": { "now": true, - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "browser", "title": "Prometheus Stats", diff --git a/public/app/plugins/panel/barchart/BarChartPanel.tsx b/public/app/plugins/panel/barchart/BarChartPanel.tsx index dba432c57d0..424bbd16de0 100644 --- a/public/app/plugins/panel/barchart/BarChartPanel.tsx +++ b/public/app/plugins/panel/barchart/BarChartPanel.tsx @@ -157,8 +157,8 @@ export const BarChartPanel = (props: PanelProps) => { hoverMode={ options.tooltip.mode === TooltipDisplayMode.Single ? TooltipHoverMode.xOne : TooltipHoverMode.xAll } - getDataLinks={(seriesIdx: number, dataIdx: number) => - vizSeries[0].fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? [] + getDataLinks={(seriesIdx, dataIdx) => + vizSeries[0].fields[seriesIdx].getLinks?.({ valueRowIndex: dataIdx }) ?? [] } render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync, dataLinks) => { return ( diff --git a/public/app/plugins/panel/barchart/module.tsx b/public/app/plugins/panel/barchart/module.tsx index 5b5ae542531..c31c17f5a81 100644 --- a/public/app/plugins/panel/barchart/module.tsx +++ b/public/app/plugins/panel/barchart/module.tsx @@ -32,6 +32,14 @@ export const plugin = new PanelPlugin(BarChartPanel) mode: FieldColorModeId.PaletteClassic, }, }, + [FieldConfigProperty.Links]: { + settings: { + showOneClick: true, + }, + }, + [FieldConfigProperty.Actions]: { + hideFromDefaults: false, + }, }, useCustomConfig: (builder) => { const cfg = defaultFieldConfig; diff --git a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx index 4ed97d59456..16eeeae3ca6 100644 --- a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx +++ b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx @@ -282,8 +282,8 @@ export const CandlestickPanel = ({ clientZoom={true} syncMode={cursorSync} syncScope={eventsScope} - getDataLinks={(seriesIdx: number, dataIdx: number) => - alignedFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? [] + getDataLinks={(seriesIdx, dataIdx) => + alignedFrame.fields[seriesIdx].getLinks?.({ valueRowIndex: dataIdx }) ?? [] } render={(u, dataIdxs, seriesIdx, isPinned = false, dismiss, timeRange2, viaSync, dataLinks) => { if (enableAnnotationCreation && timeRange2 != null) { diff --git a/public/app/plugins/panel/canvas/CanvasPanel.tsx b/public/app/plugins/panel/canvas/CanvasPanel.tsx index cd48d2ed8dd..4db38108706 100644 --- a/public/app/plugins/panel/canvas/CanvasPanel.tsx +++ b/public/app/plugins/panel/canvas/CanvasPanel.tsx @@ -85,6 +85,7 @@ export class CanvasPanel extends Component { this.scene.setBackgroundCallback = this.openSetBackground; this.scene.tooltipCallback = this.tooltipCallback; this.scene.moveableActionCallback = this.moveableActionCallback; + this.scene.actionConfirmationCallback = this.actionConfirmationCallback; this.subs.add( this.props.eventBus.subscribe(PanelEditEnteredEvent, (evt: PanelEditEnteredEvent) => { @@ -302,6 +303,10 @@ export class CanvasPanel extends Component { this.forceUpdate(); }; + actionConfirmationCallback = () => { + this.forceUpdate(); + }; + closeInlineEdit = () => { this.setState({ openInlineEdit: false }); isInlineEditOpen = false; diff --git a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx index 39390afefdf..f2a5959edc6 100644 --- a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx +++ b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx @@ -88,7 +88,6 @@ export const CanvasTooltip = ({ scene }: Props) => { } }); } - // --------- if (scene.data?.series) { getElementFields(scene.data?.series, element.options).forEach((field) => { diff --git a/public/app/plugins/panel/canvas/editor/element/DataLinksEditor.tsx b/public/app/plugins/panel/canvas/editor/element/DataLinksEditor.tsx index 9849b35d368..16af0cd074b 100644 --- a/public/app/plugins/panel/canvas/editor/element/DataLinksEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/element/DataLinksEditor.tsx @@ -20,7 +20,7 @@ export function DataLinksEditor({ value, onChange, item, context }: Props) { }} getSuggestions={() => (context.getSuggestions ? context.getSuggestions(VariableSuggestionsScope.Values) : [])} data={[]} - showOneClick={false} + showOneClick={true} /> ); } diff --git a/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx b/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx index 5e3a545b87b..78562e83e88 100644 --- a/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx @@ -1,6 +1,5 @@ -import { capitalize, get as lodashGet } from 'lodash'; +import { get as lodashGet } from 'lodash'; -import { OneClickMode } from '@grafana/data'; import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/src/utils/OptionsUIBuilders'; import { config } from '@grafana/runtime'; import { CanvasElementOptions } from 'app/features/canvas/element'; @@ -123,36 +122,10 @@ export function getElementEditor(opts: CanvasEditorOptions): NestedPanelOptions< optionBuilder.addBorder(builder, ctx); } - const oneClickModeOptions: Array<{ - value: OneClickMode; - label: string; - }> = [ - { value: OneClickMode.Off, label: capitalize(OneClickMode.Off) }, - { value: OneClickMode.Link, label: capitalize(OneClickMode.Link) }, - ]; - - let oneClickCategory = 'Data links'; - let oneClickDescription = 'When enabled, a single click opens the first link'; - - if (actionsEnabled) { - oneClickModeOptions.push({ value: OneClickMode.Action, label: capitalize(OneClickMode.Action) }); - oneClickCategory += ' and actions'; - oneClickDescription += ' or action'; - } - - builder.addRadio({ - category: [oneClickCategory], - path: 'oneClickMode', - name: 'One-click', - description: oneClickDescription, - settings: { - options: oneClickModeOptions, - }, - defaultValue: OneClickMode.Off, - }); - optionBuilder.addDataLinks(builder, ctx); - optionBuilder.addActions(builder, ctx); + if (actionsEnabled) { + optionBuilder.addActions(builder, ctx); + } }, }; } diff --git a/public/app/plugins/panel/canvas/migrations.test.ts b/public/app/plugins/panel/canvas/migrations.test.ts index 0a1354b2ae0..2c66529bd1c 100644 --- a/public/app/plugins/panel/canvas/migrations.test.ts +++ b/public/app/plugins/panel/canvas/migrations.test.ts @@ -1,4 +1,4 @@ -import { OneClickMode, PanelModel } from '@grafana/data'; +import { PanelModel } from '@grafana/data'; import { canvasMigrationHandler } from './migrations'; @@ -20,6 +20,16 @@ describe('Canvas migration', () => { }, }, ], + links: [ + { + title: 'Link1', + url: 'www.link1.com', + }, + { + title: 'Link2', + url: 'www.link2.com', + }, + ], }, ], }, @@ -29,7 +39,7 @@ describe('Canvas migration', () => { panel.options = canvasMigrationHandler(panel); - expect(panel.options.root.elements[0].oneClickMode).toBe(OneClickMode.Link); + expect(panel.options.root.elements[0].links[0].oneClick).toBe(true); expect(panel.options.root.elements[0].actions[0].fetch.url).toBe('http://test.com'); }); }); diff --git a/public/app/plugins/panel/canvas/migrations.ts b/public/app/plugins/panel/canvas/migrations.ts index f287556a2f7..479a540bff7 100644 --- a/public/app/plugins/panel/canvas/migrations.ts +++ b/public/app/plugins/panel/canvas/migrations.ts @@ -46,12 +46,6 @@ export const canvasMigrationHandler = (panel: PanelModel): Partial => { const root = panel.options?.root; if (root?.elements) { for (const element of root.elements) { - // migrate oneClickLinks to oneClickMode - if (element.oneClickLinks) { - element.oneClickMode = OneClickMode.Link; - delete element.oneClickLinks; - } - // migrate action options to new format (fetch) if (element.actions) { for (const action of element.actions) { @@ -65,5 +59,22 @@ export const canvasMigrationHandler = (panel: PanelModel): Partial => { } } + // migrate oneClickMode to first link/action oneClick + if (parseFloat(pluginVersion) <= 11.6) { + const root = panel.options?.root; + if (root?.elements) { + for (const element of root.elements) { + if (element.oneClickMode === OneClickMode.Link || element.oneClickLinks) { + element.links[0].oneClick = true; + } else if (element.oneClickMode === OneClickMode.Action) { + element.actions[0].oneClick = true; + } + + delete element.oneClickMode; + delete element.oneClickLinks; + } + } + } + return panel.options; }; diff --git a/public/app/plugins/panel/canvas/module.tsx b/public/app/plugins/panel/canvas/module.tsx index 19b9ce04ded..011c9e78682 100644 --- a/public/app/plugins/panel/canvas/module.tsx +++ b/public/app/plugins/panel/canvas/module.tsx @@ -59,7 +59,14 @@ export const plugin = new PanelPlugin(CanvasPanel) }, }, [FieldConfigProperty.Links]: { - hideFromDefaults: true, + settings: { + showOneClick: false, + }, + }, + [FieldConfigProperty.Actions]: { + settings: { + showOneClick: false, + }, }, }, }) diff --git a/public/app/plugins/panel/heatmap/module.tsx b/public/app/plugins/panel/heatmap/module.tsx index f710858e0ec..6ec45e27d9a 100644 --- a/public/app/plugins/panel/heatmap/module.tsx +++ b/public/app/plugins/panel/heatmap/module.tsx @@ -23,6 +23,13 @@ import { Options, defaultOptions, HeatmapColorMode, HeatmapColorScale } from './ export const plugin = new PanelPlugin(HeatmapPanel) .useFieldConfig({ disableStandardOptions: Object.values(FieldConfigProperty).filter((v) => v !== FieldConfigProperty.Links), + standardOptions: { + [FieldConfigProperty.Links]: { + settings: { + showOneClick: true, + }, + }, + }, useCustomConfig: (builder) => { builder.addCustomEditor({ id: 'scaleDistribution', diff --git a/public/app/plugins/panel/histogram/module.tsx b/public/app/plugins/panel/histogram/module.tsx index c73870cf739..3be418ceb87 100644 --- a/public/app/plugins/panel/histogram/module.tsx +++ b/public/app/plugins/panel/histogram/module.tsx @@ -81,6 +81,11 @@ export const plugin = new PanelPlugin(HistogramPanel) mode: FieldColorModeId.PaletteClassic, }, }, + [FieldConfigProperty.Links]: { + settings: { + showOneClick: true, + }, + }, }, useCustomConfig: (builder) => { const cfg = defaultFieldConfig; diff --git a/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx b/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx index 9524beba351..7a23e4a6898 100644 --- a/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx +++ b/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx @@ -107,8 +107,8 @@ export const StateTimelinePanel = ({ queryZoom={onChangeTimeRange} syncMode={cursorSync} syncScope={eventsScope} - getDataLinks={(seriesIdx: number, dataIdx: number) => - alignedFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? [] + getDataLinks={(seriesIdx, dataIdx) => + alignedFrame.fields[seriesIdx].getLinks?.({ valueRowIndex: dataIdx }) ?? [] } render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync, dataLinks) => { if (enableAnnotationCreation && timeRange2 != null) { diff --git a/public/app/plugins/panel/state-timeline/StateTimelineTooltip2.tsx b/public/app/plugins/panel/state-timeline/StateTimelineTooltip2.tsx index a2f2665a11d..b400b9e6e73 100644 --- a/public/app/plugins/panel/state-timeline/StateTimelineTooltip2.tsx +++ b/public/app/plugins/panel/state-timeline/StateTimelineTooltip2.tsx @@ -68,12 +68,16 @@ export const StateTimelineTooltip2 = ({ let footer: ReactNode; - if (isPinned && seriesIdx != null) { + if (seriesIdx != null) { const field = series.fields[seriesIdx]; - const dataIdx = dataIdxs[seriesIdx]!; - const actions = getFieldActions(series, field, replaceVariables!, dataIdx); + const hasOneClickLink = dataLinks.some((dataLink) => dataLink.oneClick === true); - footer = ; + if (isPinned || hasOneClickLink) { + const dataIdx = dataIdxs[seriesIdx]!; + const actions = getFieldActions(series, field, replaceVariables!, dataIdx); + + footer = ; + } } const headerItem: VizTooltipItem = { diff --git a/public/app/plugins/panel/state-timeline/module.tsx b/public/app/plugins/panel/state-timeline/module.tsx index 024d1e883ac..7d84934e74a 100644 --- a/public/app/plugins/panel/state-timeline/module.tsx +++ b/public/app/plugins/panel/state-timeline/module.tsx @@ -29,6 +29,14 @@ export const plugin = new PanelPlugin(StateTimelinePanel) mode: FieldColorModeId.ContinuousGrYlRd, }, }, + [FieldConfigProperty.Links]: { + settings: { + showOneClick: true, + }, + }, + [FieldConfigProperty.Actions]: { + hideFromDefaults: false, + }, }, useCustomConfig: (builder) => { builder diff --git a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx index 8ed1a266a27..1f410d1578c 100644 --- a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx +++ b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx @@ -119,8 +119,8 @@ export const StatusHistoryPanel = ({ queryZoom={onChangeTimeRange} syncMode={cursorSync} syncScope={eventsScope} - getDataLinks={(seriesIdx: number, dataIdx: number) => - alignedFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? [] + getDataLinks={(seriesIdx, dataIdx) => + alignedFrame.fields[seriesIdx].getLinks?.({ valueRowIndex: dataIdx }) ?? [] } render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync, dataLinks) => { if (enableAnnotationCreation && timeRange2 != null) { diff --git a/public/app/plugins/panel/status-history/module.tsx b/public/app/plugins/panel/status-history/module.tsx index 722f8936199..d556c52ccc0 100644 --- a/public/app/plugins/panel/status-history/module.tsx +++ b/public/app/plugins/panel/status-history/module.tsx @@ -17,6 +17,14 @@ export const plugin = new PanelPlugin(StatusHistoryPanel) mode: FieldColorModeId.Thresholds, }, }, + [FieldConfigProperty.Links]: { + settings: { + showOneClick: true, + }, + }, + [FieldConfigProperty.Actions]: { + hideFromDefaults: false, + }, }, useCustomConfig: (builder) => { builder diff --git a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx index c83af4b2703..f8866b705f3 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx @@ -106,8 +106,8 @@ export const TimeSeriesPanel = ({ clientZoom={true} syncMode={cursorSync} syncScope={eventsScope} - getDataLinks={(seriesIdx: number, dataIdx: number) => - alignedFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? [] + getDataLinks={(seriesIdx, dataIdx) => + alignedFrame.fields[seriesIdx].getLinks?.({ valueRowIndex: dataIdx }) ?? [] } render={(u, dataIdxs, seriesIdx, isPinned = false, dismiss, timeRange2, viaSync, dataLinks) => { if (enableAnnotationCreation && timeRange2 != null) { diff --git a/public/app/plugins/panel/timeseries/config.ts b/public/app/plugins/panel/timeseries/config.ts index f749b1f6e6c..39e6a283d01 100644 --- a/public/app/plugins/panel/timeseries/config.ts +++ b/public/app/plugins/panel/timeseries/config.ts @@ -59,6 +59,14 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig, isTime = true): SetFi mode: FieldColorModeId.PaletteClassic, }, }, + [FieldConfigProperty.Links]: { + settings: { + showOneClick: true, + }, + }, + [FieldConfigProperty.Actions]: { + hideFromDefaults: false, + }, }, useCustomConfig: (builder) => { builder diff --git a/public/app/plugins/panel/trend/TrendPanel.tsx b/public/app/plugins/panel/trend/TrendPanel.tsx index 67dcf81bb4e..1fe5ced2a49 100644 --- a/public/app/plugins/panel/trend/TrendPanel.tsx +++ b/public/app/plugins/panel/trend/TrendPanel.tsx @@ -119,8 +119,8 @@ export const TrendPanel = ({ hoverMode={ options.tooltip.mode === TooltipDisplayMode.Single ? TooltipHoverMode.xOne : TooltipHoverMode.xAll } - getDataLinks={(seriesIdx: number, dataIdx: number) => - alignedDataFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? [] + getDataLinks={(seriesIdx, dataIdx) => + alignedDataFrame.fields[seriesIdx].getLinks?.({ valueRowIndex: dataIdx }) ?? [] } render={(u, dataIdxs, seriesIdx, isPinned = false, dismiss, timeRange, viaSync, dataLinks) => { return ( diff --git a/public/app/plugins/panel/xychart/XYChartPanel.tsx b/public/app/plugins/panel/xychart/XYChartPanel.tsx index f22fd8248e1..25ef452dc6b 100644 --- a/public/app/plugins/panel/xychart/XYChartPanel.tsx +++ b/public/app/plugins/panel/xychart/XYChartPanel.tsx @@ -17,6 +17,8 @@ import { import { TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2'; import { getDisplayValuesForCalcs } from '@grafana/ui/src/components/uPlot/utils'; +import { getDataLinks } from '../status-history/utils'; + import { XYChartTooltip } from './XYChartTooltip'; import { Options } from './panelcfg.gen'; import { prepConfig } from './scatter'; @@ -113,7 +115,11 @@ export const XYChartPanel2 = (props: Props2) => { { + getDataLinks={(seriesIdx, dataIdx) => { + const xySeries = series[seriesIdx - 1]; + return getDataLinks(xySeries.y.field, dataIdx); + }} + render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync, dataLinks) => { return ( { isPinned={isPinned} seriesIdx={seriesIdx!} replaceVariables={props.replaceVariables} + dataLinks={dataLinks} /> ); }} diff --git a/public/app/plugins/panel/xychart/XYChartTooltip.tsx b/public/app/plugins/panel/xychart/XYChartTooltip.tsx index abd3cb32bc2..08bd3903bad 100644 --- a/public/app/plugins/panel/xychart/XYChartTooltip.tsx +++ b/public/app/plugins/panel/xychart/XYChartTooltip.tsx @@ -1,6 +1,6 @@ import { ReactNode } from 'react'; -import { DataFrame, InterpolateFunction } from '@grafana/data'; +import { DataFrame, InterpolateFunction, LinkModel } from '@grafana/data'; import { alpha } from '@grafana/data/src/themes/colorManipulator'; import { VizTooltipContent } from '@grafana/ui/src/components/VizTooltip/VizTooltipContent'; import { VizTooltipFooter } from '@grafana/ui/src/components/VizTooltip/VizTooltipFooter'; @@ -8,7 +8,7 @@ import { VizTooltipHeader } from '@grafana/ui/src/components/VizTooltip/VizToolt import { VizTooltipWrapper } from '@grafana/ui/src/components/VizTooltip/VizTooltipWrapper'; import { ColorIndicator, VizTooltipItem } from '@grafana/ui/src/components/VizTooltip/types'; -import { getDataLinks, getFieldActions } from '../status-history/utils'; +import { getFieldActions } from '../status-history/utils'; import { XYSeries } from './types2'; import { fmt } from './utils'; @@ -21,6 +21,7 @@ export interface Props { data: DataFrame[]; xySeries: XYSeries[]; replaceVariables: InterpolateFunction; + dataLinks: LinkModel[]; } function stripSeriesName(fieldName: string, seriesName: string) { @@ -31,7 +32,16 @@ function stripSeriesName(fieldName: string, seriesName: string) { return fieldName; } -export const XYChartTooltip = ({ dataIdxs, seriesIdx, data, xySeries, dismiss, isPinned, replaceVariables }: Props) => { +export const XYChartTooltip = ({ + dataIdxs, + seriesIdx, + data, + xySeries, + dismiss, + isPinned, + replaceVariables, + dataLinks, +}: Props) => { const rowIndex = dataIdxs.find((idx) => idx !== null)!; const series = xySeries[seriesIdx! - 1]; @@ -93,12 +103,15 @@ export const XYChartTooltip = ({ dataIdxs, seriesIdx, data, xySeries, dismiss, i let footer: ReactNode; - if (isPinned && seriesIdx != null) { - const links = getDataLinks(yField, rowIndex); - const yFieldFrame = data.find((frame) => frame.fields.includes(yField))!; - const actions = getFieldActions(yFieldFrame, yField, replaceVariables, rowIndex); + if (seriesIdx != null) { + const hasOneClickLink = dataLinks?.some((dataLink) => dataLink.oneClick === true); - footer = ; + if (isPinned || hasOneClickLink) { + const yFieldFrame = data.find((frame) => frame.fields.includes(yField))!; + const actions = getFieldActions(yFieldFrame, yField, replaceVariables, rowIndex); + + footer = ; + } } return ( diff --git a/public/app/plugins/panel/xychart/config.ts b/public/app/plugins/panel/xychart/config.ts index c3e958c8c12..bff3ffb8774 100644 --- a/public/app/plugins/panel/xychart/config.ts +++ b/public/app/plugins/panel/xychart/config.ts @@ -35,7 +35,6 @@ export function getScatterFieldConfig(cfg: FieldConfig): SetFieldConfigOptionsAr [FieldConfigProperty.DisplayName]: { hideFromDefaults: true, }, - // TODO: this still leaves Color series by: [ Last | Min | Max ] // because item.settings?.bySeriesSupport && colorMode.isByValue [FieldConfigProperty.Color]: { @@ -48,6 +47,14 @@ export function getScatterFieldConfig(cfg: FieldConfig): SetFieldConfigOptionsAr mode: FieldColorModeId.PaletteClassic, }, }, + [FieldConfigProperty.Links]: { + settings: { + showOneClick: true, + }, + }, + [FieldConfigProperty.Actions]: { + hideFromDefaults: false, + }, }, useCustomConfig: (builder) => { diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index 62f6dc3ce83..0847577522f 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -97,8 +97,10 @@ export function getAppRoutes(): RouteDescriptor[] { path: '/dashboard-solo/:type/:slug', routeName: DashboardRoutes.Normal, chromeless: true, - component: SafeDynamicImport( - () => import(/* webpackChunkName: "SoloPanelPage" */ '../features/dashboard/containers/SoloPanelPage') + component: SafeDynamicImport(() => + config.featureToggles.dashboardSceneSolo + ? import(/* webpackChunkName: "SoloPanelPage" */ '../features/dashboard-scene/solo/SoloPanelPage') + : import(/* webpackChunkName: "SoloPanelPageOld" */ '../features/dashboard/containers/SoloPanelPage') ), }, { diff --git a/public/dashboards/default.json b/public/dashboards/default.json index c59f98ec1dd..2b2ef5d1c9b 100644 --- a/public/dashboards/default.json +++ b/public/dashboards/default.json @@ -131,7 +131,6 @@ "collapse": false, "enable": true, "status": "Stable", - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"], "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], "now": true } diff --git a/public/dashboards/home.json b/public/dashboards/home.json index 718b6b52079..8d5cfd00e52 100644 --- a/public/dashboards/home.json +++ b/public/dashboards/home.json @@ -68,7 +68,6 @@ "timepicker": { "hidden": true, "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"], "type": "timepicker" }, "timezone": "browser", diff --git a/public/dashboards/scripted.js b/public/dashboards/scripted.js index daa4c4efb70..03de55bff44 100644 --- a/public/dashboards/scripted.js +++ b/public/dashboards/scripted.js @@ -13,16 +13,11 @@ 'use strict'; -// accessible variables in this scope -let window, document, $, jQuery, moment, kbn; +// accessible variables in this scope: window, document, $, jQuery, moment, kbn; // Setup some variables let dashboard; -// All url parameters are available via the ARGS object -// eslint-disable-next-line no-redeclare -let ARGS; - // Initialize a skeleton with nothing but a rows array and service object dashboard = { rows: [], @@ -56,6 +51,7 @@ for (let i = 0; i < rows; i++) { height: '300px', panels: [ { + id: 1, title: 'Events', type: 'graph', span: 12, @@ -63,23 +59,17 @@ for (let i = 0; i < rows; i++) { linewidth: 2, targets: [ { - target: "randomWalk('" + seriesName + "')", + scenarioId: 'random_walk', + refId: 'A', + seriesCount: 1, + alias: seriesName, }, { - target: "randomWalk('random walk2')", + scenarioId: 'random_walk', + refId: 'B', + seriesCount: 1, }, ], - seriesOverrides: [ - { - alias: '/random/', - yaxis: 2, - fill: 0, - linewidth: 5, - }, - ], - tooltip: { - shared: true, - }, }, ], }); diff --git a/public/dashboards/scripted_async.js b/public/dashboards/scripted_async.js index 98fb144d243..d13a80eb034 100644 --- a/public/dashboards/scripted_async.js +++ b/public/dashboards/scripted_async.js @@ -17,7 +17,7 @@ 'use strict'; // accessible variables in this scope -let window, document, ARGS, $, jQuery, moment, kbn; +// let window, document, ARGS, $, jQuery, moment, kbn; return function (callback) { // Setup some variables @@ -60,6 +60,7 @@ return function (callback) { height: '300px', panels: [ { + id: 1, title: 'Async dashboard test', type: 'text', span: 12, diff --git a/public/dashboards/scripted_templated.js b/public/dashboards/scripted_templated.js index df8647ff86e..0b09fa50986 100644 --- a/public/dashboards/scripted_templated.js +++ b/public/dashboards/scripted_templated.js @@ -14,14 +14,14 @@ 'use strict'; // accessible variables in this scope -let window, document, $, jQuery, moment, kbn; +// let window, document, $, jQuery, moment, kbn; // Setup some variables let dashboard; // All url parameters are available via the ARGS object // eslint-disable-next-line no-redeclare -let ARGS; +// let ARGS; // Initialize a skeleton with nothing but a rows array and service object dashboard = { @@ -44,19 +44,22 @@ dashboard.templating = { list: [ { name: 'test', - query: 'apps.backend.*', - refresh: 1, - type: 'query', - datasource: null, hide: 2, + includeAll: false, + multi: false, + query: 'a,b,c\n', + skipUrlSync: false, + type: 'custom', }, { - name: 'test2', - query: '*', - refresh: 1, - type: 'query', - datasource: null, - hide: 2, + name: 'seriesName', + label: 'Series name', + hide: 0, + includeAll: false, + multi: false, + query: 'series1,series2,series3\n', + skipUrlSync: false, + type: 'custom', }, ], }; @@ -78,6 +81,7 @@ for (let i = 0; i < rows; i++) { height: '300px', panels: [ { + id: 1, title: 'Events', type: 'graph', span: 12, @@ -85,10 +89,16 @@ for (let i = 0; i < rows; i++) { linewidth: 2, targets: [ { - target: "randomWalk('" + seriesName + "')", + scenarioId: 'random_walk', + refId: 'A', + seriesCount: 1, + alias: seriesName, }, { - target: "randomWalk('[[test2]]')", + scenarioId: 'random_walk', + refId: 'B', + seriesCount: 1, + alias: '${seriesName}', }, ], }, diff --git a/public/dashboards/template_vars.json b/public/dashboards/template_vars.json index 33478bc8081..04cb1c95d79 100644 --- a/public/dashboards/template_vars.json +++ b/public/dashboards/template_vars.json @@ -169,7 +169,6 @@ "notice": false, "enable": true, "status": "Stable", - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"], "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], "now": true } diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 46fe1314938..fed79005cc6 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -34,12 +34,6 @@ "save-button": "" } }, - "actions-editor": { - "inline": { - "add-button": "", - "one-click-link": "" - } - }, "admin": { "anon-users": { "not-found": "" @@ -174,6 +168,24 @@ } }, "alerting": { + "alert": { + "alert-state": "", + "annotations": "", + "evaluation": "", + "evaluation-paused": "", + "evaluation-paused-description": "", + "last-evaluated": "", + "last-evaluation-duration": "", + "last-updated-at": "", + "last-updated-by": "", + "no-annotations": "", + "pending-period": "", + "rule": "", + "rule-identifier": "", + "rule-type": "", + "state-error-timeout": "", + "state-no-data": "" + }, "alert-recording-rule-form": { "evaluation-behaviour": { "description": { @@ -283,6 +295,7 @@ "contactPointFilter": { "label": "" }, + "copy-to-clipboard": "", "export": { "subtitle": { "formats": "", @@ -299,6 +312,13 @@ } } }, + "group-actions": { + "actions-trigger": "", + "delete": "", + "edit": "", + "export": "", + "reorder": "" + }, "list-view": { "empty": { "new-alert-rule": "", @@ -484,11 +504,23 @@ }, "rule-list": { "configure-datasource": "", + "ds-error-boundary": { + "description": "", + "title": "" + }, "filter-view": { "no-more-results": "", "no-rules-found": "" }, - "new-alert-rule": "" + "new-alert-rule": "", + "pagination": { + "next-page": "", + "previous-page": "" + }, + "return-button": { + "title": "" + }, + "rulerrule-loading-error": "" }, "rule-state": { "creating": "", @@ -690,7 +722,10 @@ "title": "" }, "custom-value": { - "label": "" + "description": "" + }, + "group": { + "undefined": "" }, "options": { "no-found": "" @@ -863,6 +898,82 @@ "redirect-link": "Liste in Grafana Alerting", "subtitle": "Benachrichtigungsregeln im Zusammenhang mit diesem Dashboard" }, + "default-layout": { + "description": "", + "item-options": { + "repeat": { + "direction": { + "horizontal": "", + "title": "", + "vertical": "" + }, + "max": "", + "title": "", + "variable": { + "description": "", + "title": "" + } + } + }, + "name": "", + "row-actions": { + "delete": "", + "modal": { + "alt-action": "", + "text": "", + "title": "" + } + }, + "row-options": { + "button": { + "label": "" + }, + "form": { + "cancel": "", + "repeat-for": { + "label": "", + "learn-more": "", + "warning": { + "text": "" + } + }, + "title": "", + "update": "" + }, + "modal": { + "title": "" + } + } + }, + "edit-pane": { + "objects": { + "multi-select": { + "selection-number": "" + } + }, + "open": "", + "panels": { + "multi-select": { + "selection-number": "" + } + }, + "row": { + "header": { + "hide": "", + "title": "" + }, + "multi-select": { + "options-header": "", + "selection-number": "" + } + }, + "tab": { + "multi-select": { + "options-header": "", + "selection-number": "" + } + } + }, "empty": { "add-library-panel-body": "Visualisierungen hinzufügen, die mit anderen Dashboards geteilt werden.", "add-library-panel-button": "Bibliotheksfenster hinzufügen", @@ -874,6 +985,9 @@ "import-a-dashboard-header": "Dashboard importieren", "import-dashboard-button": "Dashboard importieren" }, + "errors": { + "failed-to-load": "" + }, "inspect": { "data-tab": "Daten", "error-tab": "Fehler", @@ -926,19 +1040,130 @@ "rows": "Gesamtanzahl an Zeilen", "table-title": "Statistiken" }, + "options": { + "description": "", + "title": "", + "title-option": "" + }, + "panel-edit": { + "alerting-tab": { + "dashboard-not-saved": "", + "no-rules": "" + } + }, + "responsive-layout": { + "description": "", + "item-options": { + "hide-no-data": "", + "title": "" + }, + "name": "", + "options": { + "columns": "", + "fixed": "", + "min": "", + "one-column": "", + "rows": "", + "three-columns": "", + "two-columns": "" + } + }, + "rows-layout": { + "description": "", + "name": "", + "row": { + "collapse": "", + "expand": "", + "new": "", + "repeat": { + "learn-more": "", + "warning": "" + } + }, + "row-options": { + "height": { + "expand": "", + "hide-row-header": "", + "min": "", + "title": "" + }, + "repeat": { + "title": "", + "variable": { + "title": "" + } + }, + "title": "", + "title-option": "" + } + }, + "tabs-layout": { + "description": "", + "name": "", + "tab": { + "new": "" + }, + "tab-options": { + "title": "", + "title-option": "" + } + }, "toolbar": { "add": "Hinzufügen", + "add-panel": "", + "add-panel-lib": "", + "add-row": "", + "add-tab": "", "alert-rules": "Warnregeln", + "back-to-dashboard": "", + "dashboard-settings": { + "label": "", + "tooltip": "" + }, + "discard-library-panel-changes": "", + "discard-panel": "", + "discard-panel-new": "", + "edit": { + "label": "", + "tooltip": "" + }, + "edit-dashboard-v2-schema": "", + "enter-edit-mode": { + "label": "", + "tooltip": "" + }, + "exit-edit-mode": { + "label": "", + "tooltip": "" + }, "mark-favorite": "Als Favorit markieren", + "more-save-options": "", "open-original": "Original-Dashboard öffnen", "playlist-next": "Zum nächsten Dashboard", "playlist-previous": "Zum vorherigen Dashboard", "playlist-stop": "Wiedergabeliste stoppen", + "public-dashboard": "", "refresh": "Dashboard aktualisieren", "save": "Dashboard speichern", + "save-dashboard": { + "label": "", + "tooltip": "" + }, + "save-dashboard-copy": { + "label": "", + "tooltip": "" + }, + "save-dashboard-short": "", + "save-library-panel": "", "settings": "Dashboard-Einstellungen", - "share": "Dashboard oder Panel teilen", + "share": { + "label": "", + "tooltip": "" + }, "share-button": "Teilen", + "show-hidden-elements": "", + "switch-old-dashboard": "", + "unlink-library-panel": "", "unmark-favorite": "Markierung als Favorit entfernen" }, "validation": { @@ -946,6 +1171,14 @@ "invalid-json": "JSON ungültig", "tags-expected-array": "Array der jeweiligen Tags", "tags-expected-strings": "String-Array der jeweiligen Tags" + }, + "viz-panel": { + "options": { + "description": "", + "title": "", + "title-option": "", + "transparent-background": "" + } } }, "dashboard-import": { @@ -1048,6 +1281,12 @@ "angular-deprecation-description": "", "angular-deprecation-heading": "" }, + "panel-queries": { + "add-query-from-library": "" + }, + "query-library": { + "add-query-button": "" + }, "settings": { "variables": { "dependencies": { @@ -1125,40 +1364,11 @@ "scan-for-older-logs": "", "stop-scan": "" }, - "query-library": { - "add-edit-description": "", - "cancel": "", - "default-description": "", - "delete-query": "", - "delete-query-text": "", - "delete-query-title": "", - "private": "", - "public": "", - "query-deleted": "", - "query-template-add-error": "", - "query-template-added": "", - "query-template-edit-error": "", - "query-template-edited": "", - "save": "" - }, - "query-template-modal": { - "add-info": "", - "add-title": "", - "auto-star": "", - "data-source-name": "", - "description": "", - "edit-info": "", - "edit-title": "", - "query": "", - "visibility": "" - }, - "query-template-modall": { - "data-source-type": "" - }, "rich-history": { "close-tooltip": "Abfrageverlauf schließen", "datasource-a-z": "Datenquelle A-Z", "datasource-z-a": "Datenquelle Z-A", + "library-history-dropdown": "", "newest-first": "Neueste zuerst", "oldest-first": "Älteste zuerst", "query-history": "Abfrageverlauf", @@ -1305,10 +1515,9 @@ "sortBy": "" }, "related-logs": { - "docsLink": "", + "LrrDocsLink": "", "openExploreLogs": "", - "relatedLogsUnavailableAfterDocsLink": "", - "relatedLogsUnavailableBeforeDocsLink": "", + "relatedLogsUnavailable": "", "warnExperimentalFeature": "" }, "viewBy": "" @@ -1357,6 +1566,24 @@ "send-custom-feedback": "" }, "grafana-ui": { + "action-editor": { + "button": { + "confirm": "", + "confirm-action": "" + }, + "inline": { + "add-action": "", + "edit-action": "" + }, + "modal": { + "action-body": "", + "action-method": "", + "action-query-params": "", + "action-title": "", + "action-title-placeholder": "", + "one-click-description": "" + } + }, "auto-save-field": { "saved": "", "saving": "" @@ -1373,11 +1600,21 @@ }, "data-link-editor-modal": { "cancel": "", + "one-click-description": "", "save": "" }, + "data-link-inline-editor": { + "one-click": "" + }, "data-links-inline-editor": { "add-link": "", - "one-click-link": "" + "edit-link": "", + "one-click": "", + "one-click-enabled": "", + "title-not-provided": "", + "tooltip-edit": "", + "tooltip-remove": "", + "url-not-provided": "" }, "data-source-http-settings": { "access-help": "", @@ -1458,7 +1695,12 @@ "right-axis-indicator": "" }, "viz-tooltip": { - "footer-add-annotation": "" + "actions-confirmation-input-placeholder": "", + "actions-confirmation-label": "", + "actions-confirmation-message": "", + "footer-add-annotation": "", + "footer-click-to-action": "", + "footer-click-to-navigate": "" } }, "graph": { @@ -1780,7 +2022,8 @@ }, "log-row-message": { "ellipsis": "", - "more": "" + "more": "", + "see-details": "" }, "log-rows": { "disable-popover": { @@ -1792,6 +2035,13 @@ "shortcut": "" } }, + "logs-navigation": { + "newer-logs": "", + "older-logs": "", + "scroll-bottom": "", + "scroll-top": "", + "start-of-range": "" + }, "popover-menu": { "copy": "", "disable-menu": "", @@ -1881,6 +2131,7 @@ "title": "" }, "migrated-counts": { + "alert_rule_groups": "", "alert_rules": "", "contact_points": "", "dashboards": "", @@ -1941,9 +2192,8 @@ "public-preview": { "button-text": "", "message": "", - "message-plugins": "", - "title": "", - "title-plugins": "" + "message-cloud": "", + "title": "" }, "resource-details": { "dismiss-button": "", @@ -1986,6 +2236,7 @@ }, "resource-type": { "alert_rule": "", + "alert_rule_group": "", "contact_point": "", "dashboard": "Dashboard", "datasource": "Datenquelle", @@ -2031,6 +2282,15 @@ "title": "Warum mit Grafana hosten?" } }, + "multicombobox": { + "all": { + "title": "", + "title-filtered": "" + }, + "clear": { + "title": "" + } + }, "nav": { "add-new-connections": { "title": "Neue Verbindung hinzufügen" @@ -2384,7 +2644,8 @@ "list-label": "Navigation", "open": "", "undock": "Menü abdocken" - } + }, + "rss-button": "" }, "news": { "drawer": { @@ -2516,19 +2777,35 @@ } }, "details": { + "connections-tab": { + "description": "" + }, "labels": { "contactGrafanaLabs": "", + "customLinks": "", + "customLinksTooltip": "", "dependencies": "", + "documentation": "", "downloads": "", "from": "", "installedVersion": "", "lastCommitDate": "", "latestVersion": "", - "links": "", + "license": "", + "raiseAnIssue": "", "reportAbuse": "", + "reportAbuseTooltip": "", + "repository": "", "signature": "", "status": "", "updatedAt": "" + }, + "modal": { + "cancel": "", + "copyEmail": "", + "description": "", + "node": "", + "title": "" } }, "empty-state": { @@ -2743,14 +3020,6 @@ "role-label": "Rolle" } }, - "query-library": { - "datasource-names": "", - "delete-query-button": "", - "query-template-get-error": "", - "search": "", - "user-info-get-error": "", - "user-names": "" - }, "query-operation": { "header": { "collapse-row": "Abfragezeile einklappen", @@ -2760,7 +3029,6 @@ "expand-row": "Suchzeile erweitern", "hide-response": "", "remove-query": "Abfrage entfernen", - "save-to-query-library": "", "show-response": "", "toggle-edit-mode": "Textbearbeitungsmodus umschalten" }, @@ -2862,6 +3130,10 @@ "title": "" }, "save-dashboards": { + "message-length": { + "info": "", + "title": "" + }, "name-exists": { "message-info": "", "message-suggestion": "", @@ -3145,6 +3417,7 @@ "home-dashboard-placeholder": "Standard-Dashboard", "locale-label": "Sprache", "locale-placeholder": "Sprache wählen", + "theme-description": "", "theme-label": "UI-Design", "week-start-label": "Wochenbeginn" }, @@ -3216,6 +3489,13 @@ "url-column-header": "Snapshot url", "view-button": "Anzeigen" }, + "table": { + "container": { + "content": "", + "show-all-series": "", + "show-only-series": "" + } + }, "tag-filter": { "clear-button": "", "loading": "Wird geladen ...", @@ -3309,20 +3589,26 @@ "start-your-metrics-exploration": "", "subtitle": "" }, - "metric-overview": { - "description-label": "", - "labels": "", - "metric-attributes": "", - "no-description": "", - "type-label": "", - "unit-label": "", - "unknown-type": "" - }, "metric-select": { "filter-by": "", + "native-histogram": "", "new-badge": "", "otel-switch": "" }, + "native-histogram-banner": { + "ch-heatmap": "", + "ch-histogram": "", + "click-histogram": "", + "hide-examples": "", + "learn-more": "", + "metric-examples": "", + "nh-heatmap": "", + "nh-histogram": "", + "now": "", + "previously": "", + "see-examples": "", + "sentence": "" + }, "recent-metrics": { "or-view-a-recent-exploration": "" }, diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 2c90bf29511..27301c8765b 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1699,6 +1699,7 @@ "actions-confirmation-label": "Confirmation message", "actions-confirmation-message": "Provide a descriptive prompt to confirm or cancel the action.", "footer-add-annotation": "Add annotation", + "footer-click-to-action": "Click to {{actionTitle}}", "footer-click-to-navigate": "Click to open {{linkTitle}}" } }, @@ -2781,17 +2782,30 @@ }, "labels": { "contactGrafanaLabs": "Contact Grafana Labs", + "customLinks": "Custom links ", + "customLinksTooltip": "These links are provided by the plugin developer to offer additional, developer-specific resources and information", "dependencies": "Dependencies", + "documentation": "Documentation", "downloads": "Downloads", "from": "From", "installedVersion": "Installed Version", "lastCommitDate": "Last commit date:", "latestVersion": "Latest Version", - "links": "Links ", + "license": "License", + "raiseAnIssue": "Raise an issue", "reportAbuse": "Report a concern ", + "reportAbuseTooltip": "Report issues related to malicious or harmful plugins directly to Grafana Labs.", + "repository": "Repository", "signature": "Signature", "status": "Status", "updatedAt": "Last updated:" + }, + "modal": { + "cancel": "Cancel", + "copyEmail": "Copy email address", + "description": "This feature is for reporting malicious or harmful behaviour within plugins. For plugin concerns, email us at: ", + "node": "Note: For general plugin issues like bugs or feature requests, please contact the plugin author using the provided links. ", + "title": "Report a plugin concern" } }, "empty-state": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index f324380f834..02db11b3d08 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -34,12 +34,6 @@ "save-button": "" } }, - "actions-editor": { - "inline": { - "add-button": "", - "one-click-link": "" - } - }, "admin": { "anon-users": { "not-found": "" @@ -174,6 +168,24 @@ } }, "alerting": { + "alert": { + "alert-state": "", + "annotations": "", + "evaluation": "", + "evaluation-paused": "", + "evaluation-paused-description": "", + "last-evaluated": "", + "last-evaluation-duration": "", + "last-updated-at": "", + "last-updated-by": "", + "no-annotations": "", + "pending-period": "", + "rule": "", + "rule-identifier": "", + "rule-type": "", + "state-error-timeout": "", + "state-no-data": "" + }, "alert-recording-rule-form": { "evaluation-behaviour": { "description": { @@ -283,6 +295,7 @@ "contactPointFilter": { "label": "" }, + "copy-to-clipboard": "", "export": { "subtitle": { "formats": "", @@ -299,6 +312,13 @@ } } }, + "group-actions": { + "actions-trigger": "", + "delete": "", + "edit": "", + "export": "", + "reorder": "" + }, "list-view": { "empty": { "new-alert-rule": "", @@ -484,11 +504,23 @@ }, "rule-list": { "configure-datasource": "", + "ds-error-boundary": { + "description": "", + "title": "" + }, "filter-view": { "no-more-results": "", "no-rules-found": "" }, - "new-alert-rule": "" + "new-alert-rule": "", + "pagination": { + "next-page": "", + "previous-page": "" + }, + "return-button": { + "title": "" + }, + "rulerrule-loading-error": "" }, "rule-state": { "creating": "", @@ -690,7 +722,10 @@ "title": "" }, "custom-value": { - "label": "" + "description": "" + }, + "group": { + "undefined": "" }, "options": { "no-found": "" @@ -863,6 +898,82 @@ "redirect-link": "Lista en Alertas de Grafana", "subtitle": "Reglas de alerta relacionadas con este tablero" }, + "default-layout": { + "description": "", + "item-options": { + "repeat": { + "direction": { + "horizontal": "", + "title": "", + "vertical": "" + }, + "max": "", + "title": "", + "variable": { + "description": "", + "title": "" + } + } + }, + "name": "", + "row-actions": { + "delete": "", + "modal": { + "alt-action": "", + "text": "", + "title": "" + } + }, + "row-options": { + "button": { + "label": "" + }, + "form": { + "cancel": "", + "repeat-for": { + "label": "", + "learn-more": "", + "warning": { + "text": "" + } + }, + "title": "", + "update": "" + }, + "modal": { + "title": "" + } + } + }, + "edit-pane": { + "objects": { + "multi-select": { + "selection-number": "" + } + }, + "open": "", + "panels": { + "multi-select": { + "selection-number": "" + } + }, + "row": { + "header": { + "hide": "", + "title": "" + }, + "multi-select": { + "options-header": "", + "selection-number": "" + } + }, + "tab": { + "multi-select": { + "options-header": "", + "selection-number": "" + } + } + }, "empty": { "add-library-panel-body": "Añadir las visualizaciones que se comparten con otros tableros.", "add-library-panel-button": "Añadir panel de biblioteca", @@ -874,6 +985,9 @@ "import-a-dashboard-header": "Importar un tablero", "import-dashboard-button": "Importar panel de control" }, + "errors": { + "failed-to-load": "" + }, "inspect": { "data-tab": "Datos", "error-tab": "Error", @@ -926,19 +1040,130 @@ "rows": "Número total de filas", "table-title": "Estadísticas" }, + "options": { + "description": "", + "title": "", + "title-option": "" + }, + "panel-edit": { + "alerting-tab": { + "dashboard-not-saved": "", + "no-rules": "" + } + }, + "responsive-layout": { + "description": "", + "item-options": { + "hide-no-data": "", + "title": "" + }, + "name": "", + "options": { + "columns": "", + "fixed": "", + "min": "", + "one-column": "", + "rows": "", + "three-columns": "", + "two-columns": "" + } + }, + "rows-layout": { + "description": "", + "name": "", + "row": { + "collapse": "", + "expand": "", + "new": "", + "repeat": { + "learn-more": "", + "warning": "" + } + }, + "row-options": { + "height": { + "expand": "", + "hide-row-header": "", + "min": "", + "title": "" + }, + "repeat": { + "title": "", + "variable": { + "title": "" + } + }, + "title": "", + "title-option": "" + } + }, + "tabs-layout": { + "description": "", + "name": "", + "tab": { + "new": "" + }, + "tab-options": { + "title": "", + "title-option": "" + } + }, "toolbar": { "add": "Añadir", + "add-panel": "", + "add-panel-lib": "", + "add-row": "", + "add-tab": "", "alert-rules": "Reglas de alerta", + "back-to-dashboard": "", + "dashboard-settings": { + "label": "", + "tooltip": "" + }, + "discard-library-panel-changes": "", + "discard-panel": "", + "discard-panel-new": "", + "edit": { + "label": "", + "tooltip": "" + }, + "edit-dashboard-v2-schema": "", + "enter-edit-mode": { + "label": "", + "tooltip": "" + }, + "exit-edit-mode": { + "label": "", + "tooltip": "" + }, "mark-favorite": "Marcar como favorito", + "more-save-options": "", "open-original": "Abrir el panel de control original", "playlist-next": "Ir al siguiente panel de control", "playlist-previous": "Ir al panel de control anterior", "playlist-stop": "Detener la lista de reproducción", + "public-dashboard": "", "refresh": "Actualizar panel de control", "save": "Guardar panel de control", + "save-dashboard": { + "label": "", + "tooltip": "" + }, + "save-dashboard-copy": { + "label": "", + "tooltip": "" + }, + "save-dashboard-short": "", + "save-library-panel": "", "settings": "Ajustes del panel de control", - "share": "Compartir panel o panel de control", + "share": { + "label": "", + "tooltip": "" + }, "share-button": "Compartir", + "show-hidden-elements": "", + "switch-old-dashboard": "", + "unlink-library-panel": "", "unmark-favorite": "Deshacer marca como favorito" }, "validation": { @@ -946,6 +1171,14 @@ "invalid-json": "JSON no válido", "tags-expected-array": "etiquetas: matriz prevista", "tags-expected-strings": "etiquetas: matriz de cadenas prevista" + }, + "viz-panel": { + "options": { + "description": "", + "title": "", + "title-option": "", + "transparent-background": "" + } } }, "dashboard-import": { @@ -1048,6 +1281,12 @@ "angular-deprecation-description": "", "angular-deprecation-heading": "" }, + "panel-queries": { + "add-query-from-library": "" + }, + "query-library": { + "add-query-button": "" + }, "settings": { "variables": { "dependencies": { @@ -1125,40 +1364,11 @@ "scan-for-older-logs": "", "stop-scan": "" }, - "query-library": { - "add-edit-description": "", - "cancel": "", - "default-description": "", - "delete-query": "", - "delete-query-text": "", - "delete-query-title": "", - "private": "", - "public": "", - "query-deleted": "", - "query-template-add-error": "", - "query-template-added": "", - "query-template-edit-error": "", - "query-template-edited": "", - "save": "" - }, - "query-template-modal": { - "add-info": "", - "add-title": "", - "auto-star": "", - "data-source-name": "", - "description": "", - "edit-info": "", - "edit-title": "", - "query": "", - "visibility": "" - }, - "query-template-modall": { - "data-source-type": "" - }, "rich-history": { "close-tooltip": "Cerrar el historial de consultas", "datasource-a-z": "Fuente de datos A-Z", "datasource-z-a": "Fuente de datos Z-A", + "library-history-dropdown": "", "newest-first": "El más reciente primero", "oldest-first": "El más antiguo primero", "query-history": "Historial de consultas", @@ -1305,10 +1515,9 @@ "sortBy": "" }, "related-logs": { - "docsLink": "", + "LrrDocsLink": "", "openExploreLogs": "", - "relatedLogsUnavailableAfterDocsLink": "", - "relatedLogsUnavailableBeforeDocsLink": "", + "relatedLogsUnavailable": "", "warnExperimentalFeature": "" }, "viewBy": "" @@ -1357,6 +1566,24 @@ "send-custom-feedback": "" }, "grafana-ui": { + "action-editor": { + "button": { + "confirm": "", + "confirm-action": "" + }, + "inline": { + "add-action": "", + "edit-action": "" + }, + "modal": { + "action-body": "", + "action-method": "", + "action-query-params": "", + "action-title": "", + "action-title-placeholder": "", + "one-click-description": "" + } + }, "auto-save-field": { "saved": "", "saving": "" @@ -1373,11 +1600,21 @@ }, "data-link-editor-modal": { "cancel": "", + "one-click-description": "", "save": "" }, + "data-link-inline-editor": { + "one-click": "" + }, "data-links-inline-editor": { "add-link": "", - "one-click-link": "" + "edit-link": "", + "one-click": "", + "one-click-enabled": "", + "title-not-provided": "", + "tooltip-edit": "", + "tooltip-remove": "", + "url-not-provided": "" }, "data-source-http-settings": { "access-help": "", @@ -1458,7 +1695,12 @@ "right-axis-indicator": "" }, "viz-tooltip": { - "footer-add-annotation": "" + "actions-confirmation-input-placeholder": "", + "actions-confirmation-label": "", + "actions-confirmation-message": "", + "footer-add-annotation": "", + "footer-click-to-action": "", + "footer-click-to-navigate": "" } }, "graph": { @@ -1780,7 +2022,8 @@ }, "log-row-message": { "ellipsis": "", - "more": "" + "more": "", + "see-details": "" }, "log-rows": { "disable-popover": { @@ -1792,6 +2035,13 @@ "shortcut": "" } }, + "logs-navigation": { + "newer-logs": "", + "older-logs": "", + "scroll-bottom": "", + "scroll-top": "", + "start-of-range": "" + }, "popover-menu": { "copy": "", "disable-menu": "", @@ -1881,6 +2131,7 @@ "title": "" }, "migrated-counts": { + "alert_rule_groups": "", "alert_rules": "", "contact_points": "", "dashboards": "", @@ -1941,9 +2192,8 @@ "public-preview": { "button-text": "", "message": "", - "message-plugins": "", - "title": "", - "title-plugins": "" + "message-cloud": "", + "title": "" }, "resource-details": { "dismiss-button": "", @@ -1986,6 +2236,7 @@ }, "resource-type": { "alert_rule": "", + "alert_rule_group": "", "contact_point": "", "dashboard": "Panel de control", "datasource": "Fuente de datos", @@ -2031,6 +2282,15 @@ "title": "¿Por qué alojar con Grafana?" } }, + "multicombobox": { + "all": { + "title": "", + "title-filtered": "" + }, + "clear": { + "title": "" + } + }, "nav": { "add-new-connections": { "title": "Añadir nueva conexión" @@ -2384,7 +2644,8 @@ "list-label": "Navegación", "open": "", "undock": "Desanclar el menú" - } + }, + "rss-button": "" }, "news": { "drawer": { @@ -2516,19 +2777,35 @@ } }, "details": { + "connections-tab": { + "description": "" + }, "labels": { "contactGrafanaLabs": "", + "customLinks": "", + "customLinksTooltip": "", "dependencies": "", + "documentation": "", "downloads": "", "from": "", "installedVersion": "", "lastCommitDate": "", "latestVersion": "", - "links": "", + "license": "", + "raiseAnIssue": "", "reportAbuse": "", + "reportAbuseTooltip": "", + "repository": "", "signature": "", "status": "", "updatedAt": "" + }, + "modal": { + "cancel": "", + "copyEmail": "", + "description": "", + "node": "", + "title": "" } }, "empty-state": { @@ -2743,14 +3020,6 @@ "role-label": "Rol" } }, - "query-library": { - "datasource-names": "", - "delete-query-button": "", - "query-template-get-error": "", - "search": "", - "user-info-get-error": "", - "user-names": "" - }, "query-operation": { "header": { "collapse-row": "Contraer la fila de la consulta", @@ -2760,7 +3029,6 @@ "expand-row": "Expandir la fila de la consulta", "hide-response": "", "remove-query": "Eliminar consulta", - "save-to-query-library": "", "show-response": "", "toggle-edit-mode": "Alternar el modo de edición de texto" }, @@ -2862,6 +3130,10 @@ "title": "" }, "save-dashboards": { + "message-length": { + "info": "", + "title": "" + }, "name-exists": { "message-info": "", "message-suggestion": "", @@ -3145,6 +3417,7 @@ "home-dashboard-placeholder": "Panel de control por defecto", "locale-label": "Idioma", "locale-placeholder": "Cambiar idioma", + "theme-description": "", "theme-label": "Tema de interfaz de usuario", "week-start-label": "Inicio de la semana" }, @@ -3216,6 +3489,13 @@ "url-column-header": "URL de la instantánea", "view-button": "Vista" }, + "table": { + "container": { + "content": "", + "show-all-series": "", + "show-only-series": "" + } + }, "tag-filter": { "clear-button": "", "loading": "Cargando...", @@ -3309,20 +3589,26 @@ "start-your-metrics-exploration": "", "subtitle": "" }, - "metric-overview": { - "description-label": "", - "labels": "", - "metric-attributes": "", - "no-description": "", - "type-label": "", - "unit-label": "", - "unknown-type": "" - }, "metric-select": { "filter-by": "", + "native-histogram": "", "new-badge": "", "otel-switch": "" }, + "native-histogram-banner": { + "ch-heatmap": "", + "ch-histogram": "", + "click-histogram": "", + "hide-examples": "", + "learn-more": "", + "metric-examples": "", + "nh-heatmap": "", + "nh-histogram": "", + "now": "", + "previously": "", + "see-examples": "", + "sentence": "" + }, "recent-metrics": { "or-view-a-recent-exploration": "" }, diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 2ea9883ffe1..33cfe2d4b40 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -34,12 +34,6 @@ "save-button": "" } }, - "actions-editor": { - "inline": { - "add-button": "", - "one-click-link": "" - } - }, "admin": { "anon-users": { "not-found": "" @@ -174,6 +168,24 @@ } }, "alerting": { + "alert": { + "alert-state": "", + "annotations": "", + "evaluation": "", + "evaluation-paused": "", + "evaluation-paused-description": "", + "last-evaluated": "", + "last-evaluation-duration": "", + "last-updated-at": "", + "last-updated-by": "", + "no-annotations": "", + "pending-period": "", + "rule": "", + "rule-identifier": "", + "rule-type": "", + "state-error-timeout": "", + "state-no-data": "" + }, "alert-recording-rule-form": { "evaluation-behaviour": { "description": { @@ -283,6 +295,7 @@ "contactPointFilter": { "label": "" }, + "copy-to-clipboard": "", "export": { "subtitle": { "formats": "", @@ -299,6 +312,13 @@ } } }, + "group-actions": { + "actions-trigger": "", + "delete": "", + "edit": "", + "export": "", + "reorder": "" + }, "list-view": { "empty": { "new-alert-rule": "", @@ -484,11 +504,23 @@ }, "rule-list": { "configure-datasource": "", + "ds-error-boundary": { + "description": "", + "title": "" + }, "filter-view": { "no-more-results": "", "no-rules-found": "" }, - "new-alert-rule": "" + "new-alert-rule": "", + "pagination": { + "next-page": "", + "previous-page": "" + }, + "return-button": { + "title": "" + }, + "rulerrule-loading-error": "" }, "rule-state": { "creating": "", @@ -690,7 +722,10 @@ "title": "" }, "custom-value": { - "label": "" + "description": "" + }, + "group": { + "undefined": "" }, "options": { "no-found": "" @@ -863,6 +898,82 @@ "redirect-link": "Liste dans Alertes Grafana", "subtitle": "Règles d'alerte liées à ce tableau de bord" }, + "default-layout": { + "description": "", + "item-options": { + "repeat": { + "direction": { + "horizontal": "", + "title": "", + "vertical": "" + }, + "max": "", + "title": "", + "variable": { + "description": "", + "title": "" + } + } + }, + "name": "", + "row-actions": { + "delete": "", + "modal": { + "alt-action": "", + "text": "", + "title": "" + } + }, + "row-options": { + "button": { + "label": "" + }, + "form": { + "cancel": "", + "repeat-for": { + "label": "", + "learn-more": "", + "warning": { + "text": "" + } + }, + "title": "", + "update": "" + }, + "modal": { + "title": "" + } + } + }, + "edit-pane": { + "objects": { + "multi-select": { + "selection-number": "" + } + }, + "open": "", + "panels": { + "multi-select": { + "selection-number": "" + } + }, + "row": { + "header": { + "hide": "", + "title": "" + }, + "multi-select": { + "options-header": "", + "selection-number": "" + } + }, + "tab": { + "multi-select": { + "options-header": "", + "selection-number": "" + } + } + }, "empty": { "add-library-panel-body": "Ajoutez des visualisations partagées avec d'autres tableaux de bord.", "add-library-panel-button": "Ajouter un panneau Bibliothèque", @@ -874,6 +985,9 @@ "import-a-dashboard-header": "Importer un tableau de bord", "import-dashboard-button": "Importer un tableau de bord" }, + "errors": { + "failed-to-load": "" + }, "inspect": { "data-tab": "Données", "error-tab": "Erreur", @@ -926,19 +1040,130 @@ "rows": "Nombre total de lignes", "table-title": "Statistiques" }, + "options": { + "description": "", + "title": "", + "title-option": "" + }, + "panel-edit": { + "alerting-tab": { + "dashboard-not-saved": "", + "no-rules": "" + } + }, + "responsive-layout": { + "description": "", + "item-options": { + "hide-no-data": "", + "title": "" + }, + "name": "", + "options": { + "columns": "", + "fixed": "", + "min": "", + "one-column": "", + "rows": "", + "three-columns": "", + "two-columns": "" + } + }, + "rows-layout": { + "description": "", + "name": "", + "row": { + "collapse": "", + "expand": "", + "new": "", + "repeat": { + "learn-more": "", + "warning": "" + } + }, + "row-options": { + "height": { + "expand": "", + "hide-row-header": "", + "min": "", + "title": "" + }, + "repeat": { + "title": "", + "variable": { + "title": "" + } + }, + "title": "", + "title-option": "" + } + }, + "tabs-layout": { + "description": "", + "name": "", + "tab": { + "new": "" + }, + "tab-options": { + "title": "", + "title-option": "" + } + }, "toolbar": { "add": "Ajouter", + "add-panel": "", + "add-panel-lib": "", + "add-row": "", + "add-tab": "", "alert-rules": "Règles d'alerte", + "back-to-dashboard": "", + "dashboard-settings": { + "label": "", + "tooltip": "" + }, + "discard-library-panel-changes": "", + "discard-panel": "", + "discard-panel-new": "", + "edit": { + "label": "", + "tooltip": "" + }, + "edit-dashboard-v2-schema": "", + "enter-edit-mode": { + "label": "", + "tooltip": "" + }, + "exit-edit-mode": { + "label": "", + "tooltip": "" + }, "mark-favorite": "Marquer comme favori", + "more-save-options": "", "open-original": "Ouvrir le tableau de bord d'origine", "playlist-next": "Accéder au tableau de bord suivant", "playlist-previous": "Accéder au tableau de bord précédent", "playlist-stop": "Arrêter la liste de lecture", + "public-dashboard": "", "refresh": "Actualiser le tableau de bord", "save": "Enregistrer le tableau de bord", + "save-dashboard": { + "label": "", + "tooltip": "" + }, + "save-dashboard-copy": { + "label": "", + "tooltip": "" + }, + "save-dashboard-short": "", + "save-library-panel": "", "settings": "Paramètres du tableau de bord", - "share": "Partager le tableau de bord ou le panneau", + "share": { + "label": "", + "tooltip": "" + }, "share-button": "Partager", + "show-hidden-elements": "", + "switch-old-dashboard": "", + "unlink-library-panel": "", "unmark-favorite": "Supprimer des favoris" }, "validation": { @@ -946,6 +1171,14 @@ "invalid-json": "JSON non valide", "tags-expected-array": "étiquettes attendues tableau", "tags-expected-strings": "étiquettes attendues tableau de chaînes" + }, + "viz-panel": { + "options": { + "description": "", + "title": "", + "title-option": "", + "transparent-background": "" + } } }, "dashboard-import": { @@ -1048,6 +1281,12 @@ "angular-deprecation-description": "", "angular-deprecation-heading": "" }, + "panel-queries": { + "add-query-from-library": "" + }, + "query-library": { + "add-query-button": "" + }, "settings": { "variables": { "dependencies": { @@ -1125,40 +1364,11 @@ "scan-for-older-logs": "", "stop-scan": "" }, - "query-library": { - "add-edit-description": "", - "cancel": "", - "default-description": "", - "delete-query": "", - "delete-query-text": "", - "delete-query-title": "", - "private": "", - "public": "", - "query-deleted": "", - "query-template-add-error": "", - "query-template-added": "", - "query-template-edit-error": "", - "query-template-edited": "", - "save": "" - }, - "query-template-modal": { - "add-info": "", - "add-title": "", - "auto-star": "", - "data-source-name": "", - "description": "", - "edit-info": "", - "edit-title": "", - "query": "", - "visibility": "" - }, - "query-template-modall": { - "data-source-type": "" - }, "rich-history": { "close-tooltip": "Fermer l'historique des requêtes", "datasource-a-z": "Source de données A-Z", "datasource-z-a": "Source de données Z-A", + "library-history-dropdown": "", "newest-first": "Plus récent en premier", "oldest-first": "Plus ancien en premier", "query-history": "Historique des requêtes", @@ -1305,10 +1515,9 @@ "sortBy": "" }, "related-logs": { - "docsLink": "", + "LrrDocsLink": "", "openExploreLogs": "", - "relatedLogsUnavailableAfterDocsLink": "", - "relatedLogsUnavailableBeforeDocsLink": "", + "relatedLogsUnavailable": "", "warnExperimentalFeature": "" }, "viewBy": "" @@ -1357,6 +1566,24 @@ "send-custom-feedback": "" }, "grafana-ui": { + "action-editor": { + "button": { + "confirm": "", + "confirm-action": "" + }, + "inline": { + "add-action": "", + "edit-action": "" + }, + "modal": { + "action-body": "", + "action-method": "", + "action-query-params": "", + "action-title": "", + "action-title-placeholder": "", + "one-click-description": "" + } + }, "auto-save-field": { "saved": "", "saving": "" @@ -1373,11 +1600,21 @@ }, "data-link-editor-modal": { "cancel": "", + "one-click-description": "", "save": "" }, + "data-link-inline-editor": { + "one-click": "" + }, "data-links-inline-editor": { "add-link": "", - "one-click-link": "" + "edit-link": "", + "one-click": "", + "one-click-enabled": "", + "title-not-provided": "", + "tooltip-edit": "", + "tooltip-remove": "", + "url-not-provided": "" }, "data-source-http-settings": { "access-help": "", @@ -1458,7 +1695,12 @@ "right-axis-indicator": "" }, "viz-tooltip": { - "footer-add-annotation": "" + "actions-confirmation-input-placeholder": "", + "actions-confirmation-label": "", + "actions-confirmation-message": "", + "footer-add-annotation": "", + "footer-click-to-action": "", + "footer-click-to-navigate": "" } }, "graph": { @@ -1780,7 +2022,8 @@ }, "log-row-message": { "ellipsis": "", - "more": "" + "more": "", + "see-details": "" }, "log-rows": { "disable-popover": { @@ -1792,6 +2035,13 @@ "shortcut": "" } }, + "logs-navigation": { + "newer-logs": "", + "older-logs": "", + "scroll-bottom": "", + "scroll-top": "", + "start-of-range": "" + }, "popover-menu": { "copy": "", "disable-menu": "", @@ -1881,6 +2131,7 @@ "title": "" }, "migrated-counts": { + "alert_rule_groups": "", "alert_rules": "", "contact_points": "", "dashboards": "", @@ -1941,9 +2192,8 @@ "public-preview": { "button-text": "", "message": "", - "message-plugins": "", - "title": "", - "title-plugins": "" + "message-cloud": "", + "title": "" }, "resource-details": { "dismiss-button": "", @@ -1986,6 +2236,7 @@ }, "resource-type": { "alert_rule": "", + "alert_rule_group": "", "contact_point": "", "dashboard": "Tableau de bord", "datasource": "Source de données", @@ -2031,6 +2282,15 @@ "title": "Pourquoi héberger avec Grafana ?" } }, + "multicombobox": { + "all": { + "title": "", + "title-filtered": "" + }, + "clear": { + "title": "" + } + }, "nav": { "add-new-connections": { "title": "Ajouter une nouvelle connexion" @@ -2384,7 +2644,8 @@ "list-label": "Navigation", "open": "", "undock": "Ancrer le menu" - } + }, + "rss-button": "" }, "news": { "drawer": { @@ -2516,19 +2777,35 @@ } }, "details": { + "connections-tab": { + "description": "" + }, "labels": { "contactGrafanaLabs": "", + "customLinks": "", + "customLinksTooltip": "", "dependencies": "", + "documentation": "", "downloads": "", "from": "", "installedVersion": "", "lastCommitDate": "", "latestVersion": "", - "links": "", + "license": "", + "raiseAnIssue": "", "reportAbuse": "", + "reportAbuseTooltip": "", + "repository": "", "signature": "", "status": "", "updatedAt": "" + }, + "modal": { + "cancel": "", + "copyEmail": "", + "description": "", + "node": "", + "title": "" } }, "empty-state": { @@ -2743,14 +3020,6 @@ "role-label": "Rôle" } }, - "query-library": { - "datasource-names": "", - "delete-query-button": "", - "query-template-get-error": "", - "search": "", - "user-info-get-error": "", - "user-names": "" - }, "query-operation": { "header": { "collapse-row": "Réduire la ligne de requête", @@ -2760,7 +3029,6 @@ "expand-row": "Développer la ligne de requête", "hide-response": "", "remove-query": "Supprimer la requête", - "save-to-query-library": "", "show-response": "", "toggle-edit-mode": "Activer/désactiver le mode édition de texte" }, @@ -2862,6 +3130,10 @@ "title": "" }, "save-dashboards": { + "message-length": { + "info": "", + "title": "" + }, "name-exists": { "message-info": "", "message-suggestion": "", @@ -3145,6 +3417,7 @@ "home-dashboard-placeholder": "Tableau de bord par défaut", "locale-label": "Langue", "locale-placeholder": "Choisir une langue", + "theme-description": "", "theme-label": "Thème de l'interface utilisateur", "week-start-label": "Début de la semaine" }, @@ -3216,6 +3489,13 @@ "url-column-header": "URL de l'instantané", "view-button": "Afficher" }, + "table": { + "container": { + "content": "", + "show-all-series": "", + "show-only-series": "" + } + }, "tag-filter": { "clear-button": "", "loading": "Chargement en cours...", @@ -3309,20 +3589,26 @@ "start-your-metrics-exploration": "", "subtitle": "" }, - "metric-overview": { - "description-label": "", - "labels": "", - "metric-attributes": "", - "no-description": "", - "type-label": "", - "unit-label": "", - "unknown-type": "" - }, "metric-select": { "filter-by": "", + "native-histogram": "", "new-badge": "", "otel-switch": "" }, + "native-histogram-banner": { + "ch-heatmap": "", + "ch-histogram": "", + "click-histogram": "", + "hide-examples": "", + "learn-more": "", + "metric-examples": "", + "nh-heatmap": "", + "nh-histogram": "", + "now": "", + "previously": "", + "see-examples": "", + "sentence": "" + }, "recent-metrics": { "or-view-a-recent-exploration": "" }, diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 61b47f9f9a5..59371299812 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -1699,6 +1699,7 @@ "actions-confirmation-label": "Cőʼnƒįřmäŧįőʼn męşşäģę", "actions-confirmation-message": "Přővįđę ä đęşčřįpŧįvę přőmpŧ ŧő čőʼnƒįřm őř čäʼnčęľ ŧĥę äčŧįőʼn.", "footer-add-annotation": "Åđđ äʼnʼnőŧäŧįőʼn", + "footer-click-to-action": "Cľįčĸ ŧő {{actionTitle}}", "footer-click-to-navigate": "Cľįčĸ ŧő őpęʼn {{linkTitle}}" } }, @@ -2781,17 +2782,30 @@ }, "labels": { "contactGrafanaLabs": "Cőʼnŧäčŧ Ğřäƒäʼnä Ŀäþş", + "customLinks": "Cūşŧőm ľįʼnĸş ", + "customLinksTooltip": "Ŧĥęşę ľįʼnĸş äřę přővįđęđ þy ŧĥę pľūģįʼn đęvęľőpęř ŧő őƒƒęř äđđįŧįőʼnäľ, đęvęľőpęř-şpęčįƒįč řęşőūřčęş äʼnđ įʼnƒőřmäŧįőʼn", "dependencies": "Đępęʼnđęʼnčįęş", + "documentation": "Đőčūmęʼnŧäŧįőʼn", "downloads": "Đőŵʼnľőäđş", "from": "Fřőm", "installedVersion": "Ĩʼnşŧäľľęđ Vęřşįőʼn", "lastCommitDate": "Ŀäşŧ čőmmįŧ đäŧę:", "latestVersion": "Ŀäŧęşŧ Vęřşįőʼn", - "links": "Ŀįʼnĸş ", + "license": "Ŀįčęʼnşę", + "raiseAnIssue": "Ŗäįşę äʼn įşşūę", "reportAbuse": "Ŗępőřŧ ä čőʼnčęřʼn ", + "reportAbuseTooltip": "Ŗępőřŧ įşşūęş řęľäŧęđ ŧő mäľįčįőūş őř ĥäřmƒūľ pľūģįʼnş đįřęčŧľy ŧő Ğřäƒäʼnä Ŀäþş.", + "repository": "Ŗępőşįŧőřy", "signature": "Ŝįģʼnäŧūřę", "status": "Ŝŧäŧūş", "updatedAt": "Ŀäşŧ ūpđäŧęđ:" + }, + "modal": { + "cancel": "Cäʼnčęľ", + "copyEmail": "Cőpy ęmäįľ äđđřęşş", + "description": "Ŧĥįş ƒęäŧūřę įş ƒőř řępőřŧįʼnģ mäľįčįőūş őř ĥäřmƒūľ þęĥävįőūř ŵįŧĥįʼn pľūģįʼnş. Főř pľūģįʼn čőʼnčęřʼnş, ęmäįľ ūş äŧ: ", + "node": "Ńőŧę: Főř ģęʼnęřäľ pľūģįʼn įşşūęş ľįĸę þūģş őř ƒęäŧūřę řęqūęşŧş, pľęäşę čőʼnŧäčŧ ŧĥę pľūģįʼn äūŧĥőř ūşįʼnģ ŧĥę přővįđęđ ľįʼnĸş. ", + "title": "Ŗępőřŧ ä pľūģįʼn čőʼnčęřʼn" } }, "empty-state": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 5e0b7cdc2d4..4e648b574bc 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -34,12 +34,6 @@ "save-button": "" } }, - "actions-editor": { - "inline": { - "add-button": "", - "one-click-link": "" - } - }, "admin": { "anon-users": { "not-found": "" @@ -174,6 +168,24 @@ } }, "alerting": { + "alert": { + "alert-state": "", + "annotations": "", + "evaluation": "", + "evaluation-paused": "", + "evaluation-paused-description": "", + "last-evaluated": "", + "last-evaluation-duration": "", + "last-updated-at": "", + "last-updated-by": "", + "no-annotations": "", + "pending-period": "", + "rule": "", + "rule-identifier": "", + "rule-type": "", + "state-error-timeout": "", + "state-no-data": "" + }, "alert-recording-rule-form": { "evaluation-behaviour": { "description": { @@ -283,6 +295,7 @@ "contactPointFilter": { "label": "" }, + "copy-to-clipboard": "", "export": { "subtitle": { "formats": "", @@ -299,6 +312,13 @@ } } }, + "group-actions": { + "actions-trigger": "", + "delete": "", + "edit": "", + "export": "", + "reorder": "" + }, "list-view": { "empty": { "new-alert-rule": "", @@ -484,11 +504,23 @@ }, "rule-list": { "configure-datasource": "", + "ds-error-boundary": { + "description": "", + "title": "" + }, "filter-view": { "no-more-results": "", "no-rules-found": "" }, - "new-alert-rule": "" + "new-alert-rule": "", + "pagination": { + "next-page": "", + "previous-page": "" + }, + "return-button": { + "title": "" + }, + "rulerrule-loading-error": "" }, "rule-state": { "creating": "", @@ -690,7 +722,10 @@ "title": "" }, "custom-value": { - "label": "" + "description": "" + }, + "group": { + "undefined": "" }, "options": { "no-found": "" @@ -863,6 +898,82 @@ "redirect-link": "Lista no alerta do Grafana", "subtitle": "Regras de alerta relacionadas a este painel de controle" }, + "default-layout": { + "description": "", + "item-options": { + "repeat": { + "direction": { + "horizontal": "", + "title": "", + "vertical": "" + }, + "max": "", + "title": "", + "variable": { + "description": "", + "title": "" + } + } + }, + "name": "", + "row-actions": { + "delete": "", + "modal": { + "alt-action": "", + "text": "", + "title": "" + } + }, + "row-options": { + "button": { + "label": "" + }, + "form": { + "cancel": "", + "repeat-for": { + "label": "", + "learn-more": "", + "warning": { + "text": "" + } + }, + "title": "", + "update": "" + }, + "modal": { + "title": "" + } + } + }, + "edit-pane": { + "objects": { + "multi-select": { + "selection-number": "" + } + }, + "open": "", + "panels": { + "multi-select": { + "selection-number": "" + } + }, + "row": { + "header": { + "hide": "", + "title": "" + }, + "multi-select": { + "options-header": "", + "selection-number": "" + } + }, + "tab": { + "multi-select": { + "options-header": "", + "selection-number": "" + } + } + }, "empty": { "add-library-panel-body": "Adicione visualizações que são compartilhadas com outros painéis de controle.", "add-library-panel-button": "Adicionar painel de biblioteca", @@ -874,6 +985,9 @@ "import-a-dashboard-header": "Importar um painel de controle", "import-dashboard-button": "Importar painel de controle" }, + "errors": { + "failed-to-load": "" + }, "inspect": { "data-tab": "Dados", "error-tab": "Erro", @@ -926,19 +1040,130 @@ "rows": "Número total de linhas", "table-title": "Estatísticas" }, + "options": { + "description": "", + "title": "", + "title-option": "" + }, + "panel-edit": { + "alerting-tab": { + "dashboard-not-saved": "", + "no-rules": "" + } + }, + "responsive-layout": { + "description": "", + "item-options": { + "hide-no-data": "", + "title": "" + }, + "name": "", + "options": { + "columns": "", + "fixed": "", + "min": "", + "one-column": "", + "rows": "", + "three-columns": "", + "two-columns": "" + } + }, + "rows-layout": { + "description": "", + "name": "", + "row": { + "collapse": "", + "expand": "", + "new": "", + "repeat": { + "learn-more": "", + "warning": "" + } + }, + "row-options": { + "height": { + "expand": "", + "hide-row-header": "", + "min": "", + "title": "" + }, + "repeat": { + "title": "", + "variable": { + "title": "" + } + }, + "title": "", + "title-option": "" + } + }, + "tabs-layout": { + "description": "", + "name": "", + "tab": { + "new": "" + }, + "tab-options": { + "title": "", + "title-option": "" + } + }, "toolbar": { "add": "Adicionar", + "add-panel": "", + "add-panel-lib": "", + "add-row": "", + "add-tab": "", "alert-rules": "Regras de alerta", + "back-to-dashboard": "", + "dashboard-settings": { + "label": "", + "tooltip": "" + }, + "discard-library-panel-changes": "", + "discard-panel": "", + "discard-panel-new": "", + "edit": { + "label": "", + "tooltip": "" + }, + "edit-dashboard-v2-schema": "", + "enter-edit-mode": { + "label": "", + "tooltip": "" + }, + "exit-edit-mode": { + "label": "", + "tooltip": "" + }, "mark-favorite": "Marcar como favorito", + "more-save-options": "", "open-original": "Abrir painel de controle original", "playlist-next": "Ir para o próximo painel de controle", "playlist-previous": "Ir para o painel de controle anterior", "playlist-stop": "Parar lista de reprodução", + "public-dashboard": "", "refresh": "Atualizar painel de controle", "save": "Salvar painel de controle", + "save-dashboard": { + "label": "", + "tooltip": "" + }, + "save-dashboard-copy": { + "label": "", + "tooltip": "" + }, + "save-dashboard-short": "", + "save-library-panel": "", "settings": "Configurações do painel de controle", - "share": "Compartilhar painel de controle", + "share": { + "label": "", + "tooltip": "" + }, "share-button": "Compartilhar", + "show-hidden-elements": "", + "switch-old-dashboard": "", + "unlink-library-panel": "", "unmark-favorite": "Desmarcar como favorito" }, "validation": { @@ -946,6 +1171,14 @@ "invalid-json": "JSON inválido", "tags-expected-array": "matriz de tags esperada", "tags-expected-strings": "matriz de strings de tags esperada" + }, + "viz-panel": { + "options": { + "description": "", + "title": "", + "title-option": "", + "transparent-background": "" + } } }, "dashboard-import": { @@ -1048,6 +1281,12 @@ "angular-deprecation-description": "", "angular-deprecation-heading": "" }, + "panel-queries": { + "add-query-from-library": "" + }, + "query-library": { + "add-query-button": "" + }, "settings": { "variables": { "dependencies": { @@ -1125,40 +1364,11 @@ "scan-for-older-logs": "", "stop-scan": "" }, - "query-library": { - "add-edit-description": "", - "cancel": "", - "default-description": "", - "delete-query": "", - "delete-query-text": "", - "delete-query-title": "", - "private": "", - "public": "", - "query-deleted": "", - "query-template-add-error": "", - "query-template-added": "", - "query-template-edit-error": "", - "query-template-edited": "", - "save": "" - }, - "query-template-modal": { - "add-info": "", - "add-title": "", - "auto-star": "", - "data-source-name": "", - "description": "", - "edit-info": "", - "edit-title": "", - "query": "", - "visibility": "" - }, - "query-template-modall": { - "data-source-type": "" - }, "rich-history": { "close-tooltip": "Fechar histórico de consultas", "datasource-a-z": "Fonte de dados A-Z", "datasource-z-a": "Fonte de dados Z-A", + "library-history-dropdown": "", "newest-first": "Mais recentes primeiro", "oldest-first": "Mais antigos primeiro", "query-history": "Histórico de consultas", @@ -1305,10 +1515,9 @@ "sortBy": "" }, "related-logs": { - "docsLink": "", + "LrrDocsLink": "", "openExploreLogs": "", - "relatedLogsUnavailableAfterDocsLink": "", - "relatedLogsUnavailableBeforeDocsLink": "", + "relatedLogsUnavailable": "", "warnExperimentalFeature": "" }, "viewBy": "" @@ -1357,6 +1566,24 @@ "send-custom-feedback": "" }, "grafana-ui": { + "action-editor": { + "button": { + "confirm": "", + "confirm-action": "" + }, + "inline": { + "add-action": "", + "edit-action": "" + }, + "modal": { + "action-body": "", + "action-method": "", + "action-query-params": "", + "action-title": "", + "action-title-placeholder": "", + "one-click-description": "" + } + }, "auto-save-field": { "saved": "", "saving": "" @@ -1373,11 +1600,21 @@ }, "data-link-editor-modal": { "cancel": "", + "one-click-description": "", "save": "" }, + "data-link-inline-editor": { + "one-click": "" + }, "data-links-inline-editor": { "add-link": "", - "one-click-link": "" + "edit-link": "", + "one-click": "", + "one-click-enabled": "", + "title-not-provided": "", + "tooltip-edit": "", + "tooltip-remove": "", + "url-not-provided": "" }, "data-source-http-settings": { "access-help": "", @@ -1458,7 +1695,12 @@ "right-axis-indicator": "" }, "viz-tooltip": { - "footer-add-annotation": "" + "actions-confirmation-input-placeholder": "", + "actions-confirmation-label": "", + "actions-confirmation-message": "", + "footer-add-annotation": "", + "footer-click-to-action": "", + "footer-click-to-navigate": "" } }, "graph": { @@ -1780,7 +2022,8 @@ }, "log-row-message": { "ellipsis": "", - "more": "" + "more": "", + "see-details": "" }, "log-rows": { "disable-popover": { @@ -1792,6 +2035,13 @@ "shortcut": "" } }, + "logs-navigation": { + "newer-logs": "", + "older-logs": "", + "scroll-bottom": "", + "scroll-top": "", + "start-of-range": "" + }, "popover-menu": { "copy": "", "disable-menu": "", @@ -1881,6 +2131,7 @@ "title": "" }, "migrated-counts": { + "alert_rule_groups": "", "alert_rules": "", "contact_points": "", "dashboards": "", @@ -1941,9 +2192,8 @@ "public-preview": { "button-text": "", "message": "", - "message-plugins": "", - "title": "", - "title-plugins": "" + "message-cloud": "", + "title": "" }, "resource-details": { "dismiss-button": "", @@ -1986,6 +2236,7 @@ }, "resource-type": { "alert_rule": "", + "alert_rule_group": "", "contact_point": "", "dashboard": "Painel de controle", "datasource": "Fonte de dados", @@ -2031,6 +2282,15 @@ "title": "Por que hospedar com o Grafana?" } }, + "multicombobox": { + "all": { + "title": "", + "title-filtered": "" + }, + "clear": { + "title": "" + } + }, "nav": { "add-new-connections": { "title": "Adicionar nova conexão" @@ -2384,7 +2644,8 @@ "list-label": "Navegação", "open": "", "undock": "Desacoplar menu" - } + }, + "rss-button": "" }, "news": { "drawer": { @@ -2516,19 +2777,35 @@ } }, "details": { + "connections-tab": { + "description": "" + }, "labels": { "contactGrafanaLabs": "", + "customLinks": "", + "customLinksTooltip": "", "dependencies": "", + "documentation": "", "downloads": "", "from": "", "installedVersion": "", "lastCommitDate": "", "latestVersion": "", - "links": "", + "license": "", + "raiseAnIssue": "", "reportAbuse": "", + "reportAbuseTooltip": "", + "repository": "", "signature": "", "status": "", "updatedAt": "" + }, + "modal": { + "cancel": "", + "copyEmail": "", + "description": "", + "node": "", + "title": "" } }, "empty-state": { @@ -2743,14 +3020,6 @@ "role-label": "Função" } }, - "query-library": { - "datasource-names": "", - "delete-query-button": "", - "query-template-get-error": "", - "search": "", - "user-info-get-error": "", - "user-names": "" - }, "query-operation": { "header": { "collapse-row": "Recolher linha de consulta", @@ -2760,7 +3029,6 @@ "expand-row": "Expandir linha de consulta", "hide-response": "", "remove-query": "Remover consulta", - "save-to-query-library": "", "show-response": "", "toggle-edit-mode": "Alternar modo de edição de texto" }, @@ -2862,6 +3130,10 @@ "title": "" }, "save-dashboards": { + "message-length": { + "info": "", + "title": "" + }, "name-exists": { "message-info": "", "message-suggestion": "", @@ -3145,6 +3417,7 @@ "home-dashboard-placeholder": "Painel de controle padrão", "locale-label": "Idioma", "locale-placeholder": "Escolher idioma", + "theme-description": "", "theme-label": "Tema da interface", "week-start-label": "Início da semana" }, @@ -3216,6 +3489,13 @@ "url-column-header": "URL da captura", "view-button": "Visualizar" }, + "table": { + "container": { + "content": "", + "show-all-series": "", + "show-only-series": "" + } + }, "tag-filter": { "clear-button": "", "loading": "Carregando...", @@ -3309,20 +3589,26 @@ "start-your-metrics-exploration": "", "subtitle": "" }, - "metric-overview": { - "description-label": "", - "labels": "", - "metric-attributes": "", - "no-description": "", - "type-label": "", - "unit-label": "", - "unknown-type": "" - }, "metric-select": { "filter-by": "", + "native-histogram": "", "new-badge": "", "otel-switch": "" }, + "native-histogram-banner": { + "ch-heatmap": "", + "ch-histogram": "", + "click-histogram": "", + "hide-examples": "", + "learn-more": "", + "metric-examples": "", + "nh-heatmap": "", + "nh-histogram": "", + "now": "", + "previously": "", + "see-examples": "", + "sentence": "" + }, "recent-metrics": { "or-view-a-recent-exploration": "" }, diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index c5cba66ace2..ba17087b1e7 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -34,12 +34,6 @@ "save-button": "" } }, - "actions-editor": { - "inline": { - "add-button": "", - "one-click-link": "" - } - }, "admin": { "anon-users": { "not-found": "" @@ -174,6 +168,24 @@ } }, "alerting": { + "alert": { + "alert-state": "", + "annotations": "", + "evaluation": "", + "evaluation-paused": "", + "evaluation-paused-description": "", + "last-evaluated": "", + "last-evaluation-duration": "", + "last-updated-at": "", + "last-updated-by": "", + "no-annotations": "", + "pending-period": "", + "rule": "", + "rule-identifier": "", + "rule-type": "", + "state-error-timeout": "", + "state-no-data": "" + }, "alert-recording-rule-form": { "evaluation-behaviour": { "description": { @@ -281,6 +293,7 @@ "contactPointFilter": { "label": "" }, + "copy-to-clipboard": "", "export": { "subtitle": { "formats": "", @@ -297,6 +310,13 @@ } } }, + "group-actions": { + "actions-trigger": "", + "delete": "", + "edit": "", + "export": "", + "reorder": "" + }, "list-view": { "empty": { "new-alert-rule": "", @@ -480,11 +500,23 @@ }, "rule-list": { "configure-datasource": "", + "ds-error-boundary": { + "description": "", + "title": "" + }, "filter-view": { "no-more-results": "", "no-rules-found": "" }, - "new-alert-rule": "" + "new-alert-rule": "", + "pagination": { + "next-page": "", + "previous-page": "" + }, + "return-button": { + "title": "" + }, + "rulerrule-loading-error": "" }, "rule-state": { "creating": "", @@ -681,7 +713,10 @@ "title": "" }, "custom-value": { - "label": "" + "description": "" + }, + "group": { + "undefined": "" }, "options": { "no-found": "" @@ -854,6 +889,82 @@ "redirect-link": "Grafana Alerting 中的列表", "subtitle": "与此仪表板相关的警报规则" }, + "default-layout": { + "description": "", + "item-options": { + "repeat": { + "direction": { + "horizontal": "", + "title": "", + "vertical": "" + }, + "max": "", + "title": "", + "variable": { + "description": "", + "title": "" + } + } + }, + "name": "", + "row-actions": { + "delete": "", + "modal": { + "alt-action": "", + "text": "", + "title": "" + } + }, + "row-options": { + "button": { + "label": "" + }, + "form": { + "cancel": "", + "repeat-for": { + "label": "", + "learn-more": "", + "warning": { + "text": "" + } + }, + "title": "", + "update": "" + }, + "modal": { + "title": "" + } + } + }, + "edit-pane": { + "objects": { + "multi-select": { + "selection-number": "" + } + }, + "open": "", + "panels": { + "multi-select": { + "selection-number": "" + } + }, + "row": { + "header": { + "hide": "", + "title": "" + }, + "multi-select": { + "options-header": "", + "selection-number": "" + } + }, + "tab": { + "multi-select": { + "options-header": "", + "selection-number": "" + } + } + }, "empty": { "add-library-panel-body": "添加与其他仪表板共享的可视化。", "add-library-panel-button": "添加库面板", @@ -865,6 +976,9 @@ "import-a-dashboard-header": "导入仪表板", "import-dashboard-button": "导入仪表板" }, + "errors": { + "failed-to-load": "" + }, "inspect": { "data-tab": "数据", "error-tab": "错误", @@ -917,19 +1031,130 @@ "rows": "总行数", "table-title": "统计信息" }, + "options": { + "description": "", + "title": "", + "title-option": "" + }, + "panel-edit": { + "alerting-tab": { + "dashboard-not-saved": "", + "no-rules": "" + } + }, + "responsive-layout": { + "description": "", + "item-options": { + "hide-no-data": "", + "title": "" + }, + "name": "", + "options": { + "columns": "", + "fixed": "", + "min": "", + "one-column": "", + "rows": "", + "three-columns": "", + "two-columns": "" + } + }, + "rows-layout": { + "description": "", + "name": "", + "row": { + "collapse": "", + "expand": "", + "new": "", + "repeat": { + "learn-more": "", + "warning": "" + } + }, + "row-options": { + "height": { + "expand": "", + "hide-row-header": "", + "min": "", + "title": "" + }, + "repeat": { + "title": "", + "variable": { + "title": "" + } + }, + "title": "", + "title-option": "" + } + }, + "tabs-layout": { + "description": "", + "name": "", + "tab": { + "new": "" + }, + "tab-options": { + "title": "", + "title-option": "" + } + }, "toolbar": { "add": "添加", + "add-panel": "", + "add-panel-lib": "", + "add-row": "", + "add-tab": "", "alert-rules": "警报规则", + "back-to-dashboard": "", + "dashboard-settings": { + "label": "", + "tooltip": "" + }, + "discard-library-panel-changes": "", + "discard-panel": "", + "discard-panel-new": "", + "edit": { + "label": "", + "tooltip": "" + }, + "edit-dashboard-v2-schema": "", + "enter-edit-mode": { + "label": "", + "tooltip": "" + }, + "exit-edit-mode": { + "label": "", + "tooltip": "" + }, "mark-favorite": "标记为收藏", + "more-save-options": "", "open-original": "打开原始仪表板", "playlist-next": "前往下一个仪表板", "playlist-previous": "前往上一个仪表板", "playlist-stop": "停止播放列表", + "public-dashboard": "", "refresh": "刷新仪表板", "save": "保存仪表板", + "save-dashboard": { + "label": "", + "tooltip": "" + }, + "save-dashboard-copy": { + "label": "", + "tooltip": "" + }, + "save-dashboard-short": "", + "save-library-panel": "", "settings": "仪表板设置", - "share": "分享仪表板或面板", + "share": { + "label": "", + "tooltip": "" + }, "share-button": "分享", + "show-hidden-elements": "", + "switch-old-dashboard": "", + "unlink-library-panel": "", "unmark-favorite": "取消标记为收藏" }, "validation": { @@ -937,6 +1162,14 @@ "invalid-json": "无有效的 JSON", "tags-expected-array": "标签预期数组", "tags-expected-strings": "标签预期字符串数组" + }, + "viz-panel": { + "options": { + "description": "", + "title": "", + "title-option": "", + "transparent-background": "" + } } }, "dashboard-import": { @@ -1039,6 +1272,12 @@ "angular-deprecation-description": "", "angular-deprecation-heading": "" }, + "panel-queries": { + "add-query-from-library": "" + }, + "query-library": { + "add-query-button": "" + }, "settings": { "variables": { "dependencies": { @@ -1116,40 +1355,11 @@ "scan-for-older-logs": "", "stop-scan": "" }, - "query-library": { - "add-edit-description": "", - "cancel": "", - "default-description": "", - "delete-query": "", - "delete-query-text": "", - "delete-query-title": "", - "private": "", - "public": "", - "query-deleted": "", - "query-template-add-error": "", - "query-template-added": "", - "query-template-edit-error": "", - "query-template-edited": "", - "save": "" - }, - "query-template-modal": { - "add-info": "", - "add-title": "", - "auto-star": "", - "data-source-name": "", - "description": "", - "edit-info": "", - "edit-title": "", - "query": "", - "visibility": "" - }, - "query-template-modall": { - "data-source-type": "" - }, "rich-history": { "close-tooltip": "关闭查询历史记录", "datasource-a-z": "数据源 A-Z", "datasource-z-a": "数据源 Z-A", + "library-history-dropdown": "", "newest-first": "最新在前", "oldest-first": "最早在前", "query-history": "查询历史记录", @@ -1296,10 +1506,9 @@ "sortBy": "" }, "related-logs": { - "docsLink": "", + "LrrDocsLink": "", "openExploreLogs": "", - "relatedLogsUnavailableAfterDocsLink": "", - "relatedLogsUnavailableBeforeDocsLink": "", + "relatedLogsUnavailable": "", "warnExperimentalFeature": "" }, "viewBy": "" @@ -1348,6 +1557,24 @@ "send-custom-feedback": "" }, "grafana-ui": { + "action-editor": { + "button": { + "confirm": "", + "confirm-action": "" + }, + "inline": { + "add-action": "", + "edit-action": "" + }, + "modal": { + "action-body": "", + "action-method": "", + "action-query-params": "", + "action-title": "", + "action-title-placeholder": "", + "one-click-description": "" + } + }, "auto-save-field": { "saved": "", "saving": "" @@ -1364,11 +1591,21 @@ }, "data-link-editor-modal": { "cancel": "", + "one-click-description": "", "save": "" }, + "data-link-inline-editor": { + "one-click": "" + }, "data-links-inline-editor": { "add-link": "", - "one-click-link": "" + "edit-link": "", + "one-click": "", + "one-click-enabled": "", + "title-not-provided": "", + "tooltip-edit": "", + "tooltip-remove": "", + "url-not-provided": "" }, "data-source-http-settings": { "access-help": "", @@ -1449,7 +1686,12 @@ "right-axis-indicator": "" }, "viz-tooltip": { - "footer-add-annotation": "" + "actions-confirmation-input-placeholder": "", + "actions-confirmation-label": "", + "actions-confirmation-message": "", + "footer-add-annotation": "", + "footer-click-to-action": "", + "footer-click-to-navigate": "" } }, "graph": { @@ -1770,7 +2012,8 @@ }, "log-row-message": { "ellipsis": "", - "more": "" + "more": "", + "see-details": "" }, "log-rows": { "disable-popover": { @@ -1782,6 +2025,13 @@ "shortcut": "" } }, + "logs-navigation": { + "newer-logs": "", + "older-logs": "", + "scroll-bottom": "", + "scroll-top": "", + "start-of-range": "" + }, "popover-menu": { "copy": "", "disable-menu": "", @@ -1871,6 +2121,7 @@ "title": "" }, "migrated-counts": { + "alert_rule_groups": "", "alert_rules": "", "contact_points": "", "dashboards": "", @@ -1931,9 +2182,8 @@ "public-preview": { "button-text": "", "message": "", - "message-plugins": "", - "title": "", - "title-plugins": "" + "message-cloud": "", + "title": "" }, "resource-details": { "dismiss-button": "", @@ -1976,6 +2226,7 @@ }, "resource-type": { "alert_rule": "", + "alert_rule_group": "", "contact_point": "", "dashboard": "仪表板", "datasource": "数据源", @@ -2021,6 +2272,15 @@ "title": "为什么使用 Grafana 托管?" } }, + "multicombobox": { + "all": { + "title": "", + "title-filtered": "" + }, + "clear": { + "title": "" + } + }, "nav": { "add-new-connections": { "title": "添加新连接" @@ -2374,7 +2634,8 @@ "list-label": "导航", "open": "", "undock": "取消停靠菜单" - } + }, + "rss-button": "" }, "news": { "drawer": { @@ -2506,19 +2767,35 @@ } }, "details": { + "connections-tab": { + "description": "" + }, "labels": { "contactGrafanaLabs": "", + "customLinks": "", + "customLinksTooltip": "", "dependencies": "", + "documentation": "", "downloads": "", "from": "", "installedVersion": "", "lastCommitDate": "", "latestVersion": "", - "links": "", + "license": "", + "raiseAnIssue": "", "reportAbuse": "", + "reportAbuseTooltip": "", + "repository": "", "signature": "", "status": "", "updatedAt": "" + }, + "modal": { + "cancel": "", + "copyEmail": "", + "description": "", + "node": "", + "title": "" } }, "empty-state": { @@ -2732,14 +3009,6 @@ "role-label": "角色" } }, - "query-library": { - "datasource-names": "", - "delete-query-button": "", - "query-template-get-error": "", - "search": "", - "user-info-get-error": "", - "user-names": "" - }, "query-operation": { "header": { "collapse-row": "折叠查询行", @@ -2749,7 +3018,6 @@ "expand-row": "展开查询行", "hide-response": "", "remove-query": "删除查询", - "save-to-query-library": "", "show-response": "", "toggle-edit-mode": "切换文本编辑模式" }, @@ -2848,6 +3116,10 @@ "title": "" }, "save-dashboards": { + "message-length": { + "info": "", + "title": "" + }, "name-exists": { "message-info": "", "message-suggestion": "", @@ -3131,6 +3403,7 @@ "home-dashboard-placeholder": "默认仪表板", "locale-label": "语言", "locale-placeholder": "选择语言", + "theme-description": "", "theme-label": "UI 主题", "week-start-label": "每周开始日" }, @@ -3202,6 +3475,13 @@ "url-column-header": "快照网址", "view-button": "查看" }, + "table": { + "container": { + "content": "", + "show-all-series": "", + "show-only-series": "" + } + }, "tag-filter": { "clear-button": "", "loading": "加载中...", @@ -3295,20 +3575,26 @@ "start-your-metrics-exploration": "", "subtitle": "" }, - "metric-overview": { - "description-label": "", - "labels": "", - "metric-attributes": "", - "no-description": "", - "type-label": "", - "unit-label": "", - "unknown-type": "" - }, "metric-select": { "filter-by": "", + "native-histogram": "", "new-badge": "", "otel-switch": "" }, + "native-histogram-banner": { + "ch-heatmap": "", + "ch-histogram": "", + "click-histogram": "", + "hide-examples": "", + "learn-more": "", + "metric-examples": "", + "nh-heatmap": "", + "nh-histogram": "", + "now": "", + "previously": "", + "see-examples": "", + "sentence": "" + }, "recent-metrics": { "or-view-a-recent-exploration": "" }, diff --git a/scripts/cli/reportBettererStats.mjs b/scripts/cli/reportBettererStats.ts similarity index 74% rename from scripts/cli/reportBettererStats.mjs rename to scripts/cli/reportBettererStats.ts index 0f1eda1d646..135f98c7752 100644 --- a/scripts/cli/reportBettererStats.mjs +++ b/scripts/cli/reportBettererStats.ts @@ -1,8 +1,7 @@ -// @ts-check import { betterer } from '@betterer/betterer'; -import _ from 'lodash'; +import { camelCase } from 'lodash'; -function logStat(name, value) { +function logStat(name: string, value: number) { // Note that this output format must match the parsing in ci-frontend-metrics.sh // which expects the two values to be separated by a space console.log(`${name} ${value}`); @@ -13,11 +12,11 @@ async function main() { for (const testResults of results.resultSummaries) { const countByMessage = {}; - const name = _.camelCase(testResults.name); + const name = camelCase(testResults.name); Object.values(testResults.details) .flatMap((v) => v) .forEach((detail) => { - const message = _.camelCase(detail.message); + const message = camelCase(detail.message); const metricName = `${name}_${message}`; if (metricName in countByMessage) { countByMessage[metricName]++; @@ -26,7 +25,7 @@ async function main() { } }); - for (const [metricName, count] of Object.entries(countByMessage)) { + for (const [metricName, count] of Object.entries(countByMessage)) { logStat(metricName, count); } } diff --git a/scripts/cli/tsconfig.json b/scripts/cli/tsconfig.json index 9ad59a649d1..0d18e875c5e 100644 --- a/scripts/cli/tsconfig.json +++ b/scripts/cli/tsconfig.json @@ -6,6 +6,9 @@ "extends": "../../tsconfig.json", "ts-node": { "transpileOnly": true, - "swc": true + "swc": true, + "compilerOptions": { + "module": "commonjs" + } } } diff --git a/scripts/import_many_dashboards.sh b/scripts/import_many_dashboards.sh index 60d4fcd55b3..47cbb2c36c2 100755 --- a/scripts/import_many_dashboards.sh +++ b/scripts/import_many_dashboards.sh @@ -3,6 +3,6 @@ for index in {0..3000} do echo -n "index $index" - curl 'http://localhost:3000/api/dashboards/import' -H 'Pragma: no-cache' -H 'Origin: http://localhost:3000' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: en-US,en;q=0.8,sv;q=0.6' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36' -H 'Content-Type: application/json;charset=UTF-8' -H 'Accept: application/json, text/plain, */*' -H 'Cache-Control: no-cache' -H 'Referer: http://localhost:3000/dashboard/new?editview=import' -H 'Cookie: grafana_sess=662a67f11b47e657; grafana_user=admin; grafana_remember=bd839923f24f648c7cb53ede6ff9ef40826204e9a22df8f9; toggles=%7B%7D' -H 'Connection: keep-alive' --data-binary $'{"dashboard":{"__inputs":[{"name":"DS_GRAPHITE","label":"graphite","description":"","type":"datasource","pluginId":"graphite","pluginName":"Graphite"}],"__requires":[{"type":"panel","id":"singlestat","name":"Singlestat","version":""},{"type":"panel","id":"graph","name":"Graph","version":""},{"type":"grafana","id":"grafana","name":"Grafana","version":"3.1.0"},{"type":"datasource","id":"graphite","name":"Graphite","version":"1.0.0"}],"id":null,"title":"Big Dashboard dashname '"$index"$'","tags":["startpage","home","presentation"],"style":"dark","timezone":"browser","editable":true,"hideControls":false,"sharedCrosshair":true,"rows":[{"collapse":false,"editable":true,"height":"100px","panels":[{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(245, 54, 54, 0.9)","rgba(237, 129, 40, 0.89)","rgba(50, 172, 45, 0.97)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":100,"minValue":0,"show":false,"thresholdLabels":false,"thresholdMarkers":true},"id":16,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":3,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":true},"targets":[{"refId":"A","target":"apps.backend.backend_02.counters.requests.count"}],"thresholds":"100,270","title":"Sign ups","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(245, 54, 54, 0.9)","rgba(237, 129, 40, 0.89)","rgba(50, 172, 45, 0.97)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":100,"minValue":0,"show":false,"thresholdLabels":false,"thresholdMarkers":true},"id":15,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":3,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":true},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.7)"}],"thresholds":"100,270","title":"Logins","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(245, 54, 54, 0.9)","rgba(237, 129, 40, 0.89)","rgba(50, 172, 45, 0.97)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":100,"minValue":0,"show":false,"thresholdLabels":false,"thresholdMarkers":true},"id":17,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":3,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":true},"targets":[{"refId":"A","target":"apps.backend.backend_04.counters.requests.count"}],"thresholds":"100,270","title":"Sign outs","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(245, 54, 54, 0.9)","rgba(237, 129, 40, 0.89)","rgba(50, 172, 45, 0.97)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":100,"minValue":0,"show":false,"thresholdLabels":false,"thresholdMarkers":true},"id":18,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":3,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":true},"targets":[{"refId":"A","target":"scale(apps.backend.backend_03.counters.requests.count, 0.3)"}],"thresholds":"100,270","title":"Support calls","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1}],"title":"New row"},{"collapse":false,"editable":true,"height":218.4375,"panels":[{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":20,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.7)"}],"thresholds":"200,270","title":"Logins","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":24,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.2)"}],"thresholds":"200,270","title":"Google hits","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"bytes","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":22,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.4)"}],"thresholds":"200,270","title":"Memory","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":21,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.8)"}],"thresholds":"200,270","title":"Logouts","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":26,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.2)"}],"thresholds":"200,270","title":"Google hits","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":25,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.8)"}],"thresholds":"200,270","title":"Logouts","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1}],"title":"New row"},{"collapsable":true,"collapse":false,"editable":true,"height":"250px","notice":false,"panels":[{"aliasColors":{"cpu":"#E24D42","memory":"#6ED0E0","statsd.fakesite.counters.session_start.desktop.count":"#6ED0E0"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":3,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":4,"interactive":true,"legend":{"avg":false,"current":true,"max":false,"min":true,"show":true,"total":false,"values":false},"legend_counts":true,"lines":true,"linewidth":2,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[{"alias":"cpu","fill":0,"lines":true,"yaxis":2,"zindex":2},{"alias":"memory","pointradius":2,"points":true}],"span":4,"spyable":true,"stack":false,"steppedLine":false,"targets":[{"hide":false,"refId":"A","target":"alias(movingAverage(scaleToSeconds(apps.fakesite.web_server_01.counters.request_status.code_302.count, 10), 20), \'cpu\')"},{"refId":"B","target":"alias(statsd.fakesite.counters.session_start.desktop.count, \'memory\')"}],"timeFrom":null,"timeShift":null,"timezone":"browser","title":"Memory / CPU","tooltip":{"msResolution":false,"query_as_alias":true,"shared":false,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"bytes","logBase":1,"max":null,"min":null,"show":true},{"format":"percent","logBase":1,"max":null,"min":0,"show":true}],"zerofill":true},{"aliasColors":{"logins":"#7EB26D","logins (-1 day)":"#447EBC"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":1,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":3,"interactive":true,"legend":{"alignAsTable":false,"avg":false,"current":true,"max":true,"min":true,"rightSide":false,"show":true,"total":false,"values":false},"legend_counts":true,"lines":true,"linewidth":1,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[],"span":4,"spyable":true,"stack":true,"steppedLine":false,"targets":[{"refId":"A","target":"alias(movingAverage(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count, 1), 2), \'logins\')"},{"refId":"B","target":"alias(movingAverage(timeShift(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count, 1), \'1h\'), 2), \'logins (-1 hour)\')"}],"timeFrom":null,"timeShift":"1h","timezone":"browser","title":"logins","tooltip":{"msResolution":false,"query_as_alias":true,"shared":false,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"short","logBase":1,"max":null,"min":null,"show":true},{"format":"short","logBase":1,"max":null,"min":null,"show":true}],"zerofill":true},{"aliasColors":{"cpu":"#E24D42","memory":"#6ED0E0","statsd.fakesite.counters.session_start.desktop.count":"#6ED0E0"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":3,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":19,"interactive":true,"legend":{"avg":false,"current":true,"max":false,"min":true,"show":true,"total":false,"values":false},"legend_counts":true,"lines":true,"linewidth":2,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[{"alias":"cpu","fill":0,"lines":true,"yaxis":2,"zindex":2},{"alias":"memory","pointradius":2,"points":true}],"span":4,"spyable":true,"stack":false,"steppedLine":false,"targets":[{"hide":false,"refId":"A","target":"alias(movingAverage(scaleToSeconds(apps.fakesite.web_server_01.counters.request_status.code_302.count, 10), 20), \'cpu\')"},{"refId":"B","target":"alias(statsd.fakesite.counters.session_start.desktop.count, \'memory\')"}],"timeFrom":null,"timeShift":"1h","timezone":"browser","title":"Memory / CPU","tooltip":{"msResolution":false,"query_as_alias":true,"shared":false,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"bytes","logBase":1,"max":null,"min":null,"show":true},{"format":"percent","logBase":1,"max":null,"min":0,"show":true}],"zerofill":true}],"title":"test"},{"collapsable":true,"collapse":false,"editable":true,"height":"300px","notice":false,"panels":[{"aliasColors":{"web_server_01":"#B7DBAB","web_server_02":"#7EB26D","web_server_03":"#508642","web_server_04":"#3F6833"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":8,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":2,"interactive":true,"legend":{"alignAsTable":false,"avg":false,"current":false,"max":false,"min":false,"rightSide":false,"show":true,"total":false,"values":false},"legend_counts":true,"lines":true,"linewidth":2,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[],"span":4,"spyable":true,"stack":true,"steppedLine":false,"targets":[{"refId":"A","target":"aliasByNode(movingAverage(scaleToSeconds(apps.fakesite.*.counters.requests.count, 1), 2), 2)"}],"timeFrom":null,"timeShift":null,"timezone":"browser","title":"server requests","tooltip":{"msResolution":false,"query_as_alias":true,"shared":true,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"short","logBase":1,"max":null,"min":null,"show":true},{"format":"short","logBase":1,"max":null,"min":null,"show":true}],"zerofill":true},{"aliasColors":{"upper_25":"#F9E2D2","upper_50":"#F2C96D","upper_75":"#EAB839"},"annotate":{"enable":false},"bars":true,"datasource":"${DS_GRAPHITE}","editable":true,"fill":1,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":5,"interactive":true,"legend":{"alignAsTable":true,"avg":true,"current":false,"max":false,"min":false,"rightSide":true,"show":true,"total":false,"values":true},"legend_counts":true,"lines":false,"linewidth":2,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[],"span":4,"spyable":true,"stack":true,"steppedLine":false,"targets":[{"refId":"A","target":"aliasByNode(summarize(statsd.fakesite.timers.ads_timer.*, \'4min\', \'avg\'), 4)"}],"timeFrom":null,"timeShift":null,"timezone":"browser","title":"client side full page load","tooltip":{"msResolution":false,"query_as_alias":true,"shared":false,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"ms","logBase":1,"max":null,"min":null,"show":true},{"format":"short","logBase":1,"max":null,"min":null,"show":true}],"zerofill":true},{"aliasColors":{"web_server_01":"#B7DBAB","web_server_02":"#7EB26D","web_server_03":"#508642","web_server_04":"#3F6833"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":8,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":14,"interactive":true,"legend":{"alignAsTable":false,"avg":false,"current":false,"max":false,"min":false,"rightSide":false,"show":true,"total":false,"values":false},"legend_counts":true,"lines":true,"linewidth":2,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[],"span":4,"spyable":true,"stack":true,"steppedLine":false,"targets":[{"refId":"A","target":"aliasByNode(movingAverage(scaleToSeconds(apps.fakesite.*.counters.requests.count, 1), 2), 2)"}],"timeFrom":null,"timeShift":null,"timezone":"browser","title":"server requests","tooltip":{"msResolution":false,"query_as_alias":true,"shared":true,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"short","logBase":1,"max":null,"min":null,"show":true},{"format":"short","logBase":1,"max":null,"min":null,"show":true}],"zerofill":true}],"title":""},{"collapsable":true,"collapse":false,"editable":true,"height":"200px","notice":false,"panels":[{"aliasColors":{"cpu1":"#EF843C","cpu2":"#EAB839","upper_25":"#B7DBAB","upper_50":"#7EB26D","upper_75":"#629E51","upper_90":"#629E51","upper_95":"#508642"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":3,"grid":{"max":null,"min":null,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":6,"interactive":true,"legend":{"alignAsTable":true,"avg":true,"current":true,"legendSideLastValue":true,"max":false,"min":false,"rightSide":true,"show":false,"total":false,"values":true},"legend_counts":true,"lines":true,"linewidth":2,"links":[],"nullPointMode":"connected","options":false,"percentage":false,"pointradius":1,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[{"alias":"this is test of breaking","yaxis":1}],"span":12,"spyable":true,"stack":false,"steppedLine":false,"targets":[{"refId":"A","target":"aliasByNode(statsd.fakesite.timers.ads_timer.*,4)"},{"refId":"B","target":"alias(scale(statsd.fakesite.timers.ads_timer.upper_95,-1),\'cpu1\')"},{"refId":"C","target":"alias(scale(statsd.fakesite.timers.ads_timer.upper_75,-1),\'cpu2\')"}],"timeFrom":null,"timeShift":null,"timezone":"browser","title":"","tooltip":{"msResolution":false,"query_as_alias":true,"shared":false,"value_type":"cumulative","sort":0},"transparent":true,"type":"graph","xaxis":{"show":false},"yaxes":[{"format":"ms","logBase":1,"max":null,"min":null,"show":false},{"format":"short","logBase":1,"max":null,"min":null,"show":false}],"zerofill":true}],"title":"test"}],"time":{"from":"now-30m","to":"now"},"timepicker":{"collapse":false,"enable":true,"notice":false,"now":true,"refresh_intervals":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"status":"Stable","time_options":["5m","15m","1h","2h"," 6h","12h","24h","2d","7d","30d"],"type":"timepicker"},"templating":{"enable":false,"list":[]},"annotations":{"enable":false,"list":[]},"refresh":false,"schemaVersion":12,"version":5,"links":[],"gnetId":null},"overwrite":true,"inputs":[{"name":"DS_GRAPHITE","type":"datasource","pluginId":"graphite","value":"graphite"}]}' --compressed + curl 'http://localhost:3000/api/dashboards/import' -H 'Pragma: no-cache' -H 'Origin: http://localhost:3000' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: en-US,en;q=0.8,sv;q=0.6' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36' -H 'Content-Type: application/json;charset=UTF-8' -H 'Accept: application/json, text/plain, */*' -H 'Cache-Control: no-cache' -H 'Referer: http://localhost:3000/dashboard/new?editview=import' -H 'Cookie: grafana_sess=662a67f11b47e657; grafana_user=admin; grafana_remember=bd839923f24f648c7cb53ede6ff9ef40826204e9a22df8f9; toggles=%7B%7D' -H 'Connection: keep-alive' --data-binary $'{"dashboard":{"__inputs":[{"name":"DS_GRAPHITE","label":"graphite","description":"","type":"datasource","pluginId":"graphite","pluginName":"Graphite"}],"__requires":[{"type":"panel","id":"singlestat","name":"Singlestat","version":""},{"type":"panel","id":"graph","name":"Graph","version":""},{"type":"grafana","id":"grafana","name":"Grafana","version":"3.1.0"},{"type":"datasource","id":"graphite","name":"Graphite","version":"1.0.0"}],"id":null,"title":"Big Dashboard dashname '"$index"$'","tags":["startpage","home","presentation"],"style":"dark","timezone":"browser","editable":true,"hideControls":false,"sharedCrosshair":true,"rows":[{"collapse":false,"editable":true,"height":"100px","panels":[{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(245, 54, 54, 0.9)","rgba(237, 129, 40, 0.89)","rgba(50, 172, 45, 0.97)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":100,"minValue":0,"show":false,"thresholdLabels":false,"thresholdMarkers":true},"id":16,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":3,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":true},"targets":[{"refId":"A","target":"apps.backend.backend_02.counters.requests.count"}],"thresholds":"100,270","title":"Sign ups","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(245, 54, 54, 0.9)","rgba(237, 129, 40, 0.89)","rgba(50, 172, 45, 0.97)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":100,"minValue":0,"show":false,"thresholdLabels":false,"thresholdMarkers":true},"id":15,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":3,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":true},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.7)"}],"thresholds":"100,270","title":"Logins","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(245, 54, 54, 0.9)","rgba(237, 129, 40, 0.89)","rgba(50, 172, 45, 0.97)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":100,"minValue":0,"show":false,"thresholdLabels":false,"thresholdMarkers":true},"id":17,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":3,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":true},"targets":[{"refId":"A","target":"apps.backend.backend_04.counters.requests.count"}],"thresholds":"100,270","title":"Sign outs","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(245, 54, 54, 0.9)","rgba(237, 129, 40, 0.89)","rgba(50, 172, 45, 0.97)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":100,"minValue":0,"show":false,"thresholdLabels":false,"thresholdMarkers":true},"id":18,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":3,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":true},"targets":[{"refId":"A","target":"scale(apps.backend.backend_03.counters.requests.count, 0.3)"}],"thresholds":"100,270","title":"Support calls","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1}],"title":"New row"},{"collapse":false,"editable":true,"height":218.4375,"panels":[{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":20,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.7)"}],"thresholds":"200,270","title":"Logins","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":24,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.2)"}],"thresholds":"200,270","title":"Google hits","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"bytes","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":22,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.4)"}],"thresholds":"200,270","title":"Memory","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":21,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.8)"}],"thresholds":"200,270","title":"Logouts","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":26,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.2)"}],"thresholds":"200,270","title":"Google hits","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1},{"cacheTimeout":null,"colorBackground":false,"colorValue":true,"colors":["rgba(50, 172, 45, 0.97)","rgba(237, 129, 40, 0.89)","rgba(245, 54, 54, 0.9)"],"datasource":"${DS_GRAPHITE}","editable":true,"error":false,"format":"none","gauge":{"maxValue":300,"minValue":0,"show":true,"thresholdLabels":false,"thresholdMarkers":true},"id":25,"interval":null,"links":[],"maxDataPoints":100,"nullPointMode":"connected","nullText":null,"postfix":"","postfixFontSize":"50%","prefix":"","prefixFontSize":"50%","span":2,"sparkline":{"fillColor":"rgba(31, 118, 189, 0.18)","full":true,"lineColor":"rgb(31, 120, 193)","show":false},"targets":[{"refId":"A","target":"scale(apps.backend.backend_01.counters.requests.count, 0.8)"}],"thresholds":"200,270","title":"Logouts","type":"singlestat","valueFontSize":"100%","valueMaps":[{"op":"=","text":"N/A","value":"null"}],"valueName":"avg","mappingTypes":[{"name":"value to text","value":1},{"name":"range to text","value":2}],"rangeMaps":[{"from":"null","to":"null","text":"N/A"}],"mappingType":1}],"title":"New row"},{"collapsable":true,"collapse":false,"editable":true,"height":"250px","notice":false,"panels":[{"aliasColors":{"cpu":"#E24D42","memory":"#6ED0E0","statsd.fakesite.counters.session_start.desktop.count":"#6ED0E0"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":3,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":4,"interactive":true,"legend":{"avg":false,"current":true,"max":false,"min":true,"show":true,"total":false,"values":false},"legend_counts":true,"lines":true,"linewidth":2,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[{"alias":"cpu","fill":0,"lines":true,"yaxis":2,"zindex":2},{"alias":"memory","pointradius":2,"points":true}],"span":4,"spyable":true,"stack":false,"steppedLine":false,"targets":[{"hide":false,"refId":"A","target":"alias(movingAverage(scaleToSeconds(apps.fakesite.web_server_01.counters.request_status.code_302.count, 10), 20), \'cpu\')"},{"refId":"B","target":"alias(statsd.fakesite.counters.session_start.desktop.count, \'memory\')"}],"timeFrom":null,"timeShift":null,"timezone":"browser","title":"Memory / CPU","tooltip":{"msResolution":false,"query_as_alias":true,"shared":false,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"bytes","logBase":1,"max":null,"min":null,"show":true},{"format":"percent","logBase":1,"max":null,"min":0,"show":true}],"zerofill":true},{"aliasColors":{"logins":"#7EB26D","logins (-1 day)":"#447EBC"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":1,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":3,"interactive":true,"legend":{"alignAsTable":false,"avg":false,"current":true,"max":true,"min":true,"rightSide":false,"show":true,"total":false,"values":false},"legend_counts":true,"lines":true,"linewidth":1,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[],"span":4,"spyable":true,"stack":true,"steppedLine":false,"targets":[{"refId":"A","target":"alias(movingAverage(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count, 1), 2), \'logins\')"},{"refId":"B","target":"alias(movingAverage(timeShift(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count, 1), \'1h\'), 2), \'logins (-1 hour)\')"}],"timeFrom":null,"timeShift":"1h","timezone":"browser","title":"logins","tooltip":{"msResolution":false,"query_as_alias":true,"shared":false,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"short","logBase":1,"max":null,"min":null,"show":true},{"format":"short","logBase":1,"max":null,"min":null,"show":true}],"zerofill":true},{"aliasColors":{"cpu":"#E24D42","memory":"#6ED0E0","statsd.fakesite.counters.session_start.desktop.count":"#6ED0E0"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":3,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":19,"interactive":true,"legend":{"avg":false,"current":true,"max":false,"min":true,"show":true,"total":false,"values":false},"legend_counts":true,"lines":true,"linewidth":2,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[{"alias":"cpu","fill":0,"lines":true,"yaxis":2,"zindex":2},{"alias":"memory","pointradius":2,"points":true}],"span":4,"spyable":true,"stack":false,"steppedLine":false,"targets":[{"hide":false,"refId":"A","target":"alias(movingAverage(scaleToSeconds(apps.fakesite.web_server_01.counters.request_status.code_302.count, 10), 20), \'cpu\')"},{"refId":"B","target":"alias(statsd.fakesite.counters.session_start.desktop.count, \'memory\')"}],"timeFrom":null,"timeShift":"1h","timezone":"browser","title":"Memory / CPU","tooltip":{"msResolution":false,"query_as_alias":true,"shared":false,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"bytes","logBase":1,"max":null,"min":null,"show":true},{"format":"percent","logBase":1,"max":null,"min":0,"show":true}],"zerofill":true}],"title":"test"},{"collapsable":true,"collapse":false,"editable":true,"height":"300px","notice":false,"panels":[{"aliasColors":{"web_server_01":"#B7DBAB","web_server_02":"#7EB26D","web_server_03":"#508642","web_server_04":"#3F6833"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":8,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":2,"interactive":true,"legend":{"alignAsTable":false,"avg":false,"current":false,"max":false,"min":false,"rightSide":false,"show":true,"total":false,"values":false},"legend_counts":true,"lines":true,"linewidth":2,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[],"span":4,"spyable":true,"stack":true,"steppedLine":false,"targets":[{"refId":"A","target":"aliasByNode(movingAverage(scaleToSeconds(apps.fakesite.*.counters.requests.count, 1), 2), 2)"}],"timeFrom":null,"timeShift":null,"timezone":"browser","title":"server requests","tooltip":{"msResolution":false,"query_as_alias":true,"shared":true,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"short","logBase":1,"max":null,"min":null,"show":true},{"format":"short","logBase":1,"max":null,"min":null,"show":true}],"zerofill":true},{"aliasColors":{"upper_25":"#F9E2D2","upper_50":"#F2C96D","upper_75":"#EAB839"},"annotate":{"enable":false},"bars":true,"datasource":"${DS_GRAPHITE}","editable":true,"fill":1,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":5,"interactive":true,"legend":{"alignAsTable":true,"avg":true,"current":false,"max":false,"min":false,"rightSide":true,"show":true,"total":false,"values":true},"legend_counts":true,"lines":false,"linewidth":2,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[],"span":4,"spyable":true,"stack":true,"steppedLine":false,"targets":[{"refId":"A","target":"aliasByNode(summarize(statsd.fakesite.timers.ads_timer.*, \'4min\', \'avg\'), 4)"}],"timeFrom":null,"timeShift":null,"timezone":"browser","title":"client side full page load","tooltip":{"msResolution":false,"query_as_alias":true,"shared":false,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"ms","logBase":1,"max":null,"min":null,"show":true},{"format":"short","logBase":1,"max":null,"min":null,"show":true}],"zerofill":true},{"aliasColors":{"web_server_01":"#B7DBAB","web_server_02":"#7EB26D","web_server_03":"#508642","web_server_04":"#3F6833"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":8,"grid":{"max":null,"min":0,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":14,"interactive":true,"legend":{"alignAsTable":false,"avg":false,"current":false,"max":false,"min":false,"rightSide":false,"show":true,"total":false,"values":false},"legend_counts":true,"lines":true,"linewidth":2,"nullPointMode":"connected","options":false,"percentage":false,"pointradius":5,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[],"span":4,"spyable":true,"stack":true,"steppedLine":false,"targets":[{"refId":"A","target":"aliasByNode(movingAverage(scaleToSeconds(apps.fakesite.*.counters.requests.count, 1), 2), 2)"}],"timeFrom":null,"timeShift":null,"timezone":"browser","title":"server requests","tooltip":{"msResolution":false,"query_as_alias":true,"shared":true,"value_type":"cumulative","sort":0},"type":"graph","xaxis":{"show":true},"yaxes":[{"format":"short","logBase":1,"max":null,"min":null,"show":true},{"format":"short","logBase":1,"max":null,"min":null,"show":true}],"zerofill":true}],"title":""},{"collapsable":true,"collapse":false,"editable":true,"height":"200px","notice":false,"panels":[{"aliasColors":{"cpu1":"#EF843C","cpu2":"#EAB839","upper_25":"#B7DBAB","upper_50":"#7EB26D","upper_75":"#629E51","upper_90":"#629E51","upper_95":"#508642"},"annotate":{"enable":false},"bars":false,"datasource":"${DS_GRAPHITE}","editable":true,"fill":3,"grid":{"max":null,"min":null,"threshold1":null,"threshold1Color":"rgba(216, 200, 27, 0.27)","threshold2":null,"threshold2Color":"rgba(234, 112, 112, 0.22)"},"id":6,"interactive":true,"legend":{"alignAsTable":true,"avg":true,"current":true,"legendSideLastValue":true,"max":false,"min":false,"rightSide":true,"show":false,"total":false,"values":true},"legend_counts":true,"lines":true,"linewidth":2,"links":[],"nullPointMode":"connected","options":false,"percentage":false,"pointradius":1,"points":false,"renderer":"flot","resolution":100,"scale":1,"seriesOverrides":[{"alias":"this is test of breaking","yaxis":1}],"span":12,"spyable":true,"stack":false,"steppedLine":false,"targets":[{"refId":"A","target":"aliasByNode(statsd.fakesite.timers.ads_timer.*,4)"},{"refId":"B","target":"alias(scale(statsd.fakesite.timers.ads_timer.upper_95,-1),\'cpu1\')"},{"refId":"C","target":"alias(scale(statsd.fakesite.timers.ads_timer.upper_75,-1),\'cpu2\')"}],"timeFrom":null,"timeShift":null,"timezone":"browser","title":"","tooltip":{"msResolution":false,"query_as_alias":true,"shared":false,"value_type":"cumulative","sort":0},"transparent":true,"type":"graph","xaxis":{"show":false},"yaxes":[{"format":"ms","logBase":1,"max":null,"min":null,"show":false},{"format":"short","logBase":1,"max":null,"min":null,"show":false}],"zerofill":true}],"title":"test"}],"time":{"from":"now-30m","to":"now"},"timepicker":{"collapse":false,"enable":true,"notice":false,"now":true,"refresh_intervals":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"status":"Stable","type":"timepicker"},"templating":{"enable":false,"list":[]},"annotations":{"enable":false,"list":[]},"refresh":false,"schemaVersion":12,"version":5,"links":[],"gnetId":null},"overwrite":true,"inputs":[{"name":"DS_GRAPHITE","type":"datasource","pluginId":"graphite","value":"graphite"}]}' --compressed done diff --git a/tools/setup_grafana_alertmanager_integration_test_images.go b/tools/setup_grafana_alertmanager_integration_test_images.go new file mode 100644 index 00000000000..3b6e1436446 --- /dev/null +++ b/tools/setup_grafana_alertmanager_integration_test_images.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "sync" + + amtests "github.com/grafana/grafana/pkg/tests/alertmanager" +) + +func docker(args []string) { + cmd := exec.Command("docker", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + fmt.Printf("docker pull failed: %v\n", err) + os.Exit(1) + } +} + +func main() { + var wg sync.WaitGroup + + for _, cmd := range [][]string{ + {"pull", amtests.GetGrafanaImage()}, + {"pull", amtests.GetLokiImage()}, + {"pull", amtests.GetPostgresImage()}, + {"build", "-t", "webhook-receiver", "devenv/docker/blocks/stateful_webhook"}, + } { + wg.Add(1) + + go func(cmd []string) { + defer wg.Done() + + docker(cmd) + }(cmd) + } + + wg.Wait() +} diff --git a/yarn.lock b/yarn.lock index d5061c52875..49162fcfe61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3814,11 +3814,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:5.42.0": - version: 5.42.0 - resolution: "@grafana/scenes-react@npm:5.42.0" +"@grafana/scenes-react@npm:6.0.2": + version: 6.0.2 + resolution: "@grafana/scenes-react@npm:6.0.2" dependencies: - "@grafana/scenes": "npm:5.42.0" + "@grafana/scenes": "npm:6.0.2" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3829,20 +3829,21 @@ __metadata: "@grafana/ui": ^11.0.0 react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/c94db6d57b02be5f960e44dc0c4be46e5e4708ede34f2f4e30934134543db88c8b553aceb89d9b3ae4dbf5d1eab1f48e534eb9a31d5c99b8908f715e45cb6dcf + react-router-dom: ^6.28.0 + checksum: 10/9744e01f2ff912229e43cedfa41d626ccdfd034f5b9718b57c593bc90edadade960f76baf1d8ad19eed03709c17c62397df1871b89acc635172aa14f6a20e096 languageName: node linkType: hard -"@grafana/scenes@npm:5.42.0": - version: 5.42.0 - resolution: "@grafana/scenes@npm:5.42.0" +"@grafana/scenes@npm:6.0.2": + version: 6.0.2 + resolution: "@grafana/scenes@npm:6.0.2" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" "@tanstack/react-virtual": "npm:^3.9.0" - react-grid-layout: "npm:^1.3.4" - react-use: "npm:^17.5.0" - react-virtualized-auto-sizer: "npm:^1.0.24" + react-grid-layout: "npm:1.3.4" + react-use: "npm:17.5.0" + react-virtualized-auto-sizer: "npm:1.0.24" uuid: "npm:^9.0.0" peerDependencies: "@grafana/data": ">=10.4" @@ -3852,7 +3853,8 @@ __metadata: "@grafana/ui": ">=10.4" react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/3232a499b839a45c8eed924441e4423d1b1d9f6b4ea6725437d357c1afea93d9ab300c46319b1ddab76b0da1197905b22c3abe8095d78c3320b1befb4731bd24 + react-router-dom: ^6.28.0 + checksum: 10/2584f296db6299ef0a09d51f5c267ebcf7e44bd17b4d6516e38d3220f8f1d7aebc63c5fc6523979c4ac4d3f555416ca573e85e03bd36eb33a11941a5b3497149 languageName: node linkType: hard @@ -4122,7 +4124,7 @@ __metadata: react-i18next: "npm:^15.0.0" react-inlinesvg: "npm:4.1.5" react-loading-skeleton: "npm:3.5.0" - react-router-dom: "npm:5.3.3" + react-router-dom: "npm:5.3.4" react-router-dom-v5-compat: "npm:^6.26.1" react-select: "npm:5.10.0" react-select-event: "npm:^5.1.0" @@ -18149,8 +18151,8 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:5.42.0" - "@grafana/scenes-react": "npm:5.42.0" + "@grafana/scenes": "npm:6.0.2" + "@grafana/scenes-react": "npm:6.0.2" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0" @@ -18397,8 +18399,8 @@ __metadata: react-refresh: "npm:0.14.0" react-resizable: "npm:3.0.5" react-responsive-carousel: "npm:^3.2.23" - react-router: "npm:5.3.3" - react-router-dom: "npm:5.3.3" + react-router: "npm:5.3.4" + react-router-dom: "npm:5.3.4" react-router-dom-v5-compat: "npm:^6.26.1" react-select: "npm:5.10.0" react-select-event: "npm:5.5.1" @@ -22461,19 +22463,6 @@ __metadata: languageName: node linkType: hard -"mini-create-react-context@npm:^0.4.0": - version: 0.4.1 - resolution: "mini-create-react-context@npm:0.4.1" - dependencies: - "@babel/runtime": "npm:^7.12.1" - tiny-warning: "npm:^1.0.3" - peerDependencies: - prop-types: ^15.0.0 - react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/c816c785b7dccd67fdfa6a5edc673363b11845b6abca8a9d9f3ffa74520266d979b56f5db0dfc62ed912a90553c15be28c816311fc9c7856ab66a81d461d50e6 - languageName: node - linkType: hard - "mini-css-extract-plugin@npm:2.9.2": version: 2.9.2 resolution: "mini-css-extract-plugin@npm:2.9.2" @@ -23004,7 +22993,7 @@ __metadata: languageName: node linkType: hard -"nano-css@npm:^5.3.1, nano-css@npm:^5.6.2": +"nano-css@npm:^5.3.1, nano-css@npm:^5.6.1, nano-css@npm:^5.6.2": version: 5.6.2 resolution: "nano-css@npm:5.6.2" dependencies: @@ -26766,20 +26755,20 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:5.3.3": - version: 5.3.3 - resolution: "react-router-dom@npm:5.3.3" +"react-router-dom@npm:5.3.4": + version: 5.3.4 + resolution: "react-router-dom@npm:5.3.4" dependencies: "@babel/runtime": "npm:^7.12.13" history: "npm:^4.9.0" loose-envify: "npm:^1.3.1" prop-types: "npm:^15.6.2" - react-router: "npm:5.3.3" + react-router: "npm:5.3.4" tiny-invariant: "npm:^1.0.2" tiny-warning: "npm:^1.0.0" peerDependencies: react: ">=15" - checksum: 10/49552596f1a4c753b99324a5f4345b3ee91fbb780aa65851a7113f053044ef96c083d2ded12937e593b23a0fcdf58b9e49780df6bf6e27d9eeb348b3c85ae611 + checksum: 10/5e0696ae2d86f466ff700944758a227e1dcd79b48797d567776506e4e3b4a08b81336155feb86a33be9f38c17c4d3d94212b5c60c8ee9a086022e4fd3961db29 languageName: node linkType: hard @@ -26796,15 +26785,14 @@ __metadata: languageName: node linkType: hard -"react-router@npm:5.3.3": - version: 5.3.3 - resolution: "react-router@npm:5.3.3" +"react-router@npm:5.3.4": + version: 5.3.4 + resolution: "react-router@npm:5.3.4" dependencies: "@babel/runtime": "npm:^7.12.13" history: "npm:^4.9.0" hoist-non-react-statics: "npm:^3.1.0" loose-envify: "npm:^1.3.1" - mini-create-react-context: "npm:^0.4.0" path-to-regexp: "npm:^1.7.0" prop-types: "npm:^15.6.2" react-is: "npm:^16.6.0" @@ -26812,7 +26800,7 @@ __metadata: tiny-warning: "npm:^1.0.0" peerDependencies: react: ">=15" - checksum: 10/4631eed91020c73950804c7c7454e74b2eb495f803c5ca60c8b5572ca72cc06e336f3b08d9ee3fa730128a52c4d9e16d1aa7e8b7f85560629117e16d99a01cef + checksum: 10/99d54a99af6bc6d7cad2e5ea7eee9485b62a8b8e16a1182b18daa7fad7dafa5e526850eaeebff629848b297ae055a9cb5b4aba8760e81af8b903efc049d48f5c languageName: node linkType: hard @@ -27008,6 +26996,31 @@ __metadata: languageName: node linkType: hard +"react-use@npm:17.5.0": + version: 17.5.0 + resolution: "react-use@npm:17.5.0" + dependencies: + "@types/js-cookie": "npm:^2.2.6" + "@xobotyi/scrollbar-width": "npm:^1.9.5" + copy-to-clipboard: "npm:^3.3.1" + fast-deep-equal: "npm:^3.1.3" + fast-shallow-equal: "npm:^1.0.0" + js-cookie: "npm:^2.2.1" + nano-css: "npm:^5.6.1" + react-universal-interface: "npm:^0.6.2" + resize-observer-polyfill: "npm:^1.5.1" + screenfull: "npm:^5.1.0" + set-harmonic-interval: "npm:^1.0.1" + throttle-debounce: "npm:^3.0.1" + ts-easing: "npm:^0.2.0" + tslib: "npm:^2.1.0" + peerDependencies: + react: "*" + react-dom: "*" + checksum: 10/5d81fe0902303d3ed7810cdd56c6cae12b08124a3d4fcbfa3924327105b81447b039ea9d6aff20aac3c13999f949000870a7a2fa29fe20ed844ac26606462fa0 + languageName: node + linkType: hard + "react-use@npm:17.5.1": version: 17.5.1 resolution: "react-use@npm:17.5.1" @@ -27069,7 +27082,17 @@ __metadata: languageName: node linkType: hard -"react-virtualized-auto-sizer@npm:1.0.25, react-virtualized-auto-sizer@npm:^1.0.24, react-virtualized-auto-sizer@npm:^1.0.6": +"react-virtualized-auto-sizer@npm:1.0.24": + version: 1.0.24 + resolution: "react-virtualized-auto-sizer@npm:1.0.24" + peerDependencies: + react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 + react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 + checksum: 10/02101a340bdbe3e40c49dbc52e524eb7ca18832690e91f045a25675600d7adc0a63e800a4ace6a014132adcdcce0e12a8137971de408427a5a3112d7c87c9f3e + languageName: node + linkType: hard + +"react-virtualized-auto-sizer@npm:1.0.25, react-virtualized-auto-sizer@npm:^1.0.6": version: 1.0.25 resolution: "react-virtualized-auto-sizer@npm:1.0.25" peerDependencies: @@ -30272,7 +30295,7 @@ __metadata: languageName: node linkType: hard -"tiny-warning@npm:^1.0.0, tiny-warning@npm:^1.0.3": +"tiny-warning@npm:^1.0.0": version: 1.0.3 resolution: "tiny-warning@npm:1.0.3" checksum: 10/da62c4acac565902f0624b123eed6dd3509bc9a8d30c06e017104bedcf5d35810da8ff72864400ad19c5c7806fc0a8323c68baf3e326af7cb7d969f846100d71