From a03896d83d4958de4937872ef476b08443f86d4e Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 26 Jul 2022 04:49:50 -0500 Subject: [PATCH 01/24] Docs: Update docs for images in notifications (#52721) (#52785) (cherry picked from commit 23fc5383d907984451d98de8ca52009e66419080) Co-authored-by: George Robinson --- conf/defaults.ini | 4 ++-- docs/sources/alerting/images-in-notifications.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index b2034acc9ac..397f49bed26 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -842,8 +842,8 @@ max_attempts = 3 min_interval = 10s [unified_alerting.screenshots] -# Enable screenshots in notifications. This option requires a remote HTTP image rendering service. Please -# see [rendering] for further configuration options. +# Enable screenshots in notifications. This option requires the Grafana Image Renderer plugin. +# For more information on configuration options, refer to [rendering]. capture = false # The maximum number of screenshots that can be taken at the same time. This option is different from diff --git a/docs/sources/alerting/images-in-notifications.md b/docs/sources/alerting/images-in-notifications.md index 6a9bf09b9d7..1e773f0acd4 100644 --- a/docs/sources/alerting/images-in-notifications.md +++ b/docs/sources/alerting/images-in-notifications.md @@ -34,8 +34,8 @@ To use images in notifications, Grafana must be set up to use [image rendering]( If Grafana has been set up to use [image rendering]({{< relref "../setup-grafana/image-rendering/" >}}) images in notifications can be turned on via the `capture` option in `[unified_alerting.screenshots]`: - # Enable screenshots in notifications. This option requires a remote HTTP image rendering service. Please - # see [rendering] for further configuration options. + # Enable screenshots in notifications. This option requires the Grafana Image Renderer plugin. + # For more information on configuration options, refer to [rendering]. capture = true It is recommended that `max_concurrent_screenshots` is set to a value that is less than or equal to `concurrent_render_request_limit`. The default value for both `max_concurrent_screenshots` and `concurrent_render_request_limit` is `5`: From 12a81fe0bf5c2c2c6e4adac7dd5b67047f9476f9 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:04:47 -0500 Subject: [PATCH 02/24] "Release: Updated versions in package to 9.0.5" (#52812) --- 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 | 10 ++-- .../internal/input-datasource/package.json | 8 ++-- yarn.lock | 46 +++++++++---------- 12 files changed, 51 insertions(+), 51 deletions(-) diff --git a/lerna.json b/lerna.json index 21011710415..89ce6b595f4 100644 --- a/lerna.json +++ b/lerna.json @@ -4,5 +4,5 @@ "packages": [ "packages/*" ], - "version": "9.0.4" + "version": "9.0.5" } diff --git a/package.json b/package.json index 6798a76467e..2741ac47bf8 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "grafana", - "version": "9.0.4", + "version": "9.0.5", "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 94ba6a12c52..cc038353b86 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": "9.0.4", + "version": "9.0.5", "description": "Grafana Data Library", "keywords": [ "typescript" @@ -22,7 +22,7 @@ }, "dependencies": { "@braintree/sanitize-url": "6.0.0", - "@grafana/schema": "9.0.4", + "@grafana/schema": "9.0.5", "@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 442d1cb18ae..905a77f13ac 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": "9.0.4", + "version": "9.0.5", "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 97d47aaa30a..5830966d431 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": "9.0.4", + "version": "9.0.5", "description": "Grafana End-to-End Test Library", "keywords": [ "cli", @@ -48,7 +48,7 @@ "@babel/core": "7.17.8", "@babel/preset-env": "7.17.10", "@cypress/webpack-preprocessor": "5.11.1", - "@grafana/e2e-selectors": "9.0.4", + "@grafana/e2e-selectors": "9.0.5", "@grafana/tsconfig": "^1.2.0-rc1", "@mochajs/json-file-reporter": "^1.2.0", "babel-loader": "8.2.5", diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index bb232a8250c..5c823c6a14a 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": "9.0.4", + "version": "9.0.5", "description": "Grafana Runtime Library", "keywords": [ "grafana", @@ -22,9 +22,9 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@grafana/data": "9.0.4", - "@grafana/e2e-selectors": "9.0.4", - "@grafana/ui": "9.0.4", + "@grafana/data": "9.0.5", + "@grafana/e2e-selectors": "9.0.5", + "@grafana/ui": "9.0.5", "@sentry/browser": "6.19.7", "history": "4.10.1", "lodash": "4.17.21", diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index a07c77ccc72..9176832f07f 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": "9.0.4", + "version": "9.0.5", "description": "Grafana Schema Library", "keywords": [ "typescript" diff --git a/packages/grafana-toolkit/package.json b/packages/grafana-toolkit/package.json index 60525323337..a5d6720066c 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": "9.0.4", + "version": "9.0.5", "description": "Grafana Toolkit", "keywords": [ "grafana", @@ -38,10 +38,10 @@ "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.16.7", - "@grafana/data": "9.0.4", + "@grafana/data": "9.0.5", "@grafana/eslint-config": "^4.0.0", "@grafana/tsconfig": "^1.2.0-rc1", - "@grafana/ui": "9.0.4", + "@grafana/ui": "9.0.5", "@jest/core": "27.5.1", "@types/command-exists": "^1.2.0", "@types/eslint": "8.4.1", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 83700a07f4b..b5a0609b8be 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": "9.0.4", + "version": "9.0.5", "description": "Grafana Components Library", "keywords": [ "grafana", @@ -33,9 +33,9 @@ "@emotion/css": "11.9.0", "@emotion/react": "11.9.0", "@grafana/aws-sdk": "0.0.36", - "@grafana/data": "9.0.4", - "@grafana/e2e-selectors": "9.0.4", - "@grafana/schema": "9.0.4", + "@grafana/data": "9.0.5", + "@grafana/e2e-selectors": "9.0.5", + "@grafana/schema": "9.0.5", "@grafana/slate-react": "0.22.10-grafana", "@monaco-editor/react": "4.3.1", "@popperjs/core": "2.11.5", diff --git a/packages/jaeger-ui-components/package.json b/packages/jaeger-ui-components/package.json index e34d8f93336..8ada45fd21a 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": "9.0.4", + "version": "9.0.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,10 +28,10 @@ }, "dependencies": { "@emotion/css": "11.9.0", - "@grafana/data": "9.0.4", - "@grafana/e2e-selectors": "9.0.4", - "@grafana/runtime": "9.0.4", - "@grafana/ui": "9.0.4", + "@grafana/data": "9.0.5", + "@grafana/e2e-selectors": "9.0.5", + "@grafana/runtime": "9.0.5", + "@grafana/ui": "9.0.5", "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 fbd7c797fbb..9d846d31df1 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": "9.0.4", + "version": "9.0.5", "description": "Input Datasource", "private": true, "repository": { @@ -15,15 +15,15 @@ }, "author": "Grafana Labs", "devDependencies": { - "@grafana/toolkit": "9.0.4", + "@grafana/toolkit": "9.0.5", "@types/jest": "26.0.15", "@types/lodash": "4.14.149", "@types/react": "17.0.30", "lodash": "4.17.21" }, "dependencies": { - "@grafana/data": "9.0.4", - "@grafana/ui": "9.0.4", + "@grafana/data": "9.0.5", + "@grafana/ui": "9.0.5", "jquery": "3.5.1", "react": "17.0.1", "react-dom": "17.0.1", diff --git a/yarn.lock b/yarn.lock index 57c95965ad0..1bb1a2f2f50 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3791,9 +3791,9 @@ __metadata: version: 0.0.0-use.local resolution: "@grafana-plugins/input-datasource@workspace:plugins-bundled/internal/input-datasource" dependencies: - "@grafana/data": 9.0.4 - "@grafana/toolkit": 9.0.4 - "@grafana/ui": 9.0.4 + "@grafana/data": 9.0.5 + "@grafana/toolkit": 9.0.5 + "@grafana/ui": 9.0.5 "@types/jest": 26.0.15 "@types/lodash": 4.14.149 "@types/react": 17.0.30 @@ -3831,12 +3831,12 @@ __metadata: languageName: node linkType: hard -"@grafana/data@9.0.4, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data": +"@grafana/data@9.0.5, @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": 6.0.0 - "@grafana/schema": 9.0.4 + "@grafana/schema": 9.0.5 "@grafana/tsconfig": ^1.2.0-rc1 "@rollup/plugin-commonjs": 22.0.0 "@rollup/plugin-json": 4.1.0 @@ -3889,7 +3889,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/e2e-selectors@9.0.4, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": +"@grafana/e2e-selectors@9.0.5, @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: @@ -3913,7 +3913,7 @@ __metadata: "@babel/core": 7.17.8 "@babel/preset-env": 7.17.10 "@cypress/webpack-preprocessor": 5.11.1 - "@grafana/e2e-selectors": 9.0.4 + "@grafana/e2e-selectors": 9.0.5 "@grafana/tsconfig": ^1.2.0-rc1 "@mochajs/json-file-reporter": ^1.2.0 "@rollup/plugin-commonjs": 22.0.0 @@ -3998,14 +3998,14 @@ __metadata: languageName: node linkType: hard -"@grafana/runtime@9.0.4, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime": +"@grafana/runtime@9.0.5, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime": version: 0.0.0-use.local resolution: "@grafana/runtime@workspace:packages/grafana-runtime" dependencies: - "@grafana/data": 9.0.4 - "@grafana/e2e-selectors": 9.0.4 + "@grafana/data": 9.0.5 + "@grafana/e2e-selectors": 9.0.5 "@grafana/tsconfig": ^1.2.0-rc1 - "@grafana/ui": 9.0.4 + "@grafana/ui": 9.0.5 "@rollup/plugin-commonjs": 22.0.0 "@rollup/plugin-node-resolve": 13.3.0 "@sentry/browser": 6.19.7 @@ -4034,7 +4034,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/schema@9.0.4, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": +"@grafana/schema@9.0.5, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": version: 0.0.0-use.local resolution: "@grafana/schema@workspace:packages/grafana-schema" dependencies: @@ -4081,7 +4081,7 @@ __metadata: languageName: node linkType: hard -"@grafana/toolkit@9.0.4, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit": +"@grafana/toolkit@9.0.5, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit": version: 0.0.0-use.local resolution: "@grafana/toolkit@workspace:packages/grafana-toolkit" dependencies: @@ -4097,10 +4097,10 @@ __metadata: "@babel/preset-env": ^7.16.11 "@babel/preset-react": ^7.16.7 "@babel/preset-typescript": ^7.16.7 - "@grafana/data": 9.0.4 + "@grafana/data": 9.0.5 "@grafana/eslint-config": ^4.0.0 "@grafana/tsconfig": ^1.2.0-rc1 - "@grafana/ui": 9.0.4 + "@grafana/ui": 9.0.5 "@jest/core": 27.5.1 "@types/command-exists": ^1.2.0 "@types/eslint": 8.4.1 @@ -4184,7 +4184,7 @@ __metadata: languageName: node linkType: hard -"@grafana/ui@9.0.4, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": +"@grafana/ui@9.0.5, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": version: 0.0.0-use.local resolution: "@grafana/ui@workspace:packages/grafana-ui" dependencies: @@ -4192,9 +4192,9 @@ __metadata: "@emotion/css": 11.9.0 "@emotion/react": 11.9.0 "@grafana/aws-sdk": 0.0.36 - "@grafana/data": 9.0.4 - "@grafana/e2e-selectors": 9.0.4 - "@grafana/schema": 9.0.4 + "@grafana/data": 9.0.5 + "@grafana/e2e-selectors": 9.0.5 + "@grafana/schema": 9.0.5 "@grafana/slate-react": 0.22.10-grafana "@grafana/tsconfig": ^1.2.0-rc1 "@mdx-js/react": 1.6.22 @@ -4431,11 +4431,11 @@ __metadata: resolution: "@jaegertracing/jaeger-ui-components@workspace:packages/jaeger-ui-components" dependencies: "@emotion/css": 11.9.0 - "@grafana/data": 9.0.4 - "@grafana/e2e-selectors": 9.0.4 - "@grafana/runtime": 9.0.4 + "@grafana/data": 9.0.5 + "@grafana/e2e-selectors": 9.0.5 + "@grafana/runtime": 9.0.5 "@grafana/tsconfig": ^1.2.0-rc1 - "@grafana/ui": 9.0.4 + "@grafana/ui": 9.0.5 "@testing-library/react": 12.1.4 "@testing-library/user-event": 14.2.0 "@types/classnames": ^2.2.7 From b5e3497ce50489f495a4648d122bab58266983a0 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:16:41 -0500 Subject: [PATCH 03/24] ReleaseNotes: Updated changelog and release notes for 9.0.5 (#52813) (#52815) (cherry picked from commit c20d96aaeca19ec52870f8ac6daf951be55bc275) --- CHANGELOG.md | 23 ++++++++++++++++ docs/sources/release-notes/_index.md | 1 + .../release-notes/release-notes-9-0-5.md | 26 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 docs/sources/release-notes/release-notes-9-0-5.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 99aea323021..a9ef3115678 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ + + +# 9.0.5 (2022-07-26) + +### Features and enhancements + +- **Access control:** Show dashboard settings to users who can edit dashboard. [#52535](https://github.com/grafana/grafana/pull/52535), [@grafanabot](https://github.com/grafanabot) +- **Alerting:** Allow the webhook notifier to support a custom Authorization header. [#52515](https://github.com/grafana/grafana/pull/52515), [@gotjosh](https://github.com/gotjosh) +- **Chore:** Upgrade to Go version 1.17.12. [#52523](https://github.com/grafana/grafana/pull/52523), [@sakjur](https://github.com/sakjur) +- **Plugins:** Add signature wildcard globbing for dedicated private plugin type. [#52163](https://github.com/grafana/grafana/pull/52163), [@wbrowne](https://github.com/wbrowne) +- **Prometheus:** Don't show errors from unsuccessful API checks like rules or exemplar checks. [#52193](https://github.com/grafana/grafana/pull/52193), [@darrenjaneczek](https://github.com/darrenjaneczek) + +### Bug fixes + +- **Access control:** Allow organisation admins to add existing users to org (#51668). [#52553](https://github.com/grafana/grafana/pull/52553), [@vtorosyan](https://github.com/vtorosyan) +- **Alerting:** Fix alert panel instance-based rules filtering. [#52583](https://github.com/grafana/grafana/pull/52583), [@konrad147](https://github.com/konrad147) +- **Apps:** Fixes navigation between different app plugin pages. [#52571](https://github.com/grafana/grafana/pull/52571), [@torkelo](https://github.com/torkelo) +- **Cloudwatch:** Upgrade grafana-aws-sdk to fix auth issue with secret keys. [#52420](https://github.com/grafana/grafana/pull/52420), [@sarahzinger](https://github.com/sarahzinger) +- **Grafana/toolkit:** Fix incorrect image and font generation for plugin builds. [#52661](https://github.com/grafana/grafana/pull/52661), [@academo](https://github.com/academo) +- **Loki:** Fix `show context` not working in some occasions. [#52458](https://github.com/grafana/grafana/pull/52458), [@svennergr](https://github.com/svennergr) +- **RBAC:** Fix permissions on dashboards and folders created by anonymous users. [#52615](https://github.com/grafana/grafana/pull/52615), [@gamab](https://github.com/gamab) + + # 9.0.4 (2022-07-20) diff --git a/docs/sources/release-notes/_index.md b/docs/sources/release-notes/_index.md index 1a95f148b46..796d7d6009e 100644 --- a/docs/sources/release-notes/_index.md +++ b/docs/sources/release-notes/_index.md @@ -10,6 +10,7 @@ weight: 10000 Here you can find detailed release notes that list everything that is included in every release as well as notices about deprecations, breaking changes as well as changes that relate to plugin development. +- [Release notes for 9.0.5]({{< relref "release-notes-9-0-5" >}}) - [Release notes for 9.0.4]({{< relref "release-notes-9-0-4" >}}) - [Release notes for 9.0.3]({{< relref "release-notes-9-0-3" >}}) - [Release notes for 9.0.2]({{< relref "release-notes-9-0-2" >}}) diff --git a/docs/sources/release-notes/release-notes-9-0-5.md b/docs/sources/release-notes/release-notes-9-0-5.md new file mode 100644 index 00000000000..135f9d1828f --- /dev/null +++ b/docs/sources/release-notes/release-notes-9-0-5.md @@ -0,0 +1,26 @@ ++++ +title = "Release notes for Grafana 9.0.5" +hide_menu = true ++++ + + + +# Release notes for Grafana 9.0.5 + +### Features and enhancements + +- **Access control:** Show dashboard settings to users who can edit dashboard. [#52535](https://github.com/grafana/grafana/pull/52535), [@grafanabot](https://github.com/grafanabot) +- **Alerting:** Allow the webhook notifier to support a custom Authorization header. [#52515](https://github.com/grafana/grafana/pull/52515), [@gotjosh](https://github.com/gotjosh) +- **Chore:** Upgrade to Go version 1.17.12. [#52523](https://github.com/grafana/grafana/pull/52523), [@sakjur](https://github.com/sakjur) +- **Plugins:** Add signature wildcard globbing for dedicated private plugin type. [#52163](https://github.com/grafana/grafana/pull/52163), [@wbrowne](https://github.com/wbrowne) +- **Prometheus:** Don't show errors from unsuccessful API checks like rules or exemplar checks. [#52193](https://github.com/grafana/grafana/pull/52193), [@darrenjaneczek](https://github.com/darrenjaneczek) + +### Bug fixes + +- **Access control:** Allow organisation admins to add existing users to org (#51668). [#52553](https://github.com/grafana/grafana/pull/52553), [@vtorosyan](https://github.com/vtorosyan) +- **Alerting:** Fix alert panel instance-based rules filtering. [#52583](https://github.com/grafana/grafana/pull/52583), [@konrad147](https://github.com/konrad147) +- **Apps:** Fixes navigation between different app plugin pages. [#52571](https://github.com/grafana/grafana/pull/52571), [@torkelo](https://github.com/torkelo) +- **Cloudwatch:** Upgrade grafana-aws-sdk to fix auth issue with secret keys. [#52420](https://github.com/grafana/grafana/pull/52420), [@sarahzinger](https://github.com/sarahzinger) +- **Grafana/toolkit:** Fix incorrect image and font generation for plugin builds. [#52661](https://github.com/grafana/grafana/pull/52661), [@academo](https://github.com/academo) +- **Loki:** Fix `show context` not working in some occasions. [#52458](https://github.com/grafana/grafana/pull/52458), [@svennergr](https://github.com/svennergr) +- **RBAC:** Fix permissions on dashboards and folders created by anonymous users. [#52615](https://github.com/grafana/grafana/pull/52615), [@gamab](https://github.com/gamab) From 16d7897e1c07f2c855f3919526e3d6f7241cb2f6 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:44:52 -0500 Subject: [PATCH 04/24] UI/Card: Fix card items always having pointer cursor (#52809) (#52817) (cherry picked from commit aa8c9d86dddcdd3af9736087317afbc83a744ea1) Co-authored-by: Gilles De Mey --- packages/grafana-ui/src/components/Card/CardContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Card/CardContainer.tsx b/packages/grafana-ui/src/components/Card/CardContainer.tsx index 3f81e998dc2..09a2a25df79 100644 --- a/packages/grafana-ui/src/components/Card/CardContainer.tsx +++ b/packages/grafana-ui/src/components/Card/CardContainer.tsx @@ -67,7 +67,7 @@ export const CardContainer = ({ }; export const getCardContainerStyles = stylesFactory( - (theme: GrafanaTheme2, disabled = false, disableHover = false, isSelected = false) => { + (theme: GrafanaTheme2, disabled = false, disableHover = false, isSelected) => { const isSelectable = isSelected !== undefined; return { From be2d8320fc74d61d164d7d870bfb1f8558c6310c Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 26 Jul 2022 12:25:17 -0500 Subject: [PATCH 05/24] Docs: updated configuration description for auto_assign_org (#50180) (#52835) * Docs: updated configuration description for auto_assign_org * Update docs/sources/setup-grafana/configure-grafana/_index.md * Resolve prettier issues for CI * Update docs/sources/setup-grafana/configure-grafana/_index.md Remove trailing space Co-authored-by: Garrett Guillotte <100453168+gguillotte-grafana@users.noreply.github.com> Co-authored-by: Garrett Guillotte Co-authored-by: Ursula Kallio (cherry picked from commit 6968478d94441a07c4be06778bd6924c16f017aa) Co-authored-by: gjed --- docs/sources/setup-grafana/configure-grafana/_index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 912bf9b0e3f..a871ce5ca14 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -675,7 +675,8 @@ Default is `false`. Set to `true` to automatically add new users to the main organization (id 1). When set to `false`, new users automatically cause a new -organization to be created for that new user. Default is `true`. +organization to be created for that new user. The organization will be +created even if the `allow_org_create` setting is set to `false`. Default is `true`. ### auto_assign_org_id From e78045a05942b0f12488b411367e7418157f3d23 Mon Sep 17 00:00:00 2001 From: Armand Grillet <2117580+armandgrillet@users.noreply.github.com> Date: Wed, 27 Jul 2022 10:26:19 +0200 Subject: [PATCH 06/24] Docs: fix data source MSSQL link (#52854) --- docs/sources/alerting/fundamentals/data-source-alerting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/alerting/fundamentals/data-source-alerting.md b/docs/sources/alerting/fundamentals/data-source-alerting.md index adb43109506..a868149de2f 100644 --- a/docs/sources/alerting/fundamentals/data-source-alerting.md +++ b/docs/sources/alerting/fundamentals/data-source-alerting.md @@ -23,7 +23,7 @@ These are the data sources that are compatible with and supported by Grafana Ale - [Graphite]({{< relref "../../datasources/graphite/" >}}) - [InfluxDB]({{< relref "../../datasources/influxdb/" >}}) - [Loki]({{< relref "../../datasources/loki/" >}}) -- ]Microsoft SQL Server (MSSQL)]({{< relref "../../datasources/mssql/" >}}) +- [Microsoft SQL Server (MSSQL)]({{< relref "../../datasources/mssql/" >}}) - [MySQL]({{< relref "../../datasources/mysql/" >}}) - [Open TSDB]({{< relref "../../datasources/opentsdb/" >}}) - [PostgreSQL]({{< relref "../../datasources/postgres/" >}}) From 7e1df2ac61ab9cd11527691f673d52f337263bf8 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 27 Jul 2022 07:02:17 -0500 Subject: [PATCH 07/24] Instrumentation: Collect database connection stats (#52797) (#52866) Signed-off-by: bergquist (cherry picked from commit 7825ad6aa309a6bbdafe7e692460374f156a1c10) Co-authored-by: Carl Bergquist --- pkg/services/sqlstore/sqlstore.go | 129 ++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 240637389cd..3b9d70f1908 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -13,6 +13,7 @@ import ( "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" + "github.com/prometheus/client_golang/prometheus" "xorm.io/xorm" "github.com/grafana/grafana/pkg/bus" @@ -53,6 +54,17 @@ type SQLStore struct { skipEnsureDefaultOrgAndUser bool migrations registry.DatabaseMigrator tracer tracing.Tracer + metrics struct { + maxOpenConnections prometheus.Gauge + openConnections prometheus.Gauge + inUse prometheus.Gauge + idle prometheus.Gauge + waitCount prometheus.Counter + waitDuration prometheus.Counter + maxIdleClosed prometheus.Counter + maxIdleTimeClosed prometheus.Counter + maxLifetimeClosed prometheus.Counter + } } func ProvideService(cfg *setting.Cfg, cacheService *localcache.CacheService, migrations registry.DatabaseMigrator, bus bus.Bus, tracer tracing.Tracer) (*SQLStore, error) { @@ -73,6 +85,11 @@ func ProvideService(cfg *setting.Cfg, cacheService *localcache.CacheService, mig return nil, err } s.tracer = tracer + + s.initMetrics() + + prometheus.MustRegister(s) + return s, nil } @@ -425,6 +442,118 @@ func (ss *SQLStore) readConfig() error { return nil } +// initMetrics initializes the database connection metrics +func (ss *SQLStore) initMetrics() { + namespace := "grafana" + subsystem := "database" + + ss.metrics.maxOpenConnections = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "conn_max_open", + Help: "Maximum number of open connections to the database", + }) + + ss.metrics.openConnections = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "conn_open", + Help: "The number of established connections both in use and idle", + }) + + ss.metrics.inUse = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "conn_in_use", + Help: "The number of connections currently in use", + }) + + ss.metrics.idle = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "conn_idle", + Help: "The number of idle connections", + }) + + ss.metrics.waitCount = prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "conn_wait_count_total", + Help: "The total number of connections waited for", + }) + + ss.metrics.waitDuration = prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "conn_wait_duration_seconds", + Help: "The total time blocked waiting for a new connection", + }) + + ss.metrics.maxIdleClosed = prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "conn_max_idle_closed_total", + Help: "The total number of connections closed due to SetMaxIdleConns", + }) + + ss.metrics.maxIdleTimeClosed = prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "conn_max_idle_closed_seconds", + Help: "The total number of connections closed due to SetConnMaxIdleTime", + }) + + ss.metrics.maxLifetimeClosed = prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "conn_max_lifetime_closed_total", + Help: "The total number of connections closed due to SetConnMaxLifetime", + }) +} + +// collectDBStats instruments connections stats from the database. +func (ss *SQLStore) collectDBstats() { + dbstats := ss.engine.DB().Stats() + ss.metrics.maxOpenConnections.Set(float64(dbstats.MaxOpenConnections)) + ss.metrics.openConnections.Set(float64(dbstats.MaxOpenConnections)) + ss.metrics.inUse.Set(float64(dbstats.InUse)) + ss.metrics.idle.Set(float64(dbstats.Idle)) + + ss.metrics.waitCount.Add(float64(dbstats.WaitCount)) + ss.metrics.waitDuration.Add(float64(dbstats.WaitDuration / time.Second)) + ss.metrics.maxIdleClosed.Add(float64(dbstats.MaxIdleClosed)) + ss.metrics.maxIdleTimeClosed.Add(float64(dbstats.MaxIdleTimeClosed)) + ss.metrics.maxLifetimeClosed.Add(float64(dbstats.MaxLifetimeClosed)) +} + +// Collect implements Prometheus.Collector. +func (ss *SQLStore) Collect(ch chan<- prometheus.Metric) { + ss.collectDBstats() + + ss.metrics.maxOpenConnections.Collect(ch) + ss.metrics.openConnections.Collect(ch) + ss.metrics.inUse.Collect(ch) + ss.metrics.idle.Collect(ch) + ss.metrics.waitCount.Collect(ch) + ss.metrics.waitDuration.Collect(ch) + ss.metrics.maxIdleClosed.Collect(ch) + ss.metrics.maxIdleTimeClosed.Collect(ch) + ss.metrics.maxLifetimeClosed.Collect(ch) +} + +// Describe implements Prometheus.Collector. +func (ss *SQLStore) Describe(ch chan<- *prometheus.Desc) { + ss.metrics.maxOpenConnections.Describe(ch) + ss.metrics.openConnections.Describe(ch) + ss.metrics.inUse.Describe(ch) + ss.metrics.idle.Describe(ch) + ss.metrics.waitCount.Describe(ch) + ss.metrics.waitDuration.Describe(ch) + ss.metrics.maxIdleClosed.Describe(ch) + ss.metrics.maxIdleTimeClosed.Describe(ch) + ss.metrics.maxLifetimeClosed.Describe(ch) +} + // ITestDB is an interface of arguments for testing db type ITestDB interface { Helper() From 649e3aaf2ecb1cbcf5ef56f1aa4878cc9faa6d90 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 27 Jul 2022 07:43:30 -0500 Subject: [PATCH 08/24] Plugins: Add validation for plugin manifest (#52787) (#52864) * add validation for plugin manifest * no more semver checking * undo go.mod changes * undo go.mod changes * only validate v2 fields where necessary * remove manifest version field check (cherry picked from commit 6d066a7aa89615315002875125900ff168fd2420) Co-authored-by: Will Browne --- pkg/plugins/manager/signature/manifest.go | 63 +++++++++++--- .../manager/signature/manifest_test.go | 84 +++++++++++++++++++ pkg/plugins/models.go | 10 +++ 3 files changed, 147 insertions(+), 10 deletions(-) diff --git a/pkg/plugins/manager/signature/manifest.go b/pkg/plugins/manager/signature/manifest.go index 9643b7e778a..49b126390b3 100644 --- a/pkg/plugins/manager/signature/manifest.go +++ b/pkg/plugins/manager/signature/manifest.go @@ -16,6 +16,7 @@ import ( "strings" "github.com/gobwas/glob" + // TODO: replace deprecated `golang.org/x/crypto` package https://github.com/grafana/grafana/issues/46050 // nolint:staticcheck "golang.org/x/crypto/openpgp" @@ -88,15 +89,8 @@ func readPluginManifest(body []byte) (*pluginManifest, error) { return nil, fmt.Errorf("%v: %w", "Error parsing manifest JSON", err) } - keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(publicKeyText)) - if err != nil { - return nil, fmt.Errorf("%v: %w", "failed to parse public key", err) - } - - if _, err := openpgp.CheckDetachedSignature(keyring, - bytes.NewBuffer(block.Bytes), - block.ArmoredSignature.Body); err != nil { - return nil, fmt.Errorf("%v: %w", "failed to check signature", err) + if err = validateManifest(manifest, block); err != nil { + return nil, err } return &manifest, nil @@ -132,7 +126,7 @@ func Calculate(mlog log.Logger, plugin *plugins.Plugin) (plugins.Signature, erro manifest, err := readPluginManifest(byteValue) if err != nil { - mlog.Debug("Plugin signature invalid", "id", plugin.ID) + mlog.Debug("Plugin signature invalid", "id", plugin.ID, "err", err) return plugins.Signature{ Status: plugins.SignatureInvalid, }, nil @@ -314,3 +308,52 @@ func urlMatch(specs []string, target string, signatureType plugins.SignatureType } return false, nil } + +type invalidFieldErr struct { + field string +} + +func (r invalidFieldErr) Error() string { + return fmt.Sprintf("valid manifest field %s is required", r.field) +} + +func validateManifest(m pluginManifest, block *clearsign.Block) error { + if len(m.Plugin) == 0 { + return invalidFieldErr{field: "plugin"} + } + if len(m.Version) == 0 { + return invalidFieldErr{field: "version"} + } + if len(m.KeyID) == 0 { + return invalidFieldErr{field: "keyId"} + } + if m.Time == 0 { + return invalidFieldErr{field: "time"} + } + if len(m.Files) == 0 { + return invalidFieldErr{field: "files"} + } + if m.isV2() { + if len(m.SignedByOrg) == 0 { + return invalidFieldErr{field: "signedByOrg"} + } + if len(m.SignedByOrgName) == 0 { + return invalidFieldErr{field: "signedByOrgName"} + } + if !m.SignatureType.IsValid() { + return fmt.Errorf("%s is not a valid signature type", m.SignatureType) + } + } + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(publicKeyText)) + if err != nil { + return fmt.Errorf("%v: %w", "failed to parse public key", err) + } + + if _, err = openpgp.CheckDetachedSignature(keyring, + bytes.NewBuffer(block.Bytes), + block.ArmoredSignature.Body); err != nil { + return fmt.Errorf("%v: %w", "failed to check signature", err) + } + + return nil +} diff --git a/pkg/plugins/manager/signature/manifest_test.go b/pkg/plugins/manager/signature/manifest_test.go index d3857a6a580..518274e6d1f 100644 --- a/pkg/plugins/manager/signature/manifest_test.go +++ b/pkg/plugins/manager/signature/manifest_test.go @@ -393,3 +393,87 @@ func Test_urlMatch_private(t *testing.T) { }) } } + +func Test_validateManifest(t *testing.T) { + tcs := []struct { + name string + manifest *pluginManifest + expectedErr string + }{ + { + name: "Empty plugin field", + manifest: createV2Manifest(t, func(m *pluginManifest) { m.Plugin = "" }), + expectedErr: "valid manifest field plugin is required", + }, + { + name: "Empty keyId field", + manifest: createV2Manifest(t, func(m *pluginManifest) { m.KeyID = "" }), + expectedErr: "valid manifest field keyId is required", + }, + { + name: "Empty signedByOrg field", + manifest: createV2Manifest(t, func(m *pluginManifest) { m.SignedByOrg = "" }), + expectedErr: "valid manifest field signedByOrg is required", + }, + { + name: "Empty signedByOrgName field", + manifest: createV2Manifest(t, func(m *pluginManifest) { m.SignedByOrgName = "" }), + expectedErr: "valid manifest field SignedByOrgName is required", + }, + { + name: "Empty signatureType field", + manifest: createV2Manifest(t, func(m *pluginManifest) { m.SignatureType = "" }), + expectedErr: "valid manifest field signatureType is required", + }, + { + name: "Invalid signatureType field", + manifest: createV2Manifest(t, func(m *pluginManifest) { m.SignatureType = "invalidSignatureType" }), + expectedErr: "valid manifest field signatureType is required", + }, + { + name: "Empty files field", + manifest: createV2Manifest(t, func(m *pluginManifest) { m.Files = map[string]string{} }), + expectedErr: "valid manifest field files is required", + }, + { + name: "Empty time field", + manifest: createV2Manifest(t, func(m *pluginManifest) { m.Time = 0 }), + expectedErr: "valid manifest field time is required", + }, + { + name: "Empty version field", + manifest: createV2Manifest(t, func(m *pluginManifest) { m.Version = "" }), + expectedErr: "valid manifest field version is required", + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + err := validateManifest(*tc.manifest, nil) + require.Errorf(t, err, tc.expectedErr) + }) + } +} + +func createV2Manifest(t *testing.T, cbs ...func(*pluginManifest)) *pluginManifest { + t.Helper() + + m := &pluginManifest{ + Plugin: "grafana-test-app", + Version: "2.5.3", + KeyID: "7e4d0c6a708866e7", + Time: 1586817677115, + Files: map[string]string{ + "plugin.json": "55556b845e91935cc48fae3aa67baf0f22694c3f", + }, + ManifestVersion: "2.0.0", + SignatureType: plugins.GrafanaSignature, + SignedByOrg: "grafana", + SignedByOrgName: "grafana", + } + + for _, cb := range cbs { + cb(m) + } + + return m +} diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 4f4f7d5536d..f3ba3e3900b 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -177,10 +177,20 @@ type SignatureType string const ( GrafanaSignature SignatureType = "grafana" + CommercialSignature SignatureType = "commercial" + CommunitySignature SignatureType = "community" PrivateSignature SignatureType = "private" PrivateGlobSignature SignatureType = "private-glob" ) +func (s SignatureType) IsValid() bool { + switch s { + case GrafanaSignature, CommercialSignature, CommunitySignature, PrivateSignature, PrivateGlobSignature: + return true + } + return false +} + type PluginFiles map[string]struct{} type Signature struct { From 5f947c293230b876ebae29393911bc2700388a84 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 27 Jul 2022 12:09:24 -0500 Subject: [PATCH 09/24] CloudWatch: returnData should default to true (#52759) (#52874) (cherry picked from commit 542b4f243192b1b76f1fa3c04af3545682f1c836) Co-authored-by: Isabella Siu --- pkg/tsdb/cloudwatch/cloudwatch.go | 15 +++++---- pkg/tsdb/cloudwatch/request_parser.go | 6 ++-- pkg/tsdb/cloudwatch/request_parser_test.go | 38 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index 1130d68a785..d49d85b45cb 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/pkg/tsdb/cloudwatch/cloudwatch.go @@ -73,6 +73,11 @@ const ( alertMaxAttempts = 8 alertPollPeriod = 1000 * time.Millisecond logsQueryMode = "Logs" + + // QueryTypes + annotationQuery = "annotationQuery" + logAction = "logAction" + timeSeriesQuery = "timeSeriesQuery" ) var plog = log.New("tsdb.cloudwatch") @@ -356,15 +361,13 @@ func (e *cloudWatchExecutor) QueryData(ctx context.Context, req *backend.QueryDa return e.executeLogAlertQuery(ctx, req) } - queryType := model.QueryType - var result *backend.QueryDataResponse - switch queryType { - case "annotationQuery": + switch model.QueryType { + case annotationQuery: result, err = e.executeAnnotationQuery(req.PluginContext, model, q) - case "logAction": + case logAction: result, err = e.executeLogActions(ctx, req) - case "timeSeriesQuery": + case timeSeriesQuery: fallthrough default: result, err = e.executeTimeSeriesQuery(ctx, req) diff --git a/pkg/tsdb/cloudwatch/request_parser.go b/pkg/tsdb/cloudwatch/request_parser.go index 39d6b651555..0a0d2a09c40 100644 --- a/pkg/tsdb/cloudwatch/request_parser.go +++ b/pkg/tsdb/cloudwatch/request_parser.go @@ -37,7 +37,7 @@ type QueryJson struct { Statistic *string `json:"statistic,omitempty"` Statistics []*string `json:"statistics,omitempty"` TimezoneUTCOffset string `json:"timezoneUTCOffset,omitempty"` - QueryType string `json:"queryType,omitempty"` + QueryType string `json:"type,omitempty"` Hide *bool `json:"hide,omitempty"` Alias *string `json:"alias,omitempty"` } @@ -58,7 +58,7 @@ func (e *cloudWatchExecutor) parseQueries(queries []backend.DataQuery, startTime } queryType := model.QueryType - if queryType != "timeSeriesQuery" && queryType != "" { + if queryType != timeSeriesQuery && queryType != "" { continue } @@ -166,7 +166,7 @@ func parseRequestQuery(model QueryJson, refId string, startTime time.Time, endTi Label: "", MatchExact: true, Statistic: "", - ReturnData: false, + ReturnData: true, UsedExpression: "", RefId: refId, Id: model.Id, diff --git a/pkg/tsdb/cloudwatch/request_parser_test.go b/pkg/tsdb/cloudwatch/request_parser_test.go index bb28ed205d5..e49e5100606 100644 --- a/pkg/tsdb/cloudwatch/request_parser_test.go +++ b/pkg/tsdb/cloudwatch/request_parser_test.go @@ -11,6 +11,16 @@ import ( "github.com/stretchr/testify/require" ) +func TestQueryJSON(t *testing.T) { + jsonString := []byte(`{ + "type": "timeSeriesQuery" + }`) + var res QueryJson + err := json.Unmarshal(jsonString, &res) + require.NoError(t, err) + assert.Equal(t, "timeSeriesQuery", res.QueryType) +} + func TestRequestParser(t *testing.T) { t.Run("Query migration ", func(t *testing.T) { t.Run("legacy statistics field is migrated", func(t *testing.T) { @@ -306,6 +316,34 @@ func TestRequestParser(t *testing.T) { }) }) + t.Run("hide and returnData", func(t *testing.T) { + t.Run("default", func(t *testing.T) { + query := getBaseJsonQuery() + query.QueryType = "timeSeriesQuery" + res, err := parseRequestQuery(query, "ref1", time.Now().Add(-2*time.Hour), time.Now().Add(-time.Hour)) + require.NoError(t, err) + require.True(t, res.ReturnData) + }) + t.Run("hide is true", func(t *testing.T) { + query := getBaseJsonQuery() + query.QueryType = "timeSeriesQuery" + true := true + query.Hide = &true + res, err := parseRequestQuery(query, "ref1", time.Now().Add(-2*time.Hour), time.Now().Add(-time.Hour)) + require.NoError(t, err) + require.False(t, res.ReturnData) + }) + t.Run("hide is false", func(t *testing.T) { + query := getBaseJsonQuery() + query.QueryType = "timeSeriesQuery" + false := false + query.Hide = &false + res, err := parseRequestQuery(query, "ref1", time.Now().Add(-2*time.Hour), time.Now().Add(-time.Hour)) + require.NoError(t, err) + require.True(t, res.ReturnData) + }) + }) + 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)) From b9c1a3e7f609feb2d3f72f03c82ba0bc6f6cbc54 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 27 Jul 2022 12:17:25 -0500 Subject: [PATCH 10/24] [v9.0.x] Access control: role checks before updates (#52900) * Access control: role checks before updates (#51449) * add role checks * linting (cherry picked from commit 0c0cf36ab8ccee1511bd1d116962f0c1ad91a560) * fix tests Co-authored-by: Ieva --- pkg/api/org_invite.go | 3 ++ pkg/api/org_users.go | 24 +++++---- pkg/api/org_users_test.go | 106 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 9 deletions(-) diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go index 39230014222..d9dceb7ef1e 100644 --- a/pkg/api/org_invite.go +++ b/pkg/api/org_invite.go @@ -40,6 +40,9 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response { if !inviteDto.Role.IsValid() { return response.Error(400, "Invalid role specified", nil) } + if !c.OrgRole.Includes(inviteDto.Role) && !c.IsGrafanaAdmin { + return response.Error(http.StatusForbidden, "Cannot assign a role higher than user's role", nil) + } // first try get existing user userQuery := models.GetUserByLoginQuery{LoginOrEmail: inviteDto.LoginOrEmail} diff --git a/pkg/api/org_users.go b/pkg/api/org_users.go index 7d07bc086a2..aa10fe8b354 100644 --- a/pkg/api/org_users.go +++ b/pkg/api/org_users.go @@ -21,7 +21,7 @@ func (hs *HTTPServer) AddOrgUserToCurrentOrg(c *models.ReqContext) response.Resp return response.Error(http.StatusBadRequest, "bad request data", err) } cmd.OrgId = c.OrgId - return hs.addOrgUserHelper(c.Req.Context(), cmd) + return hs.addOrgUserHelper(c, cmd) } // POST /api/orgs/:orgId/users @@ -36,16 +36,19 @@ func (hs *HTTPServer) AddOrgUser(c *models.ReqContext) response.Response { if err != nil { return response.Error(http.StatusBadRequest, "orgId is invalid", err) } - return hs.addOrgUserHelper(c.Req.Context(), cmd) + return hs.addOrgUserHelper(c, cmd) } -func (hs *HTTPServer) addOrgUserHelper(ctx context.Context, cmd models.AddOrgUserCommand) response.Response { +func (hs *HTTPServer) addOrgUserHelper(c *models.ReqContext, cmd models.AddOrgUserCommand) response.Response { if !cmd.Role.IsValid() { return response.Error(400, "Invalid role specified", nil) } + if !c.OrgRole.Includes(cmd.Role) && !c.IsGrafanaAdmin { + return response.Error(http.StatusForbidden, "Cannot assign a role higher than user's role", nil) + } userQuery := models.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail} - err := hs.SQLStore.GetUserByLogin(ctx, &userQuery) + err := hs.SQLStore.GetUserByLogin(c.Req.Context(), &userQuery) if err != nil { return response.Error(404, "User not found", nil) } @@ -54,7 +57,7 @@ func (hs *HTTPServer) addOrgUserHelper(ctx context.Context, cmd models.AddOrgUse cmd.UserId = userToAdd.Id - if err := hs.SQLStore.AddOrgUser(ctx, &cmd); err != nil { + if err := hs.SQLStore.AddOrgUser(c.Req.Context(), &cmd); err != nil { if errors.Is(err, models.ErrOrgUserAlreadyAdded) { return response.JSON(409, util.DynMap{ "message": "User is already member of this organization", @@ -217,7 +220,7 @@ func (hs *HTTPServer) UpdateOrgUserForCurrentOrg(c *models.ReqContext) response. if err != nil { return response.Error(http.StatusBadRequest, "userId is invalid", err) } - return hs.updateOrgUserHelper(c.Req.Context(), cmd) + return hs.updateOrgUserHelper(c, cmd) } // PATCH /api/orgs/:orgId/users/:userId @@ -235,14 +238,17 @@ func (hs *HTTPServer) UpdateOrgUser(c *models.ReqContext) response.Response { if err != nil { return response.Error(http.StatusBadRequest, "userId is invalid", err) } - return hs.updateOrgUserHelper(c.Req.Context(), cmd) + return hs.updateOrgUserHelper(c, cmd) } -func (hs *HTTPServer) updateOrgUserHelper(ctx context.Context, cmd models.UpdateOrgUserCommand) response.Response { +func (hs *HTTPServer) updateOrgUserHelper(c *models.ReqContext, cmd models.UpdateOrgUserCommand) response.Response { if !cmd.Role.IsValid() { return response.Error(400, "Invalid role specified", nil) } - if err := hs.SQLStore.UpdateOrgUser(ctx, &cmd); err != nil { + if !c.OrgRole.Includes(cmd.Role) && !c.IsGrafanaAdmin { + return response.Error(http.StatusForbidden, "Cannot assign a role higher than user's role", nil) + } + if err := hs.SQLStore.UpdateOrgUser(c.Req.Context(), &cmd); err != nil { if errors.Is(err, models.ErrLastOrgAdmin) { return response.Error(400, "Cannot change role so that there is no organization admin left", nil) } diff --git a/pkg/api/org_users_test.go b/pkg/api/org_users_test.go index 3634879e3f0..7674650dbcd 100644 --- a/pkg/api/org_users_test.go +++ b/pkg/api/org_users_test.go @@ -570,6 +570,112 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) { } } +func TestOrgUsersAPIEndpointWithSetPerms_AccessControl(t *testing.T) { + type accessControlTestCase2 struct { + expectedCode int + desc string + url string + method string + permissions []*accesscontrol.Permission + input string + } + tests := []accessControlTestCase2{ + { + expectedCode: http.StatusOK, + desc: "org viewer with the correct permissions can add a user as a viewer to his org", + url: "/api/org/users", + method: http.MethodPost, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}}, + input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(models.ROLE_VIEWER) + `"}`, + }, + { + expectedCode: http.StatusForbidden, + desc: "org viewer with the correct permissions cannot add a user as an editor to his org", + url: "/api/org/users", + method: http.MethodPost, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}}, + input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(models.ROLE_EDITOR) + `"}`, + }, + { + expectedCode: http.StatusOK, + desc: "org viewer with the correct permissions can add a user as a viewer to his org", + url: "/api/orgs/1/users", + method: http.MethodPost, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}}, + input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(models.ROLE_VIEWER) + `"}`, + }, + { + expectedCode: http.StatusForbidden, + desc: "org viewer with the correct permissions cannot add a user as an editor to his org", + url: "/api/orgs/1/users", + method: http.MethodPost, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}}, + input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(models.ROLE_EDITOR) + `"}`, + }, + { + expectedCode: http.StatusOK, + desc: "org viewer with the correct permissions can update a user's role to a viewer in his org", + url: fmt.Sprintf("/api/org/users/%d", testEditorOrg1.UserId), + method: http.MethodPatch, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: accesscontrol.ScopeUsersAll}}, + input: `{"role": "` + string(models.ROLE_VIEWER) + `"}`, + }, + { + expectedCode: http.StatusForbidden, + desc: "org viewer with the correct permissions cannot update a user's role to a viewer in his org", + url: fmt.Sprintf("/api/org/users/%d", testEditorOrg1.UserId), + method: http.MethodPatch, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: accesscontrol.ScopeUsersAll}}, + input: `{"role": "` + string(models.ROLE_EDITOR) + `"}`, + }, + { + expectedCode: http.StatusOK, + desc: "org viewer with the correct permissions can update a user's role to a viewer in his org", + url: fmt.Sprintf("/api/orgs/1/users/%d", testEditorOrg1.UserId), + method: http.MethodPatch, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: accesscontrol.ScopeUsersAll}}, + input: `{"role": "` + string(models.ROLE_VIEWER) + `"}`, + }, + { + expectedCode: http.StatusForbidden, + desc: "org viewer with the correct permissions cannot update a user's role to a viewer in his org", + url: fmt.Sprintf("/api/orgs/1/users/%d", testEditorOrg1.UserId), + method: http.MethodPatch, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: accesscontrol.ScopeUsersAll}}, + input: `{"role": "` + string(models.ROLE_EDITOR) + `"}`, + }, + { + expectedCode: http.StatusOK, + desc: "org viewer with the correct permissions can invite a user as a viewer in his org", + url: "/api/org/invites", + method: http.MethodPost, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionUsersCreate}}, + input: `{"loginOrEmail": "newUserEmail@test.com", "sendEmail": false, "role": "` + string(models.ROLE_VIEWER) + `"}`, + }, + { + expectedCode: http.StatusForbidden, + desc: "org viewer with the correct permissions cannot invite a user as an editor in his org", + url: "/api/org/invites", + method: http.MethodPost, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionUsersCreate}}, + input: `{"loginOrEmail": "newUserEmail@test.com", "sendEmail": false, "role": "` + string(models.ROLE_EDITOR) + `"}`, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + sc := setupHTTPServer(t, true, true) + setInitCtxSignedInViewer(sc.initCtx) + setupOrgUsersDBForAccessControlTests(t, sc.db) + setAccessControlPermissions(sc.acmock, test.permissions, sc.initCtx.OrgId) + + input := strings.NewReader(test.input) + response := callAPI(sc.server, test.method, test.url, input, t) + assert.Equal(t, test.expectedCode, response.Code) + }) + } +} + func TestPatchOrgUsersAPIEndpoint_AccessControl(t *testing.T) { url := "/api/orgs/%v/users/%v" type testCase struct { From ca710b4935e5207495267108aef240803726aa77 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 27 Jul 2022 12:36:47 -0500 Subject: [PATCH 11/24] Cloudwatch: Add template variable query function for listing log groups (#50100) (#52898) * cloud-datasources mob! :shipit: * cloud-datasources mob! :shipit: lastFile:public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx * mob next [ci-skip] [ci skip] [skip ci] lastFile:public/app/plugins/datasource/cloudwatch/variables.ts * cloud-datasources mob! :baby: lastFile:public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx * cloud-datasources mob! :baby: lastFile:public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx * mob next [ci-skip] [ci skip] [skip ci] lastFile:public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx * cloud-datasources mob! :shipit: lastFile:public/app/plugins/datasource/cloudwatch/variables.test.ts * cloud-datasources mob! :coffee: * prettier md Co-authored-by: Kevin Yu Co-authored-by: Andres Co-authored-by: Erik Sundell Co-authored-by: Adam Simpson (cherry picked from commit bcf8320e079a61d784a4a40ad2ac5c4ebc2e413d) Co-authored-by: Yaelle Chaudy <42030685+yaelleC@users.noreply.github.com> --- .../template-queries-cloudwatch.md | 1 + .../VariableQueryEditor.test.tsx | 23 ++++++++++++++++--- .../VariableQueryEditor.tsx | 19 ++++++++++----- .../VariableQueryEditor/VariableTextField.tsx | 2 +- .../plugins/datasource/cloudwatch/types.ts | 2 ++ .../datasource/cloudwatch/variables.test.ts | 11 +++++++++ .../datasource/cloudwatch/variables.ts | 11 +++++++++ 7 files changed, 59 insertions(+), 10 deletions(-) diff --git a/docs/sources/datasources/aws-cloudwatch/template-queries-cloudwatch.md b/docs/sources/datasources/aws-cloudwatch/template-queries-cloudwatch.md index 4df6638d26d..761da05cf04 100644 --- a/docs/sources/datasources/aws-cloudwatch/template-queries-cloudwatch.md +++ b/docs/sources/datasources/aws-cloudwatch/template-queries-cloudwatch.md @@ -30,6 +30,7 @@ Read more about the available dimensions in the [CloudWatch Metrics and Dimensio | `EC2 Instance Attributes` | Returns a list of attributes matching the specified `region`, `attribute_name`, and `filters`. | | `Resource ARNs` | Returns a list of ARNs matching the specified `region`, `resource_type` and `tags`. | | `Statistics` | Returns a list of all the standard statistics. | +| `LogGroups` | Returns a list of all log groups matching the specified `region`. | For details about the metrics CloudWatch provides, please refer to the [CloudWatch documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html). diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx index 838c719fc1e..4b9234cb904 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx @@ -81,7 +81,7 @@ describe('VariableEditor', () => { render(); await waitFor(() => { - const querySelect = screen.queryByRole('combobox', { name: 'Query Type' }); + const querySelect = screen.queryByRole('combobox', { name: 'Query type' }); expect(querySelect).toBeInTheDocument(); expect(screen.queryByText('Regions')).toBeInTheDocument(); // Should not render any fields besides Query Type @@ -103,7 +103,7 @@ describe('VariableEditor', () => { render(); await waitFor(() => { - const querySelect = screen.queryByRole('combobox', { name: 'Query Type' }); + const querySelect = screen.queryByRole('combobox', { name: 'Query type' }); expect(querySelect).toBeInTheDocument(); expect(screen.queryByText('Metrics')).toBeInTheDocument(); const regionSelect = screen.queryByRole('combobox', { name: 'Region' }); @@ -216,7 +216,7 @@ describe('VariableEditor', () => { }; render(); - const querySelect = screen.queryByLabelText('Query Type'); + const querySelect = screen.queryByLabelText('Query type'); expect(querySelect).toBeInTheDocument(); expect(screen.queryByText('Dimension Values')).toBeInTheDocument(); const regionSelect = screen.getByRole('combobox', { name: 'Region' }); @@ -240,4 +240,21 @@ describe('VariableEditor', () => { }); }); }); + describe('LogGroups queryType is selected', () => { + it('should only render region and prefix', async () => { + const props = defaultProps; + props.query = { + ...defaultQuery, + queryType: VariableQueryType.LogGroups, + }; + render(); + + await waitFor(() => { + screen.getByLabelText('Log group prefix'); + screen.getByLabelText('Region'); + }); + + expect(screen.queryByLabelText('Namespace')).not.toBeInTheDocument(); + }); + }); }); diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx index 292ad4dd4f7..80330d06fc4 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx @@ -25,6 +25,7 @@ const queryTypes: Array<{ value: string; label: string }> = [ { value: VariableQueryType.EC2InstanceAttributes, label: 'EC2 Instance Attributes' }, { value: VariableQueryType.ResourceArns, label: 'Resource ARNs' }, { value: VariableQueryType.Statistics, label: 'Statistics' }, + { value: VariableQueryType.LogGroups, label: 'Log Groups' }, ]; export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { @@ -88,6 +89,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { VariableQueryType.EBSVolumeIDs, VariableQueryType.EC2InstanceAttributes, VariableQueryType.ResourceArns, + VariableQueryType.LogGroups, ].includes(parsedQuery.queryType); const hasNamespaceField = [ VariableQueryType.Metrics, @@ -100,7 +102,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { value={parsedQuery.queryType} options={queryTypes} onChange={(value: VariableQueryType) => onQueryChange({ ...parsedQuery, queryType: value })} - label="Query Type" + label="Query type" inputId={`variable-query-type-${query.refId}`} /> {hasRegionField && ( @@ -135,7 +137,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { value={dimensionKey || null} options={dimensionKeys} onChange={(value: string) => onQueryChange({ ...parsedQuery, dimensionKey: value })} - label="Dimension Key" + label="Dimension key" inputId={`variable-query-dimension-key-${query.refId}`} /> @@ -163,9 +165,8 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { <> onQueryChange({ ...parsedQuery, attributeName: value })} - label="Attribute Name" + label="Attribute name" interactive={true} tooltip={ <> @@ -210,9 +211,8 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { <> onQueryChange({ ...parsedQuery, resourceType: value })} - label="Resource Type" + label="Resource type" /> { )} + {parsedQuery.queryType === VariableQueryType.LogGroups && ( + onQueryChange({ ...parsedQuery, logGroupPrefix: value })} + label="Log group prefix" + /> + )} ); }; diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableTextField.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableTextField.tsx index 693c9776e8b..7e66cc0d4eb 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableTextField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableTextField.tsx @@ -6,9 +6,9 @@ const LABEL_WIDTH = 20; interface VariableTextFieldProps { onBlur: (value: string) => void; - placeholder: string; value: string; label: string; + placeholder?: string; tooltip?: PopoverContent; interactive?: boolean; } diff --git a/public/app/plugins/datasource/cloudwatch/types.ts b/public/app/plugins/datasource/cloudwatch/types.ts index 4383053acbb..20b66dd1749 100644 --- a/public/app/plugins/datasource/cloudwatch/types.ts +++ b/public/app/plugins/datasource/cloudwatch/types.ts @@ -377,6 +377,7 @@ export enum VariableQueryType { EC2InstanceAttributes = 'ec2InstanceAttributes', ResourceArns = 'resourceARNs', Statistics = 'statistics', + LogGroups = 'logGroups', } export interface OldVariableQuery extends DataQuery { @@ -405,6 +406,7 @@ export interface VariableQuery extends DataQuery { attributeName: string; resourceType: string; tags?: MultiFilters; + logGroupPrefix?: string; } export interface LegacyAnnotationQuery extends MetricStat, DataQuery { diff --git a/public/app/plugins/datasource/cloudwatch/variables.test.ts b/public/app/plugins/datasource/cloudwatch/variables.test.ts index 57589b526dc..52c70aae428 100644 --- a/public/app/plugins/datasource/cloudwatch/variables.test.ts +++ b/public/app/plugins/datasource/cloudwatch/variables.test.ts @@ -19,6 +19,7 @@ ds.datasource.getRegions = jest.fn().mockResolvedValue([{ label: 'a', value: 'a' ds.datasource.getNamespaces = jest.fn().mockResolvedValue([{ label: 'b', value: 'b' }]); ds.datasource.getMetrics = jest.fn().mockResolvedValue([{ label: 'c', value: 'c' }]); ds.datasource.getDimensionKeys = jest.fn().mockResolvedValue([{ label: 'd', value: 'd' }]); +ds.datasource.describeLogGroups = jest.fn().mockResolvedValue(['a', 'b']); const getDimensionValues = jest.fn().mockResolvedValue([{ label: 'e', value: 'e' }]); const getEbsVolumeIds = jest.fn().mockResolvedValue([{ label: 'f', value: 'f' }]); const getEc2InstanceAttribute = jest.fn().mockResolvedValue([{ label: 'g', value: 'g' }]); @@ -167,4 +168,14 @@ describe('variables', () => { { text: 'SampleCount', value: 'SampleCount', expandable: true }, ]); }); + + describe('log groups', () => { + it('should call describe log groups', async () => { + const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.LogGroups }); + expect(result).toEqual([ + { text: 'a', value: 'a', expandable: true }, + { text: 'b', value: 'b', expandable: true }, + ]); + }); + }); }); diff --git a/public/app/plugins/datasource/cloudwatch/variables.ts b/public/app/plugins/datasource/cloudwatch/variables.ts index 20c58214aba..78809786374 100644 --- a/public/app/plugins/datasource/cloudwatch/variables.ts +++ b/public/app/plugins/datasource/cloudwatch/variables.ts @@ -45,6 +45,8 @@ export class CloudWatchVariableSupport extends CustomVariableSupport ({ + text: s, + value: s, + expandable: true, + })); + } + async handleRegionsQuery() { const regions = await this.datasource.getRegions(); return regions.map((s: { label: string; value: string }) => ({ From c71dfb49daf8783d11e7f7c7f355061479ddc878 Mon Sep 17 00:00:00 2001 From: Ieva Date: Wed, 27 Jul 2022 20:11:17 +0100 Subject: [PATCH 12/24] allow org admins to invite new users to Grafana (#52904) --- .../access-control/custom-role-actions-scopes.md | 2 +- .../rbac-fixed-basic-role-definitions.md | 2 +- pkg/api/accesscontrol.go | 7 ------- pkg/api/api.go | 8 ++++---- pkg/api/org_invite.go | 10 +--------- pkg/api/org_invite_test.go | 16 ++++------------ public/app/features/users/UsersActionBar.tsx | 4 +--- 7 files changed, 12 insertions(+), 37 deletions(-) diff --git a/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes.md b/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes.md index e8f58e5a696..ff1a51d0316 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes.md +++ b/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes.md @@ -77,7 +77,7 @@ The following list contains role-based access control actions. | `licensing:read` | n/a | Read licensing information. | | `licensing:write` | n/a | Update the license token. | | `org.users:write` | `users:*`
`users:id:*` | Update the organization role (`Viewer`, `Editor`, or `Admin`) of a user. | -| `org.users:add` | `users:*` | Add a user to an organization. | +| `org.users:add` | `users:*` | Add a user to an organization or invite a new user to an organization. | | `org.users:read` | `users:*`
`users:id:*` | Get user profiles within an organization. | | `org.users:remove` | `users:*`
`users:id:*` | Remove a user from an organization. | | `org:create` | n/a | Create an organization. | diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions.md index 04f7ef6c84a..343a9e00b85 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions.md +++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions.md @@ -60,7 +60,7 @@ The following tables list permissions associated with basic and fixed roles. | `fixed:licensing:reader` | `licensing:read`
`licensing.reports:read` | Read licensing information and licensing reports. | | `fixed:licensing:writer` | All permissions from `fixed:licensing:viewer` and
`licensing:write`
`licensing:delete` | Read licensing information and licensing reports, update and delete the license token. | | `fixed:org.users:reader` | `org.users:read` | Read users within a single organization. | -| `fixed:org.users:writer` | All permissions from `fixed:org.users:reader` and
`org.users:add`
`org.users:remove`
`org.users:write` | Within a single organization, add a user, invite a user, read information about a user and their role, remove a user from that organization, or change the role of a user. | +| `fixed:org.users:writer` | All permissions from `fixed:org.users:reader` and
`org.users:add`
`org.users:remove`
`org.users:write` | Within a single organization, add a user, invite a new user, read information about a user and their role, remove a user from that organization, or change the role of a user. | | `fixed:organization:maintainer` | All permissions from `fixed:organization:reader` and
`orgs:write`
`orgs:create`
`orgs:delete`
`orgs.quotas:write` | Create, read, write, or delete an organization. Read or write its quotas. This role needs to be assigned globally. | | `fixed:organization:reader` | `orgs:read`
`orgs.quotas:read` | Read an organization and its quotas. | | `fixed:organization:writer` | All permissions from `fixed:organization:reader` and
`orgs:write`
`orgs.preferences:read`
`orgs.preferences:write` | Read an organization, its quotas, or its preferences. Update organization properties, or its preferences. | diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go index 6bf4fbb5b4d..82c82670bfc 100644 --- a/pkg/api/accesscontrol.go +++ b/pkg/api/accesscontrol.go @@ -443,13 +443,6 @@ var orgsCreateAccessEvaluator = ac.EvalAll( ac.EvalPermission(ActionOrgsCreate), ) -// usersInviteEvaluator is used to protect the "Configuration > Users > Invite" page access -// accessible to org admins and server admins by default -var usersInviteEvaluator = ac.EvalAny( - ac.EvalPermission(ac.ActionUsersCreate), - ac.EvalPermission(ac.ActionOrgUsersAdd), -) - // teamsAccessEvaluator is used to protect the "Configuration > Teams" page access // grants access to a user when they can either create teams or can read and update a team var teamsAccessEvaluator = ac.EvalAny( diff --git a/pkg/api/api.go b/pkg/api/api.go index 3a726c157f9..88509cf575c 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -59,7 +59,7 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/datasources/edit/*", authorize(reqOrgAdmin, datasources.EditPageAccess), hs.Index) r.Get("/org/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), hs.Index) r.Get("/org/users/new", reqOrgAdmin, hs.Index) - r.Get("/org/users/invite", authorize(reqOrgAdmin, usersInviteEvaluator), hs.Index) + r.Get("/org/users/invite", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), hs.Index) r.Get("/org/teams", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsRead)), hs.Index) r.Get("/org/teams/edit/*", authorize(reqCanAccessTeams, teamsEditAccessEvaluator), hs.Index) r.Get("/org/teams/new", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsCreate)), hs.Index) @@ -238,9 +238,9 @@ func (hs *HTTPServer) registerRoutes() { orgRoute.Delete("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUserForCurrentOrg)) // invites - orgRoute.Get("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(hs.GetPendingOrgInvites)) - orgRoute.Post("/invites", authorize(reqOrgAdmin, usersInviteEvaluator), quota("user"), routing.Wrap(hs.AddOrgInvite)) - orgRoute.Patch("/invites/:code/revoke", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(hs.RevokeInvite)) + orgRoute.Get("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), routing.Wrap(hs.GetPendingOrgInvites)) + orgRoute.Post("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), quota("user"), routing.Wrap(hs.AddOrgInvite)) + orgRoute.Patch("/invites/:code/revoke", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), routing.Wrap(hs.RevokeInvite)) // prefs orgRoute.Get("/preferences", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsPreferencesRead)), routing.Wrap(hs.GetOrgPreferences)) diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go index d9dceb7ef1e..c4a9dc07c7e 100644 --- a/pkg/api/org_invite.go +++ b/pkg/api/org_invite.go @@ -63,15 +63,6 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response { return hs.inviteExistingUserToOrg(c, userQuery.Result, &inviteDto) } - // Evaluate permissions for inviting a new user to Grafana - hasAccess, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, ac.EvalPermission(ac.ActionUsersCreate)) - if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to evaluate permissions", err) - } - if !hasAccess { - return response.Error(http.StatusForbidden, "Permission denied: not permitted to create a new user", err) - } - if setting.DisableLoginForm { return response.Error(400, "Cannot invite when login is disabled.", nil) } @@ -82,6 +73,7 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response { cmd.Name = inviteDto.Name cmd.Status = models.TmpUserInvitePending cmd.InvitedByUserId = c.UserId + var err error cmd.Code, err = util.GetRandomString(30) if err != nil { return response.Error(500, "Could not generate random string", err) diff --git a/pkg/api/org_invite_test.go b/pkg/api/org_invite_test.go index cdd338a5a49..319a11b1167 100644 --- a/pkg/api/org_invite_test.go +++ b/pkg/api/org_invite_test.go @@ -23,7 +23,7 @@ func TestOrgInvitesAPIEndpointAccess(t *testing.T) { tests := []accessControlTestCase2{ { expectedCode: http.StatusOK, - desc: "org viewer with the correct permissions can invite and existing user to his org", + desc: "org viewer with the correct permissions can invite an existing user to his org", url: "/api/org/invites", method: http.MethodPost, permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}}, @@ -31,7 +31,7 @@ func TestOrgInvitesAPIEndpointAccess(t *testing.T) { }, { expectedCode: http.StatusForbidden, - desc: "org viewer with missing permissions cannot invite and existing user to his org", + desc: "org viewer with missing permissions cannot invite an existing user to his org", url: "/api/org/invites", method: http.MethodPost, permissions: []*accesscontrol.Permission{}, @@ -39,26 +39,18 @@ func TestOrgInvitesAPIEndpointAccess(t *testing.T) { }, { expectedCode: http.StatusForbidden, - desc: "org viewer with the wrong scope cannot invite and existing user to his org", + desc: "org viewer with the wrong scope cannot invite an existing user to his org", url: "/api/org/invites", method: http.MethodPost, permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: "users:id:100"}}, input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(models.ROLE_VIEWER) + `"}`, }, - { - expectedCode: http.StatusForbidden, - desc: "org viewer with user add permission cannot invite a new user to his org", - url: "/api/org/invites", - method: http.MethodPost, - permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}}, - input: `{"loginOrEmail": "new user", "role": "` + string(models.ROLE_VIEWER) + `"}`, - }, { expectedCode: http.StatusOK, desc: "org viewer with the correct permissions can invite a new user to his org", url: "/api/org/invites", method: http.MethodPost, - permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionUsersCreate}}, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}}, input: `{"loginOrEmail": "new user", "role": "` + string(models.ROLE_VIEWER) + `"}`, }, { diff --git a/public/app/features/users/UsersActionBar.tsx b/public/app/features/users/UsersActionBar.tsx index 0d69e835bf3..b9dc2906313 100644 --- a/public/app/features/users/UsersActionBar.tsx +++ b/public/app/features/users/UsersActionBar.tsx @@ -37,9 +37,7 @@ export class UsersActionBar extends PureComponent { { label: 'Users', value: 'users' }, { label: `Pending Invites (${pendingInvitesCount})`, value: 'invites' }, ]; - const canAddToOrg: boolean = - contextSrv.hasAccess(AccessControlAction.UsersCreate, canInvite) || - contextSrv.hasAccess(AccessControlAction.OrgUsersAdd, canInvite); + const canAddToOrg: boolean = contextSrv.hasAccess(AccessControlAction.OrgUsersAdd, canInvite); return (
From 02fb825de07a8a0920ec5f7744eeefcf91b6ea57 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 27 Jul 2022 15:21:42 -0500 Subject: [PATCH 13/24] fixes links (#52899) (#52906) (cherry picked from commit d06ea6ea0e77c93339a38941a3843047ab1ea7e5) Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> --- docs/sources/developers/http_api/admin.md | 2 +- docs/sources/developers/http_api/alerting.md | 2 +- .../developers/http_api/alerting_notification_channels.md | 2 +- docs/sources/shared/visualizations/change-axis-link.md | 2 +- docs/sources/shared/visualizations/stack-series-link.md | 2 +- docs/sources/whatsnew/whats-new-in-v7-2.md | 2 +- docs/sources/whatsnew/whats-new-in-v7-4.md | 2 -- 7 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/sources/developers/http_api/admin.md b/docs/sources/developers/http_api/admin.md index e83c7c941eb..bff51b59554 100644 --- a/docs/sources/developers/http_api/admin.md +++ b/docs/sources/developers/http_api/admin.md @@ -471,7 +471,7 @@ Content-Type: application/json `POST /api/admin/pause-all-alerts` -> **Note:** This API is relevant for the [legacy dashboard alerts]({{< relref "../../old-alerting/" >}}) only. For default alerting, use [silences]({{< relref "../../alerting/silences/" >}}) to stop alerts from being delivered. +> **Note:** This API is relevant for the [legacy dashboard alerts](https://grafana.com/docs/grafana/v8.5/alerting/old-alerting/) only. For default alerting, use [silences]({{< relref "../../alerting/silences/" >}}) to stop alerts from being delivered. Only works with Basic Authentication (username and password). See [introduction](http://docs.grafana.org/http_api/admin/#admin-api) for an explanation. diff --git a/docs/sources/developers/http_api/alerting.md b/docs/sources/developers/http_api/alerting.md index 9f8c02ea300..dd450917638 100644 --- a/docs/sources/developers/http_api/alerting.md +++ b/docs/sources/developers/http_api/alerting.md @@ -15,7 +15,7 @@ title: 'Alerting HTTP API ' # Alerting API -> **Note:** This topic is relevant for the [legacy dashboard alerts]({{< relref "../../old-alerting/" >}}) only. +> **Note:** This topic is relevant for the [legacy dashboard alerts](https://grafana.com/docs/grafana/v8.5/alerting/old-alerting/) only. You can find Grafana alerting API specification details [here](https://editor.swagger.io/?url=https://raw.githubusercontent.com/grafana/grafana/main/pkg/services/ngalert/api/tooling/post.json). Also, refer to [Grafana alerting alerts documentation]({{< relref "../../alerting/" >}}) for details on how to create and manage new alerts. diff --git a/docs/sources/developers/http_api/alerting_notification_channels.md b/docs/sources/developers/http_api/alerting_notification_channels.md index 06329181a35..8923309fefb 100644 --- a/docs/sources/developers/http_api/alerting_notification_channels.md +++ b/docs/sources/developers/http_api/alerting_notification_channels.md @@ -185,7 +185,7 @@ Content-Type: application/json ## Create notification channel -You can find the full list of [supported notifiers]({{< ref "/docs/grafana/v8.5/alerting/old-alerting/notifications/" >}}) on the alert notifiers page. +You can find the full list of [supported notifiers](https://grafana.com/docs/grafana/v8.5/alerting/old-alerting/notifications/) on the alert notifiers page. `POST /api/alert-notifications` diff --git a/docs/sources/shared/visualizations/change-axis-link.md b/docs/sources/shared/visualizations/change-axis-link.md index aa6b7d78691..70ac41c3d33 100644 --- a/docs/sources/shared/visualizations/change-axis-link.md +++ b/docs/sources/shared/visualizations/change-axis-link.md @@ -6,4 +6,4 @@ title: Stack series link ## Axis -For full instructions, refer to [Change axis display]({{< relref "../time-series/change-axis-display/" >}}). +For full instructions, refer to [Change axis display]({{< relref "../../visualizations/time-series/change-axis-display/" >}}). diff --git a/docs/sources/shared/visualizations/stack-series-link.md b/docs/sources/shared/visualizations/stack-series-link.md index 5ef5b6785e2..c2beac05fc9 100644 --- a/docs/sources/shared/visualizations/stack-series-link.md +++ b/docs/sources/shared/visualizations/stack-series-link.md @@ -6,4 +6,4 @@ title: Stack series link ### Stack series -For full instructions, refer to [Graph stacked time series]({{< relref "../time-series/graph-time-series-stacking/" >}}). +For full instructions, refer to [Graph stacked time series]({{< relref "../../visualizations/time-series/graph-time-series-stacking/" >}}). diff --git a/docs/sources/whatsnew/whats-new-in-v7-2.md b/docs/sources/whatsnew/whats-new-in-v7-2.md index 95adfc46366..69353e58f4d 100644 --- a/docs/sources/whatsnew/whats-new-in-v7-2.md +++ b/docs/sources/whatsnew/whats-new-in-v7-2.md @@ -74,7 +74,7 @@ You can now dynamically apply value filters to any table column. This option can {{< figure src="/static/img/docs/v72/table_column_filters.png" max-width="800px" caption="Table column filters" >}} -[Filter table columns]({{< relref "../visualizations/table/filter-table-columns/" >}}) has been added as a result of this feature. +[Filter table columns]({{< relref "../visualizations/table/#filter-table-columns" >}}) has been added as a result of this feature. ### New field override selection options diff --git a/docs/sources/whatsnew/whats-new-in-v7-4.md b/docs/sources/whatsnew/whats-new-in-v7-4.md index 1e8be70d24b..3fcfbdba355 100644 --- a/docs/sources/whatsnew/whats-new-in-v7-4.md +++ b/docs/sources/whatsnew/whats-new-in-v7-4.md @@ -114,8 +114,6 @@ You can now provide detailed information to alert notification recipients by inj {{< figure src="/static/img/docs/alerting/alert-notification-template-7-4.png" max-width="700px" caption="Variable support in alert notifications" >}} -For more information, refer to the [alert notification docs]({{< ref "/docs/grafana/v8.5/alerting/old-alerting/add-notification-template/" >}}). - ### Content security policy support We have added support for [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), a layer of security that helps detect and mitigate certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks. From ba8549cfccb875ed7749f30b961f929465ee091c Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 27 Jul 2022 16:15:36 -0500 Subject: [PATCH 14/24] Separate task with additional information (#52871) (#52883) (cherry picked from commit c968b76279f50fd1f935ae12f402fb647e484c6a) Co-authored-by: Ursula Kallio --- .../getting-started/build-first-dashboard.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/sources/getting-started/build-first-dashboard.md b/docs/sources/getting-started/build-first-dashboard.md index edb0ad3ee3e..b964c2804a2 100644 --- a/docs/sources/getting-started/build-first-dashboard.md +++ b/docs/sources/getting-started/build-first-dashboard.md @@ -27,9 +27,15 @@ Grafana can be installed on many different operating systems. For a list of the To sign in to Grafana for the first time: -1. Open your web browser and go to http://localhost:3000/. The default HTTP port that Grafana listens to is `3000` unless you have configured a different port. -1. On the signin page, enter `admin` for username and password. -1. Click **Sign in**. If successful, you will see a prompt to change the password. +1. Open your web browser and go to http://localhost:3000/. + + The default HTTP port that Grafana listens to is `3000` unless you have configured a different port. + +1. On the sign-in page, enter `admin` for the username and password. +1. Click **Sign in**. + + If successful, you will see a prompt to change the password. + 1. Click **OK** on the prompt and change your password. > **Note:** We strongly recommend that you change the default administrator password. @@ -41,7 +47,10 @@ To create your first dashboard: 1. Click the **New dashboard** item under the **Dashboards** icon in the side menu. 1. On the dashboard, click **Add an empty panel**. 1. In the New dashboard/Edit panel view, go to the **Query** tab. -1. Configure your [query]({{< relref "../panels/query-a-data-source/add-a-query/" >}}) by selecting `-- Grafana --` from the data source selector. This generates the Random Walk dashboard. +1. Configure your [query]({{< relref "../panels/query-a-data-source/add-a-query/" >}}) by selecting `-- Grafana --` from the data source selector. + + This generates the Random Walk dashboard. + 1. Click the **Save** icon in the top right corner of your screen to save the dashboard. 1. Add a descriptive name, and then click **Save**. From 7a621b93d056a2cfffd393d37d382fd96874d391 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 27 Jul 2022 16:15:57 -0500 Subject: [PATCH 15/24] Fix small typo in panel doc (#52568) (#52727) (cherry picked from commit bdcef92e3532066ec4a0ec2fd96638d3e14f457e) Co-authored-by: Chris Hodges --- docs/sources/panels/working-with-panels/organize-dashboard.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/panels/working-with-panels/organize-dashboard.md b/docs/sources/panels/working-with-panels/organize-dashboard.md index 8c615242727..8d7db1d0df7 100644 --- a/docs/sources/panels/working-with-panels/organize-dashboard.md +++ b/docs/sources/panels/working-with-panels/organize-dashboard.md @@ -19,7 +19,7 @@ You can place any panel in any location you want and controls its size. The chan 1. Hover your cursor over the panel, and click-and-drag the panel to its new location. -1. To resize a penal, click the zoom in (+) and zoom out (-) icons. +1. To resize a panel, click the zoom in (+) and zoom out (-) icons. ![](/static/img/docs/animated_gifs/drag_drop.gif) From e44196551423039f96c16947e726d0c33ff26ae8 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Wed, 27 Jul 2022 16:17:02 -0500 Subject: [PATCH 16/24] Fix relrefs in alerting (#51939) (#51993) (cherry picked from commit a243dc8713cc0c5c4b81a3a69f5dec40bf21f2ee) Co-authored-by: Garrett Guillotte <100453168+gguillotte-grafana@users.noreply.github.com> --- .../alerting/alerting-rules/create-grafana-managed-rule.md | 2 +- .../alerting/alerting-rules/create-mimir-loki-managed-rule.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md index 28682a6acbb..7983ac6b0ab 100644 --- a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md +++ b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md @@ -15,7 +15,7 @@ weight: 400 # Create a Grafana managed alerting rule -Grafana allows you to create alerting rules that query one or more data sources, reduce or transform the results and compare them to each other or to fix thresholds. When these are executed, Grafana sends notifications to the contact point. For information on Grafana Alerting, see [About Grafana Alerting]({{< relref "../about-alerting/" >}}) which explains the various components of Grafana Alerting. We also recommend that you familiarize yourself with some of the [fundamental concepts]({{< relref "../fundamentals/" >}}) of Grafana Alerting. +Grafana allows you to create alerting rules that query one or more data sources, reduce or transform the results and compare them to each other or to fix thresholds. When these are executed, Grafana sends notifications to the contact point. For information on Grafana Alerting, see [About Grafana Alerting]({{< relref "../" >}}) which explains the various components of Grafana Alerting. We also recommend that you familiarize yourself with some of the [fundamental concepts]({{< relref "../fundamentals/" >}}) of Grafana Alerting. Watch this video to learn more about creating alerts: {{< vimeo 720001934 >}} diff --git a/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-rule.md b/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-rule.md index 3284a3a5879..f229c3f7d4d 100644 --- a/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-rule.md +++ b/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-rule.md @@ -17,7 +17,7 @@ weight: 400 # Create a Grafana Mimir or Loki managed alerting rule -Grafana allows you to create alerting rules for an external Grafana Mimir or Loki instance that has ruler API enabled. For information on Grafana Alerting, see [About Grafana Alerting]({{< relref "../about-alerting/" >}}) which explains the various components of Grafana Alerting. We also recommend that you familiarize yourself with some of the [fundamental concepts]({{< relref "../fundamentals/" >}}) of Grafana Alerting. +Grafana allows you to create alerting rules for an external Grafana Mimir or Loki instance that has ruler API enabled. For information on Grafana Alerting, see [About Grafana Alerting]({{< relref "../" >}}) which explains the various components of Grafana Alerting. We also recommend that you familiarize yourself with some of the [fundamental concepts]({{< relref "../fundamentals/" >}}) of Grafana Alerting. ## Before you begin From e26a51fc006963216efa33b22ba1988468deb7b4 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Thu, 28 Jul 2022 13:47:36 -0400 Subject: [PATCH 17/24] Cloud Datasources: Add necessary inputs and bump version for code coverage workflow (#52055) (#52902) * Add necessary inputs and bump version * Test coverage commit * Bump coverage workflow version * Bump workflow version * Try updated workflow * Bump commit sha * Rename empty file for testing * Update sha * Update sha * Update sha * Bump SHA * Update SHA * Update sha * Bump SHA * Bump SHA * Bump sha * Bump SHA * Test all plugins (including backend) * Bump SHA * Update SHA * Remove empty files and set appropriate workflow version (cherry picked from commit caec0f87719e902f8438a6729e74e236b019921f) Co-authored-by: Andreas Christou --- .github/workflows/cloud-data-sources-code-coverage.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cloud-data-sources-code-coverage.yml b/.github/workflows/cloud-data-sources-code-coverage.yml index f49cd30e3a2..8e1e0767c50 100644 --- a/.github/workflows/cloud-data-sources-code-coverage.yml +++ b/.github/workflows/cloud-data-sources-code-coverage.yml @@ -14,4 +14,7 @@ on: jobs: workflow-call: - uses: grafana/code-coverage/.github/workflows/code-coverage.yml@v0.1.2 + uses: grafana/code-coverage/.github/workflows/code-coverage.yml@v0.1.6 + with: + frontend-path-regexp: public\/app\/plugins\/datasource\/(grafana-azure-monitor-datasource|cloud-monitoring|cloudwatch) + backend-path-regexp: pkg\/tsdb\/(azuremonitor|cloudmonitoring|cloudwatch) From b0055b3e613078ee49c87636fe8259d6417677ed Mon Sep 17 00:00:00 2001 From: Ieva Date: Fri, 29 Jul 2022 12:57:55 +0200 Subject: [PATCH 18/24] fix tests (#52984) --- pkg/api/org_users_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/org_users_test.go b/pkg/api/org_users_test.go index 7674650dbcd..ea9cac8be28 100644 --- a/pkg/api/org_users_test.go +++ b/pkg/api/org_users_test.go @@ -649,7 +649,7 @@ func TestOrgUsersAPIEndpointWithSetPerms_AccessControl(t *testing.T) { desc: "org viewer with the correct permissions can invite a user as a viewer in his org", url: "/api/org/invites", method: http.MethodPost, - permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionUsersCreate}}, + permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}}, input: `{"loginOrEmail": "newUserEmail@test.com", "sendEmail": false, "role": "` + string(models.ROLE_VIEWER) + `"}`, }, { From 44bdccbfd153d06994bdecafcd992426a820a126 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 29 Jul 2022 08:34:15 -0400 Subject: [PATCH 19/24] Tooltip mode works in BarChart panel (#52941) (#52989) (cherry picked from commit e88e3482e0e66bc94d76d60d7503e6d63bcc499e) Co-authored-by: Victor Marin <36818606+mdvictor@users.noreply.github.com> --- .../plugins/panel/barchart/BarChartPanel.tsx | 6 +++++ .../panel/geomap/components/DataHoverView.tsx | 22 +++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/panel/barchart/BarChartPanel.tsx b/public/app/plugins/panel/barchart/BarChartPanel.tsx index 0a58e65beaa..7072eae680f 100644 --- a/public/app/plugins/panel/barchart/BarChartPanel.tsx +++ b/public/app/plugins/panel/barchart/BarChartPanel.tsx @@ -19,6 +19,7 @@ import { measureText, PlotLegend, Portal, + TooltipDisplayMode, UPlotConfigBuilder, UPLOT_AXIS_FONT_SIZE, usePanelContext, @@ -176,6 +177,7 @@ export const BarChartPanel: React.FunctionComponent = ({ rowIndex={datapointIdx} columnIndex={seriesIdx} sortOrder={options.tooltip.sort} + mode={options.tooltip.mode} /> ); @@ -285,6 +287,10 @@ export const BarChartPanel: React.FunctionComponent = ({ }); } + if (options.tooltip.mode === TooltipDisplayMode.None) { + return null; + } + return ( {hover && coords && ( diff --git a/public/app/plugins/panel/geomap/components/DataHoverView.tsx b/public/app/plugins/panel/geomap/components/DataHoverView.tsx index 6a7a750ee4e..b6b55960d51 100644 --- a/public/app/plugins/panel/geomap/components/DataHoverView.tsx +++ b/public/app/plugins/panel/geomap/components/DataHoverView.tsx @@ -10,7 +10,7 @@ import { GrafanaTheme2, LinkModel, } from '@grafana/data'; -import { SortOrder } from '@grafana/schema'; +import { SortOrder, TooltipDisplayMode } from '@grafana/schema'; import { LinkButton, useStyles2, VerticalGroup } from '@grafana/ui'; export interface Props { @@ -18,9 +18,10 @@ export interface Props { rowIndex?: number | null; // the hover row columnIndex?: number | null; // the hover column sortOrder?: SortOrder; + mode?: TooltipDisplayMode | null; } -export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder }: Props) => { +export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode }: Props) => { const styles = useStyles2(getStyles); if (!data || rowIndex == null) { @@ -60,12 +61,19 @@ export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder }: Props) return ( - {displayValues.map((v, i) => ( - - - + {(mode === TooltipDisplayMode.Multi || mode === null) && + displayValues.map((v, i) => ( + + + + + ))} + {mode === TooltipDisplayMode.Single && columnIndex && ( + + + - ))} + )} {links.length > 0 && (
{v[0]}:{v[2]}
{v[0]}:{v[2]}
{displayValues[columnIndex][0]}:{displayValues[columnIndex][2]}
From 457c110b26ac8c2699d7406b36b2aaa8af30babd Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 29 Jul 2022 09:18:53 -0400 Subject: [PATCH 20/24] API: Do not expose user input in datasource error responses (#52992) (#52996) (cherry picked from commit 87004cc22dcd096e496bd94bf8ff0269ae290297) Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com> --- pkg/api/datasources.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 7a918ca7799..833981091c8 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -235,7 +235,8 @@ func (hs *HTTPServer) DeleteDataSourceByName(c *models.ReqContext) response.Resp func validateURL(cmdType string, url string) response.Response { if _, err := datasource.ValidateURL(cmdType, url); err != nil { - return response.Error(400, fmt.Sprintf("Validation error, invalid URL: %q", url), err) + datasourcesLogger.Error("Failed to validate URL", "url", url) + return response.Error(http.StatusBadRequest, "Validation error, invalid URL", err) } return nil From 04bdcc09ed946ea185fa9f413565e5f29d6b5dea Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 29 Jul 2022 11:58:52 -0400 Subject: [PATCH 21/24] Prometheus: Fix adding of multiple values for regex operator (#52978) (#53003) * Prometheus, Loki: Fix adding of multiple values for regex operator * Refactor (cherry picked from commit 1eb3513781b9b0b3d916809d5610863c648a952a) Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --- .../querybuilder/shared/LabelFilterItem.tsx | 10 +++++----- .../querybuilder/shared/LabelFilters.test.tsx | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx index 44800ebb69b..f4d3c775658 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx @@ -25,7 +25,7 @@ export function LabelFilterItem({ item, defaultOp, onChange, onDelete, onGetLabe }>({}); const isMultiSelect = () => { - return item.op === operators[0].label; + return operators.find((op) => op.label === item.op)?.isMultiValue; }; const getSelectOptionsFromString = (item?: string): string[] => { @@ -124,8 +124,8 @@ export function LabelFilterItem({ item, defaultOp, onChange, onDelete, onGetLabe } const operators = [ - { label: '=~', value: '=~' }, - { label: '=', value: '=' }, - { label: '!=', value: '!=' }, - { label: '!~', value: '!~' }, + { label: '=~', value: '=~', isMultiValue: true }, + { label: '=', value: '=', isMultiValue: false }, + { label: '!=', value: '!=', isMultiValue: false }, + { label: '!~', value: '!~', isMultiValue: true }, ]; diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters.test.tsx index 62dc1a169e7..fb7d8154ee7 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters.test.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters.test.tsx @@ -31,6 +31,21 @@ describe('LabelFilters', () => { expect(getAddButton()).toBeInTheDocument(); }); + it('renders multiple values for regex selectors', async () => { + setup([ + { label: 'bar', op: '!~', value: 'baz|bat|bau' }, + { label: 'foo', op: '!~', value: 'fop|for|fos' }, + ]); + expect(screen.getByText(/bar/)).toBeInTheDocument(); + expect(screen.getByText(/baz/)).toBeInTheDocument(); + expect(screen.getByText(/bat/)).toBeInTheDocument(); + expect(screen.getByText(/bau/)).toBeInTheDocument(); + expect(screen.getByText(/foo/)).toBeInTheDocument(); + expect(screen.getByText(/for/)).toBeInTheDocument(); + expect(screen.getByText(/fos/)).toBeInTheDocument(); + expect(getAddButton()).toBeInTheDocument(); + }); + it('adds new label', async () => { const { onChange } = setup([{ label: 'foo', op: '=', value: 'bar' }]); await userEvent.click(getAddButton()); From 721b17a7326e29256d65aa4fa68c1f3f3fb1a21f Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 29 Jul 2022 13:45:50 -0400 Subject: [PATCH 22/24] docker: The cue directory is gone. Catch-up (#52606) (#53004) The files got removed in 8876d56495bf88a2114fcbe6b415bdf1ed52466f but the container was not updated yet. (cherry picked from commit 27709adac3f7b3b89fec06e98ef2ae81a846bae4) Co-authored-by: Holger Freyther --- Dockerfile | 1 - Dockerfile.ubuntu | 1 - 2 files changed, 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 08a7d6f01ad..cd05b655ba4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,6 @@ RUN apk add --no-cache gcc g++ make WORKDIR /grafana COPY go.mod go.sum embed.go Makefile build.go package.json ./ -COPY cue cue COPY packages/grafana-schema packages/grafana-schema COPY public/app/plugins public/app/plugins COPY public/api-spec.json public/api-spec.json diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index 48ef4cc3920..0dc2c263f2f 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -29,7 +29,6 @@ COPY go.mod go.sum embed.go ./ COPY Makefile build.go package.json ./ COPY .bingo .bingo COPY pkg pkg/ -COPY cue cue/ COPY cue.mod cue.mod/ COPY packages/grafana-schema packages/grafana-schema/ COPY public/app/plugins public/app/plugins/ From 0857a0a1713f0d7cfa61eaa845cf9f782246a8ee Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Mon, 1 Aug 2022 05:04:57 -0400 Subject: [PATCH 23/24] BarchartPanel: Fix color from thresholds show incorrectly (#52038) (#52969) Signed-off-by: Kyle Cunningham (cherry picked from commit 11c79cd6da1cf4824e1a48d98d0b5935b1a22351) Co-authored-by: mingozh <77652617+mingozh@users.noreply.github.com> --- public/app/plugins/panel/barchart/BarChartPanel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/barchart/BarChartPanel.tsx b/public/app/plugins/panel/barchart/BarChartPanel.tsx index 7072eae680f..1a7fde5392e 100644 --- a/public/app/plugins/panel/barchart/BarChartPanel.tsx +++ b/public/app/plugins/panel/barchart/BarChartPanel.tsx @@ -208,7 +208,7 @@ export const BarChartPanel: React.FunctionComponent = ({ }; // Color by value - let getColor: ((seriesIdx: number, valueIdx: number) => string) | undefined = undefined; + let getColor: ((seriesIdx: number, valueIdx: number, value: number) => string) | undefined = undefined; let fillOpacity = 1; @@ -217,7 +217,7 @@ export const BarChartPanel: React.FunctionComponent = ({ const disp = colorByField.display!; fillOpacity = (colorByField.config.custom.fillOpacity ?? 100) / 100; // gradientMode? ignore? - getColor = (seriesIdx: number, valueIdx: number) => disp(colorByField.values.get(valueIdx)).color!; + getColor = (seriesIdx: number, valueIdx: number, value: number) => disp(value).color!; } const prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => { From fafab7712777020984800d69e2b0b8d61d2df32d Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Mon, 1 Aug 2022 12:14:08 +0200 Subject: [PATCH 24/24] Update dependency moment to v2.29.4 [SECURITY] (#51886) (#53027) * Update dependency moment to v2.29.4 [SECURITY] * chore(moment): make sure nested moment deps resolve to moment@2.29.4 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jack Westbrook (cherry picked from commit b4c6efa07b2c9b94a67db511bc223245cfe24c3c) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-data/package.json | 2 +- packages/grafana-ui/package.json | 2 +- packages/jaeger-ui-components/package.json | 2 +- yarn.lock | 16 ++++++++-------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 2741ac47bf8..91332f36811 100644 --- a/package.json +++ b/package.json @@ -330,7 +330,7 @@ "logfmt": "^1.3.2", "lru-cache": "7.10.1", "memoize-one": "6.0.0", - "moment": "2.29.3", + "moment": "2.29.4", "moment-timezone": "0.5.34", "monaco-editor": "^0.31.1", "monaco-promql": "1.7.4", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index cc038353b86..d5e2d14781a 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -29,7 +29,7 @@ "eventemitter3": "4.0.7", "lodash": "4.17.21", "marked": "4.0.16", - "moment": "2.29.3", + "moment": "2.29.4", "moment-timezone": "0.5.34", "ol": "6.14.1", "papaparse": "5.3.2", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index b5a0609b8be..afe2b540ba1 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -58,7 +58,7 @@ "jquery": "3.6.0", "lodash": "4.17.21", "memoize-one": "6.0.0", - "moment": "2.29.3", + "moment": "2.29.4", "monaco-editor": "^0.31.1", "ol": "6.14.1", "prismjs": "1.28.0", diff --git a/packages/jaeger-ui-components/package.json b/packages/jaeger-ui-components/package.json index 8ada45fd21a..ba21da3c431 100644 --- a/packages/jaeger-ui-components/package.json +++ b/packages/jaeger-ui-components/package.json @@ -43,7 +43,7 @@ "lodash": "4.17.21", "lru-memoize": "^1.1.0", "memoize-one": "6.0.0", - "moment": "2.29.3", + "moment": "2.29.4", "moment-timezone": "0.5.34", "prop-types": "15.8.1", "react": "17.0.2", diff --git a/yarn.lock b/yarn.lock index 1bb1a2f2f50..21b4fcd7789 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3867,7 +3867,7 @@ __metadata: history: 4.10.1 lodash: 4.17.21 marked: 4.0.16 - moment: 2.29.3 + moment: 2.29.4 moment-timezone: 0.5.34 ol: 6.14.1 papaparse: 5.3.2 @@ -4281,7 +4281,7 @@ __metadata: lodash: 4.17.21 memoize-one: 6.0.0 mock-raf: 1.0.1 - moment: 2.29.3 + moment: 2.29.4 monaco-editor: ^0.31.1 ol: 6.14.1 postcss: 8.4.14 @@ -4460,7 +4460,7 @@ __metadata: lodash: 4.17.21 lru-memoize: ^1.1.0 memoize-one: 6.0.0 - moment: 2.29.3 + moment: 2.29.4 moment-timezone: 0.5.34 prop-types: 15.8.1 react: 17.0.2 @@ -19629,7 +19629,7 @@ __metadata: lru-cache: 7.10.1 memoize-one: 6.0.0 mini-css-extract-plugin: 2.6.0 - moment: 2.29.3 + moment: 2.29.4 moment-timezone: 0.5.34 monaco-editor: ^0.31.1 monaco-promql: 1.7.4 @@ -24569,10 +24569,10 @@ __metadata: languageName: node linkType: hard -"moment@npm:2.29.3, moment@npm:2.x, moment@npm:>= 2.9.0, moment@npm:^2.19.4, moment@npm:^2.20.1": - version: 2.29.3 - resolution: "moment@npm:2.29.3" - checksum: 2e780e36d9a1823c08a1b6313cbb08bd01ecbb2a9062095820a34f42c878991ccba53abaa6abb103fd5c01e763724f295162a8c50b7e95b4f1c992ef0772d3f0 +"moment@npm:2.29.4, moment@npm:2.x, moment@npm:>= 2.9.0, moment@npm:^2.19.4, moment@npm:^2.20.1": + version: 2.29.4 + resolution: "moment@npm:2.29.4" + checksum: 0ec3f9c2bcba38dc2451b1daed5daded747f17610b92427bebe1d08d48d8b7bdd8d9197500b072d14e326dd0ccf3e326b9e3d07c5895d3d49e39b6803b76e80e languageName: node linkType: hard