From bec2878fe4c9c2d9a52fa417f07e0c023a46c9a2 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 2 Mar 2022 10:03:42 -0500 Subject: [PATCH 01/52] Dashboard: Add feature reporting for dashboard import (#46080) (#46099) * Add feature reporting for dashboard import * Update tracking event names (cherry picked from commit 3427ae463dfd73d2e8e335b4a0772aa74ca8c979) Co-authored-by: Ashley Harrison --- .../manage-dashboards/DashboardImportPage.tsx | 15 +++++++++++++++ .../components/ImportDashboardOverview.tsx | 6 +++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/public/app/features/manage-dashboards/DashboardImportPage.tsx b/public/app/features/manage-dashboards/DashboardImportPage.tsx index a8744ae3291..378b9fe4cc5 100644 --- a/public/app/features/manage-dashboards/DashboardImportPage.tsx +++ b/public/app/features/manage-dashboards/DashboardImportPage.tsx @@ -3,6 +3,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { css } from '@emotion/css'; import { AppEvents, GrafanaTheme2, LoadingState } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { reportInteraction } from '@grafana/runtime'; import { Button, Field, @@ -33,6 +34,8 @@ type DashboardImportPageRouteSearchParams = { type OwnProps = Themeable2 & GrafanaRouteComponentProps<{}, DashboardImportPageRouteSearchParams>; +const IMPORT_STARTED_EVENT_NAME = 'dashboard_import_loaded'; + const mapStateToProps = (state: StoreState) => ({ navModel: getNavModel(state.navIndex, 'import', undefined, true), loadingState: state.importDashboard.state, @@ -63,6 +66,10 @@ class UnthemedDashboardImport extends PureComponent { } onFileUpload = (event: FormEvent) => { + reportInteraction(IMPORT_STARTED_EVENT_NAME, { + import_source: 'json_uploaded', + }); + const { importDashboardJson } = this.props; const file = event.currentTarget.files && event.currentTarget.files.length > 0 && event.currentTarget.files[0]; @@ -89,10 +96,18 @@ class UnthemedDashboardImport extends PureComponent { }; getDashboardFromJson = (formData: { dashboardJson: string }) => { + reportInteraction(IMPORT_STARTED_EVENT_NAME, { + import_source: 'json_pasted', + }); + this.props.importDashboardJson(JSON.parse(formData.dashboardJson)); }; getGcomDashboard = (formData: { gcomDashboard: string }) => { + reportInteraction(IMPORT_STARTED_EVENT_NAME, { + import_source: 'gcom', + }); + let dashboardId; const match = /(^\d+$)|dashboards\/(\d+)/.exec(formData.gcomDashboard); if (match && match[1]) { diff --git a/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx b/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx index c94f75c5b5b..275a12d18ac 100644 --- a/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx +++ b/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx @@ -6,7 +6,9 @@ import { ImportDashboardForm } from './ImportDashboardForm'; import { clearLoadedDashboard, importDashboard } from '../state/actions'; import { DashboardSource, ImportDashboardDTO } from '../state/reducers'; import { StoreState } from 'app/types'; -import { locationService } from '@grafana/runtime'; +import { locationService, reportInteraction } from '@grafana/runtime'; + +const IMPORT_FINISHED_EVENT_NAME = 'dashboard_import_imported'; const mapStateToProps = (state: StoreState) => { const searchObj = locationService.getSearchObject(); @@ -39,6 +41,8 @@ class ImportDashboardOverviewUnConnected extends PureComponent { }; onSubmit = (form: ImportDashboardDTO) => { + reportInteraction(IMPORT_FINISHED_EVENT_NAME); + this.props.importDashboard(form); }; From 84f41f4fe88424cdfeeeb872d14575654a2daafe Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 2 Mar 2022 13:04:00 -0500 Subject: [PATCH 02/52] Document available Alerting annotations (#46081) (#46112) * Document available Alerting annotations * Update docs/sources/alerting/unified-alerting/alerting-rules/alert-annotation-label.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> (cherry picked from commit cd3fb8e8321259c41d566b7fda394dd4b8c61398) Co-authored-by: Armand Grillet <2117580+armandgrillet@users.noreply.github.com> --- .../unified-alerting/alerting-rules/alert-annotation-label.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/alerting/unified-alerting/alerting-rules/alert-annotation-label.md b/docs/sources/alerting/unified-alerting/alerting-rules/alert-annotation-label.md index e7b634905c5..0b88bb1b866 100644 --- a/docs/sources/alerting/unified-alerting/alerting-rules/alert-annotation-label.md +++ b/docs/sources/alerting/unified-alerting/alerting-rules/alert-annotation-label.md @@ -11,7 +11,7 @@ Annotations and labels are key value pairs associated with alerts originating fr ## Annotations -Annotations are key-value pairs that provide additional meta-information about an alert. For example: a description, a summary, and runbook URL. These are displayed in rule and alert details in the UI and can be used in contact point message templates. +Annotations are key-value pairs that provide additional meta-information about an alert. You can use the following annotations: `description`, `summary`, `runbook_url`, `alertId`, `dashboardUid`, and `panelId`. For example, a description, a summary, and a runbook URL. These are displayed in rule and alert details in the UI and can be used in contact point message templates. ## Labels From b2918bf4d91faa62dc85cfff06895c7e74c7fa38 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 2 Mar 2022 14:15:52 -0500 Subject: [PATCH 03/52] "Release: Updated versions in package to 8.4.3" (#46118) --- lerna.json | 2 +- package.json | 2 +- packages/grafana-data/package.json | 4 +- packages/grafana-e2e-selectors/package.json | 2 +- packages/grafana-e2e/package.json | 4 +- packages/grafana-runtime/package.json | 8 ++-- packages/grafana-schema/package.json | 2 +- packages/grafana-toolkit/package.json | 6 +-- packages/grafana-ui/package.json | 8 ++-- packages/jaeger-ui-components/package.json | 6 +-- .../internal/input-datasource/package.json | 8 ++-- yarn.lock | 40 +++++++++---------- 12 files changed, 46 insertions(+), 46 deletions(-) diff --git a/lerna.json b/lerna.json index b5f6c46be20..7e22dd1214b 100644 --- a/lerna.json +++ b/lerna.json @@ -4,5 +4,5 @@ "packages": [ "packages/*" ], - "version": "8.4.2" + "version": "8.4.3" } diff --git a/package.json b/package.json index ec2fc291394..c431ca56cba 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "grafana", - "version": "8.4.2", + "version": "8.4.3", "repository": "github:grafana/grafana", "scripts": { "api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 00c27092da5..b41f50786f6 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/data", - "version": "8.4.2", + "version": "8.4.3", "description": "Grafana Data Library", "keywords": [ "typescript" @@ -22,7 +22,7 @@ }, "dependencies": { "@braintree/sanitize-url": "5.0.2", - "@grafana/schema": "8.4.2", + "@grafana/schema": "8.4.3", "@types/d3-interpolate": "^1.4.0", "d3-interpolate": "1.4.0", "date-fns": "2.28.0", diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 9d4376a95e2..37c721003e3 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/e2e-selectors", - "version": "8.4.2", + "version": "8.4.3", "description": "Grafana End-to-End Test Selectors Library", "keywords": [ "cli", diff --git a/packages/grafana-e2e/package.json b/packages/grafana-e2e/package.json index 521c85adc54..ca5a447172a 100644 --- a/packages/grafana-e2e/package.json +++ b/packages/grafana-e2e/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/e2e", - "version": "8.4.2", + "version": "8.4.3", "description": "Grafana End-to-End Test Library", "keywords": [ "cli", @@ -48,7 +48,7 @@ "@babel/core": "7.16.7", "@babel/preset-env": "7.16.7", "@cypress/webpack-preprocessor": "5.11.0", - "@grafana/e2e-selectors": "8.4.2", + "@grafana/e2e-selectors": "8.4.3", "@grafana/tsconfig": "^1.0.0-rc1", "@mochajs/json-file-reporter": "^1.2.0", "babel-loader": "8.2.3", diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index 10b059b50f2..518586f12be 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/runtime", - "version": "8.4.2", + "version": "8.4.3", "description": "Grafana Runtime Library", "keywords": [ "grafana", @@ -22,9 +22,9 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@grafana/data": "8.4.2", - "@grafana/e2e-selectors": "8.4.2", - "@grafana/ui": "8.4.2", + "@grafana/data": "8.4.3", + "@grafana/e2e-selectors": "8.4.3", + "@grafana/ui": "8.4.3", "@sentry/browser": "6.17.2", "history": "4.10.1", "lodash": "4.17.21", diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index 1c7e11ea12d..9bce6a301c2 100644 --- a/packages/grafana-schema/package.json +++ b/packages/grafana-schema/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/schema", - "version": "8.4.2", + "version": "8.4.3", "description": "Grafana Schema Library", "keywords": [ "typescript" diff --git a/packages/grafana-toolkit/package.json b/packages/grafana-toolkit/package.json index af59f9fcb29..7d8aa6dbbb4 100644 --- a/packages/grafana-toolkit/package.json +++ b/packages/grafana-toolkit/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/toolkit", - "version": "8.4.2", + "version": "8.4.3", "description": "Grafana Toolkit", "keywords": [ "grafana", @@ -28,10 +28,10 @@ "dependencies": { "@babel/core": "7.13.14", "@babel/preset-env": "7.13.12", - "@grafana/data": "8.4.2", + "@grafana/data": "8.4.3", "@grafana/eslint-config": "2.5.2", "@grafana/tsconfig": "^1.0.0-rc1", - "@grafana/ui": "8.4.2", + "@grafana/ui": "8.4.3", "@jest/core": "26.6.3", "@rushstack/eslint-patch": "1.0.6", "@types/command-exists": "^1.2.0", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 67e07ac2705..0718b359b3b 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/ui", - "version": "8.4.2", + "version": "8.4.3", "description": "Grafana Components Library", "keywords": [ "grafana", @@ -33,9 +33,9 @@ "@emotion/css": "11.7.1", "@emotion/react": "11.7.1", "@grafana/aws-sdk": "0.0.31", - "@grafana/data": "8.4.2", - "@grafana/e2e-selectors": "8.4.2", - "@grafana/schema": "8.4.2", + "@grafana/data": "8.4.3", + "@grafana/e2e-selectors": "8.4.3", + "@grafana/schema": "8.4.3", "@grafana/slate-react": "0.22.10-grafana", "@monaco-editor/react": "4.3.1", "@popperjs/core": "2.11.2", diff --git a/packages/jaeger-ui-components/package.json b/packages/jaeger-ui-components/package.json index 168a2d05ec2..e07342afb85 100644 --- a/packages/jaeger-ui-components/package.json +++ b/packages/jaeger-ui-components/package.json @@ -1,6 +1,6 @@ { "name": "@jaegertracing/jaeger-ui-components", - "version": "8.4.2", + "version": "8.4.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,8 +26,8 @@ }, "dependencies": { "@emotion/css": "11.7.1", - "@grafana/data": "8.4.2", - "@grafana/ui": "8.4.2", + "@grafana/data": "8.4.3", + "@grafana/ui": "8.4.3", "chance": "^1.0.10", "classnames": "^2.2.5", "combokeys": "^3.0.0", diff --git a/plugins-bundled/internal/input-datasource/package.json b/plugins-bundled/internal/input-datasource/package.json index a524514aaf1..32d72eb84fc 100644 --- a/plugins-bundled/internal/input-datasource/package.json +++ b/plugins-bundled/internal/input-datasource/package.json @@ -1,6 +1,6 @@ { "name": "@grafana-plugins/input-datasource", - "version": "8.4.2", + "version": "8.4.3", "description": "Input Datasource", "private": true, "repository": { @@ -24,9 +24,9 @@ "webpack": "5.58.1" }, "dependencies": { - "@grafana/data": "8.4.2", - "@grafana/toolkit": "8.4.2", - "@grafana/ui": "8.4.2", + "@grafana/data": "8.4.3", + "@grafana/toolkit": "8.4.3", + "@grafana/ui": "8.4.3", "jquery": "3.5.1", "react": "17.0.1", "react-dom": "17.0.1", diff --git a/yarn.lock b/yarn.lock index 10ad153e0a5..3627a1099c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3633,9 +3633,9 @@ __metadata: version: 0.0.0-use.local resolution: "@grafana-plugins/input-datasource@workspace:plugins-bundled/internal/input-datasource" dependencies: - "@grafana/data": 8.4.2 - "@grafana/toolkit": 8.4.2 - "@grafana/ui": 8.4.2 + "@grafana/data": 8.4.3 + "@grafana/toolkit": 8.4.3 + "@grafana/ui": 8.4.3 "@types/jest": 26.0.15 "@types/lodash": 4.14.149 "@types/react": 17.0.30 @@ -3676,12 +3676,12 @@ __metadata: languageName: node linkType: hard -"@grafana/data@8.4.2, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data": +"@grafana/data@8.4.3, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data": version: 0.0.0-use.local resolution: "@grafana/data@workspace:packages/grafana-data" dependencies: "@braintree/sanitize-url": 5.0.2 - "@grafana/schema": 8.4.2 + "@grafana/schema": 8.4.3 "@grafana/tsconfig": ^1.0.0-rc1 "@rollup/plugin-commonjs": 21.0.1 "@rollup/plugin-json": 4.1.0 @@ -3733,7 +3733,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/e2e-selectors@8.4.2, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": +"@grafana/e2e-selectors@8.4.3, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": version: 0.0.0-use.local resolution: "@grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors" dependencies: @@ -3757,7 +3757,7 @@ __metadata: "@babel/core": 7.16.7 "@babel/preset-env": 7.16.7 "@cypress/webpack-preprocessor": 5.11.0 - "@grafana/e2e-selectors": 8.4.2 + "@grafana/e2e-selectors": 8.4.3 "@grafana/tsconfig": ^1.0.0-rc1 "@mochajs/json-file-reporter": ^1.2.0 "@rollup/plugin-commonjs": 21.0.1 @@ -3837,10 +3837,10 @@ __metadata: version: 0.0.0-use.local resolution: "@grafana/runtime@workspace:packages/grafana-runtime" dependencies: - "@grafana/data": 8.4.2 - "@grafana/e2e-selectors": 8.4.2 + "@grafana/data": 8.4.3 + "@grafana/e2e-selectors": 8.4.3 "@grafana/tsconfig": ^1.0.0-rc1 - "@grafana/ui": 8.4.2 + "@grafana/ui": 8.4.3 "@rollup/plugin-commonjs": 21.0.1 "@rollup/plugin-node-resolve": 13.1.3 "@sentry/browser": 6.17.2 @@ -3869,7 +3869,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/schema@8.4.2, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": +"@grafana/schema@8.4.3, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": version: 0.0.0-use.local resolution: "@grafana/schema@workspace:packages/grafana-schema" dependencies: @@ -3916,16 +3916,16 @@ __metadata: languageName: node linkType: hard -"@grafana/toolkit@8.4.2, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit": +"@grafana/toolkit@8.4.3, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit": version: 0.0.0-use.local resolution: "@grafana/toolkit@workspace:packages/grafana-toolkit" dependencies: "@babel/core": 7.13.14 "@babel/preset-env": 7.13.12 - "@grafana/data": 8.4.2 + "@grafana/data": 8.4.3 "@grafana/eslint-config": 2.5.2 "@grafana/tsconfig": ^1.0.0-rc1 - "@grafana/ui": 8.4.2 + "@grafana/ui": 8.4.3 "@jest/core": 26.6.3 "@rushstack/eslint-patch": 1.0.6 "@types/command-exists": ^1.2.0 @@ -4016,7 +4016,7 @@ __metadata: languageName: node linkType: hard -"@grafana/ui@8.4.2, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": +"@grafana/ui@8.4.3, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": version: 0.0.0-use.local resolution: "@grafana/ui@workspace:packages/grafana-ui" dependencies: @@ -4024,9 +4024,9 @@ __metadata: "@emotion/css": 11.7.1 "@emotion/react": 11.7.1 "@grafana/aws-sdk": 0.0.31 - "@grafana/data": 8.4.2 - "@grafana/e2e-selectors": 8.4.2 - "@grafana/schema": 8.4.2 + "@grafana/data": 8.4.3 + "@grafana/e2e-selectors": 8.4.3 + "@grafana/schema": 8.4.3 "@grafana/slate-react": 0.22.10-grafana "@grafana/tsconfig": ^1.0.0-rc1 "@mdx-js/react": 1.6.22 @@ -4243,9 +4243,9 @@ __metadata: resolution: "@jaegertracing/jaeger-ui-components@workspace:packages/jaeger-ui-components" dependencies: "@emotion/css": 11.7.1 - "@grafana/data": 8.4.2 + "@grafana/data": 8.4.3 "@grafana/tsconfig": ^1.0.0-rc1 - "@grafana/ui": 8.4.2 + "@grafana/ui": 8.4.3 "@types/classnames": ^2.2.7 "@types/deep-freeze": ^0.1.1 "@types/grafana__slate-react": "npm:@types/slate-react@0.22.5" From d5e4a542716fd5e5eef62db38b913182e0872f28 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 2 Mar 2022 14:30:19 -0500 Subject: [PATCH 04/52] ReleaseNotes: Updated changelog and release notes for 8.4.3 (#46119) (#46120) (cherry picked from commit 50fb63a46887a6692c90d047d812e653a1004f91) --- CHANGELOG.md | 22 ++++++++++++++++ docs/sources/release-notes/_index.md | 1 + .../release-notes/release-notes-8-4-3.md | 25 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 docs/sources/release-notes/release-notes-8-4-3.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a85334d4ec..75e6acb70bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ + + +# 8.4.3 (2022-03-02) + +### Features and enhancements + +- **Alerting:** Grafana uses > instead of >= when checking the For duration. [#46010](https://github.com/grafana/grafana/issues/46010) +- **Alerting:** Use expanded labels in dashboard annotations. [#45726](https://github.com/grafana/grafana/pull/45726), [@grobinson-grafana](https://github.com/grobinson-grafana) +- **Logs:** Escape windows newline into single newline. [#45771](https://github.com/grafana/grafana/pull/45771), [@perosb](https://github.com/perosb) + +### Bug fixes + +- **Alerting:** Fix use of > instead of >= when checking the For duration. [#46011](https://github.com/grafana/grafana/pull/46011), [@grobinson-grafana](https://github.com/grobinson-grafana) +- **Azure Monitor:** Fixes broken log queries that use workspace. [#45820](https://github.com/grafana/grafana/pull/45820), [@sunker](https://github.com/sunker) +- **CloudWatch:** Remove error message when using multi-valued template vars in region field. [#45886](https://github.com/grafana/grafana/pull/45886), [@sunker](https://github.com/sunker) +- **Middleware:** Fix IPv6 host parsing in CSRF check. [#45911](https://github.com/grafana/grafana/pull/45911), [@ying-jeanne](https://github.com/ying-jeanne) + +### Plugin development fixes & changes + +- **ClipboardButton:** Use a fallback when the Clipboard API is unavailable. [#45831](https://github.com/grafana/grafana/pull/45831), [@ashharrison90](https://github.com/ashharrison90) + + # 8.4.2 (2022-02-23) diff --git a/docs/sources/release-notes/_index.md b/docs/sources/release-notes/_index.md index 8cb3c83df60..5b4dca9698b 100644 --- a/docs/sources/release-notes/_index.md +++ b/docs/sources/release-notes/_index.md @@ -8,6 +8,7 @@ weight = 10000 Here you can find detailed release notes that list everything that is included in every release as well as notices about deprecations, breaking changes as well as changes that relate to plugin development. +- [Release notes for 8.4.3]({{< relref "release-notes-8-4-3" >}}) - [Release notes for 8.4.2]({{< relref "release-notes-8-4-2" >}}) - [Release notes for 8.4.1]({{< relref "release-notes-8-4-1" >}}) - [Release notes for 8.4.0-beta1]({{< relref "release-notes-8-4-0-beta1" >}}) diff --git a/docs/sources/release-notes/release-notes-8-4-3.md b/docs/sources/release-notes/release-notes-8-4-3.md new file mode 100644 index 00000000000..90f989988f3 --- /dev/null +++ b/docs/sources/release-notes/release-notes-8-4-3.md @@ -0,0 +1,25 @@ ++++ +title = "Release notes for Grafana 8.4.3" +hide_menu = true ++++ + + + +# Release notes for Grafana 8.4.3 + +### Features and enhancements + +- **Alerting:** Grafana uses > instead of >= when checking the For duration. [#46010](https://github.com/grafana/grafana/issues/46010) +- **Alerting:** Use expanded labels in dashboard annotations. [#45726](https://github.com/grafana/grafana/pull/45726), [@grobinson-grafana](https://github.com/grobinson-grafana) +- **Logs:** Escape windows newline into single newline. [#45771](https://github.com/grafana/grafana/pull/45771), [@perosb](https://github.com/perosb) + +### Bug fixes + +- **Alerting:** Fix use of > instead of >= when checking the For duration. [#46011](https://github.com/grafana/grafana/pull/46011), [@grobinson-grafana](https://github.com/grobinson-grafana) +- **Azure Monitor:** Fixes broken log queries that use workspace. [#45820](https://github.com/grafana/grafana/pull/45820), [@sunker](https://github.com/sunker) +- **CloudWatch:** Remove error message when using multi-valued template vars in region field. [#45886](https://github.com/grafana/grafana/pull/45886), [@sunker](https://github.com/sunker) +- **Middleware:** Fix IPv6 host parsing in CSRF check. [#45911](https://github.com/grafana/grafana/pull/45911), [@ying-jeanne](https://github.com/ying-jeanne) + +### Plugin development fixes & changes + +- **ClipboardButton:** Use a fallback when the Clipboard API is unavailable. [#45831](https://github.com/grafana/grafana/pull/45831), [@ashharrison90](https://github.com/ashharrison90) From 1a3f892ddc067b8048ae1f61d0619758009c19cc Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 2 Mar 2022 14:57:47 -0500 Subject: [PATCH 05/52] Docs: Include ID token example in Forward OAuth identity for the logged-in user section (#46121) (#46125) Ref #45938 (cherry picked from commit 76495374dceecdae3c3bedbaf221627a0f77843e) Co-authored-by: Marcus Efraimsson --- .../add-authentication-for-data-source-plugins.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md b/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md index c4a8e4e0bda..4cb9c2bbc0b 100644 --- a/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md +++ b/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md @@ -303,4 +303,16 @@ func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataReque } ``` +In addition, if the user's token includes an ID token, Grafana will pass the user's ID token to the plugin in an `X-ID-Token` header, available on the `QueryDataRequest` object on the `QueryData` request in your backend data source. + +```go +func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + idToken := req.Headers["X-ID-Token"] + + for _, q := range req.Queries { + // ... + } +} +``` + > **Note:** Due to a bug in Grafana, using this feature with PostgreSQL can cause a deadlock. For more information, refer to [Grafana causes deadlocks in PostgreSQL, while trying to refresh users token](https://github.com/grafana/grafana/issues/20515). From 5212a7a47a5e0bab060b4a47ed3d9e0780902113 Mon Sep 17 00:00:00 2001 From: Giordano Ricci Date: Wed, 2 Mar 2022 19:59:12 +0000 Subject: [PATCH 06/52] chore: update latest.json (#46122) (#46123) (cherry picked from commit e8a54d58c56554c807a0a27060e1a370742063ef) --- latest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/latest.json b/latest.json index 537d81c0610..360c9f7b7d9 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "8.4.0", - "testing": "8.4.0" + "stable": "8.4.3", + "testing": "8.4.3" } From 4cca6e11da106f7afc46a05c90bf8be46b25c443 Mon Sep 17 00:00:00 2001 From: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Date: Wed, 2 Mar 2022 16:26:59 -0500 Subject: [PATCH 07/52] Docs: Update ldap authentication topic (#46128) (#46130) * Fixed broken relrefs, and updated password format in ldap authentication topic. * Fixed few more broken links. --- .../administration/preferences/change-grafana-name.md | 2 +- docs/sources/auth/ldap.md | 8 ++++---- .../using-aws-kms-to-encrypt-database-secrets.md | 2 +- .../using-azure-key-vault-to-encrypt-database-secrets.md | 2 +- .../using-google-cloud-kms-to-encrypt-database-secrets.md | 2 +- ...ing-hashicorp-key-vault-to-encrypt-database-secrets.md | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/sources/administration/preferences/change-grafana-name.md b/docs/sources/administration/preferences/change-grafana-name.md index 69d4394a8e2..7924ffcc2b4 100644 --- a/docs/sources/administration/preferences/change-grafana-name.md +++ b/docs/sources/administration/preferences/change-grafana-name.md @@ -53,4 +53,4 @@ To change the team name or email, follow these steps: ## Change user name or email -To learn how to edit your user information, refer to [Grafana user account profile]({{< relref "../../manage-users/user-admin/user-profile.md" >}}). +To learn how to edit your user information, refer to [Edit your profile]({{< relref "../manage-user-preferences/_index.md#edit-your-profile" >}}). diff --git a/docs/sources/auth/ldap.md b/docs/sources/auth/ldap.md index 15b3f29cbd3..a289fc12038 100644 --- a/docs/sources/auth/ldap.md +++ b/docs/sources/auth/ldap.md @@ -68,7 +68,7 @@ ssl_skip_verify = false bind_dn = "cn=admin,dc=grafana,dc=org" # Search user bind password # If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;""" -bind_password = 'grafana' +bind_password = "grafana" # User search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)" # Allow login from email or username, example "(|(sAMAccountName=%s)(userPrincipalName=%s))" @@ -236,7 +236,7 @@ use_ssl = false start_tls = false ssl_skip_verify = false bind_dn = "cn=admin,dc=grafana,dc=org" -bind_password = 'grafana' +bind_password = "grafana" search_filter = "(cn=%s)" search_base_dns = ["dc=grafana,dc=org"] @@ -263,7 +263,7 @@ use_ssl = false start_tls = false ssl_skip_verify = false bind_dn = "cn=admin,dc=grafana,dc=org" -bind_password = 'grafana' +bind_password = "grafana" search_filter = "(cn=%s)" search_base_dns = ["ou=users,dc=grafana,dc=org"] @@ -286,7 +286,7 @@ start_tls = false ssl_skip_verify = false bind_dn = "cn=admin,dc=grafana,dc=org" -bind_password = 'grafana' +bind_password = "grafana" search_filter = "(cn=%s)" search_base_dns = ["ou=users,dc=grafana,dc=org"] diff --git a/docs/sources/enterprise/enterprise-encryption/using-aws-kms-to-encrypt-database-secrets.md b/docs/sources/enterprise/enterprise-encryption/using-aws-kms-to-encrypt-database-secrets.md index b91655c753c..9cf0cd04040 100644 --- a/docs/sources/enterprise/enterprise-encryption/using-aws-kms-to-encrypt-database-secrets.md +++ b/docs/sources/enterprise/enterprise-encryption/using-aws-kms-to-encrypt-database-secrets.md @@ -23,7 +23,7 @@ You can use an encryption key from AWS Key Management Service to encrypt secrets 3. Create a [programmatic credential](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys) (access key ID and secret access key), which has permission to view the key that you created.

In AWS, you can control access to your KMS keys by using [key policies](https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html), [IAM policies](https://docs.aws.amazon.com/kms/latest/developerguide/iam-policies.html), and [grants](https://docs.aws.amazon.com/kms/latest/developerguide/grants.html). You can also create [temporary credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html), which must provide a session token along with an access key ID and a secret access key. -4. From within Grafana, turn on [envelope encryption]({{< relref "../../administration/envelope-encryption.md" >}}). +4. From within Grafana, turn on [envelope encryption]({{< relref "../../administration//database-encryption.md" >}}). 5. Add your AWS KMS details to the Grafana configuration file; depending on your operating system, it is usually named `grafana.ini`:

a. Add a new section to the configuration file, with a name in the format of `[security.encryption.awskms.]`, where `` is any name that uniquely identifies this key among other provider keys.

b. Fill in the section with the following values: diff --git a/docs/sources/enterprise/enterprise-encryption/using-azure-key-vault-to-encrypt-database-secrets.md b/docs/sources/enterprise/enterprise-encryption/using-azure-key-vault-to-encrypt-database-secrets.md index 8747fa81394..489649f24cd 100644 --- a/docs/sources/enterprise/enterprise-encryption/using-azure-key-vault-to-encrypt-database-secrets.md +++ b/docs/sources/enterprise/enterprise-encryption/using-azure-key-vault-to-encrypt-database-secrets.md @@ -24,7 +24,7 @@ You can use an encryption key from Azure Key Vault to encrypt secrets in the Gra 5. In the Key Permissions section, set encrypt and decrypt permissions, and click **Save**. -6. From within Grafana, turn on [envelope encryption]({{< relref "../../administration/envelope-encryption.md" >}}). +6. From within Grafana, turn on [envelope encryption]({{< relref "../../administration/database-encryption.md" >}}). 7. Add your Azure Key Vault details to the Grafana configuration file; depending on your operating system, is usually named `grafana.ini`:

a. Add a new section to the configuration file, with a name in the format of `[security.encryption.azurekv.]`, where `` is any name that uniquely identifies this key among other provider keys. diff --git a/docs/sources/enterprise/enterprise-encryption/using-google-cloud-kms-to-encrypt-database-secrets.md b/docs/sources/enterprise/enterprise-encryption/using-google-cloud-kms-to-encrypt-database-secrets.md index 0d66b4a89a4..0ef4491ce27 100644 --- a/docs/sources/enterprise/enterprise-encryption/using-google-cloud-kms-to-encrypt-database-secrets.md +++ b/docs/sources/enterprise/enterprise-encryption/using-google-cloud-kms-to-encrypt-database-secrets.md @@ -22,7 +22,7 @@ You can use an encryption key from Google Cloud Key Management Service to encryp 4. [Create a service account key and save its JSON file](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#creating) to you computer, for example, as `~/.config/gcloud/sample-project-credentials.json`. -5. From within Grafana, turn on [envelope encryption]({{< relref "../../administration/envelope-encryption.md" >}}). +5. From within Grafana, turn on [envelope encryption]({{< relref "../../administration/database-encryption.md" >}}). 6. Add your Google Cloud KMS details to the Grafana configuration file; depending on your operating system, is usually named `grafana.ini`:

a. Add a new section to the configuration file, with a name in the format of `[security.encryption.azurekv.]`, where `` is any name that uniquely identifies this key among other provider keys. diff --git a/docs/sources/enterprise/enterprise-encryption/using-hashicorp-key-vault-to-encrypt-database-secrets.md b/docs/sources/enterprise/enterprise-encryption/using-hashicorp-key-vault-to-encrypt-database-secrets.md index b09bd2d582b..83d843bbab2 100644 --- a/docs/sources/enterprise/enterprise-encryption/using-hashicorp-key-vault-to-encrypt-database-secrets.md +++ b/docs/sources/enterprise/enterprise-encryption/using-hashicorp-key-vault-to-encrypt-database-secrets.md @@ -20,7 +20,7 @@ You can use an encryption key from Hashicorp Vault to encrypt secrets in the Gra 3. [Create a periodic service token](https://learn.hashicorp.com/tutorials/vault/tokens#periodic-service-tokens). -4. From within Grafana, turn on [envelope encryption]({{< relref "../../administration/envelope-encryption.md" >}}). +4. From within Grafana, turn on [envelope encryption]({{< relref "../../administration/database-encryption.md" >}}). 5. Add your Hashicorp Vault details to the Grafana configuration file; depending on your operating system, is usually named `grafana.ini`:

a. Add a new section to the configuration file, with a name in the format of `[security.encryption.hashicorpvault.]`, where `` is any name that uniquely identifies this key among other provider keys. From 5bc24bf0185348caf6c345ed3f56b364049f51f2 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 2 Mar 2022 19:38:08 -0500 Subject: [PATCH 08/52] StateTimeline: insert trailing null value at +interval (#45997) (#46133) (cherry picked from commit 854f872b40bbf1f41ba4604a48e48d1906bf3a94) Co-authored-by: Leon Sorokin --- .../src/components/GraphNG/GraphNG.tsx | 3 +- .../GraphNG/nullInsertThreshold.test.ts | 34 +++++++++---------- .../components/GraphNG/nullInsertThreshold.ts | 16 +++++++-- .../src/components/GraphNG/utils.ts | 6 ++-- .../app/plugins/panel/state-timeline/utils.ts | 11 ------ 5 files changed, 36 insertions(+), 34 deletions(-) diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx index e33de639ef2..1581c4abd34 100755 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx +++ b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx @@ -116,7 +116,8 @@ export class GraphNG extends React.Component { fields || { x: fieldMatchers.get(FieldMatcherID.firstTimeField).get({}), y: fieldMatchers.get(FieldMatcherID.numeric).get({}), - } + }, + props.timeRange ); pluginLog('GraphNG', false, 'data aligned', alignedFrame); diff --git a/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts b/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts index c74fb32dbf5..dd546cc70d9 100644 --- a/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts +++ b/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts @@ -97,6 +97,23 @@ describe('nullInsertThreshold Transformer', () => { expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c']); }); + test('should insert trailing null at end +interval when timeRange.to.valueOf() exceeds threshold', () => { + const df = new MutableDataFrame({ + refId: 'A', + fields: [ + { name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1, 3, 10] }, + { name: 'One', type: FieldType.number, values: [4, 6, 8] }, + { name: 'Two', type: FieldType.string, values: ['a', 'b', 'c'] }, + ], + }); + + const result = applyNullInsertThreshold(df, null, 13); + + expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 10, 11]); + expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, 8, null]); + expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c', null]); + }); + // TODO: make this work test.skip('should insert nulls at +threshold (when defined) instead of +interval', () => { const df = new MutableDataFrame({ @@ -115,23 +132,6 @@ describe('nullInsertThreshold Transformer', () => { expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c']); }); - test('should insert nulls at midpoints between adjacent > interval: 2', () => { - const df = new MutableDataFrame({ - refId: 'A', - fields: [ - { name: 'Time', type: FieldType.time, config: { interval: 2 }, values: [5, 7, 11] }, - { name: 'One', type: FieldType.number, values: [4, 6, 8] }, - { name: 'Two', type: FieldType.string, values: ['a', 'b', 'c'] }, - ], - }); - - const result = applyNullInsertThreshold(df); - - expect(result.fields[0].values.toArray()).toStrictEqual([5, 7, 9, 11]); - expect(result.fields[1].values.toArray()).toStrictEqual([4, 6, null, 8]); - expect(result.fields[2].values.toArray()).toStrictEqual(['a', 'b', null, 'c']); - }); - test('should noop on fewer than two values', () => { const df = new MutableDataFrame({ refId: 'A', diff --git a/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.ts b/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.ts index 4e22d0af2b1..35b0fcd325f 100644 --- a/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.ts +++ b/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.ts @@ -12,6 +12,7 @@ const INSERT_MODES = { export function applyNullInsertThreshold( frame: DataFrame, refFieldName?: string | null, + refFieldPseudoMax: number | null = null, insertMode: InsertMode = INSERT_MODES.threshold ): DataFrame { if (frame.length < 2) { @@ -48,7 +49,7 @@ export function applyNullInsertThreshold( const frameValues = frame.fields.map((field) => field.values.toArray()); - const filledFieldValues = nullInsertThreshold(refValues, frameValues, threshold, insertMode); + const filledFieldValues = nullInsertThreshold(refValues, frameValues, threshold, refFieldPseudoMax, insertMode); if (filledFieldValues === frameValues) { return frame; @@ -70,7 +71,14 @@ export function applyNullInsertThreshold( return frame; } -function nullInsertThreshold(refValues: number[], frameValues: any[][], threshold: number, getInsertValue: InsertMode) { +function nullInsertThreshold( + refValues: number[], + frameValues: any[][], + threshold: number, + // will insert a trailing null when refFieldPseudoMax > last datapoint + threshold + refFieldPseudoMax: number | null = null, + getInsertValue: InsertMode +) { const len = refValues.length; let prevValue: number = refValues[0]; const refValuesNew: number[] = [prevValue]; @@ -87,6 +95,10 @@ function nullInsertThreshold(refValues: number[], frameValues: any[][], threshol prevValue = curValue; } + if (refFieldPseudoMax != null && prevValue + threshold <= refFieldPseudoMax) { + refValuesNew.push(getInsertValue(prevValue, refFieldPseudoMax, threshold)); + } + const filledLen = refValuesNew.length; if (filledLen === len) { diff --git a/packages/grafana-ui/src/components/GraphNG/utils.ts b/packages/grafana-ui/src/components/GraphNG/utils.ts index cc7feecd4a0..b67d694d793 100644 --- a/packages/grafana-ui/src/components/GraphNG/utils.ts +++ b/packages/grafana-ui/src/components/GraphNG/utils.ts @@ -1,5 +1,5 @@ import { XYFieldMatchers } from './types'; -import { ArrayVector, DataFrame, FieldConfig, FieldType, outerJoinDataFrames } from '@grafana/data'; +import { ArrayVector, DataFrame, FieldConfig, FieldType, outerJoinDataFrames, TimeRange } from '@grafana/data'; import { nullToUndefThreshold } from './nullToUndefThreshold'; import { applyNullInsertThreshold } from './nullInsertThreshold'; import { AxisPlacement, GraphFieldConfig, ScaleDistribution, ScaleDistributionConfig } from '@grafana/schema'; @@ -29,9 +29,9 @@ function applySpanNullsThresholds(frame: DataFrame) { return frame; } -export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers) { +export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers, timeRange?: TimeRange | null) { let alignedFrame = outerJoinDataFrames({ - frames: frames.map((frame) => applyNullInsertThreshold(frame)), + frames: frames.map((frame) => applyNullInsertThreshold(frame, null, timeRange?.to.valueOf())), joinBy: dimFields.x, keep: dimFields.y, keepOriginIndices: true, diff --git a/public/app/plugins/panel/state-timeline/utils.ts b/public/app/plugins/panel/state-timeline/utils.ts index 5db103c4e61..21a07dd20df 100644 --- a/public/app/plugins/panel/state-timeline/utils.ts +++ b/public/app/plugins/panel/state-timeline/utils.ts @@ -1,5 +1,4 @@ import React from 'react'; -import { XYFieldMatchers } from '@grafana/ui/src/components/GraphNG/types'; import { ArrayVector, DataFrame, @@ -19,7 +18,6 @@ import { getActiveThreshold, Threshold, getFieldConfigWithMinMax, - outerJoinDataFrames, ThresholdsMode, } from '@grafana/data'; import { @@ -48,15 +46,6 @@ export function mapMouseEventToMode(event: React.MouseEvent): SeriesVisibilityCh return SeriesVisibilityChangeMode.ToggleSelection; } -export function preparePlotFrame(data: DataFrame[], dimFields: XYFieldMatchers) { - return outerJoinDataFrames({ - frames: data, - joinBy: dimFields.x, - keep: dimFields.y, - keepOriginIndices: true, - }); -} - export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({ frame, theme, From b58fac8f07a928cfc1636c5680ff2f7b3c7f6c67 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 3 Mar 2022 03:14:19 -0500 Subject: [PATCH 09/52] Graph (old): fix null interval insertion, again (follow-up to #46069) (#46139) (#46141) (cherry picked from commit 89140c83531ccdcc0e8da58a3fa2b487cdaadb2b) Co-authored-by: Leon Sorokin --- public/app/plugins/panel/graph/data_processor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/panel/graph/data_processor.ts b/public/app/plugins/panel/graph/data_processor.ts index 37c19b9423e..edd5973cccf 100644 --- a/public/app/plugins/panel/graph/data_processor.ts +++ b/public/app/plugins/panel/graph/data_processor.ts @@ -32,13 +32,14 @@ export class DataProcessor { for (let i = 0; i < dataList.length; i++) { let series = dataList[i]; - const { timeField } = getTimeField(series); + let { timeField } = getTimeField(series); if (!timeField) { continue; } series = applyNullInsertThreshold(series, timeField.name); + timeField = getTimeField(series).timeField!; // use updated length for (let j = 0; j < series.fields.length; j++) { const field = series.fields[j]; From 61335700d31d42df7709c1fa8c5f9bcef62a16df Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 3 Mar 2022 08:04:30 -0500 Subject: [PATCH 10/52] Unexport mocks, use aws-sdk-go helpers, rename test utilities file (#46107) (#46144) (cherry picked from commit 1255f8a76a2302647e9b43874a2bd352a1bd3558) Co-authored-by: Shirley <4163034+fridgepoet@users.noreply.github.com> --- pkg/tsdb/cloudwatch/annotation_query_test.go | 12 +++--- pkg/tsdb/cloudwatch/log_actions_test.go | 42 +++++++++---------- pkg/tsdb/cloudwatch/metric_find_query_test.go | 16 +++---- pkg/tsdb/cloudwatch/time_series_query_test.go | 4 +- .../{test_utils.go => utils_test.go} | 31 ++++++-------- 5 files changed, 49 insertions(+), 56 deletions(-) rename pkg/tsdb/cloudwatch/{test_utils.go => utils_test.go} (87%) diff --git a/pkg/tsdb/cloudwatch/annotation_query_test.go b/pkg/tsdb/cloudwatch/annotation_query_test.go index ad947c538c9..4463975bf59 100644 --- a/pkg/tsdb/cloudwatch/annotation_query_test.go +++ b/pkg/tsdb/cloudwatch/annotation_query_test.go @@ -22,13 +22,13 @@ func TestQuery_AnnotationQuery(t *testing.T) { NewCWClient = origNewCWClient }) - var client FakeCWAnnotationsClient + var client fakeCWAnnotationsClient NewCWClient = func(sess *session.Session) cloudwatchiface.CloudWatchAPI { return &client } t.Run("DescribeAlarmsForMetric is called with minimum parameters", func(t *testing.T) { - client = FakeCWAnnotationsClient{describeAlarmsForMetricOutput: &cloudwatch.DescribeAlarmsForMetricOutput{}} + client = fakeCWAnnotationsClient{describeAlarmsForMetricOutput: &cloudwatch.DescribeAlarmsForMetricOutput{}} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { return datasourceInfo{}, nil }) @@ -62,7 +62,7 @@ func TestQuery_AnnotationQuery(t *testing.T) { }) t.Run("DescribeAlarms is called when prefixMatching is true", func(t *testing.T) { - client = FakeCWAnnotationsClient{describeAlarmsOutput: &cloudwatch.DescribeAlarmsOutput{}} + client = fakeCWAnnotationsClient{describeAlarmsOutput: &cloudwatch.DescribeAlarmsOutput{}} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { return datasourceInfo{}, nil }) @@ -91,9 +91,9 @@ func TestQuery_AnnotationQuery(t *testing.T) { require.Len(t, client.calls.describeAlarms, 1) assert.Equal(t, &cloudwatch.DescribeAlarmsInput{ - MaxRecords: pointerInt64(100), - ActionPrefix: pointerString("some_action_prefix"), - AlarmNamePrefix: pointerString("some_alarm_name_prefix"), + MaxRecords: aws.Int64(100), + ActionPrefix: aws.String("some_action_prefix"), + AlarmNamePrefix: aws.String("some_alarm_name_prefix"), }, client.calls.describeAlarms[0]) }) } diff --git a/pkg/tsdb/cloudwatch/log_actions_test.go b/pkg/tsdb/cloudwatch/log_actions_test.go index bfe6780a52f..8a641c1b9b7 100644 --- a/pkg/tsdb/cloudwatch/log_actions_test.go +++ b/pkg/tsdb/cloudwatch/log_actions_test.go @@ -25,14 +25,14 @@ func TestQuery_DescribeLogGroups(t *testing.T) { NewCWLogsClient = origNewCWLogsClient }) - var cli FakeCWLogsClient + var cli fakeCWLogsClient NewCWLogsClient = func(sess *session.Session) cloudwatchlogsiface.CloudWatchLogsAPI { return &cli } t.Run("Empty log group name prefix", func(t *testing.T) { - cli = FakeCWLogsClient{ + cli = fakeCWLogsClient{ logGroups: cloudwatchlogs.DescribeLogGroupsOutput{ LogGroups: []*cloudwatchlogs.LogGroup{ { @@ -88,7 +88,7 @@ func TestQuery_DescribeLogGroups(t *testing.T) { }) t.Run("Non-empty log group name prefix", func(t *testing.T) { - cli = FakeCWLogsClient{ + cli = fakeCWLogsClient{ logGroups: cloudwatchlogs.DescribeLogGroupsOutput{ LogGroups: []*cloudwatchlogs.LogGroup{ { @@ -152,13 +152,13 @@ func TestQuery_GetLogGroupFields(t *testing.T) { NewCWLogsClient = origNewCWLogsClient }) - var cli FakeCWLogsClient + var cli fakeCWLogsClient NewCWLogsClient = func(sess *session.Session) cloudwatchlogsiface.CloudWatchLogsAPI { return &cli } - cli = FakeCWLogsClient{ + cli = fakeCWLogsClient{ logGroupFields: cloudwatchlogs.GetLogGroupFieldsOutput{ LogGroupFields: []*cloudwatchlogs.LogGroupField{ { @@ -229,14 +229,14 @@ func TestQuery_StartQuery(t *testing.T) { NewCWLogsClient = origNewCWLogsClient }) - var cli FakeCWLogsClient + var cli fakeCWLogsClient NewCWLogsClient = func(sess *session.Session) cloudwatchlogsiface.CloudWatchLogsAPI { return &cli } t.Run("invalid time range", func(t *testing.T) { - cli = FakeCWLogsClient{ + cli = fakeCWLogsClient{ logGroupFields: cloudwatchlogs.GetLogGroupFieldsOutput{ LogGroupFields: []*cloudwatchlogs.LogGroupField{ { @@ -289,7 +289,7 @@ func TestQuery_StartQuery(t *testing.T) { t.Run("valid time range", func(t *testing.T) { const refID = "A" - cli = FakeCWLogsClient{ + cli = fakeCWLogsClient{ logGroupFields: cloudwatchlogs.GetLogGroupFieldsOutput{ LogGroupFields: []*cloudwatchlogs.LogGroupField{ { @@ -363,14 +363,14 @@ func Test_executeStartQuery(t *testing.T) { NewCWLogsClient = origNewCWLogsClient }) - var cli FakeCWLogsClient + var cli fakeCWLogsClient NewCWLogsClient = func(sess *session.Session) cloudwatchlogsiface.CloudWatchLogsAPI { return &cli } t.Run("successfully parses information from JSON to StartQueryWithContext", func(t *testing.T) { - cli = FakeCWLogsClient{} + cli = fakeCWLogsClient{} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { return datasourceInfo{}, nil }) @@ -396,17 +396,17 @@ func Test_executeStartQuery(t *testing.T) { assert.NoError(t, err) assert.Equal(t, []*cloudwatchlogs.StartQueryInput{ { - StartTime: pointerInt64(0), - EndTime: pointerInt64(1), - Limit: pointerInt64(12), - QueryString: pointerString("fields @timestamp,ltrim(@log) as __log__grafana_internal__,ltrim(@logStream) as __logstream__grafana_internal__|fields @message"), - LogGroupNames: []*string{pointerString("some name"), pointerString("another name")}, + StartTime: aws.Int64(0), + EndTime: aws.Int64(1), + Limit: aws.Int64(12), + QueryString: aws.String("fields @timestamp,ltrim(@log) as __log__grafana_internal__,ltrim(@logStream) as __logstream__grafana_internal__|fields @message"), + LogGroupNames: []*string{aws.String("some name"), aws.String("another name")}, }, }, cli.calls.startQueryWithContext) }) t.Run("cannot parse limit as float", func(t *testing.T) { - cli = FakeCWLogsClient{} + cli = fakeCWLogsClient{} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { return datasourceInfo{}, nil }) @@ -433,7 +433,7 @@ func Test_executeStartQuery(t *testing.T) { }) t.Run("does not populate StartQueryInput.limit when no limit provided", func(t *testing.T) { - cli = FakeCWLogsClient{} + cli = fakeCWLogsClient{} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { return datasourceInfo{}, nil }) @@ -465,13 +465,13 @@ func TestQuery_StopQuery(t *testing.T) { NewCWLogsClient = origNewCWLogsClient }) - var cli FakeCWLogsClient + var cli fakeCWLogsClient NewCWLogsClient = func(sess *session.Session) cloudwatchlogsiface.CloudWatchLogsAPI { return &cli } - cli = FakeCWLogsClient{ + cli = fakeCWLogsClient{ logGroupFields: cloudwatchlogs.GetLogGroupFieldsOutput{ LogGroupFields: []*cloudwatchlogs.LogGroupField{ { @@ -537,14 +537,14 @@ func TestQuery_GetQueryResults(t *testing.T) { NewCWLogsClient = origNewCWLogsClient }) - var cli FakeCWLogsClient + var cli fakeCWLogsClient NewCWLogsClient = func(sess *session.Session) cloudwatchlogsiface.CloudWatchLogsAPI { return &cli } const refID = "A" - cli = FakeCWLogsClient{ + cli = fakeCWLogsClient{ queryResults: cloudwatchlogs.GetQueryResultsOutput{ Results: [][]*cloudwatchlogs.ResultField{ { diff --git a/pkg/tsdb/cloudwatch/metric_find_query_test.go b/pkg/tsdb/cloudwatch/metric_find_query_test.go index 705bb04c328..17ed1f26191 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query_test.go +++ b/pkg/tsdb/cloudwatch/metric_find_query_test.go @@ -29,14 +29,14 @@ func TestQuery_Metrics(t *testing.T) { NewCWClient = origNewCWClient }) - var cwClient FakeCWClient + var cwClient fakeCWClient NewCWClient = func(sess *session.Session) cloudwatchiface.CloudWatchAPI { return cwClient } t.Run("Custom metrics", func(t *testing.T) { - cwClient = FakeCWClient{ + cwClient = fakeCWClient{ Metrics: []*cloudwatch.Metric{ { MetricName: aws.String("Test_MetricName"), @@ -91,7 +91,7 @@ func TestQuery_Metrics(t *testing.T) { }) t.Run("Dimension keys for custom metrics", func(t *testing.T) { - cwClient = FakeCWClient{ + cwClient = fakeCWClient{ Metrics: []*cloudwatch.Metric{ { MetricName: aws.String("Test_MetricName"), @@ -503,7 +503,7 @@ func TestQuery_GetDimensionKeys(t *testing.T) { NewCWClient = origNewCWClient }) - var client FakeCWClient + var client fakeCWClient NewCWClient = func(sess *session.Session) cloudwatchiface.CloudWatchAPI { return client @@ -521,7 +521,7 @@ func TestQuery_GetDimensionKeys(t *testing.T) { } t.Run("should fetch dimension keys from list metrics api and return unique dimensions when a dimension filter is specified", func(t *testing.T) { - client = FakeCWClient{Metrics: metrics, MetricsPerPage: 2} + client = fakeCWClient{Metrics: metrics, MetricsPerPage: 2} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { return datasourceInfo{}, nil }) @@ -662,7 +662,7 @@ func TestQuery_ListMetricsPagination(t *testing.T) { NewCWClient = origNewCWClient }) - var client FakeCWClient + var client fakeCWClient NewCWClient = func(sess *session.Session) cloudwatchiface.CloudWatchAPI { return client @@ -682,7 +682,7 @@ func TestQuery_ListMetricsPagination(t *testing.T) { } t.Run("List Metrics and page limit is reached", func(t *testing.T) { - client = FakeCWClient{Metrics: metrics, MetricsPerPage: 2} + client = fakeCWClient{Metrics: metrics, MetricsPerPage: 2} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { return datasourceInfo{}, nil }) @@ -697,7 +697,7 @@ func TestQuery_ListMetricsPagination(t *testing.T) { }) t.Run("List Metrics and page limit is not reached", func(t *testing.T) { - client = FakeCWClient{Metrics: metrics, MetricsPerPage: 2} + client = fakeCWClient{Metrics: metrics, MetricsPerPage: 2} im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { return datasourceInfo{}, nil }) diff --git a/pkg/tsdb/cloudwatch/time_series_query_test.go b/pkg/tsdb/cloudwatch/time_series_query_test.go index 8e5c8d1535d..c9aeddb8872 100644 --- a/pkg/tsdb/cloudwatch/time_series_query_test.go +++ b/pkg/tsdb/cloudwatch/time_series_query_test.go @@ -27,14 +27,14 @@ func TestTimeSeriesQuery(t *testing.T) { NewCWClient = origNewCWClient }) - var cwClient FakeCWClient + var cwClient fakeCWClient NewCWClient = func(sess *session.Session) cloudwatchiface.CloudWatchAPI { return cwClient } t.Run("Custom metrics", func(t *testing.T) { - cwClient = FakeCWClient{ + cwClient = fakeCWClient{ CloudWatchAPI: nil, GetMetricDataOutput: cloudwatch.GetMetricDataOutput{ NextToken: nil, diff --git a/pkg/tsdb/cloudwatch/test_utils.go b/pkg/tsdb/cloudwatch/utils_test.go similarity index 87% rename from pkg/tsdb/cloudwatch/test_utils.go rename to pkg/tsdb/cloudwatch/utils_test.go index d8c8ce1f07a..377170a940c 100644 --- a/pkg/tsdb/cloudwatch/test_utils.go +++ b/pkg/tsdb/cloudwatch/utils_test.go @@ -18,7 +18,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -type FakeCWLogsClient struct { +type fakeCWLogsClient struct { cloudwatchlogsiface.CloudWatchLogsAPI calls logsQueryCalls @@ -32,11 +32,11 @@ type logsQueryCalls struct { startQueryWithContext []*cloudwatchlogs.StartQueryInput } -func (m *FakeCWLogsClient) GetQueryResultsWithContext(ctx context.Context, input *cloudwatchlogs.GetQueryResultsInput, option ...request.Option) (*cloudwatchlogs.GetQueryResultsOutput, error) { +func (m *fakeCWLogsClient) GetQueryResultsWithContext(ctx context.Context, input *cloudwatchlogs.GetQueryResultsInput, option ...request.Option) (*cloudwatchlogs.GetQueryResultsOutput, error) { return &m.queryResults, nil } -func (m *FakeCWLogsClient) StartQueryWithContext(ctx context.Context, input *cloudwatchlogs.StartQueryInput, option ...request.Option) (*cloudwatchlogs.StartQueryOutput, error) { +func (m *fakeCWLogsClient) StartQueryWithContext(ctx context.Context, input *cloudwatchlogs.StartQueryInput, option ...request.Option) (*cloudwatchlogs.StartQueryOutput, error) { m.calls.startQueryWithContext = append(m.calls.startQueryWithContext, input) return &cloudwatchlogs.StartQueryOutput{ @@ -44,21 +44,21 @@ func (m *FakeCWLogsClient) StartQueryWithContext(ctx context.Context, input *clo }, nil } -func (m *FakeCWLogsClient) StopQueryWithContext(ctx context.Context, input *cloudwatchlogs.StopQueryInput, option ...request.Option) (*cloudwatchlogs.StopQueryOutput, error) { +func (m *fakeCWLogsClient) StopQueryWithContext(ctx context.Context, input *cloudwatchlogs.StopQueryInput, option ...request.Option) (*cloudwatchlogs.StopQueryOutput, error) { return &cloudwatchlogs.StopQueryOutput{ Success: aws.Bool(true), }, nil } -func (m *FakeCWLogsClient) DescribeLogGroupsWithContext(ctx context.Context, input *cloudwatchlogs.DescribeLogGroupsInput, option ...request.Option) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { +func (m *fakeCWLogsClient) DescribeLogGroupsWithContext(ctx context.Context, input *cloudwatchlogs.DescribeLogGroupsInput, option ...request.Option) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { return &m.logGroups, nil } -func (m *FakeCWLogsClient) GetLogGroupFieldsWithContext(ctx context.Context, input *cloudwatchlogs.GetLogGroupFieldsInput, option ...request.Option) (*cloudwatchlogs.GetLogGroupFieldsOutput, error) { +func (m *fakeCWLogsClient) GetLogGroupFieldsWithContext(ctx context.Context, input *cloudwatchlogs.GetLogGroupFieldsInput, option ...request.Option) (*cloudwatchlogs.GetLogGroupFieldsOutput, error) { return &m.logGroupFields, nil } -type FakeCWClient struct { +type fakeCWClient struct { cloudwatchiface.CloudWatchAPI cloudwatch.GetMetricDataOutput @@ -67,11 +67,11 @@ type FakeCWClient struct { MetricsPerPage int } -func (c FakeCWClient) GetMetricDataWithContext(aws.Context, *cloudwatch.GetMetricDataInput, ...request.Option) (*cloudwatch.GetMetricDataOutput, error) { +func (c fakeCWClient) GetMetricDataWithContext(aws.Context, *cloudwatch.GetMetricDataInput, ...request.Option) (*cloudwatch.GetMetricDataOutput, error) { return &c.GetMetricDataOutput, nil } -func (c FakeCWClient) ListMetricsPages(input *cloudwatch.ListMetricsInput, fn func(*cloudwatch.ListMetricsOutput, bool) bool) error { +func (c fakeCWClient) ListMetricsPages(input *cloudwatch.ListMetricsInput, fn func(*cloudwatch.ListMetricsOutput, bool) bool) error { if c.MetricsPerPage == 0 { c.MetricsPerPage = 1000 } @@ -88,7 +88,7 @@ func (c FakeCWClient) ListMetricsPages(input *cloudwatch.ListMetricsInput, fn fu return nil } -type FakeCWAnnotationsClient struct { +type fakeCWAnnotationsClient struct { cloudwatchiface.CloudWatchAPI calls annontationsQueryCalls @@ -101,13 +101,13 @@ type annontationsQueryCalls struct { describeAlarms []*cloudwatch.DescribeAlarmsInput } -func (c *FakeCWAnnotationsClient) DescribeAlarmsForMetric(params *cloudwatch.DescribeAlarmsForMetricInput) (*cloudwatch.DescribeAlarmsForMetricOutput, error) { +func (c *fakeCWAnnotationsClient) DescribeAlarmsForMetric(params *cloudwatch.DescribeAlarmsForMetricInput) (*cloudwatch.DescribeAlarmsForMetricOutput, error) { c.calls.describeAlarmsForMetric = append(c.calls.describeAlarmsForMetric, params) return c.describeAlarmsForMetricOutput, nil } -func (c *FakeCWAnnotationsClient) DescribeAlarms(params *cloudwatch.DescribeAlarmsInput) (*cloudwatch.DescribeAlarmsOutput, error) { +func (c *fakeCWAnnotationsClient) DescribeAlarms(params *cloudwatch.DescribeAlarmsInput) (*cloudwatch.DescribeAlarmsOutput, error) { c.calls.describeAlarms = append(c.calls.describeAlarms, params) return c.describeAlarmsOutput, nil @@ -201,10 +201,3 @@ func (s fakeSessionCache) GetSession(c awsds.SessionConfig) (*session.Session, e Config: &aws.Config{}, }, nil } - -func pointerString(s string) *string { - return &s -} -func pointerInt64(i int64) *int64 { - return &i -} From afc761746d106468cd5a78e869a7e613b8b7c348 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 3 Mar 2022 08:53:13 -0500 Subject: [PATCH 11/52] NodeGraph: Include frames with name edges or nodes in node graph (#46047) (#46164) * Include frames with name edges or nodes in node graph (cherry picked from commit cb1649e9b8ceddb89e71e6ef096983181a27a50b) Co-authored-by: Connor Lindsey --- docs/sources/visualizations/node-graph.md | 2 +- public/app/plugins/panel/nodeGraph/utils.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/sources/visualizations/node-graph.md b/docs/sources/visualizations/node-graph.md index 1f4ba8c435b..2545d108325 100644 --- a/docs/sources/visualizations/node-graph.md +++ b/docs/sources/visualizations/node-graph.md @@ -76,7 +76,7 @@ Click on the node and select "Show in Graph layout" option to switch back to gra This visualization needs a specific shape of the data to be returned from the data source in order to correctly display it. -Data source needs to return two data frames, one for nodes and one for edges, and you also have to set `frame.meta.preferredVisualisationType = 'nodeGraph'` on both data frames if you want them to be automatically shown in node graph in Explore. +Data source needs to return two data frames, one for nodes and one for edges. You have to set `frame.meta.preferredVisualisationType = 'nodeGraph'` on both data frames or name them `nodes` and `edges` respectively for the node graph to render. ### Node parameters diff --git a/public/app/plugins/panel/nodeGraph/utils.ts b/public/app/plugins/panel/nodeGraph/utils.ts index c1005a326f0..c79274427f1 100644 --- a/public/app/plugins/panel/nodeGraph/utils.ts +++ b/public/app/plugins/panel/nodeGraph/utils.ts @@ -323,5 +323,14 @@ export function getNodeGraphDataFrames(frames: DataFrame[]) { // processing pipeline which ends up populating redux state with proper data. As we move towards more dataFrame // oriented API it seems like a better direction to move such processing into to visualisations and do minimal // and lazy processing here. Needs bigger refactor so keeping nodeGraph and Traces as they are for now. - return frames.filter((frame) => frame.meta?.preferredVisualisationType === 'nodeGraph'); + return frames.filter((frame) => { + if (frame.meta?.preferredVisualisationType === 'nodeGraph') { + return true; + } + if (frame.name === 'nodes' || frame.name === 'edges') { + return true; + } + + return false; + }); } From 82cbff094a6184c48edf7ff159bda8112c5fedd6 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 3 Mar 2022 10:55:14 -0500 Subject: [PATCH 12/52] Fixing docs comma (#46169) (#46175) (cherry picked from commit 32170e401f8fb9c194709dbac697939a469c3420) Co-authored-by: Timur Olzhabayev --- docs/sources/panels/library-panels/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/panels/library-panels/_index.md b/docs/sources/panels/library-panels/_index.md index 4faa00668ef..8b78baf7eb8 100644 --- a/docs/sources/panels/library-panels/_index.md +++ b/docs/sources/panels/library-panels/_index.md @@ -1,6 +1,6 @@ +++ title = "Create reusable Grafana panels" -aliases = ["/docs/grafana/latest/panels/panel-library/" "/docs/sources/panels/library-panels/"] +aliases = ["/docs/grafana/latest/panels/panel-library/", "/docs/sources/panels/library-panels/"] weight = 700 +++ From 205874431c935dc1f6a5f6f70e52adc8d6f8b284 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 3 Mar 2022 12:00:53 -0500 Subject: [PATCH 13/52] TextPanel: Sanitize after markdown has been rendered to html (#46166) (#46185) * Sanitize after markdown has been rendered to html * Update e2e test (cherry picked from commit b1125c0ba323af5f3335338ceed4801455ebab2a) Co-authored-by: Ashley Harrison --- e2e/dashboards-suite/dashboard-templating.spec.ts | 2 +- public/app/plugins/panel/text/TextPanel.tsx | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/e2e/dashboards-suite/dashboard-templating.spec.ts b/e2e/dashboards-suite/dashboard-templating.spec.ts index a7c4db01033..4a6187e4be1 100644 --- a/e2e/dashboards-suite/dashboard-templating.spec.ts +++ b/e2e/dashboards-suite/dashboard-templating.spec.ts @@ -27,7 +27,7 @@ e2e.scenario({ `Server:pipe = A'A"A|BB\\B|CCC`, `Server:distributed = A'A"A,Server=BB\\B,Server=CCC`, `Server:csv = A'A"A,BB\\B,CCC`, - `Server:html = A'A"A, BB\\B, CCC`, + `Server:html = A'A"A, BB\\B, CCC`, `Server:json = ["A'A\\"A","BB\\\\B","CCC"]`, `Server:percentencode = %7BA%27A%22A%2CBB%5CB%2CCCC%7D`, `Server:singlequote = 'A\\'A"A','BB\\B','CCC'`, diff --git a/public/app/plugins/panel/text/TextPanel.tsx b/public/app/plugins/panel/text/TextPanel.tsx index f0251f9d658..b9e918ce259 100644 --- a/public/app/plugins/panel/text/TextPanel.tsx +++ b/public/app/plugins/panel/text/TextPanel.tsx @@ -44,9 +44,11 @@ export class TextPanel extends PureComponent { prepareMarkdown(content: string): string { // Sanitize is disabled here as we handle that after variable interpolation - return renderTextPanelMarkdown(this.interpolateAndSanitizeString(content), { - noSanitize: config.disableSanitizeHtml, - }); + return this.interpolateAndSanitizeString( + renderTextPanelMarkdown(content, { + noSanitize: config.disableSanitizeHtml, + }) + ); } interpolateAndSanitizeString(content: string): string { From 57423746cefed6c68af3ff0f01a9e13cb2f430d8 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 3 Mar 2022 12:11:23 -0500 Subject: [PATCH 14/52] BarChart: fix hovered series highlight in tooltip (#44882) (#46187) (cherry picked from commit 3e3a6573434036bad6224d7bc9301ce5bcd2286c) Co-authored-by: Leon Sorokin --- .../barchart/__snapshots__/utils.test.ts.snap | 56 ++++++++---- public/app/plugins/panel/barchart/bars.ts | 86 ++++++++++--------- 2 files changed, 86 insertions(+), 56 deletions(-) diff --git a/public/app/plugins/panel/barchart/__snapshots__/utils.test.ts.snap b/public/app/plugins/panel/barchart/__snapshots__/utils.test.ts.snap index 300a917e722..2410f353ef2 100644 --- a/public/app/plugins/panel/barchart/__snapshots__/utils.test.ts.snap +++ b/public/app/plugins/panel/barchart/__snapshots__/utils.test.ts.snap @@ -59,15 +59,18 @@ Object { }, ], "cursor": Object { + "dataIdx": [Function], "drag": Object { "setScale": false, + "x": false, + "y": false, }, "focus": Object { "prox": 30, }, "points": Object { - "fill": [Function], - "show": false, + "bbox": [Function], + "fill": "rgba(255,255,255,0.4)", "size": [Function], "stroke": [Function], "width": [Function], @@ -195,15 +198,18 @@ Object { }, ], "cursor": Object { + "dataIdx": [Function], "drag": Object { "setScale": false, + "x": false, + "y": false, }, "focus": Object { "prox": 30, }, "points": Object { - "fill": [Function], - "show": false, + "bbox": [Function], + "fill": "rgba(255,255,255,0.4)", "size": [Function], "stroke": [Function], "width": [Function], @@ -331,15 +337,18 @@ Object { }, ], "cursor": Object { + "dataIdx": [Function], "drag": Object { "setScale": false, + "x": false, + "y": false, }, "focus": Object { "prox": 30, }, "points": Object { - "fill": [Function], - "show": false, + "bbox": [Function], + "fill": "rgba(255,255,255,0.4)", "size": [Function], "stroke": [Function], "width": [Function], @@ -467,15 +476,18 @@ Object { }, ], "cursor": Object { + "dataIdx": [Function], "drag": Object { "setScale": false, + "x": false, + "y": false, }, "focus": Object { "prox": 30, }, "points": Object { - "fill": [Function], - "show": false, + "bbox": [Function], + "fill": "rgba(255,255,255,0.4)", "size": [Function], "stroke": [Function], "width": [Function], @@ -603,15 +615,18 @@ Object { }, ], "cursor": Object { + "dataIdx": [Function], "drag": Object { "setScale": false, + "x": false, + "y": false, }, "focus": Object { "prox": 30, }, "points": Object { - "fill": [Function], - "show": false, + "bbox": [Function], + "fill": "rgba(255,255,255,0.4)", "size": [Function], "stroke": [Function], "width": [Function], @@ -739,15 +754,18 @@ Object { }, ], "cursor": Object { + "dataIdx": [Function], "drag": Object { "setScale": false, + "x": false, + "y": false, }, "focus": Object { "prox": 30, }, "points": Object { - "fill": [Function], - "show": false, + "bbox": [Function], + "fill": "rgba(255,255,255,0.4)", "size": [Function], "stroke": [Function], "width": [Function], @@ -875,15 +893,18 @@ Object { }, ], "cursor": Object { + "dataIdx": [Function], "drag": Object { "setScale": false, + "x": false, + "y": false, }, "focus": Object { "prox": 30, }, "points": Object { - "fill": [Function], - "show": false, + "bbox": [Function], + "fill": "rgba(255,255,255,0.4)", "size": [Function], "stroke": [Function], "width": [Function], @@ -1011,15 +1032,18 @@ Object { }, ], "cursor": Object { + "dataIdx": [Function], "drag": Object { "setScale": false, + "x": false, + "y": false, }, "focus": Object { "prox": 30, }, "points": Object { - "fill": [Function], - "show": false, + "bbox": [Function], + "fill": "rgba(255,255,255,0.4)", "size": [Function], "stroke": [Function], "width": [Function], diff --git a/public/app/plugins/panel/barchart/bars.ts b/public/app/plugins/panel/barchart/bars.ts index 31a0cfdc71b..bc1be7c39bd 100644 --- a/public/app/plugins/panel/barchart/bars.ts +++ b/public/app/plugins/panel/barchart/bars.ts @@ -74,12 +74,7 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) { } let qt: Quadtree; - let hovered: Rect | undefined = undefined; - - let barMark = document.createElement('div'); - barMark.classList.add('bar-mark'); - barMark.style.position = 'absolute'; - barMark.style.background = 'rgba(255,255,255,0.4)'; + let hRect: Rect | null; const xSplits: Axis.Splits = (u: uPlot) => { const dim = isXHorizontal ? u.bbox.width : u.bbox.height; @@ -258,7 +253,47 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) { const init = (u: uPlot) => { let over = u.over; over.style.overflow = 'hidden'; - over.appendChild(barMark); + u.root.querySelectorAll('.u-cursor-pt').forEach((el) => { + (el as HTMLElement).style.borderRadius = '0'; + }); + }; + + const cursor: uPlot.Cursor = { + x: false, + y: false, + drag: { + x: false, + y: false, + }, + dataIdx: (u, seriesIdx) => { + if (seriesIdx === 1) { + hRect = null; + + let cx = u.cursor.left! * devicePixelRatio; + let cy = u.cursor.top! * devicePixelRatio; + + qt.get(cx, cy, 1, 1, (o) => { + if (pointWithin(cx, cy, o.x, o.y, o.x + o.w, o.y + o.h)) { + hRect = o; + } + }); + } + + return hRect && seriesIdx === hRect.sidx ? hRect.didx : null; + }, + points: { + fill: 'rgba(255,255,255,0.4)', + bbox: (u, seriesIdx) => { + let isHovered = hRect && seriesIdx === hRect.sidx; + + return { + left: isHovered ? hRect!.x / devicePixelRatio : -10, + top: isHovered ? hRect!.y / devicePixelRatio : -10, + width: isHovered ? hRect!.w / devicePixelRatio : 0, + height: isHovered ? hRect!.h / devicePixelRatio : 0, + }; + }, + }, }; // Build bars @@ -388,35 +423,10 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) { updateTooltipPosition, u ) => { - let found: Rect | undefined; - let cx = u.cursor.left! * devicePixelRatio; - let cy = u.cursor.top! * devicePixelRatio; - - qt.get(cx, cy, 1, 1, (o) => { - if (pointWithin(cx, cy, o.x, o.y, o.x + o.w, o.y + o.h)) { - found = o; - } - }); - - if (found) { - // prettier-ignore - if (found !== hovered) { - barMark.style.display = ''; - barMark.style.left = found.x / devicePixelRatio + 'px'; - barMark.style.top = found.y / devicePixelRatio + 'px'; - barMark.style.width = found.w / devicePixelRatio + 'px'; - barMark.style.height = found.h / devicePixelRatio + 'px'; - hovered = found; - updateActiveSeriesIdx(hovered.sidx); - updateActiveDatapointIdx(hovered.didx); - updateTooltipPosition(); - } - } else if (hovered !== undefined) { - updateActiveSeriesIdx(hovered!.sidx); - updateActiveDatapointIdx(hovered!.didx); + if (hRect) { + updateActiveSeriesIdx(hRect.sidx); + updateActiveDatapointIdx(hRect.didx); updateTooltipPosition(); - hovered = undefined; - barMark.style.display = 'none'; } else { updateTooltipPosition(true); } @@ -437,11 +447,7 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) { } return { - cursor: { - x: false, - y: false, - points: { show: false }, - }, + cursor, // scale & axis opts xRange, xValues, From 270f7b2d9c0b0193ab283e134c8a645d2a75201f Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 3 Mar 2022 17:10:25 -0500 Subject: [PATCH 15/52] CloudWatch: Update minimal policies documentation (#45478) (#46213) * Separate metrics and logs policies * Update policy examples Removes GetMetricStatistics which is no longer used * WIP for testDatasource * WIP add datasource health check test * Undo changes on imports (cherry picked from commit e39d5382cfd12002bd9ff178faa70ed031a9fbe4) Co-authored-by: Shirley <4163034+fridgepoet@users.noreply.github.com> --- .../datasources/aws-cloudwatch/_index.md | 73 ++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/docs/sources/datasources/aws-cloudwatch/_index.md b/docs/sources/datasources/aws-cloudwatch/_index.md index 7311be8cec5..22833348ea2 100644 --- a/docs/sources/datasources/aws-cloudwatch/_index.md +++ b/docs/sources/datasources/aws-cloudwatch/_index.md @@ -26,7 +26,77 @@ For authentication options and configuration details, see [AWS authentication]({ Grafana needs permissions granted via IAM to be able to read CloudWatch metrics and EC2 tags/instances/regions/alarms. You can attach these permissions to the IAM role or IAM user configured in the previous step. -Here is a minimal policy example: +##### Metrics only example: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowReadingMetricsFromCloudWatch", + "Effect": "Allow", + "Action": [ + "cloudwatch:DescribeAlarmsForMetric", + "cloudwatch:DescribeAlarmHistory", + "cloudwatch:DescribeAlarms", + "cloudwatch:ListMetrics", + "cloudwatch:GetMetricData", + "cloudwatch:GetInsightRuleReport" + ], + "Resource": "*" + }, + { + "Sid": "AllowReadingTagsInstancesRegionsFromEC2", + "Effect": "Allow", + "Action": ["ec2:DescribeTags", "ec2:DescribeInstances", "ec2:DescribeRegions"], + "Resource": "*" + }, + { + "Sid": "AllowReadingResourcesForTags", + "Effect": "Allow", + "Action": "tag:GetResources", + "Resource": "*" + } + ] +} +``` + +##### Logs only example: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowReadingLogsFromCloudWatch", + "Effect": "Allow", + "Action": [ + "logs:DescribeLogGroups", + "logs:GetLogGroupFields", + "logs:StartQuery", + "logs:StopQuery", + "logs:GetQueryResults", + "logs:GetLogEvents" + ], + "Resource": "*" + }, + { + "Sid": "AllowReadingTagsInstancesRegionsFromEC2", + "Effect": "Allow", + "Action": ["ec2:DescribeTags", "ec2:DescribeInstances", "ec2:DescribeRegions"], + "Resource": "*" + }, + { + "Sid": "AllowReadingResourcesForTags", + "Effect": "Allow", + "Action": "tag:GetResources", + "Resource": "*" + } + ] +} +``` + +##### Metrics and Logs example: ```json { @@ -40,7 +110,6 @@ Here is a minimal policy example: "cloudwatch:DescribeAlarmHistory", "cloudwatch:DescribeAlarms", "cloudwatch:ListMetrics", - "cloudwatch:GetMetricStatistics", "cloudwatch:GetMetricData", "cloudwatch:GetInsightRuleReport" ], From b288d6c8058b0cab10b92e295750dbe7bbeec508 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 4 Mar 2022 04:11:09 -0500 Subject: [PATCH 16/52] Plugins: allow using both Function and Class components for app plugins (#46148) (#46228) * fix: make it possible to use both class and functional components for plugins * fix: accept both class and functional components as plugin root pages * refactor: import types by name * refactor: use `ComponentType` (cherry picked from commit c331af93b925d6837d704b6ca155e9d3217c8bc2) Co-authored-by: Levente Balogh --- packages/grafana-data/src/types/app.ts | 6 +++--- packages/grafana-data/src/types/plugin.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/grafana-data/src/types/app.ts b/packages/grafana-data/src/types/app.ts index d2cee306a28..fe23e8fc6f1 100644 --- a/packages/grafana-data/src/types/app.ts +++ b/packages/grafana-data/src/types/app.ts @@ -1,4 +1,4 @@ -import { ComponentClass } from 'react'; +import { ComponentType } from 'react'; import { KeyValue } from './data'; import { NavModel } from './navModel'; import { PluginMeta, GrafanaPlugin, PluginIncludeType } from './plugin'; @@ -48,7 +48,7 @@ export interface AppPluginMeta extends PluginMeta { export class AppPlugin extends GrafanaPlugin> { // Content under: /a/${plugin-id}/* - root?: ComponentClass>; + root?: ComponentType>; rootNav?: NavModel; // Initial navigation model /** @@ -66,7 +66,7 @@ export class AppPlugin extends GrafanaPlugin> { * * NOTE: this structure will change in 7.2+ so that it is managed with a normal react router */ - setRootPage(root: ComponentClass>, rootNav?: NavModel) { + setRootPage(root: ComponentType>, rootNav?: NavModel) { this.root = root; this.rootNav = rootNav; return this; diff --git a/packages/grafana-data/src/types/plugin.ts b/packages/grafana-data/src/types/plugin.ts index e663be6a0d8..d2f027a1f92 100644 --- a/packages/grafana-data/src/types/plugin.ts +++ b/packages/grafana-data/src/types/plugin.ts @@ -1,4 +1,4 @@ -import { ComponentClass } from 'react'; +import { ComponentType } from 'react'; import { KeyValue } from './data'; /** Describes plugins life cycle status */ @@ -159,7 +159,7 @@ export interface PluginConfigPage { icon?: string; id: string; // Unique, in URL - body: ComponentClass>; + body: ComponentType>; } export class GrafanaPlugin { From 50d614098ae0868c77445a494da1c603f8ddf701 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 4 Mar 2022 04:36:33 -0500 Subject: [PATCH 17/52] feat: add a new `` component (#46073) (#46230) (cherry picked from commit 47d1d83673e491b3eb14da8c73db94a845bd9159) Co-authored-by: Levente Balogh --- .../SecretInput/SecretInput.story.tsx | 60 +++++++++++++++++ .../SecretInput/SecretInput.test.tsx | 65 +++++++++++++++++++ .../components/SecretInput/SecretInput.tsx | 26 ++++++++ .../src/components/SecretInput/index.tsx | 1 + 4 files changed, 152 insertions(+) create mode 100644 packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx create mode 100644 packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx create mode 100644 packages/grafana-ui/src/components/SecretInput/SecretInput.tsx create mode 100644 packages/grafana-ui/src/components/SecretInput/index.tsx diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx new file mode 100644 index 00000000000..60da75ef7de --- /dev/null +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx @@ -0,0 +1,60 @@ +import React, { useState, ChangeEvent } from 'react'; +import { Story, Meta } from '@storybook/react'; +import { SecretInput, Props } from './SecretInput'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +export default { + title: 'Forms/SecretInput', + component: SecretInput, + decorators: [withCenteredStory], + parameters: { + controls: { + exclude: [ + 'prefix', + 'suffix', + 'addonBefore', + 'addonAfter', + 'type', + 'disabled', + 'invalid', + 'loading', + 'before', + 'after', + ], + }, + }, + args: { + width: 50, + placeholder: 'Enter your secret...', + }, + argTypes: { + width: { control: { type: 'range', min: 10, max: 200, step: 10 } }, + }, +} as Meta; + +const Template: Story = (args) => { + const [secret, setSecret] = useState(''); + + return ( + ) => setSecret(event.target.value.trim())} + onReset={() => setSecret('')} + /> + ); +}; + +export const basic = Template.bind({}); + +basic.args = { + isConfigured: false, +}; + +export const secretIsConfigured = Template.bind({}); + +secretIsConfigured.args = { + isConfigured: true, +}; diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx new file mode 100644 index 00000000000..010ed2fdf59 --- /dev/null +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SecretInput, RESET_BUTTON_TEXT, CONFIGURED_TEXT } from './SecretInput'; + +const PLACEHOLDER_TEXT = 'Your secret...'; + +describe('', () => { + it('should render an input if the secret is not configured', () => { + render( {}} onReset={() => {}} placeholder={PLACEHOLDER_TEXT} />); + + const input = screen.getByPlaceholderText(PLACEHOLDER_TEXT); + + // Should show an enabled input + expect(input).toBeInTheDocument(); + expect(input).not.toBeDisabled(); + + // Should not show a "Reset" button + expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).not.toBeInTheDocument(); + }); + + it('should render a disabled input with a reset button if the secret is already configured', () => { + render( {}} onReset={() => {}} placeholder={PLACEHOLDER_TEXT} />); + + const input = screen.getByPlaceholderText(PLACEHOLDER_TEXT); + + // Should show a disabled input + expect(input).toBeInTheDocument(); + expect(input).toBeDisabled(); + expect(input).toHaveValue(CONFIGURED_TEXT); + + // Should show a reset button + expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument(); + }); + + it('should be possible to reset a configured secret', () => { + const onReset = jest.fn(); + + render( {}} onReset={onReset} placeholder={PLACEHOLDER_TEXT} />); + + // Should show a reset button and a disabled input + expect(screen.queryByPlaceholderText(PLACEHOLDER_TEXT)).toBeDisabled(); + expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument(); + + // Click on "Reset" + userEvent.click(screen.getByRole('button', { name: RESET_BUTTON_TEXT })); + + expect(onReset).toHaveBeenCalledTimes(1); + }); + + it('should be possible to change the value of the secret', () => { + const onChange = jest.fn(); + + render( {}} placeholder={PLACEHOLDER_TEXT} />); + + const input = screen.getByPlaceholderText(PLACEHOLDER_TEXT); + + expect(input).toHaveValue(''); + + userEvent.type(input, 'Foo'); + + expect(onChange).toHaveBeenCalled(); + expect(input).toHaveValue('Foo'); + }); +}); diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx new file mode 100644 index 00000000000..d46a70903a1 --- /dev/null +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { Input } from '../Input/Input'; +import { HorizontalGroup } from '../Layout/Layout'; +import { Button } from '../Button'; + +export type Props = React.ComponentProps & { + /** TRUE if the secret was already configured. (It is needed as often the backend doesn't send back the actual secret, only the information that it was configured) */ + isConfigured: boolean; + /** Called when the user clicks on the "Reset" button in order to clear the secret */ + onReset: () => void; +}; + +export const CONFIGURED_TEXT = 'configured'; +export const RESET_BUTTON_TEXT = 'Reset'; + +export const SecretInput = ({ isConfigured, onReset, ...props }: Props) => ( + + {!isConfigured && } + {isConfigured && } + {isConfigured && ( + + )} + +); diff --git a/packages/grafana-ui/src/components/SecretInput/index.tsx b/packages/grafana-ui/src/components/SecretInput/index.tsx new file mode 100644 index 00000000000..ff621221c96 --- /dev/null +++ b/packages/grafana-ui/src/components/SecretInput/index.tsx @@ -0,0 +1 @@ +export { SecretInput } from './SecretInput'; From ba1e26796ddc8b5d53d965a77bc5b69e44fe295d Mon Sep 17 00:00:00 2001 From: Timur Olzhabayev Date: Fri, 4 Mar 2022 10:39:56 +0100 Subject: [PATCH 18/52] Backporting bump version fixes (#46233) --- .github/workflows/bump-version.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index debee02c499..38c56c50106 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -4,7 +4,7 @@ on: inputs: version: required: true - default: '7.x.x' + default: '8.x.x' workflow_call: inputs: version_call: From 4377817b278131f88d8ab218bdfdb0d5f7d65ffa Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 4 Mar 2022 05:47:44 -0500 Subject: [PATCH 19/52] Added test coverage commands to package.json (#46235) (#46239) (cherry picked from commit 45e4611807729af078f29f05c98e0c24369c8e3e) Co-authored-by: Yaelle Chaudy <42030685+yaelleC@users.noreply.github.com> --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index c431ca56cba..c39a536e8fc 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "e2e:dev": "./e2e/start-and-run-suite dev", "e2e:benchmark:live": "./e2e/start-and-run-suite benchmark live", "test": "jest --notify --watch", + "test:coverage": "jest --coverage", + "test:coverage:changes": "jest --coverage --changedSince=origin/main", "test:accessibility-report": "./scripts/generate-a11y-report.sh", "lint": "yarn run lint:ts && yarn run lint:sass", "lint:ts": "eslint . --ext .js,.tsx,.ts --cache", From 1e780bdaac228bbf8fc7854bff10dc1ccc6df1d0 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 4 Mar 2022 05:48:45 -0500 Subject: [PATCH 20/52] Live: Fix support for `StreamingFrameAction.Replace` (#46086) (#46237) * fix StreamingFrameAction.replace * simplify * beautify * cover `streamingDataFrame.ts` changes with unit tests * formatting * cover `LiveDataStream.ts` changes with unit tests * update frame length after pushing new values (cherry picked from commit bdbb9ed54a0d1986b1db59a21426e027d326ae6b) Co-authored-by: Artur Wierzbicki --- .../live/centrifuge/LiveDataStream.test.ts | 272 +++++++++++++++++- .../live/centrifuge/LiveDataStream.ts | 77 ++++- .../live/data/StreamingDataFrame.test.ts | 112 +++++++- .../features/live/data/StreamingDataFrame.ts | 42 ++- 4 files changed, 468 insertions(+), 35 deletions(-) diff --git a/public/app/features/live/centrifuge/LiveDataStream.test.ts b/public/app/features/live/centrifuge/LiveDataStream.test.ts index 3129d14e445..4a96ec03411 100644 --- a/public/app/features/live/centrifuge/LiveDataStream.test.ts +++ b/public/app/features/live/centrifuge/LiveDataStream.test.ts @@ -181,6 +181,17 @@ describe('LiveDataStream', () => { action: StreamingFrameAction.Append, }, }, + withReplaceMode: { + addr: dummyLiveChannelAddress, + buffer: { + maxLength: 5, + maxDelta: 10, + action: StreamingFrameAction.Replace, + }, + filter: { + fields: ['time', 'b'], + }, + }, }; const dataFrameJsons = { @@ -205,6 +216,11 @@ describe('LiveDataStream', () => { values: [[102], ['c'], [3]], }, }), + schema1newValues2: () => ({ + data: { + values: [[103], ['d'], [4]], + }, + }), schema2: () => ({ schema: { fields: [ @@ -224,7 +240,7 @@ describe('LiveDataStream', () => { }), }; - describe('happy path with a single subscriber', () => { + describe('happy path with a single subscriber in `append` mode', () => { let deps: ReturnType; let liveDataStream: LiveDataStream; const valuesCollection = new ValuesCollection(); @@ -466,6 +482,229 @@ describe('LiveDataStream', () => { }); }); + describe('happy path with a single subscriber in `replace` mode', () => { + let deps: ReturnType; + let liveDataStream: LiveDataStream; + const valuesCollection = new ValuesCollection(); + + beforeAll(() => { + deps = createDeps(); + + expect(deps.liveEventsObservable.observed).toBeFalsy(); + expect(deps.subscriberReadiness.observed).toBeFalsy(); + liveDataStream = new LiveDataStream(deps); + valuesCollection.subscribeTo(liveDataStream.get(liveDataStreamOptions.withReplaceMode, subscriptionKey)); + }); + + it('should emit the first live channel message event as a serialized streamingDataFrame', async () => { + const valuesCount = valuesCollection.valuesCount(); + + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema1())); + + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount + 1, complete: false }); + const response = valuesCollection.lastValue(); + + expectStreamingResponse(response, StreamingResponseDataType.FullFrame); + const data = response.data[0] as StreamingResponseData; + + expect(data.frame.options).toEqual(liveDataStreamOptions.withReplaceMode.buffer); + + const deserializedFrame = StreamingDataFrame.deserialize(data.frame); + expect(deserializedFrame.fields).toEqual([ + { + config: {}, + name: 'time', + type: 'time', + values: { + buffer: [100, 101], + }, + }, + { + config: {}, + name: 'b', + type: 'number', + values: { + buffer: [1, 2], + }, + }, + ]); + expect(deserializedFrame.length).toEqual(dataFrameJsons.schema1().data.values[0].length); + }); + + it('should emit subsequent messages as deltas if the schema stays the same', async () => { + const valuesCount = valuesCollection.valuesCount(); + + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema1newValues())); + + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount + 1, complete: false }); + const response = valuesCollection.lastValue(); + + expectStreamingResponse(response, StreamingResponseDataType.NewValuesSameSchema); + const data = response.data[0] as StreamingResponseData; + + expect(data.values).toEqual([[102], [3]]); + }); + + it('should emit a full frame if schema changes', async () => { + const valuesCount = valuesCollection.valuesCount(); + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema2())); + + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount + 1, complete: false }); + const response = valuesCollection.lastValue(); + + expectStreamingResponse(response, StreamingResponseDataType.FullFrame); + const data = response.data[0] as StreamingResponseData; + + expect(fieldsOf(data)).toEqual([ + { + name: 'time', + values: [103], + }, + { + name: 'b', + values: ['y'], + }, + ]); + }); + + it('should emit a full frame if received a status live channel event with error', async () => { + const valuesCount = valuesCollection.valuesCount(); + + const error = new Error(`oh no!`); + deps.liveEventsObservable.next(liveChannelStatusEvent(LiveChannelConnectionState.Connected, error)); + + expectValueCollectionState(valuesCollection, { + errors: 0, + values: valuesCount + 1, + complete: false, + }); + const response = valuesCollection.lastValue(); + + expectErrorResponse(response, StreamingResponseDataType.FullFrame); + }); + + it('should buffer new values until subscriber is ready', async () => { + const valuesCount = valuesCollection.valuesCount(); + + deps.subscriberReadiness.next(false); + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema2newValues())); + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount, complete: false }); + + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema2newValues())); + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount, complete: false }); + + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema2newValues())); + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount, complete: false }); + + deps.subscriberReadiness.next(true); + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount + 1, complete: false }); + + const response = valuesCollection.lastValue(); + + expectStreamingResponse(response, StreamingResponseDataType.NewValuesSameSchema); + const data = response.data[0] as StreamingResponseData; + + expect(data.values).toEqual([[104], ['o']]); + }); + + it(`should reduce buffer to a full frame if schema changed at any point during subscriber's unavailability`, async () => { + const valuesCount = valuesCollection.valuesCount(); + + deps.subscriberReadiness.next(false); + + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema2newValues())); + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount, complete: false }); + + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema2newValues())); + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount, complete: false }); + + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema1())); + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount, complete: false }); + + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema1newValues())); + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount, complete: false }); + + deps.subscriberReadiness.next(true); + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount + 1, complete: false }); + + const response = valuesCollection.lastValue(); + + expectStreamingResponse(response, StreamingResponseDataType.FullFrame); + expect(fieldsOf(response.data[0])).toEqual([ + { + name: 'time', + values: [102], + }, + { + name: 'b', + values: [3], + }, + ]); + }); + + it(`should reduce buffer to an empty full frame with last error if one or more errors occur during subscriber's unavailability`, async () => { + const firstError = new Error('first error'); + const secondError = new Error(dummyErrorMessage); + const valuesCount = valuesCollection.valuesCount(); + + deps.subscriberReadiness.next(false); + + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema1newValues())); + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema1newValues())); + deps.liveEventsObservable.next(liveChannelStatusEvent(LiveChannelConnectionState.Connected, firstError)); + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema1newValues())); + deps.liveEventsObservable.next(liveChannelStatusEvent(LiveChannelConnectionState.Connected, secondError)); + + deps.subscriberReadiness.next(true); + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount + 1, complete: false }); + + const response = valuesCollection.lastValue(); + expectErrorResponse(response, StreamingResponseDataType.FullFrame); + + const errorMessage = response?.error?.message; + expect(errorMessage?.includes(dummyErrorMessage)).toBeTruthy(); + + expect(fieldsOf(response.data[0])).toEqual([ + { + name: 'time', + values: [], + }, + { + name: 'b', + values: [], + }, + ]); + }); + + it('should ignore messages without payload', async () => { + const valuesCount = valuesCollection.valuesCount(); + + deps.liveEventsObservable.next(liveChannelStatusEvent(LiveChannelConnectionState.Connected)); + deps.liveEventsObservable.next(liveChannelStatusEvent(LiveChannelConnectionState.Pending)); + deps.liveEventsObservable.next(liveChannelStatusEvent(LiveChannelConnectionState.Pending)); + deps.liveEventsObservable.next(liveChannelLeaveEvent()); + + expectValueCollectionState(valuesCollection, { errors: 0, values: valuesCount, complete: false }); + }); + + it(`should shutdown when source observable completes`, async () => { + expect(deps.onShutdown).not.toHaveBeenCalled(); + expect(deps.subscriberReadiness.observed).toBeTruthy(); + expect(deps.liveEventsObservable.observed).toBeTruthy(); + + deps.liveEventsObservable.complete(); + expectValueCollectionState(valuesCollection, { + errors: 0, + values: valuesCollection.valuesCount(), + complete: true, + }); + + expect(deps.subscriberReadiness.observed).toBeFalsy(); + expect(deps.liveEventsObservable.observed).toBeFalsy(); + expect(deps.onShutdown).toHaveBeenCalled(); + }); + }); + describe('single subscriber with initial frame', () => { it('should emit the initial frame right after subscribe', async () => { const deps = createDeps(); @@ -603,6 +842,7 @@ describe('LiveDataStream', () => { withTimeBFilter: new ValuesCollection(), withTimeAFilter: new ValuesCollection(), withoutFilter: new ValuesCollection(), + withReplaceMode: new ValuesCollection(), }; beforeAll(() => { @@ -626,16 +866,18 @@ describe('LiveDataStream', () => { valuesCollections.withoutFilter.subscribeTo( liveDataStream.get(liveDataStreamOptions.withoutFilter, subscriptionKey) ); - - console.log(JSON.stringify(valuesCollections.withTimeAFilter, null, 2)); + valuesCollections.withReplaceMode.subscribeTo( + liveDataStream.get(liveDataStreamOptions.withReplaceMode, subscriptionKey) + ); expectValueCollectionState(valuesCollections.withTimeAFilter, { errors: 0, values: 2, complete: false }); expectValueCollectionState(valuesCollections.withTimeBFilter, { errors: 0, values: 1, complete: false }); expectValueCollectionState(valuesCollections.withoutFilter, { errors: 0, values: 1, complete: false }); + expectValueCollectionState(valuesCollections.withReplaceMode, { errors: 0, values: 1, complete: false }); }); it('should emit filtered data to each subscriber', async () => { - deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema1newValues())); + deps.liveEventsObservable.next(liveChannelMessageEvent(dataFrameJsons.schema1newValues2())); expect( mapValues(valuesCollections, (collection) => collection.values.map((response) => { @@ -660,7 +902,7 @@ describe('LiveDataStream', () => { }, ], [[102], ['c']], - [[102], ['c']], + [[103], ['d']], ], withTimeBFilter: [ [ @@ -673,7 +915,7 @@ describe('LiveDataStream', () => { values: [2, 3], }, ], - [[102], [3]], + [[103], [4]], ], withoutFilter: [ [ @@ -690,7 +932,21 @@ describe('LiveDataStream', () => { values: [1, 2, 3], }, ], - [[102], ['c'], [3]], + [[103], ['d'], [4]], + ], + withReplaceMode: [ + // only last packet + [ + { + name: 'time', + values: [102], + }, + { + name: 'b', + values: [3], + }, + ], + [[103], [4]], ], }); }); @@ -703,6 +959,7 @@ describe('LiveDataStream', () => { withTimeAFilter: true, withTimeBFilter: false, withoutFilter: false, + withReplaceMode: false, }); expect(deps.subscriberReadiness.observed).toBeTruthy(); expect(deps.liveEventsObservable.observed).toBeTruthy(); @@ -716,6 +973,7 @@ describe('LiveDataStream', () => { withTimeAFilter: true, withTimeBFilter: true, withoutFilter: true, + withReplaceMode: true, }); expect(deps.subscriberReadiness.observed).toBeFalsy(); expect(deps.liveEventsObservable.observed).toBeFalsy(); diff --git a/public/app/features/live/centrifuge/LiveDataStream.ts b/public/app/features/live/centrifuge/LiveDataStream.ts index 7ac052bded0..3f735a52f2c 100644 --- a/public/app/features/live/centrifuge/LiveDataStream.ts +++ b/public/app/features/live/centrifuge/LiveDataStream.ts @@ -1,4 +1,4 @@ -import type { LiveDataStreamOptions, StreamingFrameOptions } from '@grafana/runtime/src/services/live'; +import { LiveDataStreamOptions, StreamingFrameAction, StreamingFrameOptions } from '@grafana/runtime/src/services/live'; import { toDataQueryError } from '@grafana/runtime/src/utils/toDataQueryError'; import { DataFrameJSON, @@ -228,30 +228,89 @@ export class LiveDataStream { this.resizeBuffer(buffer); this.prepareInternalStreamForNewSubscription(options); + const shouldSendLastPacketOnly = options?.buffer?.action === StreamingFrameAction.Replace; const fieldsNamesFilter = options.filter?.fields; const dataNeedsFiltering = fieldsNamesFilter?.length; const fieldFilterPredicate = dataNeedsFiltering ? ({ name }: Field) => fieldsNamesFilter.includes(name) : undefined; let matchingFieldIndexes: number[] | undefined = undefined; - const getFullFrameResponseData = (error?: DataQueryError): StreamingDataQueryResponse => { + const getFullFrameResponseData = ( + messages: InternalStreamMessage[], + error?: DataQueryError + ): StreamingDataQueryResponse => { matchingFieldIndexes = fieldFilterPredicate ? this.frameBuffer.getMatchingFieldIndexes(fieldFilterPredicate) : undefined; + if (!shouldSendLastPacketOnly) { + return { + key: subKey, + state: error ? LoadingState.Error : LoadingState.Streaming, + data: [ + { + type: StreamingResponseDataType.FullFrame, + frame: this.frameBuffer.serialize(fieldFilterPredicate, buffer), + }, + ], + error, + }; + } + + if (error) { + // send empty frame with error + return { + key: subKey, + state: LoadingState.Error, + data: [ + { + type: StreamingResponseDataType.FullFrame, + frame: this.frameBuffer.serialize(fieldFilterPredicate, buffer, { maxLength: 0 }), + }, + ], + error, + }; + } + + if (!messages.length) { + console.warn(`expected to find at least one non error message ${messages.map(({ type }) => type)}`); + // send empty frame + return { + key: subKey, + state: LoadingState.Streaming, + data: [ + { + type: StreamingResponseDataType.FullFrame, + frame: this.frameBuffer.serialize(fieldFilterPredicate, buffer, { maxLength: 0 }), + }, + ], + error, + }; + } + return { key: subKey, - state: error ? LoadingState.Error : LoadingState.Streaming, + state: LoadingState.Streaming, data: [ { type: StreamingResponseDataType.FullFrame, - frame: this.frameBuffer.serialize(fieldFilterPredicate, buffer), + frame: this.frameBuffer.serialize(fieldFilterPredicate, buffer, { + maxLength: this.frameBuffer.packetInfo.length, + }), }, ], error, }; }; - const getNewValuesSameSchemaResponseData = (values: unknown[][]): StreamingDataQueryResponse => { + const getNewValuesSameSchemaResponseData = ( + messages: Array> + ): StreamingDataQueryResponse => { + const lastMessage = messages.length ? messages[messages.length - 1] : undefined; + const values = + shouldSendLastPacketOnly && lastMessage + ? lastMessage.values + : reduceNewValuesSameSchemaMessages(messages).values; + const filteredValues = matchingFieldIndexes ? values.filter((v, i) => (matchingFieldIndexes as number[]).includes(i)) : values; @@ -277,18 +336,18 @@ export class LiveDataStream { if (shouldSendFullFrame) { shouldSendFullFrame = false; - return getFullFrameResponseData(lastError); + return getFullFrameResponseData(messages, lastError); } if (errors.length) { // send the latest frame with the last error, discard everything else - return getFullFrameResponseData(lastError); + return getFullFrameResponseData(messages, lastError); } const schemaChanged = messages.some((n) => n.type === InternalStreamMessageType.ChangedSchema); if (schemaChanged) { // send the latest frame, discard intermediate appends - return getFullFrameResponseData(); + return getFullFrameResponseData(messages, undefined); } const newValueSameSchemaMessages = filterMessages(messages, InternalStreamMessageType.NewValuesSameSchema); @@ -296,7 +355,7 @@ export class LiveDataStream { console.warn(`unsupported message type ${messages.map(({ type }) => type)}`); } - return getNewValuesSameSchemaResponseData(reduceNewValuesSameSchemaMessages(newValueSameSchemaMessages).values); + return getNewValuesSameSchemaResponseData(newValueSameSchemaMessages); }) ); diff --git a/public/app/features/live/data/StreamingDataFrame.test.ts b/public/app/features/live/data/StreamingDataFrame.test.ts index 1c59069e6e5..410746d25e2 100644 --- a/public/app/features/live/data/StreamingDataFrame.test.ts +++ b/public/app/features/live/data/StreamingDataFrame.test.ts @@ -304,7 +304,8 @@ describe('Streaming JSON', () => { }); it('should resize the buffer', function () { - const serializedFrame = frame.serialize((f) => ['time', 'name'].includes(f.name), { maxLength: 2 }); + const options = { maxLength: 2 }; + const serializedFrame = frame.serialize((f) => ['time', 'name'].includes(f.name), options); expect(serializedFrame.fields).toEqual([ { config: {}, @@ -320,6 +321,48 @@ describe('Streaming JSON', () => { }, ]); }); + + it('should trim values and retain option override values', function () { + const options = { maxLength: 2 }; + const trimValues = { maxLength: 1 }; + const serializedFrame = frame.serialize((f) => ['time', 'name'].includes(f.name), options, trimValues); + expect(serializedFrame.fields).toEqual([ + { + config: {}, + name: 'time', + type: 'time', + values: [300], + }, + { + config: {}, + name: 'name', + type: 'string', + values: ['c'], + }, + ]); + expect(serializedFrame.options.maxLength).toEqual(options.maxLength); + }); + + it('should use maxLength from options if its lower than maxLength from trimValues', function () { + const options = { maxLength: 1 }; + const trimValues = { maxLength: 2 }; + const serializedFrame = frame.serialize((f) => ['time', 'name'].includes(f.name), options, trimValues); + expect(serializedFrame.fields).toEqual([ + { + config: {}, + name: 'time', + type: 'time', + values: [300], + }, + { + config: {}, + name: 'name', + type: 'string', + values: ['c'], + }, + ]); + expect(serializedFrame.options.maxLength).toEqual(options.maxLength); + }); }); describe('resizing', function () { @@ -438,14 +481,20 @@ describe('Streaming JSON', () => { }, }; - const serializedFrame = StreamingDataFrame.fromDataFrameJSON(json, { - maxLength: 5, - maxDelta: 300, - }).serialize(); - - it('should support pushing new values matching the existing schema', function () { - const frame = StreamingDataFrame.deserialize(serializedFrame); - frame.pushNewValues([[601], ['x'], [10]]); + it('should support pushing new values matching the existing schema in `append` mode', function () { + const frame = StreamingDataFrame.deserialize( + StreamingDataFrame.fromDataFrameJSON(json, { + maxLength: 5, + maxDelta: 300, + }).serialize() + ); + expect(frame.length).toEqual(3); + frame.pushNewValues([ + [601, 602], + ['x', 'y'], + [10, 11], + ]); + expect(frame.length).toEqual(3); expect(frame.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` Array [ Object { @@ -453,6 +502,7 @@ describe('Streaming JSON', () => { "value": Array [ 300, 601, + 602, ], }, Object { @@ -460,6 +510,7 @@ describe('Streaming JSON', () => { "value": Array [ "c", "x", + "y", ], }, Object { @@ -467,6 +518,49 @@ describe('Streaming JSON', () => { "value": Array [ 3, 10, + 11, + ], + }, + ] + `); + }); + + it('should support pushing new values matching the existing schema in `replace` mode', function () { + const frame = StreamingDataFrame.deserialize( + StreamingDataFrame.fromDataFrameJSON(json, { + maxLength: 5, + maxDelta: 300, + action: StreamingFrameAction.Replace, + }).serialize() + ); + expect(frame.length).toEqual(3); + frame.pushNewValues([ + [601, 602], + ['x', 'y'], + [10, 11], + ]); + expect(frame.length).toEqual(2); + expect(frame.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` + Array [ + Object { + "name": "time", + "value": Array [ + 601, + 602, + ], + }, + Object { + "name": "name", + "value": Array [ + "x", + "y", + ], + }, + Object { + "name": "value", + "value": Array [ + 10, + 11, ], }, ] diff --git a/public/app/features/live/data/StreamingDataFrame.ts b/public/app/features/live/data/StreamingDataFrame.ts index b1f69db9183..36694bf5710 100644 --- a/public/app/features/live/data/StreamingDataFrame.ts +++ b/public/app/features/live/data/StreamingDataFrame.ts @@ -93,14 +93,17 @@ export class StreamingDataFrame implements DataFrame { serialize = ( fieldPredicate?: (f: Field) => boolean, - optionsOverride?: Partial + optionsOverride?: Partial, + trimValues?: { + maxLength?: number; + } ): SerializedStreamingDataFrame => { const options = optionsOverride ? Object.assign({}, { ...this.options, ...optionsOverride }) : this.options; const dataFrameDTO = toFilteredDataFrameDTO(this, fieldPredicate); const numberOfItemsToRemove = getNumberOfItemsToRemove( dataFrameDTO.fields.map((f) => f.values) as unknown[][], - options.maxLength, + typeof trimValues?.maxLength === 'number' ? Math.min(trimValues.maxLength, options.maxLength) : options.maxLength, this.timeFieldIndex, options.maxDelta ); @@ -360,18 +363,37 @@ export class StreamingDataFrame implements DataFrame { return; } - this.packetInfo.action = StreamingFrameAction.Append; + this.packetInfo.action = this.options.action; this.packetInfo.number++; this.packetInfo.length = values[0].length; this.packetInfo.schemaChanged = false; - circPush( - this.fields.map((f) => f.values.buffer), - values, - this.options.maxLength, - this.timeFieldIndex, - this.options.maxDelta - ); + if (this.options.action === StreamingFrameAction.Append) { + circPush( + this.fields.map((f) => f.values.buffer), + values, + this.options.maxLength, + this.timeFieldIndex, + this.options.maxDelta + ); + } else { + values.forEach((v, i) => { + if (this.fields[i]?.values) { + this.fields[i].values.buffer = v; + } + }); + + assureValuesAreWithinLengthLimit( + this.fields.map((f) => f.values.buffer), + this.options.maxLength, + this.timeFieldIndex, + this.options.maxDelta + ); + } + const newLength = this.fields?.[0]?.values?.buffer?.length; + if (newLength !== undefined) { + this.length = newLength; + } }; resetStateCalculations = () => { From a022ca233397af73eab29d0253ac085b4e63a45b Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 4 Mar 2022 09:53:58 -0500 Subject: [PATCH 21/52] TraceView: Reduce whitespace in KeyValuesTable (#46105) (#46250) (cherry picked from commit 23956557d8c6a119b7de5be5c42024e29634d002) Co-authored-by: Connor Lindsey --- .../src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx index 2fb4191dc5b..1ae9e07baef 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx @@ -34,7 +34,7 @@ export const getStyles = (theme: GrafanaTheme2) => { label: KeyValueTable; background: ${autoColor(theme, '#fff')}; border: 1px solid ${autoColor(theme, '#ddd')}; - margin-bottom: 0.7em; + margin-bottom: 0.5rem; max-height: 450px; overflow: auto; `, @@ -45,9 +45,7 @@ export const getStyles = (theme: GrafanaTheme2) => { row: css` label: row; & > td { - padding: 0.25rem 0.5rem; - padding: 0.25rem 0.5rem; - vertical-align: top; + padding: 0rem 0.5rem; } &:nth-child(2n) > td { background: ${autoColor(theme, '#f5f5f5')}; From 711bfa8ae9e51497a466361faa29f1399e3fb3ec Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Mon, 7 Mar 2022 07:18:32 -0500 Subject: [PATCH 22/52] Adding migration guide for dashboard and fixing signing doc (#46238) (#46289) (cherry picked from commit 00a8542ecf46a73473bf46a84b0fcb171cb1f99f) Co-authored-by: Timur Olzhabayev --- .../developers/plugins/migration-guide.md | 97 ++++++++++++++----- .../developers/plugins/sign-a-plugin.md | 6 +- 2 files changed, 75 insertions(+), 28 deletions(-) diff --git a/docs/sources/developers/plugins/migration-guide.md b/docs/sources/developers/plugins/migration-guide.md index 7f6056af876..6bc76e47e6e 100644 --- a/docs/sources/developers/plugins/migration-guide.md +++ b/docs/sources/developers/plugins/migration-guide.md @@ -12,31 +12,42 @@ This guide helps you identify the steps you need to take based on the Grafana ve ## Table of contents -- [From version 8.3.x to 8.4.x](#from-version-83x-to-84x) - - [Value Mapping Editor has been removed from @grafana-ui library](#value-mapping-editor-has-been-removed-from-grafana-ui-library) - - [Thresholds Editor has been removed from @grafana-ui library](#thresholds-editor-has-been-removed-from-grafana-ui-library) - - [8.4 Deprecations](#84-deprecations) - - [LocationService replaces getLocationSrv](#locationservice-replaces-getlocationsrv) -- [From version 7.x.x to 8.x.x](#from-version-7xx-to-8xx) - - [Backend plugin v1 support has been dropped](#backend-plugin-v1-support-has-been-dropped) - - [1. Add dependency on grafana-plugin-sdk-go](#1-add-dependency-on-grafana-plugin-sdk-go) - - [2. Update the way you bootstrap your plugin](#2-update-the-way-you-bootstrap-your-plugin) - - [3. Update the plugin package](#3-update-the-plugin-package) - - [Sign and load backend plugins](#sign-and-load-backend-plugins) - - [Update react-hook-form from v6 to v7](#update-react-hook-form-from-v6-to-v7) - - [Update the plugin.json](#update-the-pluginjson) - - [Update imports to match emotion 11](#update-imports-to-match-emotion-11) - - [8.0 Deprecations](#80-deprecations) - - [Grafana theme v1](#grafana-theme-v1) -- [From version 6.2.x to 7.4.0](#from-version-62x-to-740) - - [Legend components](#legend-components) -- [From version 6.x.x to 7.0.0](#from-version-6xx-to-700) - - [What's new in Grafana 7.0?](#whats-new-in-grafana-70) - - [Migrate a plugin from Angular to React](#migrate-a-plugin-from-angular-to-react) - - [Migrate a panel plugin](#migrate-a-panel-plugin) - - [Migrate a data source plugin](#migrate-a-data-source-plugin) - - [Migrate to data frames](#migrate-to-data-frames) - - [Troubleshoot plugin migration](#troubleshoot-plugin-migration) +- [Plugin migration guide](#plugin-migration-guide) + - [Introduction](#introduction) + - [Table of contents](#table-of-contents) + - [From version 8.3.x to 8.4.x](#from-version-83x-to-84x) + - [Value Mapping Editor has been removed from @grafana-ui library](#value-mapping-editor-has-been-removed-from-grafana-ui-library) + - [Thresholds Editor has been removed from @grafana-ui library](#thresholds-editor-has-been-removed-from-grafana-ui-library) + - [8.4 deprecations](#84-deprecations) + - [LocationService replaces getLocationSrv](#locationservice-replaces-getlocationsrv) + - [From version 7.x.x to 8.x.x](#from-version-7xx-to-8xx) + - [Backend plugin v1 support has been dropped](#backend-plugin-v1-support-has-been-dropped) + - [1. Add dependency on grafana-plugin-sdk-go](#1-add-dependency-on-grafana-plugin-sdk-go) + - [2. Update the way you bootstrap your plugin](#2-update-the-way-you-bootstrap-your-plugin) + - [3. Update the plugin package](#3-update-the-plugin-package) + - [Sign and load backend plugins](#sign-and-load-backend-plugins) + - [Update react-hook-form from v6 to v7](#update-react-hook-form-from-v6-to-v7) + - [Update the plugin.json](#update-the-pluginjson) + - [Update imports to match emotion 11](#update-imports-to-match-emotion-11) + - [Update needed for app plugins using dashboards](#update-needed-for-app-plugins-using-dashboards) + - [8.0 deprecations](#80-deprecations) + - [Grafana theme v1](#grafana-theme-v1) + - [From version 6.2.x to 7.4.0](#from-version-62x-to-740) + - [Legend components](#legend-components) + - [From version 6.5.x to 7.3.0](#from-version-65x-to-730) + - [getColorForTheme changes](#getcolorfortheme-changes) + - [From version 6.x.x to 7.0.0](#from-version-6xx-to-700) + - [What's new in Grafana 7.0?](#whats-new-in-grafana-70) + - [New data format](#new-data-format) + - [Improved TypeScript support](#improved-typescript-support) + - [Grafana Toolkit](#grafana-toolkit) + - [Field options](#field-options) + - [Backend plugins](#backend-plugins) + - [Migrate a plugin from Angular to React](#migrate-a-plugin-from-angular-to-react) + - [Migrate a panel plugin](#migrate-a-panel-plugin) + - [Migrate a data source plugin](#migrate-a-data-source-plugin) + - [Migrate to data frames](#migrate-to-data-frames) + - [Troubleshoot plugin migration](#troubleshoot-plugin-migration) ## From version 8.3.x to 8.4.x @@ -308,6 +319,42 @@ import { cx, css } from 'emotion'; import { cx, css } from '@emotion/css'; ``` +### Update needed for app plugins using dashboards + +To make side navigation work properly - app plugins targeting Grafana `8.+` and integrating into the side menu via [addToNav]({{< relref "metadata.md#properties-4" >}}) property need to adjust their `plugin.json` and all dashboard json files to have a matching `uid`. + +**`plugin.json`** + +```json "linenos=inline,hl_lines=7,linenostart=1" +{ + "id": "plugin-id", + // ... + "includes": [ + { + "type": "dashboard", + "name": "(Team) Situation Overview", + "path": "dashboards/example-dashboard.json", + "addToNav": true, + "defaultNav": false, + "uid": "l3KqBxCMz" + } + ] + // ... +} +``` + +**`dashboards/example-dashboard.json`** + +```json +{ + // ... + "title": "Example Dashboard", + "uid": "l3KqBxCMz", + "version": 1 + // ... +} +``` + ### 8.0 deprecations #### Grafana theme v1 diff --git a/docs/sources/developers/plugins/sign-a-plugin.md b/docs/sources/developers/plugins/sign-a-plugin.md index 9138ae8644e..d8db5c53798 100644 --- a/docs/sources/developers/plugins/sign-a-plugin.md +++ b/docs/sources/developers/plugins/sign-a-plugin.md @@ -32,9 +32,9 @@ To verify ownership of your plugin, you need to generate an API key that you'll Public plugins need to be reviewed by the Grafana team before you can sign them. -1. Submit your plugin for review by creating a pull request in the [grafana-plugin-repository](https://github.com/grafana/grafana-plugin-repository). -1. When your plugin is approved, you're granted a plugin signature level. **Without a plugin signature level, you won't be able to sign your plugin**. -1. In your plugin directory, sign the plugin with the API key you just created. Grafana Toolkit creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin. +1. Submit your plugin for [review]({{< relref "package-a-plugin.md#publishing-your-plugin-for-the-first-time" >}}) +2. When your plugin is approved, you're granted a plugin signature level. **Without a plugin signature level, you won't be able to sign your plugin**. +3. In your plugin directory, sign the plugin with the API key you just created. Grafana Toolkit creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin. ```bash export GRAFANA_API_KEY= From 94a794b315fd2df4d2489f50d06cbfc828cdb8b4 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Mon, 7 Mar 2022 14:36:49 -0600 Subject: [PATCH 23/52] LegacyBrowser: fix Safari 13.1 (lacks logical nullish assignent) (#46260) (#46300) (cherry picked from commit 0ee552dd652e19da137c896db7e57e9c94e02cae) --- .../src/transformations/transformers/labelsToFields.ts | 2 +- public/app/features/dashboard/state/DashboardModel.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grafana-data/src/transformations/transformers/labelsToFields.ts b/packages/grafana-data/src/transformations/transformers/labelsToFields.ts index b203c6f6d91..f60e68cb86c 100644 --- a/packages/grafana-data/src/transformations/transformers/labelsToFields.ts +++ b/packages/grafana-data/src/transformations/transformers/labelsToFields.ts @@ -70,7 +70,7 @@ export const labelsToFieldsTransformer: SynchronousDataTransformerInfo Date: Tue, 8 Mar 2022 04:15:28 -0500 Subject: [PATCH 24/52] TagsInput: fix tags remove button accessibility issues (#46254) (#46315) * TagsInput: fix remove button focusable state * Add tests * use IconButton * reverted style changes & disable iconbutton hover animation (cherry picked from commit 1ef247e0c6214d67051b400b0c2adf1195d003d5) Co-authored-by: Giordano Ricci --- .../src/components/TagsInput/TagItem.tsx | 23 +++++++++++++++---- .../components/TagsInput/TagsInput.test.tsx | 23 +++++++++++++++++++ .../src/components/TagsInput/TagsInput.tsx | 7 ++---- 3 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx diff --git a/packages/grafana-ui/src/components/TagsInput/TagItem.tsx b/packages/grafana-ui/src/components/TagsInput/TagItem.tsx index 766bd3b1733..7af715e5f00 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagItem.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagItem.tsx @@ -2,12 +2,12 @@ import React, { FC } from 'react'; import { css } from '@emotion/css'; import { getTagColorsFromName } from '../../utils'; import { stylesFactory, useTheme } from '../../themes'; -import { Icon } from '../Icon/Icon'; import { GrafanaTheme } from '@grafana/data'; +import { IconButton } from '../IconButton/IconButton'; interface Props { name: string; - + disabled?: boolean; onRemove: (tag: string) => void; } @@ -36,6 +36,13 @@ const getStyles = stylesFactory(({ theme, name }: { theme: GrafanaTheme; name: s nameStyle: css` margin-right: 3px; `, + + buttonStyles: css` + margin: 0; + &:hover::before { + display: none; + } + `, }; }); @@ -43,14 +50,22 @@ const getStyles = stylesFactory(({ theme, name }: { theme: GrafanaTheme; name: s * @internal * Only used internally by TagsInput * */ -export const TagItem: FC = ({ name, onRemove }) => { +export const TagItem: FC = ({ name, disabled, onRemove }) => { const theme = useTheme(); const styles = getStyles({ theme, name }); return (
{name} - onRemove(name)} /> + onRemove(name)} + type="button" + className={styles.buttonStyles} + />
); }; diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx new file mode 100644 index 00000000000..15e8697c1fd --- /dev/null +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import { TagsInput } from './TagsInput'; + +describe('TagsInput', () => { + it('removes tag when clicking on remove button', async () => { + const onChange = jest.fn(); + render(); + + fireEvent.click(await screen.findByRole('button', { name: /remove one/i })); + + expect(onChange).toHaveBeenCalledWith(['Two']); + }); + + it('does NOT remove tag when clicking on remove button when disabled', async () => { + const onChange = jest.fn(); + render(); + + fireEvent.click(await screen.findByRole('button', { name: /remove one/i })); + + expect(onChange).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx index a0f45e3b547..bcd41ca683e 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx @@ -42,10 +42,7 @@ export const TagsInput: FC = ({ }; const onRemove = (tagToRemove: string) => { - if (disabled) { - return; - } - onChange(tags?.filter((x) => x !== tagToRemove)); + onChange(tags.filter((x) => x !== tagToRemove)); }; const onAdd = (event?: React.MouseEvent) => { @@ -74,7 +71,7 @@ export const TagsInput: FC = ({
{tags?.map((tag: string, index: number) => { - return ; + return ; })}
From d7f27135db420d837493bb825f04a6b6f0298657 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 8 Mar 2022 06:59:50 -0500 Subject: [PATCH 25/52] A11y : Updated EmptyListCTA to switch between Button and LinkButton (#46292) (#46319) * Updated EmptyListCTA to switch between Button and LinkButton * Added tests (cherry picked from commit bfde8ee6030fe829c906fe127baadee127594ac7) Co-authored-by: Yaelle Chaudy <42030685+yaelleC@users.noreply.github.com> --- .../EmptyListCTA/EmptyListCTA.test.tsx | 18 ++++++++++++++++++ .../components/EmptyListCTA/EmptyListCTA.tsx | 9 +++++---- 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 public/app/core/components/EmptyListCTA/EmptyListCTA.test.tsx diff --git a/public/app/core/components/EmptyListCTA/EmptyListCTA.test.tsx b/public/app/core/components/EmptyListCTA/EmptyListCTA.test.tsx new file mode 100644 index 00000000000..35d720e70cf --- /dev/null +++ b/public/app/core/components/EmptyListCTA/EmptyListCTA.test.tsx @@ -0,0 +1,18 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import EmptyListCTA from './EmptyListCTA'; + +describe('EmptyListCTA', () => { + it('should return a button element if there is no buttonLink prop', () => { + render(); + + expect(screen.getByRole('button', { name: 'button title' })); + }); + + it('should return an anchor element if there is a buttonLink prop', () => { + render(); + + expect(screen.getByRole('link', { name: 'button title' })); + }); +}); diff --git a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx index a8c7220fcee..9dd81f6c69d 100644 --- a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx +++ b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx @@ -1,7 +1,7 @@ -import React, { MouseEvent } from 'react'; import { css } from '@emotion/css'; -import { CallToActionCard, Icon, IconName, LinkButton } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; +import { Button, CallToActionCard, Icon, IconName, LinkButton } from '@grafana/ui'; +import React, { MouseEvent } from 'react'; export interface Props { title: string; @@ -75,8 +75,9 @@ const EmptyListCTA: React.FunctionComponent = ({ ` : ''; + const ButtonEl = buttonLink ? LinkButton : Button; const ctaElement = ( - = ({ disabled={buttonDisabled} > {buttonTitle} - + ); return ; From f7d66fd00843cda75d6a27cbcaad4a6f9bd01760 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Tue, 8 Mar 2022 12:44:03 -0500 Subject: [PATCH 26/52] Prometheus: Fix timestamp truncation (#46302) (#46332) (cherry picked from commit db5f480a8fdd5d1b33608e89327fa8d825124e94) --- .../testdata/range_simple.result.golden.txt | 40 +++++++++---------- .../testdata/range_simple.result.json | 12 +++--- pkg/tsdb/prometheus/time_series_query.go | 8 ++-- pkg/tsdb/prometheus/time_series_query_test.go | 6 ++- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/pkg/tsdb/prometheus/testdata/range_simple.result.golden.txt b/pkg/tsdb/prometheus/testdata/range_simple.result.golden.txt index bd1e0d265da..19387cefee6 100644 --- a/pkg/tsdb/prometheus/testdata/range_simple.result.golden.txt +++ b/pkg/tsdb/prometheus/testdata/range_simple.result.golden.txt @@ -7,15 +7,15 @@ Frame[0] { } Name: prometheus_http_requests_total{code="200", handler="/api/v1/query_range", job="prometheus"} Dimensions: 2 Fields by 3 Rows -+-------------------------------+--------------------------------------------------------------------------------------------------------+ -| Name: Time | Name: Value | -| Labels: | Labels: __name__=prometheus_http_requests_total, code=200, handler=/api/v1/query_range, job=prometheus | -| Type: []time.Time | Type: []*float64 | -+-------------------------------+--------------------------------------------------------------------------------------------------------+ -| 2022-01-11 08:25:30 +0000 UTC | 21 | -| 2022-01-11 08:25:31 +0000 UTC | 32 | -| 2022-01-11 08:25:32 +0000 UTC | 43 | -+-------------------------------+--------------------------------------------------------------------------------------------------------+ ++-----------------------------------+--------------------------------------------------------------------------------------------------------+ +| Name: Time | Name: Value | +| Labels: | Labels: __name__=prometheus_http_requests_total, code=200, handler=/api/v1/query_range, job=prometheus | +| Type: []time.Time | Type: []*float64 | ++-----------------------------------+--------------------------------------------------------------------------------------------------------+ +| 2022-01-11 08:25:30.123 +0000 UTC | 21 | +| 2022-01-11 08:25:31.123 +0000 UTC | 32 | +| 2022-01-11 08:25:32.123 +0000 UTC | 43 | ++-----------------------------------+--------------------------------------------------------------------------------------------------------+ @@ -26,17 +26,17 @@ Frame[1] { } Name: prometheus_http_requests_total{code="400", handler="/api/v1/query_range", job="prometheus"} Dimensions: 2 Fields by 3 Rows -+-------------------------------+--------------------------------------------------------------------------------------------------------+ -| Name: Time | Name: Value | -| Labels: | Labels: __name__=prometheus_http_requests_total, code=400, handler=/api/v1/query_range, job=prometheus | -| Type: []time.Time | Type: []*float64 | -+-------------------------------+--------------------------------------------------------------------------------------------------------+ -| 2022-01-11 08:25:30 +0000 UTC | 54 | -| 2022-01-11 08:25:31 +0000 UTC | 65 | -| 2022-01-11 08:25:32 +0000 UTC | 76 | -+-------------------------------+--------------------------------------------------------------------------------------------------------+ ++-----------------------------------+--------------------------------------------------------------------------------------------------------+ +| Name: Time | Name: Value | +| Labels: | Labels: __name__=prometheus_http_requests_total, code=400, handler=/api/v1/query_range, job=prometheus | +| Type: []time.Time | Type: []*float64 | ++-----------------------------------+--------------------------------------------------------------------------------------------------------+ +| 2022-01-11 08:25:30.123 +0000 UTC | 54 | +| 2022-01-11 08:25:31.123 +0000 UTC | 65 | +| 2022-01-11 08:25:32.123 +0000 UTC | 76 | ++-----------------------------------+--------------------------------------------------------------------------------------------------------+ ====== TEST DATA RESPONSE (arrow base64) ====== -FRAME=QVJST1cxAAD/////eAMAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEEAAoADAAAAAgABAAKAAAACAAAAOwAAAADAAAApAAAACgAAAAEAAAAKP3//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAABI/f//CAAAAGQAAABbAAAAcHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9IjIwMCIsIGhhbmRsZXI9Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCBqb2I9InByb21ldGhldXMifQAEAAAAbmFtZQAAAADA/f//CAAAACwAAAAiAAAAeyJjdXN0b20iOnsicmVzdWx0VHlwZSI6Im1hdHJpeCJ9fQAABAAAAG1ldGEAAAAAAgAAALQBAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAGgBAABoAQAAAAADAWgBAAADAAAAvAAAACwAAAAEAAAASP7//wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAbP7//wgAAAB4AAAAbQAAAHsiX19uYW1lX18iOiJwcm9tZXRoZXVzX2h0dHBfcmVxdWVzdHNfdG90YWwiLCJjb2RlIjoiMjAwIiwiaGFuZGxlciI6Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCJqb2IiOiJwcm9tZXRoZXVzIn0AAAAGAAAAbGFiZWxzAAD4/v//CAAAAIQAAAB5AAAAeyJkaXNwbGF5TmFtZUZyb21EUyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbHtjb2RlPVwiMjAwXCIsIGhhbmRsZXI9XCIvYXBpL3YxL3F1ZXJ5X3JhbmdlXCIsIGpvYj1cInByb21ldGhldXNcIn0ifQAAAAYAAABjb25maWcAAAAAAABW////AAACAAUAAABWYWx1ZQASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHgAAACAAAAAAAAACoAAAAACAAAANAAAAAQAAADc////CAAAABAAAAAEAAAAVGltZQAAAAAEAAAAbmFtZQAAAAAIAAwACAAEAAgAAAAIAAAAHAAAABEAAAB7ImludGVydmFsIjoxMDAwfQAAAAYAAABjb25maWcAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAMAAAAAAAAAAUAAAAAAAAAwQACgAYAAwACAAEAAoAAAAUAAAAWAAAAAMAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAIAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAABEFRTUKckWAA6wT9QpyRYA2EqL1CnJFgAAAAAAADVAAAAAAAAAQEAAAAAAAIBFQBAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAAEAAEAAACIAwAAAAAAAMAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAA7AAAAAMAAACkAAAAKAAAAAQAAAAo/f//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAEj9//8IAAAAZAAAAFsAAABwcm9tZXRoZXVzX2h0dHBfcmVxdWVzdHNfdG90YWx7Y29kZT0iMjAwIiwgaGFuZGxlcj0iL2FwaS92MS9xdWVyeV9yYW5nZSIsIGpvYj0icHJvbWV0aGV1cyJ9AAQAAABuYW1lAAAAAMD9//8IAAAALAAAACIAAAB7ImN1c3RvbSI6eyJyZXN1bHRUeXBlIjoibWF0cml4In19AAAEAAAAbWV0YQAAAAACAAAAtAEAABgAAAAAABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAAaAEAAGgBAAAAAAMBaAEAAAMAAAC8AAAALAAAAAQAAABI/v//CAAAABAAAAAFAAAAVmFsdWUAAAAEAAAAbmFtZQAAAABs/v//CAAAAHgAAABtAAAAeyJfX25hbWVfXyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbCIsImNvZGUiOiIyMDAiLCJoYW5kbGVyIjoiL2FwaS92MS9xdWVyeV9yYW5nZSIsImpvYiI6InByb21ldGhldXMifQAAAAYAAABsYWJlbHMAAPj+//8IAAAAhAAAAHkAAAB7ImRpc3BsYXlOYW1lRnJvbURTIjoicHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9XCIyMDBcIiwgaGFuZGxlcj1cIi9hcGkvdjEvcXVlcnlfcmFuZ2VcIiwgam9iPVwicHJvbWV0aGV1c1wifSJ9AAAABgAAAGNvbmZpZwAAAAAAAFb///8AAAIABQAAAFZhbHVlABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAeAAAAIAAAAAAAAAKgAAAAAIAAAA0AAAABAAAANz///8IAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAcAAAAEQAAAHsiaW50ZXJ2YWwiOjEwMDB9AAAABgAAAGNvbmZpZwAAAAAAAAAABgAIAAYABgAAAAAAAwAEAAAAVGltZQAAAACgAwAAQVJST1cx -FRAME=QVJST1cxAAD/////eAMAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEEAAoADAAAAAgABAAKAAAACAAAAOwAAAADAAAApAAAACgAAAAEAAAAKP3//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAABI/f//CAAAAGQAAABbAAAAcHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9IjQwMCIsIGhhbmRsZXI9Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCBqb2I9InByb21ldGhldXMifQAEAAAAbmFtZQAAAADA/f//CAAAACwAAAAiAAAAeyJjdXN0b20iOnsicmVzdWx0VHlwZSI6Im1hdHJpeCJ9fQAABAAAAG1ldGEAAAAAAgAAALQBAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAGgBAABoAQAAAAADAWgBAAADAAAAvAAAACwAAAAEAAAASP7//wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAbP7//wgAAAB4AAAAbQAAAHsiX19uYW1lX18iOiJwcm9tZXRoZXVzX2h0dHBfcmVxdWVzdHNfdG90YWwiLCJjb2RlIjoiNDAwIiwiaGFuZGxlciI6Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCJqb2IiOiJwcm9tZXRoZXVzIn0AAAAGAAAAbGFiZWxzAAD4/v//CAAAAIQAAAB5AAAAeyJkaXNwbGF5TmFtZUZyb21EUyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbHtjb2RlPVwiNDAwXCIsIGhhbmRsZXI9XCIvYXBpL3YxL3F1ZXJ5X3JhbmdlXCIsIGpvYj1cInByb21ldGhldXNcIn0ifQAAAAYAAABjb25maWcAAAAAAABW////AAACAAUAAABWYWx1ZQASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHgAAACAAAAAAAAACoAAAAACAAAANAAAAAQAAADc////CAAAABAAAAAEAAAAVGltZQAAAAAEAAAAbmFtZQAAAAAIAAwACAAEAAgAAAAIAAAAHAAAABEAAAB7ImludGVydmFsIjoxMDAwfQAAAAYAAABjb25maWcAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAMAAAAAAAAAAUAAAAAAAAAwQACgAYAAwACAAEAAoAAAAUAAAAWAAAAAMAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAIAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAABEFRTUKckWAA6wT9QpyRYA2EqL1CnJFgAAAAAAAEtAAAAAAABAUEAAAAAAAABTQBAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAAEAAEAAACIAwAAAAAAAMAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAA7AAAAAMAAACkAAAAKAAAAAQAAAAo/f//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAEj9//8IAAAAZAAAAFsAAABwcm9tZXRoZXVzX2h0dHBfcmVxdWVzdHNfdG90YWx7Y29kZT0iNDAwIiwgaGFuZGxlcj0iL2FwaS92MS9xdWVyeV9yYW5nZSIsIGpvYj0icHJvbWV0aGV1cyJ9AAQAAABuYW1lAAAAAMD9//8IAAAALAAAACIAAAB7ImN1c3RvbSI6eyJyZXN1bHRUeXBlIjoibWF0cml4In19AAAEAAAAbWV0YQAAAAACAAAAtAEAABgAAAAAABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAAaAEAAGgBAAAAAAMBaAEAAAMAAAC8AAAALAAAAAQAAABI/v//CAAAABAAAAAFAAAAVmFsdWUAAAAEAAAAbmFtZQAAAABs/v//CAAAAHgAAABtAAAAeyJfX25hbWVfXyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbCIsImNvZGUiOiI0MDAiLCJoYW5kbGVyIjoiL2FwaS92MS9xdWVyeV9yYW5nZSIsImpvYiI6InByb21ldGhldXMifQAAAAYAAABsYWJlbHMAAPj+//8IAAAAhAAAAHkAAAB7ImRpc3BsYXlOYW1lRnJvbURTIjoicHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9XCI0MDBcIiwgaGFuZGxlcj1cIi9hcGkvdjEvcXVlcnlfcmFuZ2VcIiwgam9iPVwicHJvbWV0aGV1c1wifSJ9AAAABgAAAGNvbmZpZwAAAAAAAFb///8AAAIABQAAAFZhbHVlABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAeAAAAIAAAAAAAAAKgAAAAAIAAAA0AAAABAAAANz///8IAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAcAAAAEQAAAHsiaW50ZXJ2YWwiOjEwMDB9AAAABgAAAGNvbmZpZwAAAAAAAAAABgAIAAYABgAAAAAAAwAEAAAAVGltZQAAAACgAwAAQVJST1cx +FRAME=QVJST1cxAAD/////eAMAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEEAAoADAAAAAgABAAKAAAACAAAAOwAAAADAAAApAAAACgAAAAEAAAAKP3//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAABI/f//CAAAAGQAAABbAAAAcHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9IjIwMCIsIGhhbmRsZXI9Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCBqb2I9InByb21ldGhldXMifQAEAAAAbmFtZQAAAADA/f//CAAAACwAAAAiAAAAeyJjdXN0b20iOnsicmVzdWx0VHlwZSI6Im1hdHJpeCJ9fQAABAAAAG1ldGEAAAAAAgAAALQBAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAGgBAABoAQAAAAADAWgBAAADAAAAvAAAACwAAAAEAAAASP7//wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAbP7//wgAAAB4AAAAbQAAAHsiX19uYW1lX18iOiJwcm9tZXRoZXVzX2h0dHBfcmVxdWVzdHNfdG90YWwiLCJjb2RlIjoiMjAwIiwiaGFuZGxlciI6Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCJqb2IiOiJwcm9tZXRoZXVzIn0AAAAGAAAAbGFiZWxzAAD4/v//CAAAAIQAAAB5AAAAeyJkaXNwbGF5TmFtZUZyb21EUyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbHtjb2RlPVwiMjAwXCIsIGhhbmRsZXI9XCIvYXBpL3YxL3F1ZXJ5X3JhbmdlXCIsIGpvYj1cInByb21ldGhldXNcIn0ifQAAAAYAAABjb25maWcAAAAAAABW////AAACAAUAAABWYWx1ZQASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHgAAACAAAAAAAAACoAAAAACAAAANAAAAAQAAADc////CAAAABAAAAAEAAAAVGltZQAAAAAEAAAAbmFtZQAAAAAIAAwACAAEAAgAAAAIAAAAHAAAABEAAAB7ImludGVydmFsIjoxMDAwfQAAAAYAAABjb25maWcAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAMAAAAAAAAAAUAAAAAAAAAwQACgAYAAwACAAEAAoAAAAUAAAAWAAAAAMAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAIAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAMAYahvUKckWwOIEV9QpyRbArJ+S1CnJFgAAAAAAADVAAAAAAAAAQEAAAAAAAIBFQBAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAAEAAEAAACIAwAAAAAAAMAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAA7AAAAAMAAACkAAAAKAAAAAQAAAAo/f//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAEj9//8IAAAAZAAAAFsAAABwcm9tZXRoZXVzX2h0dHBfcmVxdWVzdHNfdG90YWx7Y29kZT0iMjAwIiwgaGFuZGxlcj0iL2FwaS92MS9xdWVyeV9yYW5nZSIsIGpvYj0icHJvbWV0aGV1cyJ9AAQAAABuYW1lAAAAAMD9//8IAAAALAAAACIAAAB7ImN1c3RvbSI6eyJyZXN1bHRUeXBlIjoibWF0cml4In19AAAEAAAAbWV0YQAAAAACAAAAtAEAABgAAAAAABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAAaAEAAGgBAAAAAAMBaAEAAAMAAAC8AAAALAAAAAQAAABI/v//CAAAABAAAAAFAAAAVmFsdWUAAAAEAAAAbmFtZQAAAABs/v//CAAAAHgAAABtAAAAeyJfX25hbWVfXyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbCIsImNvZGUiOiIyMDAiLCJoYW5kbGVyIjoiL2FwaS92MS9xdWVyeV9yYW5nZSIsImpvYiI6InByb21ldGhldXMifQAAAAYAAABsYWJlbHMAAPj+//8IAAAAhAAAAHkAAAB7ImRpc3BsYXlOYW1lRnJvbURTIjoicHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9XCIyMDBcIiwgaGFuZGxlcj1cIi9hcGkvdjEvcXVlcnlfcmFuZ2VcIiwgam9iPVwicHJvbWV0aGV1c1wifSJ9AAAABgAAAGNvbmZpZwAAAAAAAFb///8AAAIABQAAAFZhbHVlABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAeAAAAIAAAAAAAAAKgAAAAAIAAAA0AAAABAAAANz///8IAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAcAAAAEQAAAHsiaW50ZXJ2YWwiOjEwMDB9AAAABgAAAGNvbmZpZwAAAAAAAAAABgAIAAYABgAAAAAAAwAEAAAAVGltZQAAAACgAwAAQVJST1cx +FRAME=QVJST1cxAAD/////eAMAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEEAAoADAAAAAgABAAKAAAACAAAAOwAAAADAAAApAAAACgAAAAEAAAAKP3//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAABI/f//CAAAAGQAAABbAAAAcHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9IjQwMCIsIGhhbmRsZXI9Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCBqb2I9InByb21ldGhldXMifQAEAAAAbmFtZQAAAADA/f//CAAAACwAAAAiAAAAeyJjdXN0b20iOnsicmVzdWx0VHlwZSI6Im1hdHJpeCJ9fQAABAAAAG1ldGEAAAAAAgAAALQBAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAGgBAABoAQAAAAADAWgBAAADAAAAvAAAACwAAAAEAAAASP7//wgAAAAQAAAABQAAAFZhbHVlAAAABAAAAG5hbWUAAAAAbP7//wgAAAB4AAAAbQAAAHsiX19uYW1lX18iOiJwcm9tZXRoZXVzX2h0dHBfcmVxdWVzdHNfdG90YWwiLCJjb2RlIjoiNDAwIiwiaGFuZGxlciI6Ii9hcGkvdjEvcXVlcnlfcmFuZ2UiLCJqb2IiOiJwcm9tZXRoZXVzIn0AAAAGAAAAbGFiZWxzAAD4/v//CAAAAIQAAAB5AAAAeyJkaXNwbGF5TmFtZUZyb21EUyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbHtjb2RlPVwiNDAwXCIsIGhhbmRsZXI9XCIvYXBpL3YxL3F1ZXJ5X3JhbmdlXCIsIGpvYj1cInByb21ldGhldXNcIn0ifQAAAAYAAABjb25maWcAAAAAAABW////AAACAAUAAABWYWx1ZQASABgAFAAAABMADAAAAAgABAASAAAAFAAAAHgAAACAAAAAAAAACoAAAAACAAAANAAAAAQAAADc////CAAAABAAAAAEAAAAVGltZQAAAAAEAAAAbmFtZQAAAAAIAAwACAAEAAgAAAAIAAAAHAAAABEAAAB7ImludGVydmFsIjoxMDAwfQAAAAYAAABjb25maWcAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAMAAAAAAAAAAUAAAAAAAAAwQACgAYAAwACAAEAAoAAAAUAAAAWAAAAAMAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAAAAAAIAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAMAYahvUKckWwOIEV9QpyRbArJ+S1CnJFgAAAAAAAEtAAAAAAABAUEAAAAAAAABTQBAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAAEAAEAAACIAwAAAAAAAMAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAA7AAAAAMAAACkAAAAKAAAAAQAAAAo/f//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAEj9//8IAAAAZAAAAFsAAABwcm9tZXRoZXVzX2h0dHBfcmVxdWVzdHNfdG90YWx7Y29kZT0iNDAwIiwgaGFuZGxlcj0iL2FwaS92MS9xdWVyeV9yYW5nZSIsIGpvYj0icHJvbWV0aGV1cyJ9AAQAAABuYW1lAAAAAMD9//8IAAAALAAAACIAAAB7ImN1c3RvbSI6eyJyZXN1bHRUeXBlIjoibWF0cml4In19AAAEAAAAbWV0YQAAAAACAAAAtAEAABgAAAAAABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAAaAEAAGgBAAAAAAMBaAEAAAMAAAC8AAAALAAAAAQAAABI/v//CAAAABAAAAAFAAAAVmFsdWUAAAAEAAAAbmFtZQAAAABs/v//CAAAAHgAAABtAAAAeyJfX25hbWVfXyI6InByb21ldGhldXNfaHR0cF9yZXF1ZXN0c190b3RhbCIsImNvZGUiOiI0MDAiLCJoYW5kbGVyIjoiL2FwaS92MS9xdWVyeV9yYW5nZSIsImpvYiI6InByb21ldGhldXMifQAAAAYAAABsYWJlbHMAAPj+//8IAAAAhAAAAHkAAAB7ImRpc3BsYXlOYW1lRnJvbURTIjoicHJvbWV0aGV1c19odHRwX3JlcXVlc3RzX3RvdGFse2NvZGU9XCI0MDBcIiwgaGFuZGxlcj1cIi9hcGkvdjEvcXVlcnlfcmFuZ2VcIiwgam9iPVwicHJvbWV0aGV1c1wifSJ9AAAABgAAAGNvbmZpZwAAAAAAAFb///8AAAIABQAAAFZhbHVlABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAeAAAAIAAAAAAAAAKgAAAAAIAAAA0AAAABAAAANz///8IAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAgADAAIAAQACAAAAAgAAAAcAAAAEQAAAHsiaW50ZXJ2YWwiOjEwMDB9AAAABgAAAGNvbmZpZwAAAAAAAAAABgAIAAYABgAAAAAAAwAEAAAAVGltZQAAAACgAwAAQVJST1cx diff --git a/pkg/tsdb/prometheus/testdata/range_simple.result.json b/pkg/tsdb/prometheus/testdata/range_simple.result.json index 3c510aca9a3..4702646eba0 100644 --- a/pkg/tsdb/prometheus/testdata/range_simple.result.json +++ b/pkg/tsdb/prometheus/testdata/range_simple.result.json @@ -11,9 +11,9 @@ "job": "prometheus" }, "values": [ - [1641889530, "21"], - [1641889531, "32"], - [1641889532, "43"] + [1641889530.123, "21"], + [1641889531.123, "32"], + [1641889532.123, "43"] ] }, { @@ -24,9 +24,9 @@ "job": "prometheus" }, "values": [ - [1641889530, "54"], - [1641889531, "65"], - [1641889532, "76"] + [1641889530.123, "54"], + [1641889531.123, "65"], + [1641889532.123, "76"] ] } ] diff --git a/pkg/tsdb/prometheus/time_series_query.go b/pkg/tsdb/prometheus/time_series_query.go index ae7abf12406..4b324d51eb7 100644 --- a/pkg/tsdb/prometheus/time_series_query.go +++ b/pkg/tsdb/prometheus/time_series_query.go @@ -318,7 +318,7 @@ func matrixToDataFrames(matrix model.Matrix, query *PrometheusQuery, frames data valueField := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, len(v.Values)) for i, k := range v.Values { - timeField.Set(i, time.Unix(k.Timestamp.Unix(), 0).UTC()) + timeField.Set(i, k.Timestamp.Time().UTC()) value := float64(k.Value) if !math.IsNaN(value) { @@ -339,7 +339,7 @@ func matrixToDataFrames(matrix model.Matrix, query *PrometheusQuery, frames data } func scalarToDataFrames(scalar *model.Scalar, query *PrometheusQuery, frames data.Frames) data.Frames { - timeVector := []time.Time{time.Unix(scalar.Timestamp.Unix(), 0).UTC()} + timeVector := []time.Time{scalar.Timestamp.Time().UTC()} values := []float64{float64(scalar.Value)} name := fmt.Sprintf("%g", values[0]) @@ -358,7 +358,7 @@ func vectorToDataFrames(vector model.Vector, query *PrometheusQuery, frames data for _, v := range vector { name := formatLegend(v.Metric, query) tags := make(map[string]string, len(v.Metric)) - timeVector := []time.Time{time.Unix(v.Timestamp.Unix(), 0).UTC()} + timeVector := []time.Time{v.Timestamp.Time().UTC()} values := []float64{float64(v.Value)} for k, v := range v.Metric { @@ -387,7 +387,7 @@ func exemplarToDataFrames(response []apiv1.ExemplarQueryResult, query *Prometheu for _, exemplarData := range response { for _, exemplar := range exemplarData.Exemplars { event := ExemplarEvent{} - exemplarTime := time.Unix(exemplar.Timestamp.Unix(), 0).UTC() + exemplarTime := exemplar.Timestamp.Time().UTC() event.Time = exemplarTime event.Value = float64(exemplar.Value) event.Labels = make(map[string]string) diff --git a/pkg/tsdb/prometheus/time_series_query_test.go b/pkg/tsdb/prometheus/time_series_query_test.go index fc50c3fb78f..55e48062fcb 100644 --- a/pkg/tsdb/prometheus/time_series_query_test.go +++ b/pkg/tsdb/prometheus/time_series_query_test.go @@ -707,7 +707,7 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) { &p.Sample{ Metric: p.Metric{"app": "Application", "tag2": "tag2"}, Value: 1, - Timestamp: 1000, + Timestamp: 123, }, } query := &PrometheusQuery{ @@ -730,13 +730,14 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) { // Ensure the timestamps are UTC zoned testValue := res[0].Fields[0].At(0) require.Equal(t, "UTC", testValue.(time.Time).Location().String()) + require.Equal(t, int64(123), testValue.(time.Time).UnixMilli()) }) t.Run("scalar response should be parsed normally", func(t *testing.T) { value := make(map[TimeSeriesQueryType]interface{}) value[RangeQueryType] = &p.Scalar{ Value: 1, - Timestamp: 1000, + Timestamp: 123, } query := &PrometheusQuery{} @@ -754,6 +755,7 @@ func TestPrometheus_parseTimeSeriesResponse(t *testing.T) { // Ensure the timestamps are UTC zoned testValue := res[0].Fields[0].At(0) require.Equal(t, "UTC", testValue.(time.Time).Location().String()) + require.Equal(t, int64(123), testValue.(time.Time).UnixMilli()) }) } From 77d1f996cc8ace92f533c63691099a8c3a842ce8 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 9 Mar 2022 08:42:33 -0500 Subject: [PATCH 27/52] [v8.4.x] DataLinks: Encode variable params for data link URLs (#46257) Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> --- ...plating-dashboard-links-and-variables.json | 62 ++++++++++++++++++- ...ting-dashboard-links-and-variables.spec.ts | 10 ++- .../grafana-data/src/field/fieldOverrides.ts | 5 +- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/devenv/dev-dashboards/feature-templating/templating-dashboard-links-and-variables.json b/devenv/dev-dashboards/feature-templating/templating-dashboard-links-and-variables.json index f5751ec42f5..ab94db3f055 100644 --- a/devenv/dev-dashboards/feature-templating/templating-dashboard-links-and-variables.json +++ b/devenv/dev-dashboards/feature-templating/templating-dashboard-links-and-variables.json @@ -16,7 +16,7 @@ "gnetId": null, "graphTooltip": 0, "id": null, - "iteration": 1601526910610, + "iteration": 1646409057541, "links": [ { "icon": "external link", @@ -73,6 +73,61 @@ "timeShift": null, "title": "${custom.text}", "type": "text" + }, + { + "id": 4, + "gridPos": { + "h": 9, + "w": 9, + "x": 12, + "y": 0 + }, + "type": "stat", + "title": "Panel Title", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "color": { + "mode": "thresholds" + }, + "links": [ + { + "title": "Var Link", + "url": "/d/vmie2cmWz/bar-gauge-demo?var-custom=$custom" + } + ] + }, + "overrides": [] + }, + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" + }, + "orientation": "auto", + "textMode": "auto", + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto" + }, + "pluginVersion": "8.5.0-pre", + "datasource": null } ], "schemaVersion": 26, @@ -112,6 +167,11 @@ "selected": false, "text": "p3", "value": "p3" + }, + { + "selected": false, + "text": "p4", + "value": "test%25value" } ], "query": "p1,p2,p3", diff --git a/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts b/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts index 39c87b73448..40f04467c75 100644 --- a/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts +++ b/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts @@ -31,7 +31,7 @@ e2e.scenario({ expect(links).to.have.length.greaterThan(13); for (let index = 0; index < links.length; index++) { - expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${variableValue}`); + expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${encodeURI(variableValue)}`); } }); }; @@ -45,6 +45,14 @@ e2e.scenario({ // verify all links, should have All value verifyLinks('All'); + // Data links should percent encode var values + e2e() + .get('[aria-label="Data link"]') + .should('exist') + .and((link) => { + expect(link.attr('href')).contains(encodeURI('test%25value')); + }); + e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('p2').should('be.visible').click(); diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index a2292847c2f..d6358395d80 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -1,7 +1,6 @@ import { ApplyFieldOverrideOptions, DataFrame, - DataLink, DisplayProcessor, DisplayValue, DynamicConfigValue, @@ -353,7 +352,7 @@ export const getLinksSupplier = const timeRangeUrl = locationUtil.getTimeRangeUrlParams(); const { timeField } = getTimeField(frame); - return field.config.links.map((link: DataLink) => { + return field.config.links.map((link) => { const variablesQuery = locationUtil.getVariablesUrlParams(); let dataFrameVars = {}; let valueVars = {}; @@ -439,7 +438,7 @@ export const getLinksSupplier = } let href = locationUtil.assureBaseUrl(link.url.replace(/\n/g, '')); - href = replaceVariables(href, variables); + href = replaceVariables(href, variables, encodeURIComponent); href = locationUtil.processUrl(href); const info: LinkModel = { From bebe1c243ead9c7bc663697a80db83e6a45c1870 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 9 Mar 2022 09:29:31 -0500 Subject: [PATCH 28/52] A11y: Replace LinkButton with Button in Value Mappings to make it accessible (#46330) (#46364) (cherry picked from commit 4b57ffbb4f155faaf80ed52ae8ee241e55fd5530) Co-authored-by: Joao Silva <100691367+JoaoSilvaGrafana@users.noreply.github.com> --- .../editors/ValueMappingsEditor/ValueMappingEditRow.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx index a95bf845972..cb999de46ba 100644 --- a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx +++ b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef } from 'react'; import { GrafanaTheme2, MappingType, SpecialValueMatch, SelectableValue, ValueMappingResult } from '@grafana/data'; import { Draggable } from 'react-beautiful-dnd'; import { css } from '@emotion/css'; -import { useStyles2, Icon, Select, HorizontalGroup, ColorPicker, IconButton, LinkButton, Input } from '@grafana/ui'; +import { useStyles2, Icon, Select, HorizontalGroup, ColorPicker, IconButton, Input, Button } from '@grafana/ui'; export interface ValueMappingEditRowModel { type: MappingType; @@ -176,9 +176,9 @@ export function ValueMappingEditRow({ mapping, index, onChange, onRemove, onDupl {!result.color && ( {(props) => ( - + )} )} From 26172268157481f5e7153fa35e693158d4bea184 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 9 Mar 2022 10:44:33 -0500 Subject: [PATCH 29/52] CloudWatch: Use default http client from aws-sdk-go (#46370) (#46375) * use default http client from aws-sdk-go * temp comment out line and link to issue (cherry picked from commit 6670257c5e4a04fd0ce456d00acad6e834d56992) Co-authored-by: Erik Sundell --- pkg/tsdb/cloudwatch/cloudwatch.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index 64799982122..76deb875099 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/pkg/tsdb/cloudwatch/cloudwatch.go @@ -171,7 +171,8 @@ func (e *cloudWatchExecutor) newSession(pluginCtx backend.PluginContext, region } return e.sessions.GetSession(awsds.SessionConfig{ - HTTPClient: dsInfo.HTTPClient, + // https://github.com/grafana/grafana/issues/46365 + // HTTPClient: dsInfo.HTTPClient, Settings: awsds.AWSDatasourceSettings{ Profile: dsInfo.profile, Region: region, From 8ddab3893e513d873db1b1e3ce6533df204d867f Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 9 Mar 2022 12:20:39 -0500 Subject: [PATCH 30/52] Logging: Helper to create NopLogger (#46376) (#46377) (cherry picked from commit b1e6f7126a4772b771ca76c6c7c99c7f3e662e7a) Co-authored-by: gotjosh --- pkg/infra/log/log.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/infra/log/log.go b/pkg/infra/log/log.go index 683336045d1..ae0b99cfd1f 100644 --- a/pkg/infra/log/log.go +++ b/pkg/infra/log/log.go @@ -201,6 +201,11 @@ func New(ctx ...interface{}) *ConcreteLogger { return root.New(ctx...) } +// NewNopLogger returns a logger that doesn't do anything. +func NewNopLogger() *ConcreteLogger { + return newConcreteLogger(gokitlog.NewNopLogger()) +} + func with(ctxLogger *ConcreteLogger, withFunc func(gokitlog.Logger, ...interface{}) gokitlog.Logger, ctx []interface{}) *ConcreteLogger { if len(ctx) == 0 { return ctxLogger From 95af6176bf23041df65f2f8a1ee27beea35399be Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 9 Mar 2022 13:53:59 -0500 Subject: [PATCH 31/52] Perf: don't re-init plots when changing dashboard timeRange (#46339) (#46340) (cherry picked from commit ec911be8ef68ebe4fc7307b8fcd8b6183a011b9d) Co-authored-by: Leon Sorokin --- packages/grafana-data/src/dataframe/frameComparisons.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/grafana-data/src/dataframe/frameComparisons.ts b/packages/grafana-data/src/dataframe/frameComparisons.ts index 478bbbfff37..c4c6cb4dae9 100644 --- a/packages/grafana-data/src/dataframe/frameComparisons.ts +++ b/packages/grafana-data/src/dataframe/frameComparisons.ts @@ -60,6 +60,9 @@ export function compareDataFrameStructures(a: DataFrame, b: DataFrame, skipConfi return false; } + if (key === 'interval') { + continue; + } if (key === 'custom') { if (!shallowCompare(cfgA[key], cfgB[key])) { return false; From bc3ebce71573daf1ee1cd52d2b28a0f33f978dab Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 9 Mar 2022 14:08:20 -0500 Subject: [PATCH 32/52] update exemplars and azure authentication topics. (#46382) (#46384) (cherry picked from commit f19594e8ffd85671860f0f32c7ea41a649a87974) Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> --- docs/sources/auth/azuread.md | 88 +++++++++++++------------ docs/sources/basics/exemplars/_index.md | 2 +- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/docs/sources/auth/azuread.md b/docs/sources/auth/azuread.md index 7323f90dad7..7dcee909870 100644 --- a/docs/sources/auth/azuread.md +++ b/docs/sources/auth/azuread.md @@ -9,8 +9,12 @@ weight = 700 The Azure AD authentication allows you to use an Azure Active Directory tenant as an identity provider for Grafana. You can use Azure AD Application Roles to assign users and groups to Grafana roles from the Azure Portal. This topic has the following sections: -- [Create the Azure AD application](#create-the-azure-ad-application) -- [Enable Azure AD OAuth in Grafana](#enable-azure-ad-oauth-in-grafana) +- [Azure AD OAuth2 authentication](#azure-ad-oauth2-authentication) + - [Create the Azure AD application](#create-the-azure-ad-application) + - [Enable Azure AD OAuth in Grafana](#enable-azure-ad-oauth-in-grafana) + - [Configure allowed groups](#configure-allowed-groups) + - [Configure allowed domains](#configure-allowed-domains) + - [Team Sync (Enterprise only)](#team-sync-enterprise-only) ## Create the Azure AD application @@ -44,46 +48,46 @@ To enable the Azure AD OAuth2, register your application with Azure AD. 1. Include the unique ID in the configuration file: -```json - "appRoles": [ - { - "allowedMemberTypes": [ - "User" - ], - "description": "Grafana admin Users", - "displayName": "Grafana Admin", - "id": "SOME_UNIQUE_ID", - "isEnabled": true, - "lang": null, - "origin": "Application", - "value": "Admin" - }, - { - "allowedMemberTypes": [ - "User" - ], - "description": "Grafana read only Users", - "displayName": "Grafana Viewer", - "id": "SOME_UNIQUE_ID", - "isEnabled": true, - "lang": null, - "origin": "Application", - "value": "Viewer" - }, - { - "allowedMemberTypes": [ - "User" - ], - "description": "Grafana Editor Users", - "displayName": "Grafana Editor", - "id": "SOME_UNIQUE_ID", - "isEnabled": true, - "lang": null, - "origin": "Application", - "value": "Editor" - } - ], -``` + ```json + "appRoles": [ + { + "allowedMemberTypes": [ + "User" + ], + "description": "Grafana admin Users", + "displayName": "Grafana Admin", + "id": "SOME_UNIQUE_ID", + "isEnabled": true, + "lang": null, + "origin": "Application", + "value": "Admin" + }, + { + "allowedMemberTypes": [ + "User" + ], + "description": "Grafana read only Users", + "displayName": "Grafana Viewer", + "id": "SOME_UNIQUE_ID", + "isEnabled": true, + "lang": null, + "origin": "Application", + "value": "Viewer" + }, + { + "allowedMemberTypes": [ + "User" + ], + "description": "Grafana Editor Users", + "displayName": "Grafana Editor", + "id": "SOME_UNIQUE_ID", + "isEnabled": true, + "lang": null, + "origin": "Application", + "value": "Editor" + } + ], + ``` 1. Go to **Azure Active Directory** and then to **Enterprise Applications**. Search for your application and click on it. diff --git a/docs/sources/basics/exemplars/_index.md b/docs/sources/basics/exemplars/_index.md index de4f063aebe..d2755507a61 100644 --- a/docs/sources/basics/exemplars/_index.md +++ b/docs/sources/basics/exemplars/_index.md @@ -7,7 +7,7 @@ weight = 400 # Introduction to exemplars -An exemplar is a specific trace representative of a repeated pattern of data in a given time interval. It helps you identify higher cardinality metadata from specific events within time series data. +An exemplar is a specific trace representative of measurement taken in a given time interval. While metrics excel at giving you an aggregated view of your system, traces give you a fine grained view of a single request; exemplars are a way to link the two. Suppose your company website is experiencing a surge in traffic volumes. While more than eighty percent of the users are able to access the website in under two seconds, some users are experiencing a higher than normal response time resulting in bad user experience. From 25d8d0a49d3e0919e33eb5d3e6eaa476451433f8 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 10 Mar 2022 10:02:42 -0500 Subject: [PATCH 33/52] Removed extra word. (#46386) (#46413) (cherry picked from commit 3d5ebba0c267c358c1321d3d93aed299f1a73103) Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> --- docs/sources/dashboards/time-range-controls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/dashboards/time-range-controls.md b/docs/sources/dashboards/time-range-controls.md index 0aa3d7208c0..7b85ec078c1 100644 --- a/docs/sources/dashboards/time-range-controls.md +++ b/docs/sources/dashboards/time-range-controls.md @@ -36,7 +36,7 @@ Here are some examples: ### Note about Grafana alerting -For Grafana alerting, we do not support are the following syntaxes at this time. +For Grafana alerting, we do not support the following syntaxes at this time. - now+n for future timestamps. - now-1n/n for "start of n until end of n" since this is an absolute timestamp. From 10965b7842306e9f1c31866c157e4a7f3d5d7f1a Mon Sep 17 00:00:00 2001 From: achatterjee-grafana Date: Thu, 10 Mar 2022 11:02:51 -0500 Subject: [PATCH 34/52] Revert "update exemplars and azure authentication topics. (#46382) (#46384)" This reverts commit bc3ebce71573daf1ee1cd52d2b28a0f33f978dab. --- docs/sources/auth/azuread.md | 88 ++++++++++++------------- docs/sources/basics/exemplars/_index.md | 2 +- 2 files changed, 43 insertions(+), 47 deletions(-) diff --git a/docs/sources/auth/azuread.md b/docs/sources/auth/azuread.md index 7dcee909870..7323f90dad7 100644 --- a/docs/sources/auth/azuread.md +++ b/docs/sources/auth/azuread.md @@ -9,12 +9,8 @@ weight = 700 The Azure AD authentication allows you to use an Azure Active Directory tenant as an identity provider for Grafana. You can use Azure AD Application Roles to assign users and groups to Grafana roles from the Azure Portal. This topic has the following sections: -- [Azure AD OAuth2 authentication](#azure-ad-oauth2-authentication) - - [Create the Azure AD application](#create-the-azure-ad-application) - - [Enable Azure AD OAuth in Grafana](#enable-azure-ad-oauth-in-grafana) - - [Configure allowed groups](#configure-allowed-groups) - - [Configure allowed domains](#configure-allowed-domains) - - [Team Sync (Enterprise only)](#team-sync-enterprise-only) +- [Create the Azure AD application](#create-the-azure-ad-application) +- [Enable Azure AD OAuth in Grafana](#enable-azure-ad-oauth-in-grafana) ## Create the Azure AD application @@ -48,46 +44,46 @@ To enable the Azure AD OAuth2, register your application with Azure AD. 1. Include the unique ID in the configuration file: - ```json - "appRoles": [ - { - "allowedMemberTypes": [ - "User" - ], - "description": "Grafana admin Users", - "displayName": "Grafana Admin", - "id": "SOME_UNIQUE_ID", - "isEnabled": true, - "lang": null, - "origin": "Application", - "value": "Admin" - }, - { - "allowedMemberTypes": [ - "User" - ], - "description": "Grafana read only Users", - "displayName": "Grafana Viewer", - "id": "SOME_UNIQUE_ID", - "isEnabled": true, - "lang": null, - "origin": "Application", - "value": "Viewer" - }, - { - "allowedMemberTypes": [ - "User" - ], - "description": "Grafana Editor Users", - "displayName": "Grafana Editor", - "id": "SOME_UNIQUE_ID", - "isEnabled": true, - "lang": null, - "origin": "Application", - "value": "Editor" - } - ], - ``` +```json + "appRoles": [ + { + "allowedMemberTypes": [ + "User" + ], + "description": "Grafana admin Users", + "displayName": "Grafana Admin", + "id": "SOME_UNIQUE_ID", + "isEnabled": true, + "lang": null, + "origin": "Application", + "value": "Admin" + }, + { + "allowedMemberTypes": [ + "User" + ], + "description": "Grafana read only Users", + "displayName": "Grafana Viewer", + "id": "SOME_UNIQUE_ID", + "isEnabled": true, + "lang": null, + "origin": "Application", + "value": "Viewer" + }, + { + "allowedMemberTypes": [ + "User" + ], + "description": "Grafana Editor Users", + "displayName": "Grafana Editor", + "id": "SOME_UNIQUE_ID", + "isEnabled": true, + "lang": null, + "origin": "Application", + "value": "Editor" + } + ], +``` 1. Go to **Azure Active Directory** and then to **Enterprise Applications**. Search for your application and click on it. diff --git a/docs/sources/basics/exemplars/_index.md b/docs/sources/basics/exemplars/_index.md index d2755507a61..de4f063aebe 100644 --- a/docs/sources/basics/exemplars/_index.md +++ b/docs/sources/basics/exemplars/_index.md @@ -7,7 +7,7 @@ weight = 400 # Introduction to exemplars -An exemplar is a specific trace representative of measurement taken in a given time interval. While metrics excel at giving you an aggregated view of your system, traces give you a fine grained view of a single request; exemplars are a way to link the two. +An exemplar is a specific trace representative of a repeated pattern of data in a given time interval. It helps you identify higher cardinality metadata from specific events within time series data. Suppose your company website is experiencing a surge in traffic volumes. While more than eighty percent of the users are able to access the website in under two seconds, some users are experiencing a higher than normal response time resulting in bad user experience. From 5b5e17512717542b572e5f90c059b1543c1f923e Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 10 Mar 2022 14:52:02 -0500 Subject: [PATCH 35/52] Card: make elements in metadata clickable (#46336) (#46410) * Card: make elements in metadata clickable * add docs (cherry picked from commit 8b9e27c4ba3e51341fee0e5af3298010baefde1f) Co-authored-by: Giordano Ricci --- .../grafana-ui/src/components/Card/Card.mdx | 41 ++++++++++++++++++- .../grafana-ui/src/components/Card/Card.tsx | 1 + 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Card/Card.mdx b/packages/grafana-ui/src/components/Card/Card.mdx index e3364418c5a..0d1b1aaa386 100644 --- a/packages/grafana-ui/src/components/Card/Card.mdx +++ b/packages/grafana-ui/src/components/Card/Card.mdx @@ -126,7 +126,7 @@ Tags can be rendered inside the Card, by being wrapped in `Card.Tags` component. ### As a link -Card can be used as a clickable link item by specifying `href` prop. In this case the Card's content will be rendered inside `a`. +Card can be used as a clickable link item by specifying `href` prop. ```jsx @@ -142,6 +142,45 @@ Card can be used as a clickable link item by specifying `href` prop. In this cas +### As a button + +Card can be used as a clickable buttons item by specifying `onClick` prop. + +```jsx + alert('Hello, Grafana!')}> + Hello, Grafana + Clicking this card will create an alert + +``` + + + alert('Hello, Grafana!')}> + Hello, Grafana + Clicking this card will create an alert + + + +> **Note**: When used in conjunction with [Metadata elements](#multiple-metadata-elements), clicking on any element +> inside `` will prevent the card action to be executed (either `href` to be followed or `onClick` to be called). +> +> Example: + +```jsx + alert('Hello, Grafana!')}> + Hello, Grafana + Clicking on this text (Meta) WILL NOT trigger the alert! + Clicking on this text (Description) WILL trigger the alert! + +``` + + + alert('Hello, Grafana!')}> + Hello, Grafana + Clicking on this text (Meta) WILL NOT trigger the alert! + Clicking on this text (Description) WILL trigger the alert! + + + ### Inside a list item To render cards in a list, it is possible to nest them inside `li` items. diff --git a/packages/grafana-ui/src/components/Card/Card.tsx b/packages/grafana-ui/src/components/Card/Card.tsx index 92466b9315e..424a93481f5 100644 --- a/packages/grafana-ui/src/components/Card/Card.tsx +++ b/packages/grafana-ui/src/components/Card/Card.tsx @@ -256,6 +256,7 @@ const getMetaStyles = (theme: GrafanaTheme2) => ({ margin: theme.spacing(0.5, 0, 0), lineHeight: theme.typography.bodySmall.lineHeight, overflowWrap: 'anywhere', + zIndex: 0, }), separator: css({ margin: `0 ${theme.spacing(1)}`, From dfbd23ba7b06b7e84052111344febd01be804e90 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Fri, 11 Mar 2022 09:41:18 +0100 Subject: [PATCH 36/52] Accesscontrol: fix data source name resolver and add uid name resolver (#46409) (#46437) * Fix data source scope resolver * Adding ds UID scope resolver * Register UID resolver * use package full name * even if it cannot be empty as of now and is also checked by store, better safe than sorry (cherry picked from commit bd918927b45c0a32b54276975f8f3ca67a5535c6) --- pkg/services/datasources/service.go | 41 ++++++++-- pkg/services/datasources/service_test.go | 100 +++++++++++++++++++++-- 2 files changed, 124 insertions(+), 17 deletions(-) diff --git a/pkg/services/datasources/service.go b/pkg/services/datasources/service.go index ca38e501cef..7edc15bd971 100644 --- a/pkg/services/datasources/service.go +++ b/pkg/services/datasources/service.go @@ -73,6 +73,7 @@ func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, secretsService secret s.Bus.AddHandler(s.GetDefaultDataSource) ac.RegisterAttributeScopeResolver(NewNameScopeResolver(store)) + ac.RegisterAttributeScopeResolver(NewUidScopeResolver(store)) return s } @@ -84,16 +85,15 @@ type DataSourceRetriever interface { // NewNameScopeResolver provides an AttributeScopeResolver able to // translate a scope prefixed with "datasources:name:" into an id based scope. func NewNameScopeResolver(db DataSourceRetriever) (string, accesscontrol.AttributeScopeResolveFunc) { + prefix := accesscontrol.Scope("datasources", "name", "") dsNameResolver := func(ctx context.Context, orgID int64, initialScope string) (string, error) { - dsNames := strings.Split(initialScope, ":") - if dsNames[0] != "datasources" || len(dsNames) != 3 { + if !strings.HasPrefix(initialScope, prefix) { return "", accesscontrol.ErrInvalidScope } - dsName := dsNames[2] - // Special wildcard case - if dsName == "*" { - return accesscontrol.Scope("datasources", "id", "*"), nil + dsName := initialScope[len(prefix):] + if dsName == "" { + return "", accesscontrol.ErrInvalidScope } query := models.GetDataSourceQuery{Name: dsName, OrgId: orgID} @@ -101,10 +101,35 @@ func NewNameScopeResolver(db DataSourceRetriever) (string, accesscontrol.Attribu return "", err } - return accesscontrol.Scope("datasources", "id", fmt.Sprintf("%v", query.Result.Id)), nil + return accesscontrol.Scope("datasources", "id", strconv.FormatInt(query.Result.Id, 10)), nil } - return "datasources:name:", dsNameResolver + return prefix, dsNameResolver +} + +// NewUidScopeResolver provides an AttributeScopeResolver able to +// translate a scope prefixed with "datasources:uid:" into an id based scope. +func NewUidScopeResolver(db DataSourceRetriever) (string, accesscontrol.AttributeScopeResolveFunc) { + prefix := accesscontrol.Scope("datasources", "uid", "") + dsUIDResolver := func(ctx context.Context, orgID int64, initialScope string) (string, error) { + if !strings.HasPrefix(initialScope, prefix) { + return "", accesscontrol.ErrInvalidScope + } + + dsUID := initialScope[len(prefix):] + if dsUID == "" { + return "", accesscontrol.ErrInvalidScope + } + + query := models.GetDataSourceQuery{Uid: dsUID, OrgId: orgID} + if err := db.GetDataSource(ctx, &query); err != nil { + return "", err + } + + return accesscontrol.Scope("datasources", "id", strconv.FormatInt(query.Result.Id, 10)), nil + } + + return prefix, dsUIDResolver } func (s *Service) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error { diff --git a/pkg/services/datasources/service_test.go b/pkg/services/datasources/service_test.go index 136540fd516..f0073d926e4 100644 --- a/pkg/services/datasources/service_test.go +++ b/pkg/services/datasources/service_test.go @@ -69,19 +69,30 @@ func TestService(t *testing.T) { } type dataSourceMockRetriever struct { - res *models.DataSource + res []*models.DataSource } func (d *dataSourceMockRetriever) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error { - if query.Name == d.res.Name { - query.Result = d.res + for _, datasource := range d.res { + nameMatch := query.Name != "" && query.Name == datasource.Name + uidMatch := query.Uid != "" && query.Uid == datasource.Uid + if nameMatch || uidMatch { + query.Result = datasource - return nil + return nil + } } return models.ErrDataSourceNotFound } func TestService_NameScopeResolver(t *testing.T) { + retriever := &dataSourceMockRetriever{[]*models.DataSource{ + {Id: 1, Name: "test-datasource"}, + {Id: 2, Name: "*"}, + {Id: 3, Name: ":/*"}, + {Id: 4, Name: ":"}, + }} + type testCaseResolver struct { desc string given string @@ -97,9 +108,21 @@ func TestService_NameScopeResolver(t *testing.T) { wantErr: nil, }, { - desc: "correct", + desc: "asterisk in name", given: "datasources:name:*", - want: "datasources:id:*", + want: "datasources:id:2", + wantErr: nil, + }, + { + desc: "complex name", + given: "datasources:name::/*", + want: "datasources:id:3", + wantErr: nil, + }, + { + desc: "colon in name", + given: "datasources:name::", + want: "datasources:id:4", wantErr: nil, }, { @@ -114,11 +137,70 @@ func TestService_NameScopeResolver(t *testing.T) { want: "", wantErr: accesscontrol.ErrInvalidScope, }, + { + desc: "empty name scope", + given: "datasources:name:", + want: "", + wantErr: accesscontrol.ErrInvalidScope, + }, + } + prefix, resolver := NewNameScopeResolver(retriever) + require.Equal(t, "datasources:name:", prefix) + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + resolved, err := resolver(context.Background(), 1, tc.given) + if tc.wantErr != nil { + require.Error(t, err) + require.Equal(t, tc.wantErr, err) + } else { + require.NoError(t, err) + require.Equal(t, tc.want, resolved) + } + }) + } +} + +func TestService_UIDScopeResolver(t *testing.T) { + retriever := &dataSourceMockRetriever{[]*models.DataSource{ + {Id: 1, Uid: "NnftN9Lnz"}, + }} + + type testCaseResolver struct { + desc string + given string + want string + wantErr error } - testDataSource := &models.DataSource{Id: 1, Name: "test-datasource"} - prefix, resolver := NewNameScopeResolver(&dataSourceMockRetriever{testDataSource}) - require.Equal(t, "datasources:name:", prefix) + testCases := []testCaseResolver{ + { + desc: "correct", + given: "datasources:uid:NnftN9Lnz", + want: "datasources:id:1", + wantErr: nil, + }, + { + desc: "unknown datasource", + given: "datasources:uid:unknown", + want: "", + wantErr: models.ErrDataSourceNotFound, + }, + { + desc: "malformed scope", + given: "datasources:unknown", + want: "", + wantErr: accesscontrol.ErrInvalidScope, + }, + { + desc: "empty uid scope", + given: "datasources:uid:", + want: "", + wantErr: accesscontrol.ErrInvalidScope, + }, + } + prefix, resolver := NewUidScopeResolver(retriever) + require.Equal(t, "datasources:uid:", prefix) for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { From 3158f15ce5ce4be6ae07d24ba44210864e1cda9c Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 11 Mar 2022 06:06:25 -0500 Subject: [PATCH 37/52] Gauge: Fixes blank viz when data link is added and orientation was horizontal (#46335) (#46445) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit cb0b089d32ab41b467812462b52b9acd04a7bf41) Co-authored-by: Torkel Ödegaard --- .../src/components/DataLinks/DataLinksContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx index 7eb4714a71b..2756b65ae41 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx @@ -60,7 +60,7 @@ export const DataLinksContextMenu: React.FC = ({ chil onClick={linkModel.onClick} target={linkModel.target} title={linkModel.title} - style={{ display: 'flex' }} + style={{ display: 'flex', width: '100%' }} aria-label={selectors.components.DataLinksContextMenu.singleLink} > {children({})} From af255219a4811d5621e6691abd0d9cac637d86df Mon Sep 17 00:00:00 2001 From: Dimitris Sotirakis Date: Fri, 11 Mar 2022 19:14:16 +0200 Subject: [PATCH 38/52] [v8.4.x] CI: Sync CI changes (#46083) * CI: Introduce `build-frontend-packages` step (#45824) * Split frontend build * Fix command name * Update grabpl (cherry picked from commit 2f6c827f5d96e620fc2b20e1503512bafeb43de0) * CI: Add more checks to standalone docs pipeline (#46449) * Add build frontend package step * Reorder dependencies * Add codespell and prettier checks (cherry picked from commit 82b436afee7de6d0143df2452e241aa16ba6e76a) * Update golang (#46458) (cherry picked from commit a29159f362df23f2e2ff789e09a97979d728703f) * Fix architectures --- .drone.yml | 529 +++++++++++++++++---------- Dockerfile | 2 +- Dockerfile.ubuntu | 2 +- package.json | 1 + scripts/build/ci-build/Dockerfile | 2 +- scripts/drone/pipelines/docs.star | 22 +- scripts/drone/pipelines/main.star | 6 +- scripts/drone/pipelines/pr.star | 8 +- scripts/drone/pipelines/release.star | 6 +- scripts/drone/steps/lib.star | 42 ++- 10 files changed, 402 insertions(+), 218 deletions(-) diff --git a/.drone.yml b/.drone.yml index 327e54b779f..51c1739dd1c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -11,7 +11,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -23,7 +23,7 @@ steps: - make gen-go - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - ./bin/grabpl verify-drone @@ -43,13 +43,13 @@ steps: - rm words_to_ignore.txt depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: codespell - commands: - ./bin/grabpl shellcheck depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: shellcheck - commands: - ./bin/grabpl lint-backend --edition oss @@ -57,7 +57,7 @@ steps: - initialize environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-backend - commands: - yarn run prettier:check @@ -68,19 +68,19 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition oss depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -88,7 +88,7 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-frontend trigger: event: @@ -114,7 +114,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -126,15 +126,15 @@ steps: - make gen-go - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} - --variants linux-x64,linux-x64-musl,osx64,win64,armv6 --no-pull-enterprise + --variants linux-amd64,linux-amd64-musl,darwin-amd64,windows-amd64,armv6 --no-pull-enterprise depends_on: - initialize environment: {} - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition oss --build-id @@ -143,20 +143,29 @@ steps: - initialize environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-frontend +- commands: + - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} + --no-pull-enterprise + depends_on: + - initialize + environment: + NODE_OPTIONS: --max_old_space_size=8192 + image: grafana/build-container:1.5.3 + name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps depends_on: - initialize environment: null - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -169,17 +178,18 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: ensure-cuetsified - commands: - . scripts/build/gpg-test-vars.sh && ./bin/grabpl package --jobs 8 --edition oss - --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --variants linux-x64,linux-x64-musl,osx64,win64,armv6 + --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --variants linux-amd64,linux-amd64-musl,darwin-amd64,windows-amd64,armv6 depends_on: - build-plugins - build-backend - build-frontend + - build-frontend-packages environment: null - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: package - commands: - ./scripts/grafana-server/start-server @@ -187,11 +197,12 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages detach: true environment: ARCH: linux-amd64 PORT: 3001 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: grafana-server - commands: - apt-get install -y netcat @@ -266,9 +277,10 @@ steps: - ./bin/grabpl verify-storybook depends_on: - build-frontend + - build-frontend-packages environment: NODE_OPTIONS: --max_old_space_size=4096 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-storybook - commands: - yarn wait-on http://$HOST:$PORT @@ -288,7 +300,7 @@ steps: - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition oss -archs amd64 @@ -346,7 +358,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -363,7 +375,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: postgres-integration-tests - commands: - apt-get update @@ -378,7 +390,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: mysql-integration-tests trigger: event: @@ -410,7 +422,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -422,16 +434,44 @@ steps: - make gen-go - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - - yarn packages:build - - yarn packages:docsExtract - - yarn packages:docsToMarkdown - - ./scripts/ci-reference-docs-lint.sh ci + - |- + echo -e "unknwon + referer + errorstring + eror + iam + wan" > words_to_ignore.txt + - codespell -I words_to_ignore.txt docs/ + - rm words_to_ignore.txt depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 + name: codespell +- commands: + - yarn run prettier:checkDocs + depends_on: + - initialize + environment: + NODE_OPTIONS: --max_old_space_size=8192 + image: grafana/build-container:1.5.3 + name: lint-docs +- commands: + - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} + --no-pull-enterprise + depends_on: + - initialize + environment: + NODE_OPTIONS: --max_old_space_size=8192 + image: grafana/build-container:1.5.3 + name: build-frontend-packages +- commands: + - ./scripts/ci-reference-docs-lint.sh ci + depends_on: + - build-frontend-packages + image: grafana/build-container:1.5.3 name: build-frontend-docs - commands: - mkdir -p /hugo/content/docs/grafana @@ -466,7 +506,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -478,16 +518,44 @@ steps: - make gen-go - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - - yarn packages:build - - yarn packages:docsExtract - - yarn packages:docsToMarkdown - - ./scripts/ci-reference-docs-lint.sh ci + - |- + echo -e "unknwon + referer + errorstring + eror + iam + wan" > words_to_ignore.txt + - codespell -I words_to_ignore.txt docs/ + - rm words_to_ignore.txt depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 + name: codespell +- commands: + - yarn run prettier:checkDocs + depends_on: + - initialize + environment: + NODE_OPTIONS: --max_old_space_size=8192 + image: grafana/build-container:1.5.3 + name: lint-docs +- commands: + - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} + --no-pull-enterprise + depends_on: + - initialize + environment: + NODE_OPTIONS: --max_old_space_size=8192 + image: grafana/build-container:1.5.3 + name: build-frontend-packages +- commands: + - ./scripts/ci-reference-docs-lint.sh ci + depends_on: + - build-frontend-packages + image: grafana/build-container:1.5.3 name: build-frontend-docs - commands: - mkdir -p /hugo/content/docs/grafana @@ -519,7 +587,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -531,7 +599,7 @@ steps: - make gen-go - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - ./bin/grabpl verify-drone @@ -551,13 +619,13 @@ steps: - rm words_to_ignore.txt depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: codespell - commands: - ./bin/grabpl shellcheck depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: shellcheck - commands: - ./bin/grabpl lint-backend --edition oss @@ -565,7 +633,7 @@ steps: - initialize environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-backend - commands: - yarn run prettier:check @@ -576,19 +644,19 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition oss depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -596,7 +664,7 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-frontend trigger: branch: main @@ -620,7 +688,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -632,7 +700,7 @@ steps: - make gen-go - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - image: grafana/drone-downstream name: trigger-enterprise-downstream @@ -651,7 +719,7 @@ steps: depends_on: - initialize environment: {} - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition oss --build-id @@ -660,8 +728,17 @@ steps: - initialize environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-frontend +- commands: + - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} + --no-pull-enterprise + depends_on: + - initialize + environment: + NODE_OPTIONS: --max_old_space_size=8192 + image: grafana/build-container:1.5.3 + name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin depends_on: @@ -669,13 +746,13 @@ steps: environment: GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -688,7 +765,7 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: ensure-cuetsified - commands: - ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise @@ -697,6 +774,7 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages environment: GITHUB_TOKEN: from_secret: github_token @@ -708,7 +786,7 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: package - commands: - ./scripts/grafana-server/start-server @@ -716,11 +794,12 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages detach: true environment: ARCH: linux-amd64 PORT: 3001 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: grafana-server - commands: - apt-get install -y netcat @@ -795,9 +874,10 @@ steps: - ./bin/grabpl verify-storybook depends_on: - build-frontend + - build-frontend-packages environment: NODE_OPTIONS: --max_old_space_size=4096 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-storybook - commands: - printenv GCP_KEY | base64 -d > /tmp/gcpkey.json @@ -837,14 +917,14 @@ steps: GRAFANA_MISC_STATS_API_KEY: from_secret: grafana_misc_stats_api_key failure: ignore - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: publish-frontend-metrics - commands: - ls dist/*.tar.gz* - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition oss @@ -916,7 +996,7 @@ steps: environment: NPM_TOKEN: from_secret: npm_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: release-canary-npm-packages - commands: - ./bin/grabpl upload-packages --edition oss --packages-bucket grafana-downloads @@ -991,7 +1071,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1008,7 +1088,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: postgres-integration-tests - commands: - apt-get update @@ -1023,7 +1103,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: mysql-integration-tests trigger: branch: main @@ -1059,7 +1139,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: initialize @@ -1142,7 +1222,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1152,7 +1232,7 @@ steps: name: identify-runner - commands: - make gen-go - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - ./bin/grabpl store-packages --edition oss --gcp-key /tmp/gcpkey.json --build-id @@ -1224,7 +1304,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1237,7 +1317,7 @@ steps: - ./bin/grabpl verify-version ${DRONE_TAG} - ./bin/grabpl gen-version ${DRONE_TAG} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} @@ -1247,7 +1327,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps @@ -1256,8 +1336,17 @@ steps: - initialize environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-frontend +- commands: + - ./bin/grabpl build-frontend-packages --jobs 8 --github-token $${GITHUB_TOKEN} + --edition oss --no-pull-enterprise ${DRONE_TAG} + depends_on: + - initialize + environment: + NODE_OPTIONS: --max_old_space_size=8192 + image: grafana/build-container:1.5.3 + name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin depends_on: @@ -1265,13 +1354,13 @@ steps: environment: GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -1284,7 +1373,7 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: ensure-cuetsified - commands: - ./bin/grabpl package --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise @@ -1293,6 +1382,7 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages environment: GITHUB_TOKEN: from_secret: github_token @@ -1304,14 +1394,14 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: package - commands: - ls dist/*.tar.gz* - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition oss --shouldSave @@ -1343,11 +1433,12 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages detach: true environment: ARCH: linux-amd64 PORT: 3001 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: grafana-server - commands: - apt-get install -y netcat @@ -1422,9 +1513,10 @@ steps: - ./bin/grabpl verify-storybook depends_on: - build-frontend + - build-frontend-packages environment: NODE_OPTIONS: --max_old_space_size=4096 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-storybook - commands: - ./bin/grabpl upload-cdn --edition oss --src-bucket "$${PRERELEASE_BUCKET}" --src-dir @@ -1474,7 +1566,7 @@ steps: - ./scripts/build/build-npm-packages.sh ${DRONE_TAG} depends_on: - store-storybook - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-npm-packages - commands: - ./bin/grabpl artifacts npm store --tag ${DRONE_TAG} @@ -1520,7 +1612,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1533,7 +1625,7 @@ steps: - ./bin/grabpl verify-version ${DRONE_TAG} - ./bin/grabpl gen-version ${DRONE_TAG} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - |- @@ -1547,13 +1639,13 @@ steps: - rm words_to_ignore.txt depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: codespell - commands: - ./bin/grabpl shellcheck depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: shellcheck - commands: - ./bin/grabpl lint-backend --edition oss @@ -1561,7 +1653,7 @@ steps: - initialize environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-backend - commands: - yarn run prettier:check @@ -1572,19 +1664,19 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition oss depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -1592,7 +1684,7 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-frontend trigger: event: @@ -1641,7 +1733,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1654,7 +1746,7 @@ steps: - ./bin/grabpl verify-version ${DRONE_TAG} - ./bin/grabpl gen-version ${DRONE_TAG} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - apt-get update @@ -1669,7 +1761,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: postgres-integration-tests - commands: - apt-get update @@ -1684,7 +1776,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: mysql-integration-tests trigger: event: @@ -1725,7 +1817,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: initialize @@ -1784,7 +1876,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1799,7 +1891,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -1819,7 +1911,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} @@ -1829,7 +1921,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps @@ -1838,8 +1930,17 @@ steps: - initialize environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-frontend +- commands: + - ./bin/grabpl build-frontend-packages --jobs 8 --github-token $${GITHUB_TOKEN} + --edition enterprise --no-pull-enterprise ${DRONE_TAG} + depends_on: + - initialize + environment: + NODE_OPTIONS: --max_old_space_size=8192 + image: grafana/build-container:1.5.3 + name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition enterprise --no-install-deps --sign --signing-admin @@ -1848,13 +1949,13 @@ steps: environment: GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -1867,7 +1968,7 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: ensure-cuetsified - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise2 --github-token $${GITHUB_TOKEN} @@ -1877,7 +1978,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-backend-enterprise2 - commands: - ./bin/grabpl package --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} @@ -1886,6 +1987,7 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages - build-backend-enterprise2 environment: GITHUB_TOKEN: @@ -1898,14 +2000,14 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: package - commands: - ls dist/*.tar.gz* - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition enterprise --shouldSave @@ -1937,12 +2039,13 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages detach: true environment: ARCH: linux-amd64 PORT: 3001 RUNDIR: scripts/grafana-server/tmp-grafana-enterprise - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: grafana-server - commands: - apt-get install -y netcat @@ -2042,6 +2145,7 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages - build-backend-enterprise2 environment: GITHUB_TOKEN: @@ -2054,7 +2158,7 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: package-enterprise2 - commands: - ./bin/grabpl upload-cdn --edition enterprise2 --src-bucket "$${PRERELEASE_BUCKET}" @@ -2116,7 +2220,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2131,7 +2235,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -2151,7 +2255,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - |- @@ -2165,13 +2269,13 @@ steps: - rm words_to_ignore.txt depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: codespell - commands: - ./bin/grabpl shellcheck depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: shellcheck - commands: - ./bin/grabpl lint-backend --edition enterprise @@ -2179,7 +2283,7 @@ steps: - initialize environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-backend - commands: - yarn run prettier:check @@ -2190,19 +2294,19 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition enterprise depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition enterprise depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -2210,7 +2314,7 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-frontend - commands: - ./bin/grabpl lint-backend --edition enterprise2 @@ -2218,19 +2322,19 @@ steps: - initialize environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-backend-enterprise2 - commands: - ./bin/grabpl test-backend --edition enterprise2 depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend-enterprise2 - commands: - ./bin/grabpl integration-tests --edition enterprise2 depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend-integration-enterprise2 trigger: event: @@ -2289,7 +2393,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2304,7 +2408,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -2324,7 +2428,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - apt-get update @@ -2339,7 +2443,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: postgres-integration-tests - commands: - apt-get update @@ -2354,7 +2458,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: mysql-integration-tests - commands: - dockerize -wait tcp://redis:6379/0 -timeout 120s @@ -2363,7 +2467,7 @@ steps: - initialize environment: REDIS_URL: redis://redis:6379/0 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -2372,7 +2476,7 @@ steps: - initialize environment: MEMCACHED_HOSTS: memcached:11211 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: memcached-integration-tests trigger: event: @@ -2417,7 +2521,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -2492,7 +2596,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2570,7 +2674,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2631,7 +2735,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2710,7 +2814,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2772,7 +2876,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2808,7 +2912,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2851,11 +2955,11 @@ steps: - ./bin/grabpl verify-version ${DRONE_TAG} - ./bin/grabpl gen-version ${DRONE_TAG} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2877,7 +2981,7 @@ steps: environment: NPM_TOKEN: from_secret: npm_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: release-npm-packages trigger: event: @@ -2903,7 +3007,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2966,7 +3070,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2978,7 +3082,7 @@ steps: - make gen-go - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} @@ -2986,7 +3090,7 @@ steps: depends_on: - initialize environment: {} - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition oss --build-id @@ -2995,8 +3099,17 @@ steps: - initialize environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-frontend +- commands: + - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} + --no-pull-enterprise + depends_on: + - initialize + environment: + NODE_OPTIONS: --max_old_space_size=8192 + image: grafana/build-container:1.5.3 + name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin depends_on: @@ -3004,13 +3117,13 @@ steps: environment: GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -3023,7 +3136,7 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: ensure-cuetsified - commands: - ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise @@ -3032,6 +3145,7 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages environment: GITHUB_TOKEN: from_secret: github_token @@ -3043,14 +3157,14 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: package - commands: - ls dist/*.tar.gz* - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition oss --shouldSave @@ -3082,11 +3196,12 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages detach: true environment: ARCH: linux-amd64 PORT: 3001 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: grafana-server - commands: - apt-get install -y netcat @@ -3161,9 +3276,10 @@ steps: - ./bin/grabpl verify-storybook depends_on: - build-frontend + - build-frontend-packages environment: NODE_OPTIONS: --max_old_space_size=4096 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-storybook - commands: - ./bin/grabpl upload-cdn --edition oss --src-bucket "grafana-static-assets" @@ -3217,7 +3333,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3229,7 +3345,7 @@ steps: - make gen-go - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - |- @@ -3243,13 +3359,13 @@ steps: - rm words_to_ignore.txt depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: codespell - commands: - ./bin/grabpl shellcheck depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: shellcheck - commands: - ./bin/grabpl lint-backend --edition oss @@ -3257,7 +3373,7 @@ steps: - initialize environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-backend - commands: - yarn run prettier:check @@ -3268,19 +3384,19 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition oss depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -3288,7 +3404,7 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-frontend trigger: ref: @@ -3331,7 +3447,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3343,7 +3459,7 @@ steps: - make gen-go - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - yarn install --immutable - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - apt-get update @@ -3358,7 +3474,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: postgres-integration-tests - commands: - apt-get update @@ -3373,7 +3489,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: mysql-integration-tests trigger: ref: @@ -3408,7 +3524,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: initialize @@ -3456,7 +3572,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3471,7 +3587,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -3487,7 +3603,7 @@ steps: depends_on: - clone-enterprise environment: {} - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER} @@ -3495,7 +3611,7 @@ steps: depends_on: - initialize environment: {} - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition enterprise --build-id @@ -3504,8 +3620,17 @@ steps: - initialize environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-frontend +- commands: + - ./bin/grabpl build-frontend-packages --jobs 8 --edition enterprise --build-id + ${DRONE_BUILD_NUMBER} --no-pull-enterprise + depends_on: + - initialize + environment: + NODE_OPTIONS: --max_old_space_size=8192 + image: grafana/build-container:1.5.3 + name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition enterprise --no-install-deps --sign --signing-admin @@ -3514,13 +3639,13 @@ steps: environment: GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -3533,15 +3658,15 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: ensure-cuetsified - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise2 --build-id ${DRONE_BUILD_NUMBER} - --variants linux-x64 --no-pull-enterprise + --variants linux-amd64 --no-pull-enterprise depends_on: - initialize environment: {} - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-backend-enterprise2 - commands: - ./bin/grabpl package --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER} @@ -3550,6 +3675,7 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages - build-backend-enterprise2 environment: GITHUB_TOKEN: @@ -3562,14 +3688,14 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: package - commands: - ls dist/*.tar.gz* - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition enterprise --shouldSave @@ -3601,12 +3727,13 @@ steps: - build-plugins - build-backend - build-frontend + - build-frontend-packages detach: true environment: ARCH: linux-amd64 PORT: 3001 RUNDIR: scripts/grafana-server/tmp-grafana-enterprise - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: grafana-server - commands: - apt-get install -y netcat @@ -3681,9 +3808,10 @@ steps: - ./bin/grabpl verify-storybook depends_on: - build-frontend + - build-frontend-packages environment: NODE_OPTIONS: --max_old_space_size=4096 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: build-storybook - commands: - ./bin/grabpl upload-cdn --edition enterprise --src-bucket "grafana-static-assets" @@ -3709,11 +3837,12 @@ steps: name: upload-packages - commands: - ./bin/grabpl package --jobs 8 --edition enterprise2 --build-id ${DRONE_BUILD_NUMBER} - --no-pull-enterprise --variants linux-x64 --sign + --no-pull-enterprise --variants linux-amd64 --sign depends_on: - build-plugins - build-backend - build-frontend + - build-frontend-packages - build-backend-enterprise2 environment: GITHUB_TOKEN: @@ -3726,7 +3855,7 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: package-enterprise2 - commands: - ./bin/grabpl upload-cdn --edition enterprise2 --src-bucket "grafana-static-assets" @@ -3781,7 +3910,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3796,7 +3925,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -3812,7 +3941,7 @@ steps: depends_on: - clone-enterprise environment: {} - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - |- @@ -3826,13 +3955,13 @@ steps: - rm words_to_ignore.txt depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: codespell - commands: - ./bin/grabpl shellcheck depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: shellcheck - commands: - ./bin/grabpl lint-backend --edition enterprise @@ -3840,7 +3969,7 @@ steps: - initialize environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-backend - commands: - yarn run prettier:check @@ -3851,19 +3980,19 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition enterprise depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition enterprise depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -3871,7 +4000,7 @@ steps: - initialize environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-frontend - commands: - ./bin/grabpl lint-backend --edition enterprise2 @@ -3879,19 +4008,19 @@ steps: - initialize environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: lint-backend-enterprise2 - commands: - ./bin/grabpl test-backend --edition enterprise2 depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend-enterprise2 - commands: - ./bin/grabpl integration-tests --edition enterprise2 depends_on: - initialize - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: test-backend-integration-enterprise2 trigger: ref: @@ -3944,7 +4073,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3959,7 +4088,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -3975,7 +4104,7 @@ steps: depends_on: - clone-enterprise environment: {} - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: initialize - commands: - apt-get update @@ -3990,7 +4119,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: postgres-integration-tests - commands: - apt-get update @@ -4005,7 +4134,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: mysql-integration-tests - commands: - dockerize -wait tcp://redis:6379/0 -timeout 120s @@ -4014,7 +4143,7 @@ steps: - initialize environment: REDIS_URL: redis://redis:6379/0 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -4023,7 +4152,7 @@ steps: - initialize environment: MEMCACHED_HOSTS: memcached:11211 - image: grafana/build-container:1.5.1 + image: grafana/build-container:1.5.3 name: memcached-integration-tests trigger: ref: @@ -4062,7 +4191,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.5/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.25/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -4259,6 +4388,6 @@ kind: secret name: gcp_upload_artifacts_key --- kind: signature -hmac: 2990e0351ffd3bf432e64621ec7d82ffd619ab8cf0aa0e3c648f54392c5f0e6e +hmac: 62bc3b786fa1499c0524bc15ca374d8190dbe797d1d72817ac1cca9532205ed8 ... diff --git a/Dockerfile b/Dockerfile index 20aa6b5b3e1..711dc84fb05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ COPY emails emails ENV NODE_ENV production RUN yarn build -FROM golang:1.17.7-alpine3.15 as go-builder +FROM golang:1.17.8-alpine3.15 as go-builder RUN apk add --no-cache gcc g++ make diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index 4c3f2325a8f..a8c2760a639 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -21,7 +21,7 @@ COPY emails emails ENV NODE_ENV production RUN yarn build -FROM golang:1.17.7 AS go-builder +FROM golang:1.17.8 AS go-builder WORKDIR /src/grafana diff --git a/package.json b/package.json index c39a536e8fc..24d165828fd 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "packages:clean": "lerna run clean", "precommit": "yarn run lint-staged", "prettier:check": "prettier --check --list-different=false --loglevel=warn \"**/*.{scss,md,mdx}\"", + "prettier:checkDocs": "prettier --check --list-different=false --loglevel=warn \"docs/**.md\" \"packages/**.{ts,tsx,scss,md,mdx}\"", "prettier:write": "prettier --list-different \"**/*.{scss,md,mdx}\" --write", "start": "yarn themes:generate && yarn dev --watch", "start:noTsCheck": "yarn start --env noTsCheck=1", diff --git a/scripts/build/ci-build/Dockerfile b/scripts/build/ci-build/Dockerfile index 4cd643d9f6c..b41eb5b7ed2 100644 --- a/scripts/build/ci-build/Dockerfile +++ b/scripts/build/ci-build/Dockerfile @@ -110,7 +110,7 @@ RUN rm dockerize-linux-amd64-v${DOCKERIZE_VERSION}.tar.gz # Use old Debian (this has support into 2022) in order to ensure binary compatibility with older glibc's. FROM debian:stretch-20210208 -ENV GOVERSION=1.17.7 \ +ENV GOVERSION=1.17.8 \ PATH=/usr/local/go/bin:$PATH \ GOPATH=/go \ NODEVERSION=16.13.0-1nodesource1 \ diff --git a/scripts/drone/pipelines/docs.star b/scripts/drone/pipelines/docs.star index be19f140c73..fc6ce20cfa7 100644 --- a/scripts/drone/pipelines/docs.star +++ b/scripts/drone/pipelines/docs.star @@ -1,14 +1,14 @@ load( 'scripts/drone/steps/lib.star', + 'build_image', 'initialize_step', 'download_grabpl_step', 'lint_frontend_step', 'codespell_step', - 'shellcheck_step', - 'build_frontend_step', 'test_frontend_step', 'build_storybook_step', 'build_frontend_docs_step', + 'build_frontend_package_step', 'build_docs_website_step', ) @@ -29,6 +29,9 @@ def docs_pipelines(edition, ver_mode, trigger): # Insert remaining steps steps.extend([ + codespell_step(), + lint_docs(), + build_frontend_package_step(edition=edition, ver_mode=ver_mode), build_frontend_docs_step(edition=edition), build_docs_website_step(), ]) @@ -37,6 +40,21 @@ def docs_pipelines(edition, ver_mode, trigger): name='{}-docs'.format(ver_mode), edition=edition, trigger=trigger, services=[], steps=steps, ) +def lint_docs(): + return { + 'name': 'lint-docs', + 'image': build_image, + 'depends_on': [ + 'initialize', + ], + 'environment': { + 'NODE_OPTIONS': '--max_old_space_size=8192', + }, + 'commands': [ + 'yarn run prettier:checkDocs', + ], + } + def trigger_docs(): return { diff --git a/scripts/drone/pipelines/main.star b/scripts/drone/pipelines/main.star index 9220d81e366..380069d71ce 100644 --- a/scripts/drone/pipelines/main.star +++ b/scripts/drone/pipelines/main.star @@ -12,6 +12,7 @@ load( 'test_frontend_step', 'build_backend_step', 'build_frontend_step', + 'build_frontend_package_step', 'build_plugins_step', 'package_step', 'grafana_server_step', @@ -79,6 +80,7 @@ def get_steps(edition, is_downstream=False): enterprise_downstream_step(edition=edition), build_backend_step(edition=edition, ver_mode=ver_mode, is_downstream=is_downstream), build_frontend_step(edition=edition, ver_mode=ver_mode, is_downstream=is_downstream), + build_frontend_package_step(edition=edition, ver_mode=ver_mode, is_downstream=is_downstream), build_plugins_step(edition=edition, sign=True), validate_scuemata_step(), ensure_cuetsified_step(), @@ -98,7 +100,7 @@ def get_steps(edition, is_downstream=False): test_backend_integration_step(edition=edition2), ]) build_steps.extend([ - build_backend_step(edition=edition2, ver_mode=ver_mode, variants=['linux-x64'], is_downstream=is_downstream), + build_backend_step(edition=edition2, ver_mode=ver_mode, variants=['linux-amd64'], is_downstream=is_downstream), ]) # Insert remaining steps @@ -133,7 +135,7 @@ def get_steps(edition, is_downstream=False): if include_enterprise2: edition2 = 'enterprise2' build_steps.extend([ - package_step(edition=edition2, ver_mode=ver_mode, include_enterprise2=include_enterprise2, variants=['linux-x64'], is_downstream=is_downstream), + package_step(edition=edition2, ver_mode=ver_mode, include_enterprise2=include_enterprise2, variants=['linux-amd64'], is_downstream=is_downstream), upload_packages_step(edition=edition2, ver_mode=ver_mode, is_downstream=is_downstream), upload_cdn_step(edition=edition2, ver_mode=ver_mode) ]) diff --git a/scripts/drone/pipelines/pr.star b/scripts/drone/pipelines/pr.star index f60e3332fc0..1f768f1f5a2 100644 --- a/scripts/drone/pipelines/pr.star +++ b/scripts/drone/pipelines/pr.star @@ -9,6 +9,7 @@ load( 'shellcheck_step', 'build_backend_step', 'build_frontend_step', + 'build_frontend_package_step', 'build_plugins_step', 'test_backend_step', 'test_backend_integration_step', @@ -56,7 +57,7 @@ ver_mode = 'pr' def pr_pipelines(edition): services = integration_test_services(edition) volumes = integration_test_services_volumes() - variants = ['linux-x64', 'linux-x64-musl', 'osx64', 'win64', 'armv6',] + variants = ['linux-amd64', 'linux-amd64-musl', 'darwin-amd64', 'windows-amd64', 'armv6',] include_enterprise2 = edition == 'enterprise' test_steps = [ lint_drone_step(), @@ -71,6 +72,7 @@ def pr_pipelines(edition): build_steps = [ build_backend_step(edition=edition, ver_mode=ver_mode, variants=variants), build_frontend_step(edition=edition, ver_mode=ver_mode), + build_frontend_package_step(edition=edition, ver_mode=ver_mode), build_plugins_step(edition=edition), validate_scuemata_step(), ensure_cuetsified_step(), @@ -90,7 +92,7 @@ def pr_pipelines(edition): test_backend_integration_step(edition=edition2), ]) build_steps.extend([ - build_backend_step(edition=edition2, ver_mode=ver_mode, variants=['linux-x64']), + build_backend_step(edition=edition2, ver_mode=ver_mode, variants=['linux-amd64']), ]) # Insert remaining build_steps @@ -114,7 +116,7 @@ def pr_pipelines(edition): memcached_integration_tests_step(edition=edition, ver_mode=ver_mode), ]) build_steps.extend([ - package_step(edition=edition2, ver_mode=ver_mode, include_enterprise2=include_enterprise2, variants=['linux-x64']), + package_step(edition=edition2, ver_mode=ver_mode, include_enterprise2=include_enterprise2, variants=['linux-amd64']), ]) trigger = { diff --git a/scripts/drone/pipelines/release.star b/scripts/drone/pipelines/release.star index 237f903e005..1e8dc089cd3 100644 --- a/scripts/drone/pipelines/release.star +++ b/scripts/drone/pipelines/release.star @@ -16,6 +16,7 @@ load( 'test_frontend_step', 'build_backend_step', 'build_frontend_step', + 'build_frontend_package_step', 'build_plugins_step', 'package_step', 'grafana_server_step', @@ -179,6 +180,7 @@ def get_steps(edition, ver_mode): build_steps = [ build_backend_step(edition=edition, ver_mode=ver_mode), build_frontend_step(edition=edition, ver_mode=ver_mode), + build_frontend_package_step(edition=edition, ver_mode=ver_mode), build_plugins_step(edition=edition, sign=True), validate_scuemata_step(), ensure_cuetsified_step(), @@ -197,7 +199,7 @@ def get_steps(edition, ver_mode): test_backend_integration_step(edition=edition2), ]) build_steps.extend([ - build_backend_step(edition=edition2, ver_mode=ver_mode, variants=['linux-x64']), + build_backend_step(edition=edition2, ver_mode=ver_mode, variants=['linux-amd64']), ]) # Insert remaining steps @@ -241,7 +243,7 @@ def get_steps(edition, ver_mode): if include_enterprise2: publish_steps.extend([ - package_step(edition=edition2, ver_mode=ver_mode, include_enterprise2=include_enterprise2, variants=['linux-x64']), + package_step(edition=edition2, ver_mode=ver_mode, include_enterprise2=include_enterprise2, variants=['linux-amd64']), upload_cdn_step(edition=edition2, ver_mode=ver_mode), ]) if should_upload: diff --git a/scripts/drone/steps/lib.star b/scripts/drone/steps/lib.star index eaf8795d911..c156e40615b 100644 --- a/scripts/drone/steps/lib.star +++ b/scripts/drone/steps/lib.star @@ -1,7 +1,7 @@ load('scripts/drone/vault.star', 'from_secret', 'github_token', 'pull_secret', 'drone_token', 'prerelease_bucket') -grabpl_version = 'v2.9.5' -build_image = 'grafana/build-container:1.5.1' +grabpl_version = 'v2.9.25' +build_image = 'grafana/build-container:1.5.3' publish_image = 'grafana/grafana-ci-deploy:1.3.1' deploy_docker_image = 'us.gcr.io/kubernetes-dev/drone/plugins/deploy-image' alpine_image = 'alpine:3.15' @@ -241,6 +241,7 @@ def build_storybook_step(edition, ver_mode): 'depends_on': [ # Best to ensure that this step doesn't mess with what's getting built and packaged 'build-frontend', + 'build-frontend-packages', ], 'environment': { 'NODE_OPTIONS': '--max_old_space_size=4096', @@ -421,18 +422,45 @@ def build_frontend_step(edition, ver_mode, is_downstream=False): 'commands': cmds, } +def build_frontend_package_step(edition, ver_mode, is_downstream=False): + if not is_downstream: + build_no = '${DRONE_BUILD_NUMBER}' + else: + build_no = '$${SOURCE_BUILD_NUMBER}' + + # TODO: Use percentage for num jobs + if ver_mode == 'release': + cmds = [ + './bin/grabpl build-frontend-packages --jobs 8 --github-token $${GITHUB_TOKEN} ' + \ + '--edition {} --no-pull-enterprise ${{DRONE_TAG}}'.format(edition), + ] + else: + cmds = [ + './bin/grabpl build-frontend-packages --jobs 8 --edition {} '.format(edition) + \ + '--build-id {} --no-pull-enterprise'.format(build_no), + ] + + return { + 'name': 'build-frontend-packages', + 'image': build_image, + 'depends_on': [ + 'initialize', + ], + 'environment': { + 'NODE_OPTIONS': '--max_old_space_size=8192', + }, + 'commands': cmds, + } + def build_frontend_docs_step(edition): return { 'name': 'build-frontend-docs', 'image': build_image, 'depends_on': [ - 'initialize' + 'build-frontend-packages' ], 'commands': [ - 'yarn packages:build', - 'yarn packages:docsExtract', - 'yarn packages:docsToMarkdown', './scripts/ci-reference-docs-lint.sh ci', ] } @@ -607,6 +635,7 @@ def package_step(edition, ver_mode, include_enterprise2=False, variants=None, is 'build-plugins', 'build-backend', 'build-frontend', + 'build-frontend-packages', ] if include_enterprise2: sfx = '-enterprise2' @@ -682,6 +711,7 @@ def grafana_server_step(edition, port=3001): 'build-plugins', 'build-backend', 'build-frontend', + 'build-frontend-packages', ], 'environment': environment, 'commands': [ From 5d078ba874d3f1a7f80667ead808dac04263b358 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Mon, 14 Mar 2022 11:32:17 +0100 Subject: [PATCH 39/52] Alerting: adds support for federated rules (#46037) (#46520) (cherry picked from commit a9b1a964b0b9a6b64ed535cb3f1ed2836cc8f837) --- .../api/tooling/definitions/cortex-ruler.go | 7 +- .../tooling/definitions/cortex-ruler_test.go | 31 ++++++ pkg/services/ngalert/api/tooling/post.json | 29 +++-- pkg/services/ngalert/api/tooling/spec.json | 23 +++- public/api-merged.json | 100 +++++++++++------- public/api-spec.json | 63 ++++++----- .../features/alerting/unified/RuleViewer.tsx | 22 +++- .../rules/RuleDetailsActionButtons.tsx | 7 +- .../rules/RuleDetailsFederatedSources.tsx | 23 ++++ .../unified/components/rules/RulesGroup.tsx | 31 +++--- .../hooks/useCombinedRuleNamespaces.ts | 1 + .../features/alerting/unified/utils/rules.ts | 11 ++ public/app/types/unified-alerting-dto.ts | 1 + public/app/types/unified-alerting.ts | 1 + 14 files changed, 251 insertions(+), 99 deletions(-) create mode 100644 public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx diff --git a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go index c8c221491b0..7e25ce93059 100644 --- a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go +++ b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go @@ -150,9 +150,10 @@ func (c *PostableRuleGroupConfig) validate() error { // swagger:model type GettableRuleGroupConfig struct { - Name string `yaml:"name" json:"name"` - Interval model.Duration `yaml:"interval,omitempty" json:"interval,omitempty"` - Rules []GettableExtendedRuleNode `yaml:"rules" json:"rules"` + Name string `yaml:"name" json:"name"` + Interval model.Duration `yaml:"interval,omitempty" json:"interval,omitempty"` + SourceTenants []string `yaml:"source_tenants,omitempty" json:"source_tenants,omitempty"` + Rules []GettableExtendedRuleNode `yaml:"rules" json:"rules"` } func (c *GettableRuleGroupConfig) UnmarshalJSON(b []byte) error { diff --git a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go index 6957e83a053..364e36a5997 100644 --- a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go +++ b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go @@ -98,6 +98,21 @@ func Test_Rule_Group_Marshaling(t *testing.T) { }, }, }, + { + desc: "success federated lotex", + input: PostableRuleGroupConfig{ + Name: "foo", + Interval: 0, + Rules: []PostableExtendedRuleNode{ + { + ApiRuleNode: &ApiRuleNode{}, + }, + { + ApiRuleNode: &ApiRuleNode{}, + }, + }, + }, + }, { desc: "success grafana", input: PostableRuleGroupConfig{ @@ -189,6 +204,22 @@ func Test_Rule_Group_Type(t *testing.T) { }, expected: LoTexRulerBackend, }, + { + desc: "success federated lotex", + input: PostableRuleGroupConfig{ + Name: "foo", + Interval: 0, + Rules: []PostableExtendedRuleNode{ + { + ApiRuleNode: &ApiRuleNode{}, + }, + { + ApiRuleNode: &ApiRuleNode{}, + }, + }, + }, + expected: LoTexRulerBackend, + }, { desc: "success grafana", input: PostableRuleGroupConfig{ diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index 60e3837bc9a..c1649f0e1ae 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -978,6 +978,13 @@ }, "type": "array", "x-go-name": "Rules" + }, + "source_tenants": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "SourceTenants" } }, "type": "object", @@ -2260,6 +2267,13 @@ }, "type": "array", "x-go-name": "Rules" + }, + "source_tenants": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "SourceTenants" } }, "type": "object", @@ -3229,12 +3243,11 @@ "type": "object" }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "items": { "$ref": "#/definitions/gettableAlert" }, - "type": "array", - "x-go-name": "GettableAlerts", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + "type": "array" }, "gettableSilence": { "description": "GettableSilence gettable silence", @@ -3426,7 +3439,6 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "postableSilence": { - "description": "PostableSilence postable silence", "properties": { "comment": { "description": "comment", @@ -3466,10 +3478,11 @@ "matchers", "startsAt" ], - "type": "object" + "type": "object", + "x-go-name": "PostableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "receiver": { - "description": "Receiver receiver", "properties": { "name": { "description": "name", @@ -3480,7 +3493,9 @@ "required": [ "name" ], - "type": "object" + "type": "object", + "x-go-name": "Receiver", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "silence": { "description": "Silence silence", diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index 90ff7dfecc1..bca433a5a90 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -2001,6 +2001,13 @@ "$ref": "#/definitions/GettableExtendedRuleNode" }, "x-go-name": "Rules" + }, + "source_tenants": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "SourceTenants" } }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -3284,6 +3291,13 @@ "$ref": "#/definitions/GettableExtendedRuleNode" }, "x-go-name": "Rules" + }, + "source_tenants": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "SourceTenants" } }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -4255,12 +4269,11 @@ "$ref": "#/definitions/gettableAlert" }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "type": "array", "items": { "$ref": "#/definitions/gettableAlert" }, - "x-go-name": "GettableAlerts", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/gettableAlerts" }, "gettableSilence": { @@ -4455,7 +4468,6 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "postableSilence": { - "description": "PostableSilence postable silence", "type": "object", "required": [ "comment", @@ -4496,10 +4508,11 @@ "x-go-name": "StartsAt" } }, + "x-go-name": "PostableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/postableSilence" }, "receiver": { - "description": "Receiver receiver", "type": "object", "required": [ "name" @@ -4511,6 +4524,8 @@ "x-go-name": "Name" } }, + "x-go-name": "Receiver", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/receiver" }, "silence": { diff --git a/public/api-merged.json b/public/api-merged.json index c136ca4ffb1..9a6603027a1 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -4494,15 +4494,15 @@ "parameters": [ { "type": "string", - "x-go-name": "DatasourceID", - "name": "datasource_id", + "x-go-name": "PermissionID", + "name": "permissionId", "in": "path", "required": true }, { "type": "string", - "x-go-name": "PermissionID", - "name": "permissionId", + "x-go-name": "DatasourceID", + "name": "datasource_id", "in": "path", "required": true } @@ -7846,14 +7846,6 @@ "summary": "Add External Group.", "operationId": "addTeamGroupApi", "parameters": [ - { - "type": "integer", - "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", - "in": "path", - "required": true - }, { "x-go-name": "Body", "name": "body", @@ -7862,6 +7854,14 @@ "schema": { "$ref": "#/definitions/TeamGroupMapping" } + }, + { + "type": "integer", + "format": "int64", + "x-go-name": "TeamID", + "name": "teamId", + "in": "path", + "required": true } ], "responses": { @@ -7895,16 +7895,16 @@ { "type": "integer", "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", + "x-go-name": "GroupID", + "name": "groupId", "in": "path", "required": true }, { "type": "integer", "format": "int64", - "x-go-name": "GroupID", - "name": "groupId", + "x-go-name": "TeamID", + "name": "teamId", "in": "path", "required": true } @@ -10143,7 +10143,7 @@ }, "x-go-package": "github.com/prometheus/common/config" }, - "BrandingOptions": { + "BrandingOptionsDTO": { "type": "object", "properties": { "emailFooterLink": { @@ -10280,7 +10280,7 @@ "x-go-name": "Name" }, "options": { - "$ref": "#/definitions/ReportOptions" + "$ref": "#/definitions/ReportOptionsDTO" }, "orgId": { "type": "integer", @@ -10296,10 +10296,11 @@ "x-go-name": "ReplyTo" }, "schedule": { - "$ref": "#/definitions/Schedule" + "$ref": "#/definitions/ScheduleDTO" }, "state": { - "$ref": "#/definitions/State" + "type": "string", + "x-go-name": "State" }, "templateVars": { "type": "object", @@ -10480,7 +10481,7 @@ "x-go-name": "Name" }, "options": { - "$ref": "#/definitions/ReportOptions" + "$ref": "#/definitions/ReportOptionsDTO" }, "recipients": { "type": "string", @@ -10491,7 +10492,7 @@ "x-go-name": "ReplyTo" }, "schedule": { - "$ref": "#/definitions/Schedule" + "$ref": "#/definitions/ScheduleDTO" }, "state": { "type": "string", @@ -10762,6 +10763,10 @@ "type": "boolean", "x-go-name": "CanAdmin" }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, "canEdit": { "type": "boolean", "x-go-name": "CanEdit" @@ -11723,6 +11728,10 @@ "type": "boolean", "x-go-name": "CanAdmin" }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, "canEdit": { "type": "boolean", "x-go-name": "CanEdit" @@ -12068,8 +12077,8 @@ }, "exec_err_state": { "type": "string", - "enum": ["Alerting", "Error"], - "x-go-enum-desc": "Alerting AlertingErrState\nError ErrorErrState", + "enum": ["OK", "Alerting", "Error"], + "x-go-enum-desc": "OK OkErrState\nAlerting AlertingErrState\nError ErrorErrState", "x-go-name": "ExecErrState" }, "id": { @@ -12162,6 +12171,13 @@ "$ref": "#/definitions/GettableExtendedRuleNode" }, "x-go-name": "Rules" + }, + "source_tenants": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "SourceTenants" } }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -13906,8 +13922,8 @@ }, "exec_err_state": { "type": "string", - "enum": ["Alerting", "Error"], - "x-go-enum-desc": "Alerting AlertingErrState\nError ErrorErrState", + "enum": ["OK", "Alerting", "Error"], + "x-go-enum-desc": "OK OkErrState\nAlerting AlertingErrState\nError ErrorErrState", "x-go-name": "ExecErrState" }, "no_data_state": { @@ -14266,7 +14282,7 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" }, - "ReportOptions": { + "ReportOptionsDTO": { "type": "object", "properties": { "layout": { @@ -14539,6 +14555,13 @@ "$ref": "#/definitions/GettableExtendedRuleNode" }, "x-go-name": "Rules" + }, + "source_tenants": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "SourceTenants" } }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -14673,7 +14696,7 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/models" }, - "Schedule": { + "ScheduleDTO": { "type": "object", "properties": { "day": { @@ -14824,7 +14847,7 @@ "type": "object", "properties": { "branding": { - "$ref": "#/definitions/BrandingOptions" + "$ref": "#/definitions/BrandingOptionsDTO" }, "id": { "type": "integer", @@ -15054,10 +15077,6 @@ "SmtpNotEnabled": { "$ref": "#/definitions/ResponseDetails" }, - "State": { - "type": "string", - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" - }, "Status": { "type": "object", "properties": { @@ -16772,12 +16791,11 @@ } }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "type": "array", "items": { "$ref": "#/definitions/gettableAlert" - }, - "x-go-name": "GettableAlerts", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + } }, "gettableSilence": { "description": "GettableSilence gettable silence", @@ -16951,7 +16969,6 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "postableSilence": { - "description": "PostableSilence postable silence", "type": "object", "required": ["comment", "createdBy", "endsAt", "matchers", "startsAt"], "properties": { @@ -16985,10 +17002,11 @@ "format": "date-time", "x-go-name": "StartsAt" } - } + }, + "x-go-name": "PostableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "receiver": { - "description": "Receiver receiver", "type": "object", "required": ["name"], "properties": { @@ -16997,7 +17015,9 @@ "type": "string", "x-go-name": "Name" } - } + }, + "x-go-name": "Receiver", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "silence": { "description": "Silence silence", diff --git a/public/api-spec.json b/public/api-spec.json index 955bbce2527..5b8469bfb29 100644 --- a/public/api-spec.json +++ b/public/api-spec.json @@ -3544,15 +3544,15 @@ "parameters": [ { "type": "string", - "x-go-name": "DatasourceID", - "name": "datasource_id", + "x-go-name": "PermissionID", + "name": "permissionId", "in": "path", "required": true }, { "type": "string", - "x-go-name": "PermissionID", - "name": "permissionId", + "x-go-name": "DatasourceID", + "name": "datasource_id", "in": "path", "required": true } @@ -6424,14 +6424,6 @@ "summary": "Add External Group.", "operationId": "addTeamGroupApi", "parameters": [ - { - "type": "integer", - "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", - "in": "path", - "required": true - }, { "x-go-name": "Body", "name": "body", @@ -6440,6 +6432,14 @@ "schema": { "$ref": "#/definitions/TeamGroupMapping" } + }, + { + "type": "integer", + "format": "int64", + "x-go-name": "TeamID", + "name": "teamId", + "in": "path", + "required": true } ], "responses": { @@ -6473,16 +6473,16 @@ { "type": "integer", "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", + "x-go-name": "GroupID", + "name": "groupId", "in": "path", "required": true }, { "type": "integer", "format": "int64", - "x-go-name": "GroupID", - "name": "groupId", + "x-go-name": "TeamID", + "name": "teamId", "in": "path", "required": true } @@ -8333,7 +8333,7 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/models" }, - "BrandingOptions": { + "BrandingOptionsDTO": { "type": "object", "properties": { "emailFooterLink": { @@ -8436,7 +8436,7 @@ "x-go-name": "Name" }, "options": { - "$ref": "#/definitions/ReportOptions" + "$ref": "#/definitions/ReportOptionsDTO" }, "orgId": { "type": "integer", @@ -8452,10 +8452,11 @@ "x-go-name": "ReplyTo" }, "schedule": { - "$ref": "#/definitions/Schedule" + "$ref": "#/definitions/ScheduleDTO" }, "state": { - "$ref": "#/definitions/State" + "type": "string", + "x-go-name": "State" }, "templateVars": { "type": "object", @@ -8636,7 +8637,7 @@ "x-go-name": "Name" }, "options": { - "$ref": "#/definitions/ReportOptions" + "$ref": "#/definitions/ReportOptionsDTO" }, "recipients": { "type": "string", @@ -8647,7 +8648,7 @@ "x-go-name": "ReplyTo" }, "schedule": { - "$ref": "#/definitions/Schedule" + "$ref": "#/definitions/ScheduleDTO" }, "state": { "type": "string", @@ -8918,6 +8919,10 @@ "type": "boolean", "x-go-name": "CanAdmin" }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, "canEdit": { "type": "boolean", "x-go-name": "CanEdit" @@ -9695,6 +9700,10 @@ "type": "boolean", "x-go-name": "CanAdmin" }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, "canEdit": { "type": "boolean", "x-go-name": "CanEdit" @@ -10839,7 +10848,7 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" }, - "ReportOptions": { + "ReportOptionsDTO": { "type": "object", "properties": { "layout": { @@ -10971,7 +10980,7 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/models" }, - "Schedule": { + "ScheduleDTO": { "type": "object", "properties": { "day": { @@ -11113,7 +11122,7 @@ "type": "object", "properties": { "branding": { - "$ref": "#/definitions/BrandingOptions" + "$ref": "#/definitions/BrandingOptionsDTO" }, "id": { "type": "integer", @@ -11133,10 +11142,6 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" }, - "State": { - "type": "string", - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" - }, "Status": { "type": "object", "properties": { diff --git a/public/app/features/alerting/unified/RuleViewer.tsx b/public/app/features/alerting/unified/RuleViewer.tsx index a0701875b20..1b904541fdc 100644 --- a/public/app/features/alerting/unified/RuleViewer.tsx +++ b/public/app/features/alerting/unified/RuleViewer.tsx @@ -9,6 +9,8 @@ import { LoadingPlaceholder, PanelChromeLoadingIndicator, Icon, + Button, + VerticalGroup, } from '@grafana/ui'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { AlertingQueryRunner } from './state/AlertingQueryRunner'; @@ -28,6 +30,8 @@ import { RuleDetailsExpression } from './components/rules/RuleDetailsExpression' import { RuleDetailsAnnotations } from './components/rules/RuleDetailsAnnotations'; import * as ruleId from './utils/rule-id'; import { AlertQuery } from '../../../types/unified-alerting-dto'; +import { RuleDetailsFederatedSources } from './components/rules/RuleDetailsFederatedSources'; +import { isFederatedRuleGroup } from './utils/rules'; type RuleViewerProps = GrafanaRouteComponentProps<{ id?: string; sourceName?: string }>; @@ -115,9 +119,24 @@ export function RuleViewer({ match }: RuleViewerProps) { ); } + const annotations = Object.entries(rule.annotations).filter(([_, value]) => !!value.trim()); + const isFederatedRule = isFederatedRuleGroup(rule.group); + return ( + {isFederatedRule && ( + + + Federated rule groups are currently an experimental feature. + + + + )}

@@ -143,6 +162,7 @@ export function RuleViewer({ match }: RuleViewerProps) {

+ {isFederatedRule && } {`${rule.namespace.name} / ${rule.group.name}`}
@@ -150,7 +170,7 @@ export function RuleViewer({ match }: RuleViewerProps) {
- {data && Object.keys(data).length > 0 && ( + {!isFederatedRule && data && Object.keys(data).length > 0 && ( <>
Query results runner.cancel()} /> diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx index 1d7aea69627..07a739eea6c 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx @@ -17,6 +17,7 @@ import { CombinedRule, RulesSource } from 'app/types/unified-alerting'; import { getAlertmanagerByUid } from '../../utils/alertmanager'; import { useStateHistoryModal } from '../../hooks/useStateHistoryModal'; import { RulerGrafanaRuleDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto'; +import { isFederatedRuleGroup } from '../../utils/rules'; interface Props { rule: CombinedRule; @@ -39,6 +40,7 @@ export const RuleDetailsActionButtons: FC = ({ rule, rulesSource }) => { const leftButtons: JSX.Element[] = []; const rightButtons: JSX.Element[] = []; + const isFederated = isFederatedRuleGroup(group); const { isEditable } = useIsRuleEditable(getRulesSourceName(rulesSource), rulerRule); const returnTo = location.pathname + location.search; const isViewMode = inViewMode(location.pathname); @@ -67,7 +69,8 @@ export const RuleDetailsActionButtons: FC = ({ rule, rulesSource }) => { }; // explore does not support grafana rule queries atm - if (isCloudRulesSource(rulesSource) && contextSrv.isEditor) { + // neither do "federated rules" + if (isCloudRulesSource(rulesSource) && contextSrv.isEditor && !isFederated) { leftButtons.push( = ({ rule, rulesSource }) => { ); } - if (isEditable && rulerRule) { + if (isEditable && rulerRule && !isFederated) { const sourceName = getRulesSourceName(rulesSource); const identifier = ruleId.fromRulerRule(sourceName, namespace.name, group.name, rulerRule); diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx new file mode 100644 index 00000000000..a7ca0178276 --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx @@ -0,0 +1,23 @@ +import { CombinedRuleGroup } from 'app/types/unified-alerting'; +import React, { FC } from 'react'; +import { DetailsField } from '../DetailsField'; + +interface Props { + group: CombinedRuleGroup; +} + +const RuleDetailsFederatedSources: FC = ({ group }) => { + const sourceTenants = group.source_tenants ?? []; + + return ( + + <> + {sourceTenants.map((tenant) => ( +
{tenant}
+ ))} + +
+ ); +}; + +export { RuleDetailsFederatedSources }; diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx index b5237649c03..067bff82b0e 100644 --- a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx @@ -1,9 +1,9 @@ import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; import React, { FC, useState, useEffect } from 'react'; -import { HorizontalGroup, Icon, Spinner, Tooltip, useStyles2 } from '@grafana/ui'; +import { Badge, HorizontalGroup, Icon, Spinner, Tooltip, useStyles2 } from '@grafana/ui'; import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; -import { isGrafanaRulerRule } from '../../utils/rules'; +import { isFederatedRuleGroup, isGrafanaRulerRule } from '../../utils/rules'; import { CollapseToggle } from '../CollapseToggle'; import { RulesTable } from './RulesTable'; import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datasource'; @@ -38,6 +38,7 @@ export const RulesGroup: FC = React.memo(({ group, namespace, expandAll } // group "is deleting" if rules source has ruler, but this group has no rules that are in ruler const isDeleting = hasRuler(rulesSource) && !group.rules.find((rule) => !!rule.rulerRule); + const isFederated = isFederatedRuleGroup(group); const actionIcons: React.ReactNode[] = []; @@ -78,16 +79,18 @@ export const RulesGroup: FC = React.memo(({ group, namespace, expandAll } } } } else if (hasRuler(rulesSource)) { - actionIcons.push( - setIsEditingGroup(true)} - /> - ); + if (!isFederated) { + actionIcons.push( + setIsEditingGroup(true)} + /> + ); + } } const groupName = isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name; @@ -111,7 +114,9 @@ export const RulesGroup: FC = React.memo(({ group, namespace, expandAll } /> )} -
{groupName}
+
+ {isFederated && } {groupName} +
diff --git a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts index 13abd147ef9..657d33a9113 100644 --- a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts +++ b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts @@ -105,6 +105,7 @@ function addRulerGroupsToCombinedNamespace(namespace: CombinedRuleNamespace, gro const combinedGroup: CombinedRuleGroup = { name: group.name, interval: group.interval, + source_tenants: group.source_tenants, rules: [], }; combinedGroup.rules = group.rules.map((rule) => rulerRuleToCombinedRule(rule, namespace, combinedGroup)); diff --git a/public/app/features/alerting/unified/utils/rules.ts b/public/app/features/alerting/unified/utils/rules.ts index 8896e91d561..1e4b31a31ed 100644 --- a/public/app/features/alerting/unified/utils/rules.ts +++ b/public/app/features/alerting/unified/utils/rules.ts @@ -12,6 +12,7 @@ import { Alert, AlertingRule, CloudRuleIdentifier, + CombinedRuleGroup, GrafanaRuleIdentifier, PrometheusRuleIdentifier, PromRuleWithLocation, @@ -116,3 +117,13 @@ export function getFirstActiveAt(promRule: AlertingRule) { return prev; }, null as Date | null); } + +/** + * A rule group is "federated" when it has at least one "source_tenants" entry, federated rule groups will evaluate rules in multiple tenants + * Non-federated rules do not have this property + * + * see https://grafana.com/docs/metrics-enterprise/latest/tenant-management/tenant-federation/#cross-tenant-alerting-and-recording-rule-federation + */ +export function isFederatedRuleGroup(group: CombinedRuleGroup) { + return Array.isArray(group.source_tenants); +} diff --git a/public/app/types/unified-alerting-dto.ts b/public/app/types/unified-alerting-dto.ts index 155ff4f9790..fe2d905f0de 100644 --- a/public/app/types/unified-alerting-dto.ts +++ b/public/app/types/unified-alerting-dto.ts @@ -151,6 +151,7 @@ export type PostableRuleDTO = RulerAlertingRuleDTO | RulerRecordingRuleDTO | Pos export type RulerRuleGroupDTO = { name: string; interval?: string; + source_tenants?: string[]; rules: R[]; }; diff --git a/public/app/types/unified-alerting.ts b/public/app/types/unified-alerting.ts index 50c4c5f4c51..ff16b43e9cf 100644 --- a/public/app/types/unified-alerting.ts +++ b/public/app/types/unified-alerting.ts @@ -86,6 +86,7 @@ export interface CombinedRule { export interface CombinedRuleGroup { name: string; interval?: string; + source_tenants?: string[]; rules: CombinedRule[]; } From 5e332cc55dc38f92e30faac0dfd70e3838af3343 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Mon, 14 Mar 2022 08:00:11 -0700 Subject: [PATCH 40/52] SSE: Keep FieldConfig for data source queries (#46328) (#46415) for #46325 (cherry picked from commit cab509fbe8bbb2a76886971e389d7cd65ccee0bd) Co-authored-by: Kyle Brandt --- pkg/expr/nodes.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index 7a1f90c2bbe..ddc4ebd03df 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -328,7 +328,11 @@ func extractNumberSet(frame *data.Frame) ([]mathexp.Number, error) { } n := mathexp.NewNumber("", labels) + + // The new value fields' configs gets pointed to the one in the original frame + n.Frame.Fields[0].Config = frame.Fields[numericField].Config n.SetValue(&val) + numbers[rowIdx] = n } return numbers, nil @@ -358,6 +362,10 @@ func WideToMany(frame *data.Frame) ([]mathexp.Series, error) { f := data.NewFrameOfFieldTypes(frame.Name, l, frame.Fields[tsSchema.TimeIndex].Type(), frame.Fields[valIdx].Type()) f.Fields[0].Name = frame.Fields[tsSchema.TimeIndex].Name f.Fields[1].Name = frame.Fields[valIdx].Name + + // The new value fields' configs gets pointed to the one in the original frame + f.Fields[1].Config = frame.Fields[valIdx].Config + if frame.Fields[valIdx].Labels != nil { f.Fields[1].Labels = frame.Fields[valIdx].Labels.Copy() } From edd5d8bd92daba1fc3e7e92fad2dc14b8d4a0869 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 15 Mar 2022 05:45:05 -0700 Subject: [PATCH 41/52] TimeSeries: Add migration for Graph panel's transform series override (#46577) (#46580) (cherry picked from commit f452e068f02654d2c3cb0ddf6a22ef9a468ee43d) Co-authored-by: Dominik Prokop --- .../__snapshots__/migrations.test.ts.snap | 50 +++++++++++++++++++ .../panel/timeseries/migrations.test.ts | 18 +++++++ .../plugins/panel/timeseries/migrations.ts | 7 +++ 3 files changed, 75 insertions(+) diff --git a/public/app/plugins/panel/timeseries/__snapshots__/migrations.test.ts.snap b/public/app/plugins/panel/timeseries/__snapshots__/migrations.test.ts.snap index 1862a91b058..54174fbd013 100644 --- a/public/app/plugins/panel/timeseries/__snapshots__/migrations.test.ts.snap +++ b/public/app/plugins/panel/timeseries/__snapshots__/migrations.test.ts.snap @@ -481,6 +481,56 @@ Object { } `; +exports[`Graph Migrations transforms should preserve "constant" transform 1`] = ` +Object { + "defaults": Object { + "custom": Object { + "drawStyle": "points", + "spanNulls": false, + }, + }, + "overrides": Array [ + Object { + "matcher": Object { + "id": "byName", + "options": "out", + }, + "properties": Array [ + Object { + "id": "custom.transform", + "value": "constant", + }, + ], + }, + ], +} +`; + +exports[`Graph Migrations transforms should preserve "negative-Y" transform 1`] = ` +Object { + "defaults": Object { + "custom": Object { + "drawStyle": "points", + "spanNulls": false, + }, + }, + "overrides": Array [ + Object { + "matcher": Object { + "id": "byName", + "options": "out", + }, + "properties": Array [ + Object { + "id": "custom.transform", + "value": "negative-Y", + }, + ], + }, + ], +} +`; + exports[`Graph Migrations twoYAxis 1`] = ` Object { "alert": undefined, diff --git a/public/app/plugins/panel/timeseries/migrations.test.ts b/public/app/plugins/panel/timeseries/migrations.test.ts index 06bc7d61fac..da9cf0275d4 100644 --- a/public/app/plugins/panel/timeseries/migrations.test.ts +++ b/public/app/plugins/panel/timeseries/migrations.test.ts @@ -406,6 +406,24 @@ describe('Graph Migrations', () => { expect(panel.fieldConfig).toMatchSnapshot(); }); }); + + describe('transforms', () => { + test.each(['negative-Y', 'constant'])('should preserve %p transform', (transform) => { + const old: any = { + angular: { + seriesOverrides: [ + { + alias: 'out', + transform, + }, + ], + }, + }; + const panel = {} as PanelModel; + panel.options = graphPanelChangedHandler(panel, 'graph', old, prevFieldConfig); + expect(panel.fieldConfig).toMatchSnapshot(); + }); + }); }); const customColor = { diff --git a/public/app/plugins/panel/timeseries/migrations.ts b/public/app/plugins/panel/timeseries/migrations.ts index c62b758811e..8b336f8d3c1 100644 --- a/public/app/plugins/panel/timeseries/migrations.ts +++ b/public/app/plugins/panel/timeseries/migrations.ts @@ -27,6 +27,7 @@ import { ScaleDistribution, StackingMode, SortOrder, + GraphTransform, } from '@grafana/schema'; import { TimeSeriesOptions } from './types'; import { omitBy, pickBy, isNil, isNumber, isString } from 'lodash'; @@ -242,6 +243,12 @@ export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSour }, }); break; + case 'transform': + rule.properties.push({ + id: 'custom.transform', + value: v === 'negative-Y' ? GraphTransform.NegativeY : GraphTransform.Constant, + }); + break; default: console.log('Ignore override migration:', seriesOverride.alias, p, v); } From eecedff435a28a516a5c01156d5ea4fdc5fbb03c Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 15 Mar 2022 06:34:23 -0700 Subject: [PATCH 42/52] Add unpack to Loki Log Browser (#44623). (#46573) (#46583) (cherry picked from commit 5cf090662280fb37b8c2a7414d782da97d7c5b86) Co-authored-by: Alexander Kubyshkin --- public/app/plugins/datasource/loki/syntax.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/public/app/plugins/datasource/loki/syntax.ts b/public/app/plugins/datasource/loki/syntax.ts index cac467b365e..559d46b1523 100644 --- a/public/app/plugins/datasource/loki/syntax.ts +++ b/public/app/plugins/datasource/loki/syntax.ts @@ -81,6 +81,13 @@ export const PIPE_OPERATORS: CompletionItem[] = [ documentation: 'Take labels and use the values as sample data for metric aggregations. Only available in Loki 2.0+.', }, + { + label: 'unpack', + insertText: 'unpack', + detail: 'unpack identifier', + documentation: + 'Parses a JSON log line, unpacking all embedded labels in the pack stage. A special property "_entry" will also be used to replace the original log line. Only available in Loki 2.0+.', + }, { label: 'label_format', insertText: 'label_format', From 3bee136ba68d80a6195a7899c4d9ac5a0a93f23a Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 15 Mar 2022 08:14:57 -0700 Subject: [PATCH 43/52] Search: sort results correctly when using postgres (#46466) (#46586) * Search: sort results correctly when using postgres postgresql puts nulls first while both mysql and sqlite puts them last * linting (cherry picked from commit f46038ed3a0cfc90f75d4d1b717b3782973c3117) Co-authored-by: Leonard Gram --- pkg/services/sqlstore/migrator/dialect.go | 6 ++++++ pkg/services/sqlstore/migrator/postgres_dialect.go | 13 +++++++++++++ pkg/services/sqlstore/searchstore/builder.go | 7 ++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/migrator/dialect.go b/pkg/services/sqlstore/migrator/dialect.go index 20cb7b4156f..8ca59d25eb1 100644 --- a/pkg/services/sqlstore/migrator/dialect.go +++ b/pkg/services/sqlstore/migrator/dialect.go @@ -27,6 +27,8 @@ type Dialect interface { BooleanStr(bool) string DateTimeFunc(string) string + OrderBy(order string) string + CreateIndexSQL(tableName string, index *Index) string CreateTableSQL(table *Table) string AddColumnSQL(tableName string, col *Column) string @@ -308,3 +310,7 @@ func (b *BaseDialect) Lock(_ LockCfg) error { func (b *BaseDialect) Unlock(_ LockCfg) error { return nil } + +func (b *BaseDialect) OrderBy(order string) string { + return order +} diff --git a/pkg/services/sqlstore/migrator/postgres_dialect.go b/pkg/services/sqlstore/migrator/postgres_dialect.go index 2978f98ef00..f361b792b31 100644 --- a/pkg/services/sqlstore/migrator/postgres_dialect.go +++ b/pkg/services/sqlstore/migrator/postgres_dialect.go @@ -284,6 +284,19 @@ func (db *PostgresDialect) Lock(cfg LockCfg) error { return nil } +// OrderBy normalizes ordering so that nulls end up last in sorting, which they do by default in both sqlite and mysql but not postgres +// order should be a string like `dashboard.id ASC` +func (db *PostgresDialect) OrderBy(order string) string { + nullSort := "FIRST" + normalizedOrder := strings.ToUpper(strings.TrimSpace(order)) + + if strings.HasSuffix(normalizedOrder, " DESC") { + nullSort = "LAST" + } + + return fmt.Sprintf("%s NULLS %s", order, nullSort) +} + func (db *PostgresDialect) Unlock(cfg LockCfg) error { // trying to release a previously-acquired exclusive session level advisory lock. // it will either return true if the lock is successfully released or diff --git a/pkg/services/sqlstore/searchstore/builder.go b/pkg/services/sqlstore/searchstore/builder.go index c27451bd7e7..1ae89d05de9 100644 --- a/pkg/services/sqlstore/searchstore/builder.go +++ b/pkg/services/sqlstore/searchstore/builder.go @@ -140,7 +140,12 @@ func (b *Builder) applyFilters() (ordering string) { b.params = append(b.params, groupParams...) } - orderBy := fmt.Sprintf(" ORDER BY %s", strings.Join(orders, ", ")) + orderByCols := []string{} + for _, o := range orders { + orderByCols = append(orderByCols, b.Dialect.OrderBy(o)) + } + + orderBy := fmt.Sprintf(" ORDER BY %s", strings.Join(orderByCols, ", ")) b.sql.WriteString(orderBy) order := strings.Join(orderJoins, "") From 29fc1213d358897ec3e32b264395dd86508a264e Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 15 Mar 2022 09:31:05 -0700 Subject: [PATCH 44/52] Dashboard: Validate refId when generating id for cloudwatch query (#46182) (#46542) * Validate refId when generating id for cloudwatch query * add test case when refId is a valid metric data id (cherry picked from commit 99b4dfc27dfba338195aa7fead70501dda6c0a87) Co-authored-by: Kevin Yu --- pkg/tsdb/cloudwatch/request_parser.go | 10 +++++++++- pkg/tsdb/cloudwatch/request_parser_test.go | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/pkg/tsdb/cloudwatch/request_parser.go b/pkg/tsdb/cloudwatch/request_parser.go index c0202c97383..0edb6cf4907 100644 --- a/pkg/tsdb/cloudwatch/request_parser.go +++ b/pkg/tsdb/cloudwatch/request_parser.go @@ -10,10 +10,13 @@ import ( "strings" "time" + "github.com/google/uuid" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/components/simplejson" ) +var validMetricDataID = regexp.MustCompile(`^[a-z][a-zA-Z0-9_]*$`) + // parseQueries parses the json queries and returns a map of cloudWatchQueries by region. The cloudWatchQuery has a 1 to 1 mapping to a query editor row func (e *cloudWatchExecutor) parseQueries(queries []backend.DataQuery, startTime time.Time, endTime time.Time) (map[string][]*cloudWatchQuery, error) { requestQueries := make(map[string][]*cloudWatchQuery) @@ -140,7 +143,12 @@ func parseRequestQuery(model *simplejson.Json, refId string, startTime time.Time // Why not just use refId if id is not specified in the frontend? When specifying an id in the editor, // and alphabetical must be used. The id must be unique, so if an id like for example a, b or c would be used, // it would likely collide with some ref id. That's why the `query` prefix is used. - id = fmt.Sprintf("query%s", refId) + suffix := refId + if !validMetricDataID.MatchString(suffix) { + uuid := uuid.NewString() + suffix = strings.Replace(uuid, "-", "", -1) + } + id = fmt.Sprintf("query%s", suffix) } expression := model.Get("expression").MustString("") sqlExpression := model.Get("sqlExpression").MustString("") diff --git a/pkg/tsdb/cloudwatch/request_parser_test.go b/pkg/tsdb/cloudwatch/request_parser_test.go index cadaff50aa6..cec43f954fc 100644 --- a/pkg/tsdb/cloudwatch/request_parser_test.go +++ b/pkg/tsdb/cloudwatch/request_parser_test.go @@ -293,6 +293,23 @@ func TestRequestParser(t *testing.T) { assert.Equal(t, GMDApiModeMathExpression, res.getGMDAPIMode()) }) }) + + t.Run("ID is the string `query` appended with refId if refId is a valid MetricData ID", func(t *testing.T) { + query := getBaseJsonQuery() + res, err := parseRequestQuery(query, "ref1", time.Now().Add(-2*time.Hour), time.Now().Add(-time.Hour)) + require.NoError(t, err) + assert.Equal(t, "ref1", res.RefId) + assert.Equal(t, "queryref1", res.Id) + }) + + t.Run("Valid id is generated if ID is not provided and refId is not a valid MetricData ID", func(t *testing.T) { + query := getBaseJsonQuery() + query.Set("refId", "$$") + res, err := parseRequestQuery(query, "$$", time.Now().Add(-2*time.Hour), time.Now().Add(-time.Hour)) + require.NoError(t, err) + assert.Equal(t, "$$", res.RefId) + assert.Regexp(t, validMetricDataID, res.Id) + }) } func getBaseJsonQuery() *simplejson.Json { From 7a0a6f7b293d19c329ff991b0e6914093e2fc2be Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 15 Mar 2022 10:28:48 -0700 Subject: [PATCH 45/52] Docs: Add description to remote cache configuration (#46589) (#46596) * Add description to remote cache configuration * Apply feedback (cherry picked from commit 8e13b201ba27c5ae62931f4b1cc6a3d6cfa6f7db) Co-authored-by: Tania B --- docs/sources/administration/configuration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/administration/configuration.md b/docs/sources/administration/configuration.md index a5f6fab22eb..7e66ccdf428 100644 --- a/docs/sources/administration/configuration.md +++ b/docs/sources/administration/configuration.md @@ -361,6 +361,8 @@ Defaults to `private`. ## [remote_cache] +Caches authentication details and session information in the configured database, Redis or Memcached. This setting does not configure [Query Caching in Grafana Enterprise]({{< relref "../enterprise/query-caching.md" >}}). + ### type Either `redis`, `memcached`, or `database`. Defaults to `database` From 7e963a111040153c92689c38b4568553c87f43fc Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 15 Mar 2022 10:54:02 -0700 Subject: [PATCH 46/52] Geomap: applying multiple line builders should keep each segment #46563 (#46597) --- .../spatial/spatialTransformer.ts | 11 ++++-- public/app/features/geo/format/utils.ts | 36 ++++++++++++++++++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/public/app/core/components/TransformersUI/spatial/spatialTransformer.ts b/public/app/core/components/TransformersUI/spatial/spatialTransformer.ts index b9ce98d34f4..03b2a4d2dc3 100644 --- a/public/app/core/components/TransformersUI/spatial/spatialTransformer.ts +++ b/public/app/core/components/TransformersUI/spatial/spatialTransformer.ts @@ -1,5 +1,5 @@ import { ArrayVector, DataFrame, DataTransformerID, DataTransformerInfo, FieldType } from '@grafana/data'; -import { createLineBetween } from 'app/features/geo/format/utils'; +import { createGeometryCollection, createLineBetween } from 'app/features/geo/format/utils'; import { getGeometryField, getLocationMatchers } from 'app/features/geo/utils/location'; import { mergeMap, from } from 'rxjs'; import { SpatialOperation, SpatialAction, SpatialTransformOptions } from './models.gen'; @@ -26,10 +26,17 @@ async function doSetGeometry(frames: DataFrame[], options: SpatialTransformOptio const src = getGeometryField(frame, location); const target = getGeometryField(frame, targetLocation); if (src.field && target.field) { + const fields = [...frame.fields]; const line = createLineBetween(src.field, target.field); + const first = fields[0]; + if (first.type === FieldType.geo && first !== src.field && first !== target.field) { + fields[0] = createGeometryCollection(first, line); // + } else { + fields.unshift(line); + } return { ...frame, - fields: [line, ...frame.fields], + fields, }; } return frame; diff --git a/public/app/features/geo/format/utils.ts b/public/app/features/geo/format/utils.ts index 8dd793dae6b..623673d75d2 100644 --- a/public/app/features/geo/format/utils.ts +++ b/public/app/features/geo/format/utils.ts @@ -1,6 +1,6 @@ import { ArrayVector, Field, FieldConfig, FieldType } from '@grafana/data'; import { getCenterPoint } from 'app/core/components/TransformersUI/spatial/utils'; -import { Geometry, LineString, Point } from 'ol/geom'; +import { Geometry, GeometryCollection, LineString, Point } from 'ol/geom'; import { fromLonLat } from 'ol/proj'; import { Gazetteer } from '../gazetteer/gazetteer'; import { decodeGeohash } from './geohash'; @@ -50,6 +50,40 @@ export function getGeoFieldFromGazetteer(gaz: Gazetteer, field: Field): }; } +export function createGeometryCollection( + src: Field, + dest: Field +): Field { + const v0 = src.values.toArray(); + const v1 = dest.values.toArray(); + if (!v0 || !v1) { + throw 'missing src/dest'; + } + if (v0.length !== v1.length) { + throw 'Source and destination field lengths do not match'; + } + + const count = src.values.length!; + const geo = new Array(count); + for (let i = 0; i < count; i++) { + const a = v0[i]; + const b = v1[i]; + if (a && b) { + geo[i] = new GeometryCollection([a, b]); + } else if (a) { + geo[i] = a; + } else if (b) { + geo[i] = b; + } + } + return { + name: 'Geometry', + type: FieldType.geo, + values: new ArrayVector(geo), + config: hiddenTooltipField, + }; +} + export function createLineBetween( src: Field, dest: Field From cf2e7779ec93e78027f8c5698b1ff72f47ae2be3 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 15 Mar 2022 11:01:58 -0700 Subject: [PATCH 47/52] Grafana-UI: Fix use of Fragments as children of InlineField (#46326) (#46603) (cherry picked from commit 00c93fff8c0d0ad5c500983f9279c89a58dfcad7) Co-authored-by: Joao Silva <100691367+JoaoSilvaGrafana@users.noreply.github.com> --- .../DataSourceSettings/CertificationKey.tsx | 25 +++++++++++-------- .../components/SubMenu/AnnotationPicker.tsx | 16 ++++++------ .../panel/geomap/editor/StyleRuleEditor.tsx | 9 +++++-- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/packages/grafana-ui/src/components/DataSourceSettings/CertificationKey.tsx b/packages/grafana-ui/src/components/DataSourceSettings/CertificationKey.tsx index 1b8406ca8ff..56e238464a8 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/CertificationKey.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/CertificationKey.tsx @@ -3,6 +3,7 @@ import { Input } from '../Input/Input'; import { Button } from '../Button'; import { TextArea } from '../TextArea/TextArea'; import { InlineField } from '../Forms/InlineField'; +import { InlineFieldRow } from '../Forms/InlineFieldRow'; interface Props { label: string; @@ -15,17 +16,19 @@ interface Props { export const CertificationKey: FC = ({ hasCert, label, onChange, onClick, placeholder }) => { return ( - - {hasCert ? ( - <> - - - - ) : ( -