Merge branch 'main' into drclau/unistor/replace-authenticators-3

drclau/unistor/namespace_authorizer
Claudiu Dragalina-Paraipan 9 months ago
commit 6993f108a2
  1. 16
      .betterer.results
  2. 1
      .betterer.ts
  3. 6
      .bingo/Variables.mk
  4. 6
      .bingo/golangci-lint.mod
  5. 45
      .bingo/golangci-lint.sum
  6. 2
      .bingo/variables.env
  7. 294
      .drone.yml
  8. 2
      .eslintignore
  9. 2
      .github/CODEOWNERS
  10. 11
      .github/workflows/detect-breaking-changes-levitate.yml
  11. 4
      .github/workflows/go_lint.yml
  12. 4
      .github/workflows/pr-go-workspace-check.yml
  13. 28
      .golangci.toml
  14. 3
      Dockerfile
  15. 16
      Makefile
  16. 6
      conf/defaults.ini
  17. 12
      contribute/engineering/terminology.md
  18. 30
      contribute/style-guides/e2e-plugins.md
  19. 71
      contribute/style-guides/e2e.md
  20. 390
      contribute/style-guides/frontend.md
  21. 20
      contribute/style-guides/redux.md
  22. 66
      contribute/style-guides/storybook.md
  23. 21
      contribute/style-guides/styling.md
  24. 35
      contribute/style-guides/testing.md
  25. 135
      devenv/dev-dashboards/panel-stat/panel-stat-tests.json
  26. 2
      devenv/docker/blocks/auth/authentik/ldap_authentik.toml
  27. 4
      devenv/docker/blocks/mimir_backend/docker-compose.yaml
  28. 5
      devenv/docker/blocks/prometheus/docker-compose.yaml
  29. 4
      devenv/docker/blocks/prometheus/prometheus.yml
  30. 15
      devenv/docker/blocks/prometheus_high_card/Dockerfile
  31. 6
      devenv/docker/blocks/prometheus_high_card/docker-compose.yaml
  32. 20
      devenv/docker/blocks/prometheus_high_card/go.mod
  33. 26
      devenv/docker/blocks/prometheus_high_card/go.sum
  34. 123
      devenv/docker/blocks/prometheus_high_card/main.go
  35. 22
      devenv/plugins.yaml
  36. 12
      docs/make-docs
  37. 13
      docs/sources/administration/plugin-management/index.md
  38. 12
      docs/sources/administration/provisioning/index.md
  39. 1
      docs/sources/administration/roles-and-permissions/_index.md
  40. 1
      docs/sources/administration/service-accounts/index.md
  41. 45
      docs/sources/alerting/alerting-rules/create-alerts-panels.md
  42. 23
      docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md
  43. 4
      docs/sources/alerting/alerting-rules/create-mimir-loki-managed-rule.md
  44. 6
      docs/sources/alerting/configure-notifications/manage-contact-points/_index.md
  45. 57
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md
  46. 2
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-discord.md
  47. 2
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-email.md
  48. 2
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-google-chat.md
  49. 154
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt.md
  50. 2
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-oncall.md
  51. 2
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-opsgenie.md
  52. 2
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-slack.md
  53. 2
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-teams.md
  54. 2
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-telegram.md
  55. 2
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/pager-duty.md
  56. 2
      docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md
  57. 1
      docs/sources/alerting/configure-notifications/template-notifications/images-in-notifications.md
  58. 2
      docs/sources/alerting/manage-notifications/view-state-health.md
  59. 85
      docs/sources/alerting/set-up/configure-high-availability/_index.md
  60. 26
      docs/sources/alerting/set-up/configure-rbac/access-roles/index.md
  61. 25
      docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md
  62. 3
      docs/sources/dashboards/dashboard-public/index.md
  63. 1
      docs/sources/dashboards/manage-dashboards/index.md
  64. 1
      docs/sources/dashboards/use-dashboards/index.md
  65. 2
      docs/sources/dashboards/variables/add-template-variables/index.md
  66. 4
      docs/sources/datasources/mssql/query-editor/index.md
  67. 2
      docs/sources/developers/contribute.md
  68. 16
      docs/sources/developers/http_api/correlations.md
  69. 2
      docs/sources/developers/plugins/plugin.schema.json
  70. 1
      docs/sources/introduction/grafana-enterprise.md
  71. 1
      docs/sources/panels-visualizations/panel-editor-overview/index.md
  72. 65
      docs/sources/panels-visualizations/visualizations/table/index.md
  73. 82
      docs/sources/panels-visualizations/visualizations/time-series/index.md
  74. 98
      docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
  75. 5
      docs/sources/setup-grafana/configure-security/configure-authentication/ldap/index.md
  76. 2
      docs/sources/setup-grafana/installation/_index.md
  77. 2
      docs/sources/setup-grafana/installation/debian/index.md
  78. 4
      docs/sources/shared/alerts/alerting_provisioning.md
  79. 22
      docs/sources/upgrade-guide/upgrade-v11.2/index.md
  80. 1
      docs/sources/whatsnew/_index.md
  81. 271
      docs/sources/whatsnew/whats-new-in-v11-2.md
  82. 5
      e2e/custom-plugins/README.md
  83. 22
      e2e/custom-plugins/app-with-exposed-components/README.md
  84. 28
      e2e/custom-plugins/app-with-exposed-components/module.js
  85. 35
      e2e/custom-plugins/app-with-exposed-components/plugin.json
  86. 14
      e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/module.js
  87. 12
      e2e/custom-plugins/app-with-extension-point/README.md
  88. 141
      e2e/custom-plugins/app-with-extension-point/module.js
  89. 36
      e2e/custom-plugins/app-with-extension-point/plugin.json
  90. 26
      e2e/custom-plugins/app-with-extension-point/plugins/myorg-a-app/module.js
  91. 45
      e2e/custom-plugins/app-with-extension-point/plugins/myorg-a-app/plugin.json
  92. 27
      e2e/custom-plugins/app-with-extension-point/plugins/myorg-b-app/module.js
  93. 12
      e2e/custom-plugins/app-with-extensions/README.md
  94. 216
      e2e/custom-plugins/app-with-extensions/module.js
  95. 49
      e2e/custom-plugins/app-with-extensions/plugin.json
  96. 35
      e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions/extensionPoints.spec.ts
  97. 38
      e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions/extensions.spec.ts
  98. 9
      e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions/useExposedComponent.spec.ts
  99. 6
      e2e/scenes/dashboards-suite/general-dashboards.spec.ts
  100. 33
      e2e/test-plugins/README.md
  101. Some files were not shown because too many files have changed in this diff Show More

@ -208,7 +208,8 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"]
[0, 0, 0, "Do not use any type assertions.", "3"],
[0, 0, 0, "Do not use any type assertions.", "4"]
],
"packages/grafana-data/src/types/config.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
@ -542,9 +543,11 @@ exports[`better eslint`] = {
"packages/grafana-runtime/src/services/pluginExtensions/usePluginComponent.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"packages/grafana-runtime/src/services/pluginExtensions/usePluginComponents.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"packages/grafana-runtime/src/services/pluginExtensions/usePluginExtensions.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"packages/grafana-runtime/src/utils/DataSourceWithBackend.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
@ -2481,9 +2484,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "6"],
[0, 0, 0, "Do not use any type assertions.", "7"]
],
"public/app/features/alerting/unified/utils/rulerClient.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/alerting/unified/utils/rules.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
@ -4991,6 +4991,9 @@ exports[`better eslint`] = {
"public/app/features/plugins/extensions/getPluginExtensions.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/plugins/extensions/usePluginComponents.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/plugins/loader/sharedDependencies.ts:5381": [
[0, 0, 0, "* import is invalid because \'Layout,HorizontalGroup,VerticalGroup\' from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"]
],
@ -7810,7 +7813,6 @@ exports[`no gf-form usage`] = {
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"],
[0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"]
],
"public/app/features/variables/adhoc/picker/AdHocFilter.tsx:5381": [

@ -10,6 +10,7 @@ const eslintPathsToIgnore = [
'public/app/angular', // will be removed in Grafana 11
'public/app/plugins/panel/graph', // will be removed alongside angular
'public/app/plugins/panel/table-old', // will be removed alongside angular
'e2e/test-plugins',
];
// Avoid using functions that report the position of the issues, as this causes a lot of merge conflicts

@ -35,11 +35,11 @@ $(DRONE): $(BINGO_DIR)/drone.mod
@echo "(re)installing $(GOBIN)/drone-v1.5.0"
@cd $(BINGO_DIR) && GOWORK=off CGO_ENABLED=0 $(GO) build -mod=mod -modfile=drone.mod -o=$(GOBIN)/drone-v1.5.0 "github.com/drone/drone-cli/drone"
GOLANGCI_LINT := $(GOBIN)/golangci-lint-v1.59.1
GOLANGCI_LINT := $(GOBIN)/golangci-lint-v1.60.1
$(GOLANGCI_LINT): $(BINGO_DIR)/golangci-lint.mod
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
@echo "(re)installing $(GOBIN)/golangci-lint-v1.59.1"
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v1.59.1 "github.com/golangci/golangci-lint/cmd/golangci-lint"
@echo "(re)installing $(GOBIN)/golangci-lint-v1.60.1"
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v1.60.1 "github.com/golangci/golangci-lint/cmd/golangci-lint"
JB := $(GOBIN)/jb-v0.5.1
$(JB): $(BINGO_DIR)/jb.mod

@ -1,7 +1,7 @@
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
go 1.22
go 1.22.1
toolchain go1.22.4
toolchain go1.23.0
require github.com/golangci/golangci-lint v1.59.1 // cmd/golangci-lint
require github.com/golangci/golangci-lint v1.60.1 // cmd/golangci-lint

@ -55,18 +55,26 @@ github.com/Antonboom/testifylint v1.3.0 h1:UiqrddKs1W3YK8R0TUuWwrVKlVAnS07DTUVWW
github.com/Antonboom/testifylint v1.3.0/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM=
github.com/Antonboom/testifylint v1.3.1 h1:Uam4q1Q+2b6H7gvk9RQFw6jyVDdpzIirFOOrbs14eG4=
github.com/Antonboom/testifylint v1.3.1/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM=
github.com/Antonboom/testifylint v1.4.3 h1:ohMt6AHuHgttaQ1xb6SSnxCeK4/rnK7KKzbvs7DmEck=
github.com/Antonboom/testifylint v1.4.3/go.mod h1:+8Q9+AOLsz5ZiQiiYujJKs9mNz398+M6UgslP4qgJLA=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Crocmagnon/fatcontext v0.2.2 h1:OrFlsDdOj9hW/oBEJBNSuH7QWf+E9WPVHw+x52bXVbk=
github.com/Crocmagnon/fatcontext v0.2.2/go.mod h1:WSn/c/+MMNiD8Pri0ahRj0o9jVpeowzavOQplBJw6u0=
github.com/Crocmagnon/fatcontext v0.4.0 h1:4ykozu23YHA0JB6+thiuEv7iT6xq995qS1vcuWZq0tg=
github.com/Crocmagnon/fatcontext v0.4.0/go.mod h1:ZtWrXkgyfsYPzS6K3O88va6t2GEglG93vnII/F94WC0=
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM=
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c=
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI=
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU=
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
@ -100,6 +108,8 @@ github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ
github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=
github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM=
github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo=
github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw=
github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo=
github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY=
github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ=
github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA=
@ -132,6 +142,7 @@ github.com/ckaznocha/intrange v0.1.2/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8Vh
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo=
github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc=
github.com/daixiang0/gci v0.12.3 h1:yOZI7VAxAGPQmkb1eqt5g/11SUlwoat1fSblGLmdiQc=
@ -208,6 +219,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -248,6 +261,8 @@ github.com/golangci/golangci-lint v1.59.0 h1:st69YDnAH/v2QXDcgUaZ0seQajHScPALBVk
github.com/golangci/golangci-lint v1.59.0/go.mod h1:QNA32UWdUdHXnu+Ap5/ZU4WVwyp2tL94UxEXrSErjg0=
github.com/golangci/golangci-lint v1.59.1 h1:CRRLu1JbhK5avLABFJ/OHVSQ0Ie5c4ulsOId1h3TTks=
github.com/golangci/golangci-lint v1.59.1/go.mod h1:jX5Oif4C7P0j9++YB2MMJmoNrb01NJ8ITqKWNLewThg=
github.com/golangci/golangci-lint v1.60.1 h1:DRKNqNTQRLBJZ1il5u4fvgLQCjQc7QFs0DbhksJtVJE=
github.com/golangci/golangci-lint v1.60.1/go.mod h1:jDIPN1rYaIA+ijp9OZcUmUCoQOtZ76pOlFbi15FlLJY=
github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g=
github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI=
github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM=
@ -331,6 +346,8 @@ github.com/jjti/go-spancheck v0.5.3 h1:vfq4s2IB8T3HvbpiwDTYgVPj1Ze/ZSXrTtaZRTc7C
github.com/jjti/go-spancheck v0.5.3/go.mod h1:eQdOX1k3T+nAKvZDyLC3Eby0La4dZ+I19iOl5NzSPFE=
github.com/jjti/go-spancheck v0.6.1 h1:ZK/wE5Kyi1VX3PJpUO2oEgeoI4FWOUm7Shb2Gbv5obI=
github.com/jjti/go-spancheck v0.6.1/go.mod h1:vF1QkOO159prdo6mHRxak2CpzDpHAfKiPUDP/NeRnX8=
github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk=
github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -401,6 +418,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE=
github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA=
github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A=
github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@ -412,6 +431,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA=
github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI=
github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=
github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
@ -448,6 +469,8 @@ github.com/polyfloyd/go-errorlint v1.5.1 h1:5gHxDjLyyWij7fhfrjYNNlHsUNQeyx0LFQKU
github.com/polyfloyd/go-errorlint v1.5.1/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs=
github.com/polyfloyd/go-errorlint v1.5.2 h1:SJhVik3Umsjh7mte1vE0fVZ5T1gznasQG3PV7U5xFdA=
github.com/polyfloyd/go-errorlint v1.5.2/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs=
github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY=
github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
@ -486,6 +509,8 @@ github.com/ryancurrah/gomodguard v1.3.1 h1:fH+fUg+ngsQO0ruZXXHnA/2aNllWA1whly4a6
github.com/ryancurrah/gomodguard v1.3.1/go.mod h1:DGFHzEhi6iJ0oIDfMuo3TgrS+L9gZvrEfmjjuelnRU0=
github.com/ryancurrah/gomodguard v1.3.2 h1:CuG27ulzEB1Gu5Dk5gP8PFxSOZ3ptSdP5iI/3IXxM18=
github.com/ryancurrah/gomodguard v1.3.2/go.mod h1:LqdemiFomEjcxOqirbQCb3JFvSxH2JUYMerTFd3sF2o=
github.com/ryancurrah/gomodguard v1.3.3 h1:eiSQdJVNr9KTNxY2Niij8UReSwR8Xrte3exBrAZfqpg=
github.com/ryancurrah/gomodguard v1.3.3/go.mod h1:rsKQjj4l3LXe8N344Ow7agAy5p9yjsWOtRzUMYmA0QY=
github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=
github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=
github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc=
@ -498,6 +523,8 @@ github.com/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9
github.com/sashamelentyev/usestdlibvars v1.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8=
github.com/sashamelentyev/usestdlibvars v1.26.0 h1:LONR2hNVKxRmzIrZR0PhSF3mhCAzvnr+DcUiHgREfXE=
github.com/sashamelentyev/usestdlibvars v1.26.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8=
github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI=
github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8=
github.com/securego/gosec/v2 v2.19.0 h1:gl5xMkOI0/E6Hxx0XCY2XujA3V7SNSefA8sC+3f1gnk=
github.com/securego/gosec/v2 v2.19.0/go.mod h1:hOkDcHz9J/XIgIlPDXalxjeVYsHxoWUc5zJSHxcB8YM=
github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 h1:rnO6Zp1YMQwv8AyxzuwsVohljJgp4L0ZqiCgtACsPsc=
@ -515,6 +542,8 @@ github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+W
github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=
github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak=
github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg=
github.com/sivchari/tenv v1.10.0 h1:g/hzMA+dBCKqGXgW8AV/1xIWhAvDrx0zFKNR48NFMg0=
github.com/sivchari/tenv v1.10.0/go.mod h1:tdY24masnVoZFxYrHv/nD6Tc8FbkEtAQEEziXpyMgqY=
github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00=
github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo=
github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=
@ -525,6 +554,8 @@ github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@ -577,6 +608,8 @@ github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/
github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=
github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI=
github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k=
github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM=
github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U=
github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU=
github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg=
github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
@ -608,6 +641,8 @@ go-simpler.org/sloglint v0.7.0 h1:rMZRxD9MbaGoRFobIOicMxZzum7AXNFDlez6xxJs5V4=
go-simpler.org/sloglint v0.7.0/go.mod h1:g9SXiSWY0JJh4LS39/Q0GxzP/QX2cVcbTOYhDpXrJEs=
go-simpler.org/sloglint v0.7.1 h1:qlGLiqHbN5islOxjeLXoPtUdZXb669RW+BDQ+xOSNoU=
go-simpler.org/sloglint v0.7.1/go.mod h1:OlaVDRh/FKKd4X4sIMbsz8st97vomydceL146Fthh/c=
go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY=
go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -679,6 +714,8 @@ golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -740,6 +777,8 @@ golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -800,6 +839,8 @@ golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -890,6 +931,8 @@ golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -997,6 +1040,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs=
honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
honnef.co/go/tools v0.5.0 h1:29uoiIormS3Z6R+t56STz/oI4v+mB51TSmEOdJPgRnE=
honnef.co/go/tools v0.5.0/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs=
mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo=
mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA=
mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w=

@ -14,7 +14,7 @@ CUE="${GOBIN}/cue-v0.5.0"
DRONE="${GOBIN}/drone-v1.5.0"
GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.59.1"
GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.60.1"
JB="${GOBIN}/jb-v0.5.1"

File diff suppressed because it is too large Load Diff

@ -13,7 +13,7 @@ node_modules
/public/lib/monaco
/scripts/grafana-server/tmp
vendor
e2e/custom-plugins
e2e/test-plugins
playwright-report
# TS generate from cue by cuetsy

@ -209,6 +209,7 @@
/devenv/docker/blocks/postgres_tests/ @grafana/oss-big-tent
/devenv/docker/blocks/prometheus/ @grafana/observability-metrics
/devenv/docker/blocks/prometheus_random_data/ @grafana/observability-metrics
/devenv/docker/blocks/prometheus_high_card/ @grafana/observability-metrics
/devenv/docker/blocks/pyroscope/ @grafana/observability-traces-and-profiling
/devenv/docker/blocks/redis/ @bergquist
/devenv/docker/blocks/sensugo/ @grafana/grafana-backend-group
@ -319,6 +320,7 @@
/e2e/ @grafana/grafana-frontend-platform
/e2e/cloud-plugins-suite/ @grafana/partner-datasources
/e2e/plugin-e2e/plugin-e2e-api-tests/ @grafana/plugins-platform-frontend
/e2e/test-plugins/grafana-extensionstest-app/ @grafana/plugins-platform-frontend
# Packages
/packages/ @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend

@ -141,6 +141,7 @@ jobs:
with:
workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
service_account: ${{ secrets.LEVITATE_SA }}
project_id: 'grafanalabs-global'
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v2'
@ -149,16 +150,6 @@ jobs:
project_id: 'grafanalabs-global'
install_components: 'bq'
# This step is needed to generate a detailed levitate report
- name: Set up gcloud project
run: |
unset CLOUDSDK_CORE_PROJECT
unset GCLOUD_PROJECT
unset GCP_PROJECT
unset GOOGLE_CLOUD_PROJECT
gcloud config set project grafanalabs-global
- name: Get link for the Github Action job
id: job
uses: actions/github-script@v6

@ -25,8 +25,8 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.59.1
version: v1.60.1
args: |
--config .golangci.toml --max-same-issues=0 --max-issues-per-linter=0 --verbose $(./scripts/go-workspace/golangci-lint-includes.sh)
--config .golangci.toml --max-same-issues=0 --max-issues-per-linter=0 --verbose $(./scripts/go-workspace/golangci-lint-includes.sh)
skip-cache: true
install-mode: binary

@ -30,4 +30,6 @@ jobs:
echo "Please run 'make update-workspace' and commit the changes."
echo "If there is a change in enterprise dependencies, please update pkg/extensions/main.go."
exit 1
fi
fi
- name: Ensure Dockerfile contains submodule COPY commands
run: ./scripts/go-workspace/validate-dockerfile.sh

@ -125,6 +125,34 @@ files = [
"**/pkg/promlib/**/*"
]
[linters-settings.depguard.rules.storage-unified-resource]
list-mode = "lax"
allow = [
"github.com/grafana/grafana/pkg/apimachinery",
]
deny = [
{ pkg = "github.com/grafana/grafana/pkg", desc = "pkg/storage/unified/resource is not allowed to import grafana core" }
]
files = [
"./pkg/storage/unified/resource/*",
"./pkg/storage/unified/resource/**/*"
]
[linters-settings.depguard.rules.storage-unified-apistore]
list-mode = "lax"
allow = [
"github.com/grafana/grafana/pkg/apimachinery",
"github.com/grafana/grafana/pkg/apiserver",
"github.com/grafana/grafana/pkg/unified/resource",
]
deny = [
{ pkg = "github.com/grafana/grafana/pkg", desc = "pkg/storage/unified/apistore is not allowed to import grafana core" }
]
files = [
"./pkg/storage/unified/apistore/*",
"./pkg/storage/unified/apistore/**/*"
]
[linters-settings.gocritic]
enabled-checks = ["ruleguard"]
[linters-settings.gocritic.settings.ruleguard]

@ -3,7 +3,7 @@
ARG BASE_IMAGE=alpine:3.19.1
ARG JS_IMAGE=node:20-alpine
ARG JS_PLATFORM=linux/amd64
ARG GO_IMAGE=golang:1.22.4-alpine
ARG GO_IMAGE=golang:1.23.0-alpine
ARG GO_SRC=go-builder
ARG JS_SRC=js-builder
@ -63,6 +63,7 @@ COPY pkg/build/go.* pkg/build/
COPY pkg/build/wire/go.* pkg/build/wire/
COPY pkg/promlib/go.* pkg/promlib/
COPY pkg/storage/unified/resource/go.* pkg/storage/unified/resource/
COPY pkg/storage/unified/apistore/go.* pkg/storage/unified/apistore/
COPY pkg/semconv/go.* pkg/semconv/
COPY pkg/aggregator/go.* pkg/aggregator/

@ -8,7 +8,7 @@ WIRE_TAGS = "oss"
include .bingo/Variables.mk
GO = go
GO_VERSION = 1.22.4
GO_VERSION = 1.23.0
GO_LINT_FILES ?= $(shell ./scripts/go-workspace/golangci-lint-includes.sh)
GO_TEST_FILES ?= $(shell ./scripts/go-workspace/test-includes.sh)
SH_FILES ?= $(shell find ./scripts -name *.sh)
@ -239,6 +239,14 @@ test-go-unit: ## Run unit tests for backend with flags.
printf '$(GO_TEST_FILES)' | xargs \
$(GO) test $(GO_RACE_FLAG) -short -covermode=atomic -timeout=30m
.PHONY: test-go-unit-pretty
test-go-unit-pretty: check-tparse
@if [ -z "$(FILES)" ]; then \
echo "Notice: FILES variable is not set. Try \"make test-go-unit-pretty FILES=./pkg/services/mysvc\""; \
exit 1; \
fi
$(GO) test $(GO_RACE_FLAG) -timeout=10s $(FILES) -json | tparse -all
.PHONY: test-go-integration
test-go-integration: ## Run integration tests for backend with flags.
@echo "test backend integration tests"
@ -431,6 +439,12 @@ go-race-is-enabled:
enable-go-race:
@touch .go-race-enabled-locally
check-tparse:
@command -v tparse >/dev/null 2>&1 || { \
echo >&2 "Error: tparse is not installed. Refer to https://github.com/mfridman/tparse"; \
exit 1; \
}
.PHONY: help
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

@ -1729,6 +1729,12 @@ install_token =
hide_angular_deprecation =
# Comma separated list of plugin ids for which environment variables should be forwarded. Used only when feature flag pluginsSkipHostEnvVars is enabled.
forward_host_env_vars =
# Comma separated list of plugin ids to install as part of the startup process. Used only when feature flag backgroundPluginInstaller is enabled.
preinstall =
# Controls whether preinstall plugins asynchronously (in the background) or synchronously (blocking). Useful when preinstalled plugins are used with provisioning.
preinstall_async = true
# Disables preinstall feature. It has the same effect as setting preinstall to an empty list.
preinstall_disabled = false
#################################### Grafana Live ##########################################
[live]

@ -2,16 +2,14 @@
<!-- Keep terms in alphabetical order: -->
This document defines technical terms used in Grafana.
This glossary defines technical terms used in Grafana.
## TLS/SSL
The acronyms [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security) (Transport Layer Security) and
[SSL](https://en.wikipedia.org/wiki/SSL) (Secure Socket Layer) are both used to describe the HTTPS security layer,
and are in practice synonymous. However, TLS is considered the current name for the technology, and SSL is considered
[SSL](https://en.wikipedia.org/wiki/SSL) (Secure Socket Layer) are both used to describe the HTTPS security layer.
In practice, they are synonymous. However, TLS is considered the current name for the technology, and SSL is considered
[deprecated](https://tools.ietf.org/html/rfc7568).
As such, while both terms are in use (also in our codebase) and are indeed interchangeable, TLS is the preferred term.
That said however, we have at Grafana Labs decided to use both acronyms in combination when referring to this type of
technology, i.e. _TLS/SSL_. This is in order to not confuse those who may not be aware of them being synonymous,
and SSL still being so prevalent in common discourse.
As such, while we use both terms in our codebase and documentation, we generally prefer TLS.
However, we use both acronyms in combination when referring to this type of technology, that is, _TLS/SSL_. We do this because we don't want to confuse readers who may not be aware of them being synonymous, and SSL is still prevalent in common discourse.

@ -1,16 +1,18 @@
# end-to-end tests for plugins
# End-to-end tests for plugins
When end-to-end testing Grafana plugins, it's recommended to use the [`@grafana/plugin-e2e`](https://www.npmjs.com/package/@grafana/plugin-e2e?activeTab=readme) testing tool. `@grafana/plugin-e2e` extends [`@playwright/test`](https://playwright.dev/) capabilities with relevant fixtures, models, and expect matchers; enabling comprehensive end-to-end testing of Grafana plugins across multiple versions of Grafana. For information on how to get started with Plugin end-to-end testing and Playwright, checkout the [Get started](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/get-started) guide.
When end-to-end testing Grafana plugins, a best practice is to use the [`@grafana/plugin-e2e`](https://www.npmjs.com/package/@grafana/plugin-e2e?activeTab=readme) testing tool. The `@grafana/plugin-e2e` tool extends [`@playwright/test`](https://playwright.dev/) capabilities with relevant fixtures, models, and expect matchers. Use it to enable comprehensive end-to-end testing of Grafana plugins across multiple versions of Grafana.
## Adding end-to-end tests for a core plugin
> **Note:** To learn more, refer to our documentation on [plugin development](https://grafana.com/developers/plugin-tools/) and [end-to-end plugin testing](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/get-started).
Playwright end-to-end tests for plugins should be added to the [`e2e/plugin-e2e`](https://github.com/grafana/grafana/tree/main/e2e/plugin-e2e) directory.
## Add end-to-end tests for a core plugin
1. Add a new directory that has the name as your plugin [`here`](https://github.com/grafana/grafana/tree/main/e2e/plugin-e2e). This is where your plugin tests will be kept.
You can add Playwright end-to-end tests for plugins to the [`e2e/plugin-e2e`](https://github.com/grafana/grafana/tree/main/e2e/plugin-e2e) directory.
2. Playwright uses [projects](https://playwright.dev/docs/test-projects) to logically group tests together. All tests in a project share the same configuration.
In the [Playwright config file](https://github.com/grafana/grafana/blob/main/playwright.config.ts), add a new project item. Make sure the `name` and the `testDir` sub directory matches the name of the directory that contains your plugin tests.
Adding `'authenticate'` to the list of dependencies and specifying `'playwright/.auth/admin.json'` as storage state will ensure all tests in your project will start already authenticated as an admin user. If you wish to use a different role for and perhaps test RBAC for some of your tests, please refer to the plugin-e2e [documentation](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/use-authentication).
1. Add a new directory that has the name as your plugin [`here`](https://github.com/grafana/grafana/tree/main/e2e/plugin-e2e). This is the directory where your plugin tests will be kept.
1. Playwright uses [projects](https://playwright.dev/docs/test-projects) to logically group tests together. All tests in a project share the same configuration.
In the [Playwright config file](https://github.com/grafana/grafana/blob/main/playwright.config.ts), add a new project item. Make sure the `name` and the `testDir` subdirectory match the name of the directory that contains your plugin tests.
Add `'authenticate'` to the list of dependencies and specify `'playwright/.auth/admin.json'` as the storage state to ensure that all tests in your project will start already authenticated as an admin user. If you want to use a different role for and perhaps test RBAC for some of your tests, refer to our [documentation](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/use-authentication).
```ts
{
@ -24,14 +26,16 @@ Playwright end-to-end tests for plugins should be added to the [`e2e/plugin-e2e`
},
```
3. Update the [CODEOWNERS](https://github.com/grafana/grafana/blob/main/.github/CODEOWNERS/#L315) file so that your team is owner of the tests in the directory you added in step 1.
1. Update the [CODEOWNERS](https://github.com/grafana/grafana/blob/main/.github/CODEOWNERS/#L315) file so that your team is owner of the tests in the directory you added in step 1.
## Commands
- `yarn e2e:playwright` will run all Playwright tests. Optionally, you can provide the `--project mysql` argument to run tests in a certain project.
- `yarn e2e:playwright` runs all Playwright tests. Optionally, you can provide the `--project mysql` argument to run tests in a specific project.
The `yarn e2e:playwright` script assumes you have Grafana running on `localhost:3000`. You may change this with an environment variable:
The script above assumes you have Grafana running on `localhost:3000`. You may change this by providing environment variables.
`HOST=127.0.0.1 PORT=3001 yarn e2e:playwright`
`HOST=127.0.0.1 PORT=3001 yarn e2e:playwright`
The `yarn e2e:playwright:server` starts a Grafana [development server](https://github.com/grafana/grafana/blob/main/scripts/grafana-server/start-server) on port 3001 and runs the Playwright tests.
- `yarn e2e:playwright:server` will start a Grafana [development server](https://github.com/grafana/grafana/blob/main/scripts/grafana-server/start-server) on port 3001 and run the Playwright tests. The development server is provisioned with the [devenv](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md#add-data-sources) dashboards, data sources and apps.
- You can provision the development server with the [devenv](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md#add-data-sources) dashboards, data sources, and apps.

@ -1,4 +1,4 @@
# End-to-End tests
# End-to-end tests
Grafana Labs uses a minimal [homegrown solution](../../e2e/utils/index.ts) built on top of [Cypress](https://cypress.io) for its end-to-end (E2E) tests.
@ -6,17 +6,17 @@ Important notes:
- We generally store all element identifiers ([CSS selectors](https://mdn.io/docs/Web/CSS/CSS_Selectors)) within the framework for reuse and maintainability.
- We generally do not use stubs or mocks as to fully simulate a real user.
- Cypress' promises [do not behave as you'd expect](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Mixing-Async-and-Sync-code).
- [Testing core Grafana](e2e-core.md) is different than [testing plugins](e2e-plugins.md) - core Grafana uses Cypress whereas plugins use [Playwright test](https://playwright.dev/).
- Cypress' promises [don't behave as you might expect](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Mixing-Async-and-Sync-code).
- [Testing core Grafana](e2e-core.md) is different than [testing plugins](e2e-plugins.md)core Grafana uses Cypress whereas plugins use [Playwright test](https://playwright.dev/).
## Framework structure
Inspired by https://martinfowler.com/bliki/PageObject.html
Our framework structure is inspired by [Martin Fowler's Page Object](https://martinfowler.com/bliki/PageObject.html).
- `Selector`: A unique identifier that is used from the E2E framework to retrieve an element from the Browser
- `Page`: An abstraction for an object that contains one or more `Selectors` with `visit` function to navigate to the page.
- `Component`: An abstraction for an object that contains one or more `Selectors` but without `visit` function
- `Flow`: An abstraction that contains a sequence of actions on one or more `Pages` that can be reused and shared between tests
- **`Selector`**: A unique identifier that is used from the E2E framework to retrieve an element from the browser
- **`Page`**: An abstraction for an object that contains one or more `Selector` identifiers with the `visit` function to go to the page.
- **`Component`**: An abstraction for an object that contains one or more `Selector` identifiers but without the `visit` function
- **`Flow`**: An abstraction that contains a sequence of actions on one or more `Page` abstractions that can be reused and shared between tests
## Basic example
@ -26,13 +26,15 @@ Let's start with a simple [JSX](https://reactjs.org/docs/introducing-jsx.html) e
<input className="gf-form-input login-form-input" type="text" />
```
We _could_ target the field with a CSS selector like `.gf-form-input.login-form-input` but that would be brittle as style changes occur frequently. Furthermore there is nothing that signals to future developers that this input is part of an E2E test. At Grafana, we use `data-testid` attributes as our preferred way of defining selectors. See [Aria-Labels vs data-testid](#aria-labels-vs-data-testid) for more details.
It is possible to target the field with a CSS selector like `.gf-form-input.login-form-input`. However, doing so is a brittle solution because style changes occur frequently.
Furthermore, there is nothing that signals to future developers that this input is part of an E2E test. At Grafana, we use `data-testid` attributes as our preferred way of defining selectors. See [Aria-Labels vs data-testid](#aria-labels-vs-data-testid) for more details.
```jsx
<input data-testid="Username input field" className="gf-form-input login-form-input" type="text" />
```
The next step is to create a `Page` representation in our E2E framework to glue the test with the real implementation using the `pageFactory` function. For that function we can supply a `url` and `selectors` like in the example below:
The next step is to create a `Page` representation in our E2E framework. Doing so glues the test with the real implementation using the `pageFactory` function. For that function we can supply a `url` and selector like in the following example:
```typescript
export const Login = {
@ -43,9 +45,9 @@ export const Login = {
};
```
Note that the selector is prefixed with `data-testid` - this is a signal to the framework to look for the selector in the `data-testid` attribute.
In this example, the selector is prefixed with `data-testid`. The prefix is a signal to the framework to look for the selector in the `data-testid` attribute.
The next step is to add the `Login` page to the `Pages` export within [_\<repo-root>/packages/grafana-e2e-selectors/src/selectors/pages.ts_](../../packages/grafana-e2e-selectors/src/selectors/pages.ts) so that it appears when we type `e2e.pages` in our IDE.
The next step is to add the `Login` page to the `Pages` export within [_\<repo-root>/packages/grafana-e2e-selectors/src/selectors/pages.ts_](../../packages/grafana-e2e-selectors/src/selectors/pages.ts) so that it appears when we type `e2e.pages` in your IDE.
```typescript
export const Pages = {
@ -56,7 +58,9 @@ export const Pages = {
};
```
Now that we have a `Page` called `Login` in our `Pages` const we can use that to add a selector in our html like shown below and now this really signals to future developers that it is part of an E2E test.
Now that we have a page called `Login` in our `Pages` const, use it to add a selector in our HTML as shown in the following example. This page really signals to future developers that it is part of an E2E test.
Example:
```jsx
import { selectors } from '@grafana/e2e-selectors';
@ -66,9 +70,8 @@ import { selectors } from '@grafana/e2e-selectors';
The last step in our example is to use our `Login` page as part of a test.
- The `url` property is used whenever we call the `visit` function and is equivalent to the Cypress' [`cy.visit()`](https://docs.cypress.io/api/commands/visit.html#Syntax).
- Any defined selector can be accessed from the `Login` page by invoking it. This is equivalent to the result of the Cypress function [`cy.get(…)`](https://docs.cypress.io/api/commands/get.html#Syntax).
- Use the `url` property whenever you call the `visit` function. It is equivalent to the [`cy.visit()`](https://docs.cypress.io/api/commands/visit.html#Syntax) in Cypress.
- Access any defined selector from the `Login` page by invoking it. This is equivalent to the result of the Cypress function [`cy.get(…)`](https://docs.cypress.io/api/commands/get.html#Syntax).
```typescript
describe('Login test', () => {
@ -83,7 +86,7 @@ describe('Login test', () => {
## Advanced example
Let's take a look at an example that uses the same `selector` for multiple items in a list for instance. In this example app we have a list of data sources that we want to click on during an E2E test.
Let's take a look at an example that uses the same selector for multiple items in a list for instance. In this example app, there's a list of data sources that we want to click on during an E2E test.
```jsx
<ul>
@ -97,7 +100,7 @@ Let's take a look at an example that uses the same `selector` for multiple items
</ul>
```
Just as before in the basic example we'll start by creating a page abstraction using the `pageFactory` function:
Like in the basic example, start by creating a page abstraction using the `pageFactory` function:
```typescript
export const DataSources = {
@ -106,11 +109,11 @@ export const DataSources = {
};
```
You might have noticed that instead of a simple `string` as the `selector`, we're using a `function` that takes a string parameter as an argument and returns a formatted string using the argument.
You might have noticed that instead of a simple string as the selector, there's a function that takes a string parameter as an argument and returns a formatted string using the argument.
Just as before we need to add the `DataSources` page to the exported const `Pages` in `packages/grafana-e2e-selectors/src/selectors/pages.ts`.
Just as before, you need to add the `DataSources` page to the exported const `Pages` in `packages/grafana-e2e-selectors/src/selectors/pages.ts`.
The next step is to use the `dataSources` selector function as in our example below:
The next step is to use the `dataSources` selector function as in the following example:
```jsx
<ul>
@ -126,7 +129,7 @@ The next step is to use the `dataSources` selector function as in our example be
</ul>
```
When this list is rendered with the data sources with names `A`, `B` and `C` ,the resulting HTML would look like:
When this list is rendered with the data sources with names `A`, `B` and `C` ,the resulting HTML looks like this:
```html
<div class="card-item-name" data-testid="data-testid Data source list item A">A</div>
@ -134,7 +137,7 @@ When this list is rendered with the data sources with names `A`, `B` and `C` ,th
<div class="card-item-name" data-testid="data-testid Data source list item C">C</div>
```
Now we can write our test. The one thing that differs from the [basic example](#basic-example) above is that we pass in which data source we want to click on as an argument to the selector function:
Now we can write our test. The one thing that differs from the previous [basic example](#basic-example) is that we pass in which data source we want to click as an argument to the selector function:
```typescript
describe('List test', () => {
@ -147,17 +150,17 @@ describe('List test', () => {
});
```
## Aria-Labels vs data-testid
## aria-label versus data-testid
Our selectors are set up to work with both aria-labels and data-testid attributes. Aria-labels help assistive technologies such as screenreaders identify interactive elements of a page for our users.
Our selectors are set up to work with both `aria-label` attributes and `data-testid` attributes. The `aria-label` attributes help assistive technologies such as screen readers identify interactive elements of a page for our users.
A good example of a time to use an aria-label might be if you have a button with an X to close:
A good example of a time to use an aria-label might be if you have a button with an **X** to close:
```
<button aria-label="close">X<button>
```
It might be clear visually that the X closes the modal, but audibly it would not be clear for example.
It might be clear visually that the **X** closes the modal, but audibly it would not be clear, for example.
```
<button aria-label="close">Close<button>
@ -165,28 +168,26 @@ It might be clear visually that the X closes the modal, but audibly it would not
The example might read aloud to a user as "Close, Close" or something similar.
However adding aria-labels to elements that are already clearly labeled or not interactive can be confusing and redundant for users with assistive technologies.
However, adding an aria-label to an element that is already clearly labeled or not interactive can be confusing and redundant for users with assistive technologies.
In such cases rather than adding unnecessary aria-labels to components so as to make them selectable for testing, it is preferable to use a data attribute that would not be read aloud with an assistive technology for example:
In such cases, don't add an unnecessary aria-label to a component so as to make them selectable for testing. Instead, use a data attribute that will not be read aloud with an assistive technology. For example:
```
<button data-testid="modal-close-button">Close<button>
```
We have added support for this in our selectors, to use:
Prefix your selector string with "data-testid":
We have added support for data attributes in our selectors Prefix your selector string with `data-testid`:
```typescript
export const Components = {
Login: {
openButton: 'open-button', // this would look for an aria-label
closeButton: 'data-testid modal-close-button', // this would look for a data-testid
openButton: 'open-button', // this looks for an aria-label
closeButton: 'data-testid modal-close-button', // this looks for a data-testid
},
};
```
and in your component, import the selectors and add the data test id:
and in your component, import the selectors and add the `data-testid`:
```
<button data-testid={Selectors.Components.Login.closeButton}>

@ -1,41 +1,6 @@
# Frontend Style Guide
Generally we follow the Airbnb [React Style Guide](https://github.com/airbnb/javascript/tree/master/react).
## Table of Contents
- [Frontend Style Guide](#frontend-style-guide)
- [Table of Contents](#table-of-contents)
- [Basic rules](#basic-rules)
- [Naming conventions](#naming-conventions)
- [Use `PascalCase` for:](#use-pascalcase-for)
- [Typescript class names](#typescript-class-names)
- [Types and interfaces](#types-and-interfaces)
- [Enums](#enums)
- [Use `camelCase` for:](#use-camelcase-for)
- [Functions](#functions)
- [Methods](#methods)
- [Variables](#variables)
- [React state and properties](#react-state-and-properties)
- [Emotion class names](#emotion-class-names)
- [Use `ALL_CAPS` for constants.](#use-all_caps-for-constants)
- [Use BEM convention for SASS styles.](#use-bem-convention-for-sass-styles)
- [Typing](#typing)
- [File and directory naming conventions](#file-and-directory-naming-conventions)
- [Code organization](#code-organization)
- [Exports](#exports)
- [Comments](#comments)
- [Linting](#linting)
- [React](#react)
- [Props](#props)
- [Name callback props and handlers with an "on" prefix.](#name-callback-props-and-handlers-with-an-on-prefix)
- [React Component definitions](#react-component-definitions)
- [React Component constructor](#react-component-constructor)
- [React Component defaultProps](#react-component-defaultprops)
- [State management](#state-management)
- [Proposal for removing or replacing Angular dependencies](https://github.com/grafana/grafana/pull/23048)
# Frontend style guide
Grafana Labs follows the [Airbnb React/JSX Style Guide](https://github.com/airbnb/javascript/tree/master/react) in matters pertaining to React. This guide provides highlights of the style rules we follow.
## Basic rules
@ -43,11 +8,13 @@ Generally we follow the Airbnb [React Style Guide](https://github.com/airbnb/jav
- Break large components up into sub-components.
- Use spaces for indentation.
### Naming conventions
## Naming conventions
Follow these guidelines when naming elements of your code.
#### Use `PascalCase` for:
### Class names
##### Typescript class names
Use PascalCase. For example:
```typescript
// bad
@ -61,37 +28,44 @@ class DataLink {
}
```
##### Types and interfaces
### Constants
```
// bad
interface buttonProps {
//...
}
Use ALL CAPS for constants. For example:
```typescript
// bad
interface button_props {
//...
}
const constantValue = "This string won't change";
// bad
interface IButtonProps {
//...
}
const constant_value = "This string won't change";
// good
interface ButtonProps {
//...
}
const CONSTANT_VALUE = "This string won't change";
```
// bad
type requestInfo = ...
// bad
type request_info = ...
### Emotion class names
// good
type RequestInfo = ...
Use camelCase. For example:
```typescript
const getStyles = (theme: GrafanaTheme2) => ({
// bad
ElementWrapper: css`...`,
// bad
['element-wrapper']: css`...`,
// good
elementWrapper: css({
padding: theme.spacing(1, 2),
background: theme.colors.background.secondary,
}),
});
```
##### Enums
Use hook useStyles2(getStyles) to memoize the styles generation and try to avoid passing props to the getStyles function and instead compose classes using Emotion CX function.
### Enums
Use PascalCase. For example:
```
// bad
@ -105,9 +79,29 @@ enum ButtonVariant {
}
```
#### Use `camelCase` for:
### Files and directories
Name files according to the primary export:
- When the primary export is a class or React component, use PascalCase.
- When the primary export is a function, use camelCase.
For files that export multiple utility functions, use the name that describes the responsibility of grouped utilities. For example, a file that exports math utilities should be named `math.ts`.
- Use `constants.ts` for files that export constants.
- Use `actions.ts` for files that export Redux actions.
- Use `reducers.ts` for Redux reducers.
- Use `*.test.ts(x)` for test files.
For directory names, use dash-case (sometimes called kebab-case).
##### Functions
- Use `features/new-important-feature/utils.ts`
### Functions
Use PascalCase. For example:
Use camelCase.
```typescript
// bad
@ -119,7 +113,43 @@ const calculate_percentage = () => { ... }
const calculatePercentage = () => { ... }
```
##### Methods
### Interfaces
Use PascalCase. For example:
```
// bad
interface buttonProps {
//...
}
// bad
interface button_props {
//...
}
// bad
interface IButtonProps {
//...
}
// good
interface ButtonProps {
//...
}
// bad
type requestInfo = ...
// bad
type request_info = ...
// good
type RequestInfo = ...
```
### Methods
Use PascalCase. For example:
Use camelCase.
```typescript
class DateCalculator {
@ -137,75 +167,103 @@ class DateCalculator {
}
```
##### Variables
### React components
```typescript
// bad
const QueryTargets = [];
Follow these guidelines for naming React components.
#### React callback props and handlers
Name callback props and handlers with an _on_ prefix. For example:
```tsx
// bad
const query_targets = [];
handleChange = () => {
// good
const queryTargets = [];
```
};
##### React state and properties
render() {
return (
<MyComponent changed={this.handleChange} />
);
}
```typescript
interface ModalState {
// bad
IsActive: boolean;
// bad
is_active: boolean;
// good
onChange = () => {
// good
isActive: boolean;
};
render() {
return (
<MyComponent onChange={this.onChange} />
);
}
```
##### Emotion class names
#### React component constructor
Use the following convention when implementing these React components:
```typescript
const getStyles = (theme: GrafanaTheme2) => ({
// bad
ElementWrapper: css`...`,
// bad
['element-wrapper']: css`...`,
// bad
constructor(props) {...}
// good
elementWrapper: css({
padding: theme.spacing(1, 2),
background: theme.colors.background.secondary,
}),
});
// good
constructor(props: Props) {...}
```
Use hook useStyles2(getStyles) to memoize the styles generation and try to avoid passing props to the getStyles function and instead compose classes using emotion cx function.
#### React component defaultProps
#### Use `ALL_CAPS` for constants.
Use the following convention when implementing these React components:
```typescript
// bad
const constantValue = "This string won't change";
static defaultProps = { ... }
// good
static defaultProps: Partial<Props> = { ... }
```
#### React component definitions
Use the following convention when implementing these React components:
```jsx
// bad
const constant_value = "This string won't change";
export class YourClass extends PureComponent { ... }
// good
const CONSTANT_VALUE = "This string won't change";
export class YourClass extends PureComponent<{},{}> { ... }
```
#### React state and properties
Use camelCase. For example:
```typescript
interface ModalState {
// bad
IsActive: boolean;
// bad
is_active: boolean;
// good
isActive: boolean;
}
```
#### Use [BEM](http://getbem.com/) convention for SASS styles.
### SASS
_SASS styles are deprecated. Please migrate to Emotion whenever you need to modify SASS styles._
SASS styles are deprecated. You should migrate to Emotion whenever you need to modify SASS styles.
### Typing
### Types
In general, you should let Typescript infer the types so that there's no need to explicitly define type for each variable.
In general, you should let TypeScript infer the types so that there's no need to explicitly define the type for each variable.
There are some exceptions to this:
```typescript
// Typescript needs to know type of arrays or objects otherwise it would infer it as array of any
// TypeScript needs to know the type of arrays or objects; otherwise, it infers type as an array of any
// bad
const stringArray = [];
@ -216,7 +274,7 @@ const stringArray: string[] = [];
Specify function return types explicitly in new code. This improves readability by being able to tell what a function returns just by looking at the signature. It also prevents errors when a function's return type is broader than expected by the author.
> **Note:** We don't have linting for this enabled because of lots of old code that needs to be fixed first.
> **Note:** Linting is not enabled for this issue because there is old code that needs to be fixed first.
```typescript
// bad
@ -236,124 +294,61 @@ function transform(value?: string): TransformedValue | undefined {
}
```
### File and directory naming conventions
Name files according to the primary export:
### Variables
- When the primary export is a class or React component, use PascalCase.
- When the primary export is a function, use camelCase.
Use PascalCase. For example:
For files exporting multiple utility functions, use the name that describes the responsibility of grouped utilities. For example, a file exporting math utilities should be named `math.ts`.
Use camelCase.
- Use `constants.ts` for files exporting constants.
- Use `actions.ts` for files exporting Redux actions.
- Use `reducers.ts` Redux reducers.
- Use `*.test.ts(x)` for test files.
```typescript
// bad
const QueryTargets = [];
// bad
const query_targets = [];
- Use kebab case for directory names: lowercase, words delimited by hyphen ( `-` ). For example, `features/new-important-feature/utils.ts`.
// good
const queryTargets = [];
```
### Code organization
## Code organization
Organize your code in a directory that encloses feature code:
- Put Redux state and domain logic code in `state` directory (i.e. `features/my-feature/state/actions.ts`).
- Put React components in `components` directory (i.e. `features/my-feature/components/ButtonPeopleDreamOf.tsx`).
- Put Redux state and domain logic code in the `state` directory (for example, `features/my-feature/state/actions.ts`).
- Put React components in the `components` directory (for example, `features/my-feature/components/ButtonPeopleDreamOf.tsx`).
- Put test files next to the test subject.
- Put containers (pages) in feature root (i.e. `features/my-feature/DashboardPage.tsx`).
- Put API function calls that isn't a redux thunk in an `api.ts` file within the same directory.
- Subcomponents can live in the component folders. Small component do not need their own folder.
- Put containers (pages) in the feature root (for example, `features/my-feature/DashboardPage.tsx`).
- Put API function calls that aren't a Redux thunk in an `api.ts` file within the same directory.
- Subcomponents should live in the component folders. Small components don't need their own folder.
- Component SASS styles should live in the same folder as component code.
For code that needs to be used by external plugin:
For code that needs to be used by an external plugin:
- Put components and types in `@grafana/ui`.
- Put data models and data utilities in `@grafana/data`.
- Put runtime services interfaces in `@grafana/runtime`.
#### Exports
### Exports
- Use named exports for all code you want to export from a file.
- Use declaration exports (i.e. `export const foo = ...`).
- Use declaration exports (that is, `export const foo = ...`).
- Avoid using default exports (for example, `export default foo`).
- Export only the code that is meant to be used outside the module.
### Comments
### Code comments
- Use [TSDoc](https://github.com/microsoft/tsdoc) comments to document your code.
- Use [react-docgen](https://github.com/reactjs/react-docgen) comments (`/** ... */`) for props documentation.
- Use inline comments for comments inside functions, classes etc.
- Use inline comments for comments inside functions, classes, etc.
- Please try to follow the [code comment guidelines](./code-comments.md) when adding comments.
### Linting
## Linting
Linting is performed using [@grafana/eslint-config](https://github.com/grafana/eslint-config-grafana).
## React
Use the following conventions when implementing React components:
### Props
##### Name callback props and handlers with an "on" prefix.
```tsx
// bad
handleChange = () => {
};
render() {
return (
<MyComponent changed={this.handleChange} />
);
}
// good
onChange = () => {
## Functional components
};
render() {
return (
<MyComponent onChange={this.onChange} />
);
}
```
##### React Component definitions
```jsx
// bad
export class YourClass extends PureComponent { ... }
// good
export class YourClass extends PureComponent<{},{}> { ... }
```
##### React Component constructor
```typescript
// bad
constructor(props) {...}
// good
constructor(props: Props) {...}
```
##### React Component defaultProps
```typescript
// bad
static defaultProps = { ... }
// good
static defaultProps: Partial<Props> = { ... }
```
### How to declare functional components
We prefer using function declarations over function expressions when creating a new react functional component.
Use function declarations instead of function expressions when creating a new React functional component. For example:
```typescript
// bad
@ -366,13 +361,6 @@ export const Component: React.FC<Props> = (props) => { ... }
export function Component(props: Props) { ... }
```
Some interesting readings on the topic:
- [Create React App: Remove React.FC from typescript template](https://github.com/facebook/create-react-app/pull/8177)
- [Kent C. Dodds: How to write a React Component in Typescript](https://kentcdodds.com/blog/how-to-write-a-react-component-in-typescript)
- [Kent C. Dodds: Function forms](https://kentcdodds.com/blog/function-forms)
- [Sam Hendrickx: Why you probably shouldn't use React.FC?](https://medium.com/raccoons-group/why-you-probably-shouldnt-use-react-fc-to-type-your-react-components-37ca1243dd13)
## State management
- Don't mutate state in reducers or thunks.

@ -2,16 +2,20 @@
Grafana uses [Redux Toolkit](https://redux-toolkit.js.org/) to handle Redux boilerplate code.
> Some of our Reducers are used by Angular and therefore state is to be considered as mutable for those reducers.
> **Note:** Some of our reducers are used by Angular; therefore, consider state to be mutable for those reducers.
## Test functionality
Here's how to test the functioning of your Redux reducers.
### reducerTester
Fluent API that simplifies the testing of reducers
Use the Fluent API framework to simplify the testing of reducers.
#### Usage
Example of `reducerTester` in use:
```typescript
reducerTester()
.givenReducer(someReducer, initialState)
@ -21,9 +25,9 @@ reducerTester()
#### Complex usage
Sometimes you encounter a `resulting state` that contains properties that are hard to compare, such as `Dates`, but you still want to compare that other props in state are correct.
Sometimes you encounter a _resulting state_ that contains properties that are hard to compare, such as `Dates`, but you still want to evaluate whether other props in state are correct.
Then you can use `thenStatePredicateShouldEqual` function on `reducerTester` that will return the `resulting state` so that you can expect upon individual properties..
In these cases, you can evaluate individual properties by using `thenStatePredicateShouldEqual` function on `reducerTester` that will return the resulting state. For example:
```typescript
reducerTester()
@ -37,10 +41,12 @@ reducerTester()
### thunkTester
Fluent API that simplifies the testing of thunks.
Here's a Fluent API function that simplifies the testing of thunks.
#### Usage
Example of `thunkTester` in use:
```typescript
const dispatchedActions = await thunkTester(initialState).givenThunk(someThunk).whenThunkIsDispatched(arg1, arg2, arg3);
@ -49,7 +55,7 @@ expect(dispatchedActions).toEqual([someAction('reducer tests')]);
## Typing of connected props
It is possible to infer connected props automatically from `mapStateToProps` and `mapDispatchToProps` using a helper type `ConnectedProps` from Redux. For this to work the `connect` call has to be split into two parts.
It is possible to infer connected props automatically from `mapStateToProps` and `mapDispatchToProps` using a helper type `ConnectedProps` from Redux. For this to work properly, split the `connect` call into two parts like so:
```typescript
import { connect, ConnectedProps } from 'react-redux';
@ -80,4 +86,4 @@ class PanelEditorUnconnected extends PureComponent<Props> {}
export const PanelEditor = connector(PanelEditorUnconnected);
```
For more examples, refer to the [Redux docs](https://react-redux.js.org/using-react-redux/static-typing#inferring-the-connected-props-automatically).
For more examples, refer to the [Redux documentation](https://react-redux.js.org/using-react-redux/static-typing#inferring-the-connected-props-automatically).

@ -1,6 +1,8 @@
# Storybook
[Storybook](https://storybook.js.org/) is a tool which we use to manage our design system and the components which are a part of it. Storybook consists of _stories:_ each story represents a component and a case in which it is used. To show a wide variety of use cases is good both documentation wise and for troubleshooting -- it might be possible to reproduce a bug for an edge case in a story.
[Storybook](https://storybook.js.org/) is a tool which Grafana uses to manage our design system and its components. Storybook consists of _stories_. Each story represents a component and the case in which it is used.
To show a wide variety of use cases is good both documentation wise and for troubleshooting—it might be possible to reproduce a bug for an edge case in a story.
Storybook is:
@ -10,16 +12,22 @@ Storybook is:
## How to create stories
Stories for a component should be placed next to the component file. The Storybook file requires the same name as the component file. For example, a story for `SomeComponent.tsx` will have the file name `SomeComponent.story.tsx`. If a story should be internal, not visible in production, name the file `SomeComponent.story.internal.tsx`.
Stories for a component should be placed next to the component file. The Storybook file requires the same name as the component file. For example, a story for `SomeComponent.tsx` has the file name `SomeComponent.story.tsx`.
If a story should be internal—not visible in production—name the file `SomeComponent.story.internal.tsx`.
### Writing stories
When writing stories, we use the [CSF format](https://storybook.js.org/docs/formats/component-story-format/). For more in-depth information on writing stories, see [Storybook’s documentation on writing stories](https://storybook.js.org/docs/basics/writing-stories/).
When writing stories, we use the [CSF format](https://storybook.js.org/docs/formats/component-story-format/).
> **Note:** For more in-depth information on writing stories, see [Storybook’s documentation](https://storybook.js.org/docs/basics/writing-stories/).
With the CSF format, the default export defines some general information about the stories in the file:
- `title`: Where the component is going to live in the hierarchy
- `decorators`: A list which can contain wrappers or provide context, such as theming
- **`title`**: Where the component is going to live in the hierarchy
- **`decorators`**: A list which can contain wrappers or provide context, such as theming
Example:
```jsx
// In MyComponent.story.tsx
@ -34,18 +42,18 @@ export default {
```
When it comes to writing the actual stories, you continue in the same file with named exports. The exports are turned into the story name.
When it comes to writing the actual stories, you should continue in the same file with named exports. The exports are turned into the story name like so:
```jsx
// Will produce a story name “some story”
export const someStory = () => <MyComponent />;
```
If you want to write cover cases with different values for props, then using knobs is usually enough. You don’t need to create a new story. This will be covered further down.
If you want to write cover cases with different values for props, then using knobs is usually enough. You don’t need to create a new story. This topic will be covered further down.
### Categorization
We currently have these categories:
We have these categories of components:
- **Docs Overview** - Guidelines and information regarding the design system
- **Forms** - Components commonly used in forms such as different kind of inputs
@ -55,7 +63,7 @@ We currently have these categories:
## Writing MDX documentation
An MDX file is basically a markdown file with the possibility to add jsx. These files are used by Storybook to create a “docs” tab.
An MDX file is a markdown file with the possibility to add JSX. These files are used by Storybook to create a “docs” tab.
### Link the MDX file to a component’s stories
@ -83,7 +91,7 @@ export default {
### MDX file structure
There are some things that the MDX file should contain:
The MDX file should contain the following items:
- When and why the component should be used
- Best practices - dos and don’ts for the component
@ -101,7 +109,9 @@ import { MyComponent } from './MyComponent';
### MDX file without a relationship to a component
An MDX file can exist by itself without any connection to a story. This can be good for writing things such as a general guidelines page. Two things are required for this to work:
An MDX file can exist by itself without any connection to a story. This can be good for writing things such as a general guidelines page.
Two conditions must be met for this to work:
- The file needs to be named `*.story.mdx`
- A `Meta` tag must exist that says where in the hierarchy the component lives. It can look like this:
@ -115,7 +125,7 @@ An MDX file can exist by itself without any connection to a story. This can be g
```
You can add parameters to the Meta tag. This example shows how to hide the tools:
You can add parameters to the `Meta` tag. This example shows how to hide the tools:
```jsx
<Meta title="Docs Overview/Color Palettes" parameters={{ options: { isToolshown: false }}}/>
@ -128,11 +138,11 @@ You can add parameters to the Meta tag. This example shows how to hide the tools
## Documenting component properties
A quick way to get an overview of what a component does is by looking at its properties. That's why it is important that we document these in a good way.
A quick way to get an overview of what a component does is by looking at its properties. That's why it is important that you document these in a good way.
### Comments
When writing the props interface for a component, it is possible to add a comment to that specific property, which will end up in the Props table in the MDX file. The comments are generated by [react-docgen](https://github.com/reactjs/react-docgen) and are formatted by writing `/** */`.
When writing the props interface for a component, it's possible to add a comment to that specific property. When you do so, the comment will appear in the Props table in the MDX file. The comments are generated by [react-docgen](https://github.com/reactjs/react-docgen) and are formatted by writing `/** */`.
```jsx
interface MyProps {
@ -143,25 +153,28 @@ interface MyProps {
### Controls
The [controls addon](https://storybook.js.org/docs/react/essentials/controls) provides a way to interact with a component's properties dynamically and requires much less code than knobs. We're deprecating knobs in favor of using controls.
The [controls addon](https://storybook.js.org/docs/react/essentials/controls) provides a way to interact with a component's properties dynamically. It also requires much less code than knobs.
Knobs are deprecated in favor of using controls.
#### Migrating a story from Knobs to Controls
As a test, we migrated the [button story](https://github.com/grafana/grafana/blob/main/packages/grafana-ui/src/components/Button/Button.story.tsx). Here's the guide on how to migrate a story to controls.
As a test, we migrated the [button story](https://github.com/grafana/grafana/blob/main/packages/grafana-ui/src/components/Button/Button.story.tsx).
Here's the guide on how to migrate a story to controls.
1. Remove the `@storybook/addon-knobs` dependency.
2. Import the Story type from `@storybook/react`
2. Import the `Story` type from `@storybook/react`
`import { Story } from @storybook/react`
3. Import the props interface from the component you're working on (these must be exported in the component).
3. Import the props interface from the component you're working on (these must be exported in the component):
`import { Props } from './Component'`
4. Add the Story type to all stories in the file, then replace the props sent to the component
and remove any knobs.
4. Add the `Story` type to all stories in the file, then replace the props sent to the component and remove any knobs:
Before
Before:
```tsx
export const Simple = () => {
@ -172,7 +185,7 @@ As a test, we migrated the [button story](https://github.com/grafana/grafana/blo
};
```
After
After:
```tsx
export const Simple: Story<Props> = ({ prop1, prop2 }) => {
@ -180,7 +193,7 @@ As a test, we migrated the [button story](https://github.com/grafana/grafana/blo
};
```
5. Add default props (or args in Storybook language).
5. Add default props (or `args` in Storybook language):
```tsx
Simple.args = {
@ -189,8 +202,7 @@ As a test, we migrated the [button story](https://github.com/grafana/grafana/blo
};
```
6. If the component has advanced props type (ie. other than string, number, boolean), you need to
specify these in an `argTypes`. This is done in the default export of the story.
6. If the component has advanced props type (that is, other than string, number, or Boolean), you need to specify these in an `argTypes`. Do this in the default export of the story:
```tsx
export default {
@ -204,6 +216,6 @@ As a test, we migrated the [button story](https://github.com/grafana/grafana/blo
## Best practices
- When creating a new component or writing documentation for an existing one, always cover the basic use case it was intended for with a code example.
- When creating a new component or writing documentation for an existing one, add a code example. The example should always cover the basic use case it was intended for.
- Use stories and knobs to create edge cases. If you are trying to solve a bug, try to reproduce it with a story.
- Do not create stories in the MDX, always create them in the `*.story.tsx` file.
- Do not create stories in the MDX. Instead, create them in the `*.story.tsx` file.

@ -1,6 +1,6 @@
# Styling Grafana
[Emotion](https://emotion.sh/docs/introduction) is our default-to-be approach to styling React components. It provides a way for styles to be a consequence of properties and state of a component.
[Emotion](https://emotion.sh/docs/introduction) is Grafana's default-to-be approach to styling React components. It provides a way for styles to be a consequence of properties and state of a component.
## Usage
@ -8,9 +8,9 @@ For styling components, use [Emotion's `css` function](https://emotion.sh/docs/e
### Basic styling
To access the theme in your styles, use the `useStyles` hook. It provides basic memoization and access to the theme object.
To access the Emotion theme in your styles, use the `useStyles` hook. This hook provides basic memoization and access to the theme object.
> Please remember to put `getStyles` function at the end of the file!
> **Note:** Please remember to put `getStyles` function at the end of the file!
```tsx
import { GrafanaTheme2 } from '@grafana/data';
@ -30,12 +30,17 @@ const getStyles = (theme: GrafanaTheme2) =>
});
```
### Styling complex components
### Style complex components
In more complex cases, especially when you need to style multiple DOM elements in one component, or when using styles that depend on properties and/or state you
can have your getStyles function return an object with many class names and use [Emotion's `cx` function](https://emotion.sh/docs/@emotion/css#cx) to compose them.
In more complex cases, you can have the `getStyles` function return an object with many class names and use [Emotion's `cx` function](https://emotion.sh/docs/@emotion/css#cx) to compose them.
Let's say you need to style a component that has a different background depending on the `isActive` property :
This feature can be especially useful in certain use cases:
- when you need to style multiple DOM elements in one component
- when using styles that depend on properties
- when using styles that depend on state
Let's say you need to style a component that has a different background depending on the `isActive` property. For example:
```tsx
import { css, cx } from '@emotion/css';
@ -75,4 +80,4 @@ const getStyles = (theme: GrafanaTheme2) => {
};
```
For more information about themes at Grafana please see the [themes guide](./themes.md).
For more information about themes at Grafana, refer to the [themes guide](./themes.md).

@ -1,10 +1,11 @@
# Testing Guidelines
# Testing guidelines
The goal of this document is to address the most frequently asked "How to" questions related to unit testing.
## Best practices
## Some recommended practices for testing
- Default to the `*ByRole` queries when testing components as it encourages testing with accessibility concerns in mind. It's also possible to use `*ByLabelText` queries. However, the `*ByRole` queries are [more robust](https://testing-library.com/docs/queries/bylabeltext/#name) and are generally recommended over the former.
- Default to the `*ByRole` queries when testing components because it encourages testing with accessibility concerns in mind.
- Alternatively, you could use `*ByLabelText` queries for testing components. However, we recommend the `*ByRole` queries because they are [more robust](https://testing-library.com/docs/queries/bylabeltext/#name).
## Testing User Interactions
@ -13,7 +14,7 @@ We use the [user-event](https://testing-library.com/docs/user-event/intro) libra
There are two important considerations when working with `userEvent`:
1. All methods in `userEvent` are asynchronous, and thus require the use of `await` when called.
2. Directly calling methods from `userEvent` may not be supported in future versions. As such, it's necessary to first call `userEvent.setup()` prior to the tests. This method returns a `userEvent` instance, complete with all its methods. This setup process can be simplified using a utility function:
1. Directly calling methods from `userEvent` may not be supported in future versions. As such, it's necessary to first call `userEvent.setup()` prior to the tests. This method returns a `userEvent` instance, complete with all its methods. This setup process can be simplified using a utility function:
```tsx
import { render, screen } from '@testing-library/react';
@ -32,15 +33,15 @@ it('should render', async () => {
});
```
## Debugging Tests
## Debug tests
There are a few utilities that can be useful for debugging tests:
- [screen.debug()](https://testing-library.com/docs/queries/about/#screendebug) - This function prints a human-readable representation of the document's DOM tree when called without arguments, or the DOM tree of specific node(s) when provided with arguments. It is internally using `console.log` to log the output to terminal.
- [Testing Playground](https://testing-playground.com/) - An interactive sandbox that allows testing which queries work with specific HTML elements.
- [logRoles](https://testing-library.com/docs/dom-testing-library/api-debugging/#prettydom) - A utility function that prints out all the implicit ARIA roles for a given DOM tree.
- [screen.debug()](https://testing-library.com/docs/queries/about/#screendebug) - This function prints a human-readable representation of the document's DOM tree when called without arguments, or the DOM tree of specific node or nodes when provided with arguments. It is internally using `console.log` to log the output to terminal.
- [Testing Playground](https://testing-playground.com/) - An interactive sandbox that allows testing of which queries work with specific HTML elements.
- [prettyDOM logRoles](https://testing-library.com/docs/dom-testing-library/api-debugging/#prettydom) - A utility function that prints out all the implicit ARIA roles for a given DOM tree.
## Testing Select Components
## Testing select components
Here, the [OrgRolePicker](https://github.com/grafana/grafana/blob/38863844e7ac72c7756038a1097f89632f9985ff/public/app/features/admin/OrgRolePicker.tsx) component is used as an example. This component essentially serves as a wrapper for the `Select` component, complete with its own set of options.
@ -78,7 +79,7 @@ export function OrgRolePicker({ value, onChange, 'aria-label': ariaLabel, inputI
### Querying the Select Component
The recommended way to query `Select` components is by using a label. Add a `label` element and provide the `htmlFor` prop with a matching `inputId`. Alternatively, `aria-label` can be specified on the `Select`.
It is a recommended practice to query `Select` components by using a label. Add a `label` element and provide the `htmlFor` prop with a matching `inputId`. Alternatively, you can specify `aria-label` on the `Select` statement.
```tsx
describe('OrgRolePicker', () => {
@ -94,7 +95,7 @@ describe('OrgRolePicker', () => {
});
```
### Testing the Display of Correct Options
### Test the display of correct options
At times, it might be necessary to verify that the `Select` component is displaying the correct options. In such instances, the best solution is to click the `Select` component and match the desired option using the `*ByText` query.
@ -129,11 +130,11 @@ it('should select an option', async () => {
});
```
## Mocking Objects and Functions
## Mock objects and functions
### Mocking the `window` Object and Its Methods
### Mock the `window` object and its methods
The recommended approach for mocking the `window` object is to use Jest spies. Jest's spy functions provide a built-in mechanism for restoring mocks. This feature eliminates the need to manually save a reference to the `window` object.
The recommended approach for mocking the `window` object is to use [Jest spies](https://jestjs.io/docs/jest-object). Jest's spy functions provide a built-in mechanism for restoring mocks. This feature eliminates the need to manually save a reference to the `window` object.
```tsx
let windowSpy: jest.SpyInstance;
@ -156,7 +157,7 @@ it('should test with window', function () {
### Mocking getBackendSrv()
The `getBackendSrv()` function is used to make HTTP requests to the Grafana backend. It is possible to mock this function using the `jest.mock` method.
Use the `getBackendSrv()` function to make HTTP requests to the Grafana backend. It is possible to mock this function using the `jest.mock` method.
```tsx
jest.mock('@grafana/runtime', () => ({
@ -169,9 +170,9 @@ jest.mock('@grafana/runtime', () => ({
#### Mocking getBackendSrv for AsyncSelect
The `AsyncSelect` component is used to asynchronously load options. As such, it often relies on the `getBackendSrv` for loading the options.
Use the `AsyncSelect` component to asynchronously load options. This component often relies on the `getBackendSrv` for loading the options.
Here's how the test would look like for this [OrgPicker](https://github.com/grafana/grafana/blob/38863844e7ac72c7756038a1097f89632f9985ff/public/app/core/components/Select/OrgPicker.tsx) component, which uses `AsyncSelect` under the hood.
Here's what the test looks like for this [OrgPicker](https://github.com/grafana/grafana/blob/38863844e7ac72c7756038a1097f89632f9985ff/public/app/core/components/Select/OrgPicker.tsx) component, which uses `AsyncSelect` under the hood:
```tsx
import { screen, render } from '@testing-library/react';

@ -18,7 +18,6 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 508,
"links": [],
"liveNow": false,
"panels": [
@ -88,6 +87,7 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -102,7 +102,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__house_locations",
@ -174,6 +174,7 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -188,7 +189,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__house_locations",
@ -260,6 +261,7 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -274,7 +276,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__house_locations",
@ -346,6 +348,7 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -360,7 +363,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__server_names",
@ -431,6 +434,7 @@
"graphMode": "line",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -445,7 +449,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"datasource": {
@ -560,6 +564,7 @@
"graphMode": "line",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -574,7 +579,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"datasource": {
@ -691,6 +696,7 @@
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -705,7 +711,7 @@
"textMode": "name",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__server_names",
@ -781,6 +787,7 @@
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -795,7 +802,7 @@
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__server_names",
@ -871,6 +878,7 @@
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -885,7 +893,7 @@
"textMode": "none",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__server_names",
@ -972,6 +980,7 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -986,7 +995,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__house_locations",
@ -1058,6 +1067,7 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -1072,7 +1082,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__house_locations",
@ -1144,6 +1154,7 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -1158,7 +1169,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__house_locations",
@ -1230,6 +1241,7 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -1244,7 +1256,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__server_names",
@ -1315,6 +1327,7 @@
"graphMode": "line",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -1329,7 +1342,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"datasource": {
@ -1444,6 +1457,7 @@
"graphMode": "line",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -1458,7 +1472,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"datasource": {
@ -1575,6 +1589,7 @@
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -1589,7 +1604,7 @@
"textMode": "name",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__server_names",
@ -1665,6 +1680,7 @@
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -1679,7 +1695,7 @@
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__server_names",
@ -1755,6 +1771,7 @@
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"mean"
@ -1769,7 +1786,7 @@
"textMode": "none",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"alias": "__server_names",
@ -1819,7 +1836,7 @@
},
"gridPos": {
"h": 8,
"w": 8,
"w": 4,
"x": 0,
"y": 78
},
@ -1829,6 +1846,7 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
@ -1840,7 +1858,7 @@
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"csvContent": "time, value\n2023-12-13T00:00:00Z, 0\n2023-12-13T00:00:00Z, 100",
@ -1855,6 +1873,73 @@
"title": "Infinity Percent Change",
"type": "stat"
},
{
"datasource": {
"default": true,
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 4,
"x": 4,
"y": 78
},
"id": 32,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": true,
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "11.2.0-pre",
"targets": [
{
"csvContent": "time, value\n2023-12-13T00:00:00Z, 50\n2023-12-13T00:00:00Z, 100\n2023-12-13T00:00:00Z, 50",
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_content"
}
],
"title": "Zero Percent Change",
"type": "stat"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
@ -1894,6 +1979,7 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
@ -1905,7 +1991,7 @@
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"csvContent": "time, value\n2023-12-13T00:00:00Z, 0\n2023-12-13T00:00:00Z, 0",
@ -1959,6 +2045,7 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
@ -1970,7 +2057,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.3.0-pre",
"pluginVersion": "11.2.0-pre",
"targets": [
{
"csvContent": "Name, value\nName1, 10\nName2, 20",
@ -2017,6 +2104,6 @@
"timezone": "",
"title": "Panel Tests - Stat",
"uid": "EJ8_d9jZk",
"version": 11,
"version": 15,
"weekStart": ""
}

@ -18,7 +18,7 @@ name = "displayName"
surname = "sn"
username = "cn"
member_of = "memberOf"
email = "mail"
email = "mail"
# Map ldap groups to grafana org roles
[[servers.group_mappings]]

@ -1,5 +1,5 @@
mimir_backend:
image: grafana/mimir-alpine:r295-a23e559
image: grafana/mimir-alpine:r304-3872ccb
container_name: mimir_backend
command:
- -target=backend
@ -14,4 +14,4 @@
- 8080:8080
volumes:
- "./docker/blocks/mimir_backend/nginx/nginx.conf.template:/etc/nginx/templates/nginx.conf.template"
- "./docker/blocks/mimir_backend/nginx/.htpasswd:/etc/nginx/.htpasswd"
- "./docker/blocks/mimir_backend/nginx/.htpasswd:/etc/nginx/.htpasswd"

@ -37,3 +37,8 @@
build: docker/blocks/prometheus_random_data
ports:
- "8081:8080"
prometheus-high-card:
build: docker/blocks/prometheus_high_card
ports:
- "9111:9111"

@ -38,6 +38,10 @@ scrape_configs:
static_configs:
- targets: ['prometheus-random-data:8080']
- job_name: 'prometheus-high-card'
static_configs:
- targets: ['prometheus-high-card:9111']
- job_name: 'mysql'
static_configs:
- targets: ['mysql-exporter:9104']

@ -0,0 +1,15 @@
FROM golang:latest AS builder
ADD main.go /
ADD go.mod /
ADD go.sum /
WORKDIR /
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -o main .
FROM scratch
WORKDIR /
EXPOSE 9111
COPY --from=builder /main /main
ENTRYPOINT ["/main"]

@ -0,0 +1,6 @@
prometheus_high_card:
build: docker/blocks/prometheus_high_card
ports:
- "3012:3012"
extra_hosts:
- "host.docker.internal:host-gateway"

@ -0,0 +1,20 @@
module high-card
go 1.22.4
require (
github.com/prometheus/client_golang v1.20.2
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
golang.org/x/sys v0.22.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)

@ -0,0 +1,26 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=

@ -0,0 +1,123 @@
package main
import (
"fmt"
"log"
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/exp/rand"
)
func randomValues(max int) func() (string, bool) {
i := 0
return func() (string, bool) {
i++
return strconv.Itoa(i), i < max+1
}
}
func staticList(input []string) func() string {
return func() string {
i := rand.Intn(len(input))
return input[i]
}
}
type dimension struct {
label string
getNextValue func() string
}
func main() {
fakeMetrics := []dimension{
{
label: "cluster",
getNextValue: staticList([]string{"prod-uk1", "prod-eu1", "prod-uk2", "prod-eu2", "prod-uk3", "prod-eu3", "prod-uk4", "prod-eu4", "prod-uk5", "prod-eu5"}),
},
{
label: "namespace",
getNextValue: staticList([]string{"default", "kube-api", "kube-system", "kube-public", "kube-node-lease", "kube-ingress", "kube-logging", "kube-metrics", "kube-monitoring", "kube-network", "kube-storage"}),
},
{
label: "pod",
getNextValue: staticList([]string{"default"}),
},
{
label: "container",
getNextValue: staticList([]string{"container"}),
},
{
label: "method",
getNextValue: staticList([]string{"GET", "POST", "DELETE", "PUT", "PATCH"}),
},
{
label: "address",
getNextValue: staticList([]string{"/", "/api", "/api/dashboard", "/api/dashboard/:uid", "/api/dashboard/:uid/overview", "/api/dashboard/:uid/overview/:id", "/api/dashboard/:uid/overview/:id/summary", "/api/dashboard/:uid/overview/:id/summary/:type", "/api/dashboard/:uid/overview/:id/summary/:type/:subtype", "/api/dashboard/:uid/overview/:id/summary/:type/:subtype/:id"}),
},
{
label: "extra_label_name1",
getNextValue: staticList([]string{"default"}),
},
{
label: "extra_label_name2",
getNextValue: staticList([]string{"default"}),
},
{
label: "extra_label_name3",
getNextValue: staticList([]string{"default"}),
},
{
label: "extra_label_name4",
getNextValue: staticList([]string{"default"}),
},
{
label: "extra_label_name5",
getNextValue: staticList([]string{"default"}),
},
{
label: "extra_label_name6",
getNextValue: staticList([]string{"default"}),
},
}
dimensions := []string{}
for _, dim := range fakeMetrics {
dimensions = append(dimensions, dim.label)
}
opsProcessed := promauto.NewCounterVec(prometheus.CounterOpts{
Name: "fakedata_highcard_http_requests_total",
Help: "a high cardinality counter",
}, dimensions)
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello, is it me you're looking for?"))
})
go func() {
for {
labels := []string{}
for _, dim := range fakeMetrics {
value := dim.getNextValue()
labels = append(labels, value)
}
opsProcessed.WithLabelValues(labels...).Inc()
time.Sleep(time.Millisecond)
}
}()
fmt.Printf("Server started at :9111\n")
log.Fatal(http.ListenAndServe(":9111", nil))
}

@ -1,27 +1,19 @@
apiVersion: 1
apps:
- type: myorg-extensions-app
- type: grafana-extensionstest-app
org_id: 1
org_name: Main Org.
disabled: false
- type: myorg-a-app
jsonData:
apiUrl: http://default-url.com
secureJsonData:
apiKey: secret-key
- type: grafana-extensionexample1-app
org_id: 1
org_name: Main Org.
disabled: false
- type: myorg-b-app
org_id: 1
org_name: Main Org.
disabled: false
- type: myorg-extensionpoint-app
org_id: 1
org_name: Main Org.
disabled: false
- type: myorg-componentconsumer-app
org_id: 1
org_name: Main Org.
disabled: false
- type: myorg-componentexposer-app
- type: grafana-extensionexample2-app
org_id: 1
org_name: Main Org.
disabled: false

@ -6,6 +6,15 @@
# [Semantic versioning](https://semver.org/) is used to help the reader identify the significance of changes.
# Changes are relevant to this script and the support docs.mk GNU Make interface.
#
# ## 8.1.0 (2024-08-22)
#
# ### Added
#
# - Additional website mounts for projects that use the website repository.
#
# Mounts are required for `make docs` to work in the website repository or with the website project.
# The Makefile is also mounted for convenient development of the procedure that repository.
#
# ## 8.0.1 (2024-07-01)
#
# ### Fixed
@ -727,6 +736,9 @@ POSIX_HERESTRING
_repo="$(repo_path website)"
volumes="--volume=${_repo}/config:/hugo/config:z"
volumes="${volumes} --volume=${_repo}/content/guides:/hugo/content/guides:z"
volumes="${volumes} --volume=${_repo}/content/whats-new:/hugo/content/whats-new:z"
volumes="${volumes} --volume=${_repo}/Makefile:/hugo/Makefile:z"
volumes="${volumes} --volume=${_repo}/layouts:/hugo/layouts:z"
volumes="${volumes} --volume=${_repo}/scripts:/hugo/scripts:z"
fi

@ -149,6 +149,19 @@ Grafana Cloud handles the plugin installation automatically.
If you're logged in to Grafana Cloud when you add a plugin, log out and then log back in again to use the new plugin.
### Install plugins using the Grafana Helm chart
With the Grafana Helm chart, add the plugins you want to install as a list using the `plugins` field in the your values file. For more information about the configuration, refer to [the Helm chart configuration reference](https://github.com/grafana/helm-charts/tree/main/charts/grafana#configuration).
The following YAML snippet installs v1.9.0 of the Grafana OnCall App plugin and the Redis data source plugin.
You must incorporate this snippet within your Helm values file.
```yaml
plugins:
- https://grafana.com/api/plugins/grafana-oncall-app/versions/v1.9.0/download;grafana-oncall-app
- redis-datasource
```
### Install plugin on local Grafana
Follow the instructions on the **Install** tab. You can either install the plugin with a Grafana CLI command or by downloading and uncompressing a zip file into the Grafana plugins directory. We recommend using Grafana CLI in most instances. The zip option is available if your Grafana server doesn't have access to the internet.

@ -504,6 +504,18 @@ The following sections detail the supported settings and secure settings for eac
| ----- | -------------- |
| token | yes |
#### Alert notification `MQTT`
| Name | Secure setting |
| ------------------ | -------------- |
| brokerUrl | |
| clientId | |
| topic | |
| messageFormat |
| username | |
| password | yes |
| insecureSkipVerify | |
#### Alert notification `pagerduty`
| Name | Secure setting |

@ -10,6 +10,7 @@ labels:
products:
- enterprise
- oss
- cloud
title: Roles and permissions
weight: 300
---

@ -12,6 +12,7 @@ labels:
products:
- enterprise
- oss
- cloud
menuTitle: Service accounts
title: Service accounts
weight: 800

@ -0,0 +1,45 @@
---
canonical: https://grafana.com/docs/grafana/latest/alerting/alerting-rules/create-alerts-panels/
description: Create alert rules from panels. Reuse the queries in the panel and create alert rules based on them.
keywords:
- grafana
- alerting
- panels
- create
- grafana-managed
- data source-managed
labels:
products:
- cloud
- enterprise
- oss
title: Create alert rules from panels
weight: 400
---
## Create alert rules from panels
Create alert rules from time series panels. By doing so, you can reuse the queries in the panel and create alert rules based on them.
1. Navigate to a dashboard in the **Dashboards** section.
2. Hover over the top-right corner of a time series panel and click the panel menu icon.
3. From the dropdown menu, select **More...** > **New alert rule**.
The New alert rule form opens where you can configure and create your alert rule based on the query used in the panel.
{{% admonition type="note" %}}
Changes to the panel aren't reflected on the linked alert rules. If you change a query, you have to update it in both the panel and the alert rule.
Alert rules are only supported in [time series](ref:time-series) visualizations.
{{% /admonition %}}
{{< docs/play title="visualizations with linked alerts in Grafana" url="https://play.grafana.org/d/000000074/" >}}
## View alert rules from panels
To view alert rules associated with a time series panel, complete the following steps.
1. Open the panel editor by hovering over the top-right corner of any panel
1. Click the panel menu icon that appears.
1. Click **Edit**.
1. Click the **Alert** tab to view existing alert rules or create a new one.

@ -83,11 +83,8 @@ Grafana-managed rules are the most flexible alert rule type. They allow you to c
Multiple alert instances can be created as a result of one alert rule (also known as a multi-dimensional alerting).
{{% admonition type="note" %}}
For Grafana Cloud, there are limits on how many Grafana-managed alert rules you can create. These are as follows:
- Free: 100 alert rules
- Paid: 2000 alert rules
{{% /admonition %}}
For Grafana Cloud, you can create 100 free Grafana-managed alert rules.
{{% /admonition %}}
Grafana managed alert rules can only be edited or deleted by users with Edit permissions for the folder storing the rules.
@ -235,14 +232,12 @@ Annotations add metadata to provide more information on the alert in your alert
1. Optional: Add a custom annotation
1. Optional: Add a **dashboard and panel link**.
Links alerts to panels in a dashboard.
Links alert rules to panels in a dashboard.
{{% admonition type="note" %}}
At the moment, alerts are only supported in the [time series](ref:time-series) and [alert list](ref:alert-list) visualizations.
At the moment, alert rules are only supported in [time series](ref:time-series) and [alert list](ref:alert-list) visualizations.
{{% /admonition %}}
{{< docs/play title="visualizations with linked alerts in Grafana" url="https://play.grafana.org/d/000000074/" >}}
1. Click **Save rule**.
## Configure no data and error handling
@ -270,13 +265,3 @@ You can also configure the alert instance state when its evaluation returns an e
| Keep Last State | Maintains the alert instance in its last state. Useful for mitigating temporary issues, refer to [Keep last state](ref:keep-last-state). |
When you configure the No data or Error behavior to `Alerting` or `Normal`, Grafana will attempt to keep a stable set of fields under notification `Values`. If your query returns no data or an error, Grafana re-uses the latest known set of fields in `Values`, but will use `-1` in place of the measured value.
## Create alerts from panels
Create alerts from any panel type. This means you can reuse the queries in the panel and create alerts based on them.
1. Navigate to a dashboard in the **Dashboards** section.
2. In the top right corner of the panel, click on the three dots (ellipses).
3. From the dropdown menu, select **More...** and then choose **New alert rule**.
This will open the alert rule form, allowing you to configure and create your alert based on the current panel's query.

@ -145,9 +145,7 @@ Annotations add metadata to provide more information on the alert in your alert
Links alerts to panels in a dashboard.
{{% admonition type="note" %}}
At the moment, alerts are only supported in the [time series](ref:time-series) and [alert list](ref:alert-list) visualizations.
At the moment, alert rules are only supported in [time series](ref:time-series) and [alert list](ref:alert-list) visualizations.
{{% /admonition %}}
{{< docs/play title="visualizations with linked alerts in Grafana" url="https://play.grafana.org/d/000000074/" >}}
1. Click **Save rule**.

@ -76,6 +76,11 @@ refs:
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/set-up/configure-alertmanager/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/alerting/set-up/configure-alertmanager/
mqtt:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt/
---
# Configure contact points
@ -164,6 +169,7 @@ The following table lists the contact point integrations supported by Grafana.
| Google Chat | `googlechat` |
| [Grafana Oncall](ref:oncall) | `oncall` |
| Kafka REST Proxy | `kafka` |
| [MQTT](ref:mqtt) | `mqtt` |
| Line | `line` |
| [Microsoft Teams](ref:teams) | `teams` |
| [Opsgenie](ref:opsgenie) | `opsgenie` |

@ -0,0 +1,57 @@
---
canonical: https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns/
description: Configure the Grafana Alerting - Amazon SNS integration to receive alert notifications when your alerts are firing.
keywords:
- grafana
- alerting
- Amazon SNS
- integration
labels:
products:
- cloud
- enterprise
- oss
menuTitle: Amazon SNS
title: Configure Amazon SNS for Alerting
weight: 0
---
# Configure Amazon SNS for Alerting
Use the Grafana Alerting - Amazon SNS integration to send notifications to Amazon SNS when your alerts are firing.
## Before you begin
To configure Amazon SNS to receive alert notifications, complete the following steps.
1. Create a new topic in https://console.aws.amazon.com/sns.
1. Open the topic and create a new subscription.
1. Choose the protocol HTTPS.
1. Copy the URL.
For more information, refer to [Amazon SNS documentation](https://docs.aws.amazon.com/sns/latest/dg/welcome.html).
## Procedure
To create your Amazon SNS integration in Grafana Alerting, complete the following steps.
1. Navigate to **Alerts & IRM** -> **Alerting** -> **Contact points**.
1. Click **+ Add contact point**.
1. Enter a contact point name.
1. From the Integration list, select **AWS SNS**.
1. Copy in the URL from above into the **The Amazon SNS API URL** field.
1. Click **Test** to check that your integration works.
1. Click **Save contact point**.
## Next steps
The Amazon SNS contact point is ready to receive alert notifications.
To add this contact point to your alert, complete the following steps.
1. In Grafana, navigate to **Alerting** > **Alert rules**.
1. Edit or create a new alert rule.
1. Scroll down to the **Configure labels and notifications** section.
1. Under Notifications click **Select contact point**.
1. From the drop-down menu, select the previously created contact point.
1. **Click Save rule and exit**.

@ -13,7 +13,7 @@ labels:
- oss
menuTitle: Discord
title: Configure Discord for Alerting
weight: 10
weight: 0
---
# Configure Discord for Alerting

@ -13,7 +13,7 @@ labels:
- oss
menuTitle: Email
title: Configure email for Alerting
weight: 20
weight: 0
---
# Configure email for Alerting

@ -13,7 +13,7 @@ labels:
- oss
menuTitle: Google Chat
title: Configure Google Chat for Alerting
weight: 30
weight: 0
---
# Configure Google Chat for Alerting

@ -0,0 +1,154 @@
---
canonical: https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt/
description: Configure the MQTT notifier integration for Alerting
keywords:
- grafana
- alerting
- guide
- contact point
- mqtt
labels:
products:
- cloud
- enterprise
- oss
menuTitle: MQTT notifier
title: Configure the MQTT notifier for Alerting
weight: 80
---
# Configure the MQTT notifier for Alerting
Use the Grafana Alerting - MQTT integration to send notifications to an MQTT broker when your alerts are firing.
## Procedure
To configure the MQTT integration for Alerting, complete the following steps.
1. In the left-side menu, click **Alerts & IRM** and then **Alerting**.
1. On the **Contact Points** tab, click **+ Add contact point**.
1. Enter a descriptive name for the contact point.
1. From the Integration list, select **MQTT**.
1. Enter your broker URL in the **Broker URL** field. Supports `tcp`, `ssl`, `mqtt`, `mqtts`, `ws`, `wss` schemes. For example: `tcp://127.0.0.1:1883`.
1. Enter the MQTT topic name in the **Topic** field.
1. In **Optional MQTT settings**, specify additional settings for the MQTT integration if needed.
1. Click **Test** to check that your integration works.
A test alert notification should be sent to the MQTT broker.
1. Click **Save** contact point.
The integration sends data in JSON format by default. You can change that using **Message format** field in the **Optional MQTT settings** section. There are two supported formats:
- **JSON**: Sends the alert notification in JSON format.
- **Text**: Sends the rendered alert notification message in plain text format.
## MQTT JSON payload
If the JSON message format is selected in **Optional MQTT settings**, the payload is sent in the following structure.
```json
{
"receiver": "My MQTT integration",
"status": "firing",
"orgId": 1,
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "High memory usage",
"team": "blue",
"zone": "us-1"
},
"annotations": {
"description": "The system has high memory usage",
"runbook_url": "https://myrunbook.com/runbook/1234",
"summary": "This alert was triggered for zone us-1"
},
"startsAt": "2021-10-12T09:51:03.157076+02:00",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "https://play.grafana.org/alerting/1afz29v7z/edit",
"fingerprint": "c6eadffa33fcdf37",
"silenceURL": "https://play.grafana.org/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DT2%2Cteam%3Dblue%2Czone%3Dus-1",
"dashboardURL": "",
"panelURL": "",
"values": {
"B": 44.23943737541908,
"C": 1
}
},
{
"status": "firing",
"labels": {
"alertname": "High CPU usage",
"team": "blue",
"zone": "eu-1"
},
"annotations": {
"description": "The system has high CPU usage",
"runbook_url": "https://myrunbook.com/runbook/1234",
"summary": "This alert was triggered for zone eu-1"
},
"startsAt": "2021-10-12T09:56:03.157076+02:00",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "https://play.grafana.org/alerting/d1rdpdv7k/edit",
"fingerprint": "bc97ff14869b13e3",
"silenceURL": "https://play.grafana.org/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DT1%2Cteam%3Dblue%2Czone%3Deu-1",
"dashboardURL": "",
"panelURL": "",
"values": {
"B": 44.23943737541908,
"C": 1
}
}
],
"groupLabels": {},
"commonLabels": {
"team": "blue"
},
"commonAnnotations": {},
"externalURL": "https://play.grafana.org/",
"version": "1",
"groupKey": "{}:{}",
"message": "**Firing**\n\nLabels:\n - alertname = T2\n - team = blue\n - zone = us-1\nAnnotations:\n - description = This is the alert rule checking the second system\n - runbook_url = https://myrunbook.com\n - summary = This is my summary\nSource: https://play.grafana.org/alerting/1afz29v7z/edit\nSilence: https://play.grafana.org/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DT2%2Cteam%3Dblue%2Czone%3Dus-1\n\nLabels:\n - alertname = T1\n - team = blue\n - zone = eu-1\nAnnotations:\nSource: https://play.grafana.org/alerting/d1rdpdv7k/edit\nSilence: https://play.grafana.org/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DT1%2Cteam%3Dblue%2Czone%3Deu-1\n"
}
```
### Payload fields
Each notification payload contains the following fields.
| Key | Type | Description |
| ----------------- | ------------------------------------------- | ------------------------------------------------------------------------------- |
| receiver | string | Name of the contact point |
| status | string | Current status of the alert, `firing` or `resolved` |
| orgId | number | ID of the organization related to the payload |
| alerts | array of [alert instances](#alert-instance) | Alerts that are triggering |
| groupLabels | object | Labels that are used for grouping, map of string keys to string values |
| commonLabels | object | Labels that all alarms have in common, map of string keys to string values |
| commonAnnotations | object | Annotations that all alarms have in common, map of string keys to string values |
| externalURL | string | External URL to the Grafana instance sending this webhook |
| version | string | Version of the payload |
| groupKey | string | Key that is used for grouping |
| message | string | Rendered message of the alerts |
### Alert instance
Each alert instance in the `alerts` array has the following fields.
| Key | Type | Description |
| ------------ | ------ | ---------------------------------------------------------------------------------- |
| status | string | Current status of the alert, `firing` or `resolved` |
| labels | object | Labels that are part of this alert, map of string keys to string values |
| annotations | object | Annotations that are part of this alert, map of string keys to string values |
| startsAt | string | Start time of the alert |
| endsAt | string | End time of the alert, default value when not resolved is `0001-01-01T00:00:00Z` |
| values | object | Values that triggered the current status |
| generatorURL | string | URL of the alert rule in the Grafana UI |
| fingerprint | string | The labels fingerprint, alarms with the same labels will have the same fingerprint |
| silenceURL | string | URL to silence the alert rule in the Grafana UI |
| dashboardURL | string | **Deprecated. It will be removed in a future release.** |
| panelURL | string | **Deprecated. It will be removed in a future release.** |
| imageURL | string | URL of a screenshot of a panel assigned to the rule that created this notification |
{{< admonition type="note" >}}
Alert rules are not coupled to dashboards anymore. The fields related to dashboards `dashboardId` and `panelId` have been removed.
{{< /admonition >}}

@ -18,7 +18,7 @@ labels:
- oss
menuTitle: Grafana OnCall
title: Configure Grafana OnCall for Alerting
weight: 40
weight: 0
---
# Configure Grafana OnCall for Alerting

@ -13,7 +13,7 @@ labels:
- oss
menuTitle: Opsgenie
title: Configure Opsgenie for Alerting
weight: 60
weight: 0
---
# Configure Opsgenie for Alerting

@ -13,7 +13,7 @@ labels:
- oss
menuTitle: Slack
title: Configure Slack for Alerting
weight: 80
weight: 0
refs:
nested-policy:
- pattern: /docs/grafana/

@ -13,7 +13,7 @@ labels:
- oss
menuTitle: Microsoft Teams
title: Configure Microsoft Teams for Alerting
weight: 50
weight: 0
---
# Configure Microsoft Teams for Alerting

@ -13,7 +13,7 @@ labels:
- oss
menuTitle: Telegram
title: Configure Telegram for Alerting
weight: 90
weight: 0
---
# Configure Telegram for Alerting

@ -14,7 +14,7 @@ labels:
- oss
menuTitle: PagerDuty
title: Configure PagerDuty for Alerting
weight: 70
weight: 0
---
# Configure PagerDuty for Alerting

@ -21,7 +21,7 @@ labels:
- oss
menuTitle: Webhook notifier
title: Configure the webhook notifier for Alerting
weight: 100
weight: 0
---
# Configure the webhook notifier for Alerting

@ -99,6 +99,7 @@ Grafana supports a wide range of contact points with varied support for images i
| Google Chat | No | Yes |
| Kafka | No | No |
| Line | No | No |
| MQTT | No | No |
| Microsoft Teams | No | Yes |
| Opsgenie | No | Yes |
| Pagerduty | No | Yes |

@ -57,7 +57,7 @@ An alert event is displayed each time an alert instance changes its state over a
{{% admonition type="note" %}}
For Grafana Enterprise and OSS users:
The feature is available starting with Grafana 11.2.
To try out the new alert history page, enable the `alertingCentralAlertHistory` feature toggle and configure [Loki annotations](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/alerting/set-up/configure-alert-state-history/).
Users can only see the history and transitions of alert rules they have access to (RBAC).

@ -18,6 +18,17 @@ labels:
- oss
title: Configure high availability
weight: 600
refs:
state-history:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/manage-notifications/view-state-health/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/alerting/manage-notifications/view-state-health/
meta-monitoring:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/monitor/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/alerting/monitor/
---
# Configure high availability
@ -28,18 +39,13 @@ Grafana Alerting uses the Prometheus model of separating the evaluation of alert
When running multiple instances of Grafana, all alert rules are evaluated on all instances. You can think of the evaluation of alert rules as being duplicated by the number of running Grafana instances. This is how Grafana Alerting makes sure that as long as at least one Grafana instance is working, alert rules are still be evaluated and notifications for alerts are still sent.
You can find this duplication in state history and it is a good way to confirm if you are using high availability.
You can find this duplication in state history and it is a good way to [verify your high availability setup](#verify-your-high-availability-setup).
While the alert generator evaluates all alert rules on all instances, the alert receiver makes a best-effort attempt to avoid sending duplicate notifications. Alertmanager chooses availability over consistency, which may result in occasional duplicated or out-of-order notifications. It takes the opinion that duplicate or out-of-order notifications are better than no notifications.
While the alert generator evaluates all alert rules on all instances, the alert receiver makes a best-effort attempt to avoid duplicate notifications. The alertmanagers use a gossip protocol to share information between them to prevent sending duplicated notifications.
The Alertmanager uses a gossip protocol to share information about notifications between Grafana instances. It also gossips silences, which means a silence created on one Grafana instance is replicated to all other Grafana instances. Both notifications and silences are persisted to the database periodically, and during graceful shut down.
Alertmanager chooses availability over consistency, which may result in occasional duplicated or out-of-order notifications. It takes the opinion that duplicate or out-of-order notifications are better than no notifications.
{{% admonition type="note" %}}
If using a mix of `execute_alerts=false` and `execute_alerts=true` on the HA nodes, since the alert state is not shared amongst the Grafana instances, the instances with `execute_alerts=false` do not show any alert status.
This is because the HA settings (`ha_peers`, etc) only apply to the alert notification delivery (i.e. de-duplication of alert notifications, and silences, as mentioned above).
{{% /admonition %}}
Alertmanagers also gossip silences, which means a silence created on one Grafana instance is replicated to all other Grafana instances. Both notifications and silences are persisted to the database periodically, and during graceful shut down.
## Enable alerting high availability using Memberlist
@ -56,6 +62,8 @@ Since gossiping of notifications and silences uses both TCP and UDP port `9094`,
By default, it is set to listen to all interfaces (`0.0.0.0`).
1. Set `[ha_peer_timeout]` in the `[unified_alerting]` section of the custom.ini to specify the time to wait for an instance to send a notification via the Alertmanager. The default value is 15s, but it may increase if Grafana servers are located in different geographic regions or if the network latency between them is high.
For a demo, see this [example using Docker Compose](https://github.com/grafana/alerting-ha-docker-examples/tree/main/memberlist).
## Enable alerting high availability using Redis
As an alternative to Memberlist, you can use Redis for high availability. This is useful if you want to have a central
@ -68,19 +76,7 @@ database for HA and cannot support the meshing of all Grafana servers.
1. Optional: Set `ha_redis_prefix` to something unique if you plan to share the Redis server with multiple Grafana instances.
1. Optional: Set `ha_redis_tls_enabled` to `true` and configure the corresponding `ha_redis_tls_*` fields to secure communications between Grafana and Redis with Transport Layer Security (TLS).
The following metrics can be used for meta monitoring, exposed by the `/metrics` endpoint in Grafana:
| Metric | Description |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| alertmanager_cluster_messages_received_total | Total number of cluster messages received. |
| alertmanager_cluster_messages_received_size_total | Total size of cluster messages received. |
| alertmanager_cluster_messages_sent_total | Total number of cluster messages sent. |
| alertmanager_cluster_messages_sent_size_total | Total number of cluster messages received. |
| alertmanager_cluster_messages_publish_failures_total | Total number of messages that failed to be published. |
| alertmanager_cluster_members | Number indicating current number of members in cluster. |
| alertmanager_peer_position | Position the Alertmanager instance believes it's in. The position determines a peer's behavior in the cluster. |
| alertmanager_cluster_pings_seconds | Histogram of latencies for ping messages. |
| alertmanager_cluster_pings_failures_total | Total number of failed pings. |
For a demo, see this [example using Docker Compose](https://github.com/grafana/alerting-ha-docker-examples/tree/main/redis).
## Enable alerting high availability using Kubernetes
@ -149,3 +145,48 @@ The following metrics can be used for meta monitoring, exposed by the `/metrics`
ha_peer_timeout = 15s
ha_reconnect_timeout = 2m
```
## Verify your high availability setup
When running multiple Grafana instances, all alert rules are evaluated on every instance. This multiple evaluation of alert rules is visible in the [state history](ref:state-history) and provides a straightforward way to verify that your high availability configuration is working correctly.
{{% admonition type="note" %}}
If using a mix of `execute_alerts=false` and `execute_alerts=true` on the HA nodes, since the alert state is not shared amongst the Grafana instances, the instances with `execute_alerts=false` do not show any alert status.
The HA settings (`ha_peers`, etc.) apply only to communication between alertmanagers, synchronizing silences and attempting to avoid duplicate notifications, as described in the introduction.
{{% /admonition %}}
You can also confirm your high availability setup by monitoring Alertmanager metrics exposed by Grafana.
| Metric | Description |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| alertmanager_cluster_members | Number indicating current number of members in cluster. |
| alertmanager_cluster_messages_received_total | Total number of cluster messages received. |
| alertmanager_cluster_messages_received_size_total | Total size of cluster messages received. |
| alertmanager_cluster_messages_sent_total | Total number of cluster messages sent. |
| alertmanager_cluster_messages_sent_size_total | Total number of cluster messages received. |
| alertmanager_cluster_messages_publish_failures_total | Total number of messages that failed to be published. |
| alertmanager_cluster_pings_seconds | Histogram of latencies for ping messages. |
| alertmanager_cluster_pings_failures_total | Total number of failed pings. |
| alertmanager_peer_position | The position an Alertmanager instance believes it holds, which defines its role in the cluster. Peers should be numbered sequentially, starting from zero. |
You can confirm the number of Grafana instances in your alerting high availability setup by querying the `alertmanager_cluster_members` and `alertmanager_peer_position` metrics.
Note that these alerting high availability metrics are exposed via the `/metrics` endpoint in Grafana, and are not automatically collected or displayed. If you have a Prometheus instance connected to Grafana, add a `scrape_config` to scrape Grafana metrics and then query these metrics in Explore.
```yaml
- job_name: grafana
honor_timestamps: true
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
follow_redirects: true
static_configs:
- targets:
- grafana:3000
```
For more information on monitoring alerting metrics, refer to [Alerting meta-monitoring](ref:meta-monitoring). For a demo, see [alerting high availability examples using Docker Compose](https://github.com/grafana/alerting-ha-docker-examples/).

@ -43,19 +43,19 @@ Fixed roles provide users more granular access to create, view, and update Alert
Details of the fixed roles and the access they provide for Grafana Alerting are below.
| Fixed role | Permissions | Description |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `fixed:alerting.instances:writer` | All permissions from `fixed:alerting.instances:reader` and<br> `alert.instances:create`<br>`alert.instances:write` for organization scope <br> `alert.instances.external:write` for scope `datasources:*` | Create, update and expire all silences. |
| `fixed:alerting.instances:reader` | `alert.instances:read` for organization scope <br> `alert.instances.external:read` for scope `datasources:*` | Read all alerts and silences. |
| `fixed:alerting.notifications:writer` | All permissions from `fixed:alerting.notifications:reader` and<br>`alert.notifications:write`for organization scope<br>`alert.notifications.external:read` for scope `datasources:*` | Create, update, and delete contact points, templates, mute timings and notification policies for Grafana and external Alertmanager. |
| `fixed:alerting.notifications:reader` | `alert.notifications:read` for organization scope<br>`alert.notifications.external:read` for scope `datasources:*` | Read all Grafana and Alertmanager contact points, templates, and notification policies. |
| `fixed:alerting.rules:writer` | All permissions from `fixed:alerting.rules:reader` and <br> `alert.rule:create` <br> `alert.rule:write` <br> `alert.rule:delete` <br> `alert.silences:create` <br> `alert.silences:write` for scope `folders:*` <br> `alert.rules.external:write` for scope `datasources:*` | Create, update, and delete all alert rules and manage rule-specific silences. |
| `fixed:alerting.rules:reader` | `alert.rule:read`, `alert.silences:read` for scope `folders:*` <br> `alert.rules.external:read` for scope `datasources:*` <br> `alert.notifications.time-intervals:read` <br> `alert.notifications.receivers:list` | Read all alert rules and read rule-specific silences. |
| `fixed:alerting:writer` | All permissions from `fixed:alerting.rules:writer` <br>`fixed:alerting.instances:writer`<br>`fixed:alerting.notifications:writer` | Create, update, and delete all alert rules, silences, contact points, templates, mute timings, and notification policies. |
| `fixed:alerting:reader` | All permissions from `fixed:alerting.rules:reader` <br>`fixed:alerting.instances:reader`<br>`fixed:alerting.notifications:reader` | Read-only permissions for all alert rules, alerts, contact points, and notification policies. |
| `fixed:alerting.provisioning.secrets:reader` | `alert.provisioning:read` and `alert.provisioning.secrets:read` | Read-only permissions for Provisioning API and let export resources with decrypted secrets. |
| `fixed:alerting.provisioning:writer` | `alert.provisioning:read` and `alert.provisioning:write` | Create, update and delete Grafana alert rules, notification policies, contact points, templates, etc via provisioning API. |
| `fixed:alerting.provisioning.status:writer` | `alert.provisioning.provenance:write` | Set provenance status to alert rules, notification policies, contact points, etc. Should be used together with regular writer roles. |
| Display name in UI / Fixed role | Permissions | Description |
| ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Silences Writer: `fixed:alerting.instances:writer` | All permissions from `fixed:alerting.instances:reader` and<br> `alert.instances:create`<br>`alert.instances:write` for organization scope <br> `alert.instances.external:write` for scope `datasources:*` | Add and update silences in Grafana and external providers. |
| Instances and Silences Reader: `fixed:alerting.instances:reader` | `alert.instances:read` for organization scope <br> `alert.instances.external:read` for scope `datasources:*` | Read alert instances and silences in Grafana and external providers. |
| Notifications Writer: `fixed:alerting.notifications:writer` | All permissions from `fixed:alerting.notifications:reader` and<br>`alert.notifications:write`for organization scope<br>`alert.notifications.external:read` for scope `datasources:*` | Add, update, and delete notification policies and contact points in Grafana and external providers. |
| Notifications Reader: `fixed:alerting.notifications:reader` | `alert.notifications:read` for organization scope<br>`alert.notifications.external:read` for scope `datasources:*` | Read notification policies and contact points in Grafana and external providers. |
| Rules Writer: `fixed:alerting.rules:writer` | All permissions from `fixed:alerting.rules:reader` and <br> `alert.rule:create` <br> `alert.rule:write` <br> `alert.rule:delete` <br> `alert.silences:create` <br> `alert.silences:write` for scope `folders:*` <br> `alert.rules.external:write` for scope `datasources:*` | Create, update, and delete all alert rules and manage rule-specific silences. |
| Rules Reader: `fixed:alerting.rules:reader` | `alert.rule:read`, `alert.silences:read` for scope `folders:*` <br> `alert.rules.external:read` for scope `datasources:*` <br> `alert.notifications.time-intervals:read` <br> `alert.notifications.receivers:list` | Read all alert rules and rule-specific silences in Grafana and external providers. |
| Full access: `fixed:alerting:writer` | All permissions from `fixed:alerting.rules:writer` <br>`fixed:alerting.instances:writer`<br>`fixed:alerting.notifications:writer` | Add, update, and delete alert rules, silences, contact points, and notification policies in Grafana and external providers. |
| Full read-only access: `fixed:alerting:reader` | All permissions from `fixed:alerting.rules:reader` <br>`fixed:alerting.instances:reader`<br>`fixed:alerting.notifications:reader` | Read alert rules, alert instances, silences, contact points, and notification policies in Grafana and external providers. |
| Read via Provisioning API + Export Secrets: `fixed:alerting.provisioning.secrets:reader` | `alert.provisioning:read` and `alert.provisioning.secrets:read` | Read alert rules, alert instances, silences, contact points, and notification policies using the provisioning API and use export with decrypted secrets. |
| Access to alert rules provisioning API: `fixed:alerting.provisioning:writer` | `alert.provisioning:read` and `alert.provisioning:write` | Manage all alert rules, notification policies, contact points, templates, in the organization using the provisioning API. |
| Set provisioning status: `fixed:alerting.provisioning.status:writer` | `alert.provisioning.provenance:write` | Set provisioning rules for Alerting resources. Should be used together with other regular roles (Notifications Writer and/or Rules Writer.) |
## Create custom roles

@ -343,6 +343,31 @@ settings:
{{< /collapse >}}
{{< collapse title="MQTT" >}}
#### MQTT
```yaml
type: mqtt
settings:
# <string, required>
brokerUrl: tcp://127.0.0.1:1883
# <string>
clientId: grafana
# <string, required>
topic: grafana/alerts
# <string>
messageFormat: json
# <string>
username: grafana
# <string>
password: password1
# <bool>
insecureSkipVerify: false
```
{{< /collapse >}}
{{< collapse title="Microsoft Teams" >}}
#### Microsoft Teams

@ -1,7 +1,4 @@
---
aliases:
- ../features/dashboard/dashboards/
- dashboard-manage/
labels:
products:
- cloud

@ -1,6 +1,5 @@
---
aliases:
- ../features/dashboard/dashboards/
- ../panels/working-with-panels/organize-dashboard/
- ../reference/dashboard_folders/
- dashboard-folders/

@ -1,6 +1,5 @@
---
aliases:
- ../features/dashboard/dashboards/
- ../reference/search/
- dashboard-ui/
- dashboard-ui/dashboard-header/

@ -236,7 +236,7 @@ groupByNode(summarize(movingAverage(apps.$app.$server.counters.requests.count, 5
_Ad hoc filters_ enable you to add key/value filters that are automatically added to all metric queries that use the specified data source. Unlike other variables, you do not use ad hoc filters in queries. Instead, you use ad hoc filters to write filters for existing queries.
{{% admonition type="note" %}}
Ad hoc filter variables only work with Prometheus, Loki, InfluxDB, and Elasticsearch data sources.
Not all data sources support ad hoc filters. Examples of those that do include Prometheus, Loki, InfluxDB, and Elasticsearch.
{{% /admonition %}}
1. [Enter general options](#enter-general-options).

@ -247,6 +247,10 @@ The resulting table panel:
## Use time series queries
{{< admonition type="note" >}}
Store timestamps in UTC to avoid issues with time shifts in Grafana when using non-UTC timezones.
{{< /admonition >}}
If you set the **Format** setting in the query editor to **Time series**, then the query must have a column named `time` that returns either a SQL datetime or any numeric datatype representing Unix epoch in seconds.
Result sets of time series queries must also be sorted by time for panels to properly visualize the result.

@ -38,7 +38,7 @@ Our [style guides](https://github.com/grafana/grafana/tree/main/contribute/style
- [Backend style guide](https://github.com/grafana/grafana/blob/main/contribute/style-guides/backend.md) explains how we want to write Go code in the future.
- [Documentation style guide](https://github.com/grafana/grafana/blob/main/contribute/style-guides/documentation-style-guide.md) applies to all documentation created for Grafana products.
- [Documentation style guide](https://grafana.com/docs/writers-toolkit/write/style-guide/) applies to all documentation created for Grafana products.
- [End to end test framework](https://github.com/grafana/grafana/blob/main/contribute/style-guides/e2e.md) provides guidance for Grafana e2e tests.

@ -38,8 +38,8 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
"targetUID": "PDDA8E780A17E7EF1",
"label": "My Label",
"description": "Logs to Traces",
"type": "query",
"config": {
"type": "query",
"field": "message",
"target": {},
}
@ -65,8 +65,8 @@ Content-Type: application/json
"sourceUID": "uyBf2637k",
"targetUID": "PDDA8E780A17E7EF1",
"uid": "50xhMlg9k",
"type": "query",
"config": {
"type": "query",
"field": "message",
"target": {},
}
@ -153,8 +153,8 @@ Content-Type: application/json
"sourceUID": "uyBf2637k",
"targetUID": "PDDA8E780A17E7EF1",
"uid": "J6gn7d31L",
"type": "query",
"config": {
"type": "query",
"field": "message",
"target": {}
}
@ -197,8 +197,8 @@ Content-Type: application/json
"targetUID": "PDDA8E780A17E7EF1",
"uid": "J6gn7d31L",
"provisioned": false,
"type": "query",
"config": {
"type": "query",
"field": "message",
"target": {},
}
@ -239,8 +239,8 @@ Content-Type: application/json
"targetUID": "PDDA8E780A17E7EF1",
"uid": "J6gn7d31L",
"provisioned": false,
"type": "query",
"config": {
"type": "query",
"field": "message",
"target": {},
}
@ -252,8 +252,8 @@ Content-Type: application/json
"targetUID": "P15396BDD62B2BE29",
"uid": "uWCpURgVk",
"provisioned": false,
"type": "query",
"config": {
"type": "query",
"field": "message",
"target": {},
}
@ -301,8 +301,8 @@ Content-Type: application/json
"targetUID": "PDDA8E780A17E7EF1",
"uid": "J6gn7d31L",
"provisioned": false,
"type": "query",
"config": {
"type": "query",
"field": "message",
"target": {},
}
@ -314,8 +314,8 @@ Content-Type: application/json
"targetUID": "P15396BDD62B2BE29",
"uid": "uWCpURgVk",
"provisioned": false,
"type": "query",
"config": {
"type": "query",
"field": "message",
"target": {},
}

@ -563,7 +563,7 @@
},
"extensions": {
"type": "array",
"description": "The list of extensions that the plugin registers under other extension points.",
"description": "List of link and component extensions which the plugin registers to given extension points.",
"items": {
"type": "object",
"properties": {

@ -77,6 +77,7 @@ With a Grafana Enterprise license, you also get access to premium data sources,
- [Adobe Analytics](/grafana/plugins/grafana-adobeanalytics-datasource)
- [AppDynamics](/grafana/plugins/dlopes7-appdynamics-datasource)
- [Atlassian Statuspage](/grafana/plugins/grafana-atlassianstatuspage-datasource)
- [Aurora](/grafana/plugins/grafana-aurora-datasource)
- [Azure CosmosDB](/grafana/plugins/grafana-azurecosmosdb-datasource)
- [Azure Devops](/grafana/plugins/grafana-azuredevops-datasource)
- [Catchpoint](/grafana/plugins/grafana-catchpoint-datasource)

@ -2,7 +2,6 @@
aliases:
- ../dashboards/add-organize-panels/
- ../dashboards/dashboard-create/
- ../features/dashboard/dashboards/
- ../panels/add-panels-dynamically/about-repeating-panels-rows/
- ../panels/add-panels-dynamically/configure-repeating-panels/
- ../panels/add-panels-dynamically/configure-repeating-rows/

@ -54,13 +54,36 @@ refs:
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-overrides/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-overrides/
data-transformation:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/transform-data/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/transform-data/
build-query:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/
---
# Table
Tables are a highly flexible visualization designed to display data in columns and rows. They support various data types, including tables, time series, annotations, and raw JSON data. The table visualization can even take multiple data sets and provide the option to switch between them. With this versatility, it's the preferred visualization for viewing multiple data types, aiding in your data analysis needs.
{{< figure src="/static/img/docs/tables/table_visualization.png" max-width="1200px" lightbox="true" caption="Table visualization" >}}
{{< figure src="/static/img/docs/tables/table_visualization.png" max-width="1200px" lightbox="true" alt="Table visualization" >}}
You can use a table visualization to show datasets such as:
- Common database queries like logs, traces, metrics
- Financial reports
- Customer lists
- Product catalogs
Any information you might want to put in a spreadsheet can often be best visualized in a table.
Tables also provide different styles to visualize data inside the table cells such as colored text and cell backgrounds, gauges, sparklines, data links, JSON code, and images.
## Configure a table visualization
The following video provides a visual walkthrough of the options you can set in a table visualization. If you want to see a configuration in action, check out the video:
@ -72,6 +95,38 @@ The following video provides a visual walkthrough of the options you can set in
Annotations and alerts are not currently supported for tables.
{{< /admonition >}}
## Supported data formats
The table visualization supports any data that has a column-row structure.
### Example
```
Column1, Column2, Column3
value1 , value2 , value3
value4 , value5 , value6
value7 , value8 , value9
```
If a cell is missing or the table cell-row structure is not complete, the table visualization won’t display any of the data:
```
Column1, Column2, Column3
value1 , value2 , value3
gap1 , gap2
value4 , value5 , value6
```
If you need to hide columns, you can do so using [data transformations](ref:data-transformation), [field overrides](#field-overrides), or by [building a query](ref:build-query) that returns only the needed columns.
If you’re using a cell type such as sparkline or JSON, the data requirements may differ in a way that’s specific to that type. For more info refer to [Cell type](#cell-type).
## Debugging in tables
The table visualization helps with debugging when you need to know exactly what results your query is returning and why other visualizations might not be working. This functionality is also accessible in most visualizations by toggling on the **Table view** switch at the top of the panel:
![The Table view switch](/media/docs/grafana/panels-visualizations/screenshot-table-view-on-11.2.png)
## Sort column
Click a column title to change the sort order from default to descending to ascending. Each time you click, the sort order changes to the next option in the cycle. You can sort multiple columns by holding the `shift` key and clicking the column name.
@ -241,11 +296,11 @@ Toggle the **Wrap text** switch to wrap text in the cell with the longest conten
### Cell value inspect
Enables value inspection from table cell. The raw value is presented in a modal window.
Enables value inspection from table cells. When the **Cell inspect value** switch is toggled on, clicking the inspect icon in a cell opens the **Inspect value** drawer.
{{% admonition type="note" %}}
Cell value inspection is only available when cell display mode is set to Auto, Color text, Color background or JSON View.
{{% /admonition %}}
The **Inspect value** drawer has two tabs, **Plain text** and **Code editor**. Grafana attempts to automatically detect the type of data in the cell and opens the drawer with the associated tab showing. However, you can switch back and forth between tabs.
Cell value inspection is only available when the **Cell type** selection is **Auto**, **Colored text**, **Colored background**, or **JSON View**.
## Turn on column filtering

@ -64,11 +64,16 @@ refs:
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/panel-editor-overview/#data-section
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/panel-editor-overview/#data-section
data-transformation:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/panel-editor-overview/#data-section
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/panel-editor-overview/#data-section
---
# Time series
Time series visualizations are the default way to visualize data points over intervals of time, as a graph. They can render series as lines, points, or bars and are versatile enough to display almost any time-series data.
Time series visualizations are the default way to show the variations of a set of data values over time. Each data point is matched to a timestamp and this _time series_ is displayed as a graph. The visualization can render series as lines, points, or bars and it's versatile enough to display almost any type of [time-series data](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/fundamentals/timeseries/).
{{< figure src="/static/img/docs/time-series-panel/time_series_small_example.png" max-width="1200px" alt="Time series" >}}
@ -76,6 +81,14 @@ Time series visualizations are the default way to visualize data points over int
You can migrate from the legacy Graph visualization to the time series visualization. To migrate, open the panel and click the **Migrate** button in the side pane.
{{< /admonition >}}
A time series visualization displays an x-y graph with time progression on the x-axis and the magnitude of the values on the y-axis. This visualization is ideal for displaying large numbers of timed data points that would be hard to track in a table or list.
You can use the time series visualization if you need track:
- Temperature variations throughout the day
- The daily progress of your retirement account
- The distance you jog each day over the course of a year
## Configure a time series visualization
The following video guides you through the creation steps and common customizations of time series visualizations, and is great for beginners:
@ -86,7 +99,72 @@ The following video guides you through the creation steps and common customizati
## Supported data formats
Time series visualizations require time-series data&mdash;a sequence of measurements, ordered in time, and formatted as a table&mdash;where every row in the table represents one individual measurement at a specific time. Learn more about [time-series data](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/fundamentals/timeseries/).
Time series visualizations require time-series data—a sequence of measurements, ordered in time, and formatted as a table—where every row in the table represents one individual measurement at a specific time. Learn more about [time-series data](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/fundamentals/timeseries/).
The dataset must contain at least one numeric field, and in the case of multiple numeric fields, each one is plotted as a new line, point, or bar labeled with the field name in the tooltip.
### Example 1
In the following example, there are three numeric fields represented by three lines in the chart:
| Time | value1 | value2 | value3 |
| ------------------- | ------ | ------ | ------ |
| 2022-11-01 10:00:00 | 1 | 2 | 3 |
| 2022-11-01 11:00:00 | 4 | 5 | 6 |
| 2022-11-01 12:00:00 | 7 | 8 | 9 |
| 2022-11-01 13:00:00 | 4 | 5 | 6 |
![Time series line chart with multiple numeric fields](/media/docs/grafana/panels-visualizations/screenshot-grafana-11.1-timeseries-example1v2.png 'Time series line chart with multiple numeric fields')
If the time field isn't automatically detected, you might need to convert the data to a time format using a [data transformation](ref:data-transformation).
### Example 2
The time series visualization also supports multiple datasets. If all datasets are in the correct format, the visualization plots the numeric fields of all datasets and labels them using the column name of the field.
#### Query1
| Time | value1 | value2 | value3 |
| ------------------- | ------ | ------ | ------ |
| 2022-11-01 10:00:00 | 1 | 2 | 3 |
| 2022-11-01 11:00:00 | 4 | 5 | 6 |
| 2022-11-01 12:00:00 | 7 | 8 | 9 |
#### Query2
| timestamp | number1 | number2 | number3 |
| ------------------- | ------- | ------- | ------- |
| 2022-11-01 10:30:00 | 11 | 12 | 13 |
| 2022-11-01 11:30:00 | 14 | 15 | 16 |
| 2022-11-01 12:30:00 | 17 | 18 | 19 |
| 2022-11-01 13:30:00 | 14 | 15 | 16 |
![Time series line chart with two datasets](/media/docs/grafana/panels-visualizations/screenshot-grafana-11.1-timeseries-example2v2.png 'Time series line chart with two datasets')
### Example 3
If you want to more easily compare events between different, but overlapping, time frames, you can do this by using a time offset while querying the compared dataset:
#### Query1
| Time | value1 | value2 | value3 |
| ------------------- | ------ | ------ | ------ |
| 2022-11-01 10:00:00 | 1 | 2 | 3 |
| 2022-11-01 11:00:00 | 4 | 5 | 6 |
| 2022-11-01 12:00:00 | 7 | 8 | 9 |
#### Query2
| timestamp(-30min) | number1 | number2 | number3 |
| ------------------- | ------- | ------- | ------- |
| 2022-11-01 10:30:00 | 11 | 12 | 13 |
| 2022-11-01 11:30:00 | 14 | 15 | 16 |
| 2022-11-01 12:30:00 | 17 | 18 | 19 |
| 2022-11-01 13:30:00 | 14 | 15 | 16 |
![Time Series Example with second Data Set offset](/media/docs/grafana/panels-visualizations/screenshot-grafana-11.1-timeseries-example3v2.png 'Time Series Example with second Data Set offset')
When you add the offset, the resulting visualization makes the datasets appear to be occurring at the same time so that you can compare them more easily.
## Alert rules

@ -21,53 +21,53 @@ For more information about feature release stages, refer to [Release life cycle
Most [generally available](https://grafana.com/docs/release-life-cycle/#general-availability) features are enabled by default. You can disable these feature by setting the feature flag to "false" in the configuration.
| Feature toggle name | Description | Enabled by default |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | |
| `publicDashboards` | [Deprecated] Public dashboards are now enabled by default; to disable them, use the configuration setting. This feature toggle will be removed in the next major version. | Yes |
| `featureHighlights` | Highlight Grafana Enterprise features | |
| `correlations` | Correlations page | Yes |
| `autoMigrateXYChartPanel` | Migrate old XYChart panel to new XYChart2 model | Yes |
| `cloudWatchCrossAccountQuerying` | Enables cross-account querying in CloudWatch datasources | Yes |
| `nestedFolders` | Enable folder nesting | Yes |
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes |
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes |
| `prometheusMetricEncyclopedia` | Adds the metrics explorer component to the Prometheus query builder as an option in metric select | Yes |
| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes |
| `prometheusDataplane` | Changes responses to from Prometheus to be compliant with the dataplane specification. In particular, when this feature toggle is active, the numeric `Field.Name` is set from 'Value' to the value of the `__name__` label. | Yes |
| `lokiMetricDataplane` | Changes metric responses from Loki to be compliant with the dataplane specification. | Yes |
| `dataplaneFrontendFallback` | Support dataplane contract field name change for transformations and field name matchers where the name is different | Yes |
| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | Yes |
| `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes |
| `transformationsRedesign` | Enables the transformations redesign | Yes |
| `traceQLStreaming` | Enables response streaming of TraceQL queries of the Tempo data source | |
| `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes |
| `prometheusConfigOverhaulAuth` | Update the Prometheus configuration page with the new auth component | Yes |
| `alertingNoDataErrorExecution` | Changes how Alerting state manager handles execution of NoData/Error | Yes |
| `angularDeprecationUI` | Display Angular warnings in dashboards and panels | Yes |
| `dashgpt` | Enable AI powered features in dashboards | Yes |
| `alertingInsights` | Show the new alerting insights landing page | Yes |
| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes |
| `formatString` | Enable format string transformer | Yes |
| `transformationsVariableSupport` | Allows using variables in transformations | Yes |
| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | Yes |
| `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes |
| `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes |
| `managedPluginsInstall` | Install managed plugins directly from plugins catalog | Yes |
| `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes |
| `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes |
| `ssoSettingsApi` | Enables the SSO settings API and the OAuth configuration UIs in Grafana | Yes |
| `logsInfiniteScrolling` | Enables infinite scrolling for the Logs panel in Explore and Dashboards | Yes |
| `exploreMetrics` | Enables the new Explore Metrics core app | Yes |
| `alertingSimplifiedRouting` | Enables users to easily configure alert notifications by specifying a contact point directly when editing or creating an alert rule | Yes |
| `logRowsPopoverMenu` | Enable filtering menu displayed when text of a log line is selected | Yes |
| `lokiQueryHints` | Enables query hints for Loki | Yes |
| `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | |
| `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes |
| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes |
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
| `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | |
| `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes |
| Feature toggle name | Description | Enabled by default |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | |
| `publicDashboards` | [Deprecated] Public dashboards are now enabled by default; to disable them, use the configuration setting. This feature toggle will be removed in the next major version. | Yes |
| `featureHighlights` | Highlight Grafana Enterprise features | |
| `correlations` | Correlations page | Yes |
| `autoMigrateXYChartPanel` | Migrate old XYChart panel to new XYChart2 model | Yes |
| `cloudWatchCrossAccountQuerying` | Enables cross-account querying in CloudWatch datasources | Yes |
| `nestedFolders` | Enable folder nesting | Yes |
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes |
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes |
| `prometheusMetricEncyclopedia` | Adds the metrics explorer component to the Prometheus query builder as an option in metric select | Yes |
| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes |
| `lokiMetricDataplane` | Changes metric responses from Loki to be compliant with the dataplane specification. | Yes |
| `dataplaneFrontendFallback` | Support dataplane contract field name change for transformations and field name matchers where the name is different | Yes |
| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | Yes |
| `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes |
| `transformationsRedesign` | Enables the transformations redesign | Yes |
| `traceQLStreaming` | Enables response streaming of TraceQL queries of the Tempo data source | |
| `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes |
| `prometheusConfigOverhaulAuth` | Update the Prometheus configuration page with the new auth component | Yes |
| `alertingNoDataErrorExecution` | Changes how Alerting state manager handles execution of NoData/Error | Yes |
| `angularDeprecationUI` | Display Angular warnings in dashboards and panels | Yes |
| `dashgpt` | Enable AI powered features in dashboards | Yes |
| `alertingInsights` | Show the new alerting insights landing page | Yes |
| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes |
| `formatString` | Enable format string transformer | Yes |
| `transformationsVariableSupport` | Allows using variables in transformations | Yes |
| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | Yes |
| `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes |
| `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes |
| `managedPluginsInstall` | Install managed plugins directly from plugins catalog | Yes |
| `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes |
| `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes |
| `ssoSettingsApi` | Enables the SSO settings API and the OAuth configuration UIs in Grafana | Yes |
| `logsInfiniteScrolling` | Enables infinite scrolling for the Logs panel in Explore and Dashboards | Yes |
| `exploreMetrics` | Enables the new Explore Metrics core app | Yes |
| `alertingSimplifiedRouting` | Enables users to easily configure alert notifications by specifying a contact point directly when editing or creating an alert rule | Yes |
| `logRowsPopoverMenu` | Enable filtering menu displayed when text of a log line is selected | Yes |
| `lokiQueryHints` | Enables query hints for Loki | Yes |
| `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | |
| `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes |
| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes |
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
| `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | |
| `openSearchBackendFlowEnabled` | Enables the backend query flow for Open Search datasource plugin | Yes |
| `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes |
## Public preview feature toggles
@ -103,7 +103,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `ssoSettingsSAML` | Use the new SSO Settings API to configure the SAML connector |
| `accessActionSets` | Introduces action sets for resource permissions. Also ensures that all folder editors and admins can create subfolders without needing any additional permissions. |
| `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars |
| `openSearchBackendFlowEnabled` | Enables the backend query flow for Open Search datasource plugin |
| `cloudwatchMetricInsightsCrossAccount` | Enables cross account observability for Cloudwatch Metric Insights query builder |
## Experimental feature toggles
@ -121,7 +120,6 @@ Experimental features might be changed or removed without prior notice.
| `canvasPanelNesting` | Allow elements nesting |
| `disableSecretsCompatibility` | Disable duplicated secret storage in legacy tables |
| `logRequestsInstrumentedAsUnknown` | Logs the path for requests that are instrumented as unknown |
| `unifiedStorage` | SQL-based k8s storage |
| `showDashboardValidationWarnings` | Show warnings when dashboards do not validate against the schema |
| `mysqlAnsiQuotes` | Use double quotes to escape keyword in a MySQL query |
| `alertingBacktesting` | Rule backtesting API for alerting |
@ -151,7 +149,6 @@ Experimental features might be changed or removed without prior notice.
| `wargamesTesting` | Placeholder feature flag for internal testing |
| `externalCorePlugins` | Allow core plugins to be loaded as external |
| `pluginsAPIMetrics` | Sends metrics of public grafana packages usage by plugins |
| `idForwarding` | Generate signed id token for identity that can be forwarded to plugins and external services |
| `enableNativeHTTPHistogram` | Enables native HTTP Histograms |
| `disableClassicHTTPHistogram` | Disables classic HTTP Histogram (use with enableNativeHTTPHistogram) |
| `kubernetesSnapshots` | Routes snapshot requests from /api to the /apis endpoint |
@ -195,6 +192,7 @@ Experimental features might be changed or removed without prior notice.
| `backgroundPluginInstaller` | Enable background plugin installer |
| `dataplaneAggregator` | Enable grafana dataplane aggregator |
| `adhocFilterOneOf` | Exposes a new 'one of' operator for ad-hoc filters. This operator allows users to filter by multiple values in a single filter. |
| `lokiSendDashboardPanelNames` | Send dashboard and panel names to Loki when querying |
## Development feature toggles

@ -88,10 +88,11 @@ port = 636
use_ssl = true
# If set to true, use LDAP with STARTTLS instead of LDAPS
start_tls = false
# The value of an accepted TLS cipher. By default, this value is empty. Example value: ["TLS_AES_256_GCM_SHA384"])
# The value of an accepted TLS cipher. By default, this value is empty. Example value: ["TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"])
# For a complete list of supported ciphers and TLS versions, refer to: https://go.dev/src/crypto/tls/cipher_suites.go
# Starting with Grafana v11.0 only ciphers with ECDHE support are accepted for TLS 1.2 connections.
tls_ciphers = []
# This is the minimum TLS version allowed. By default, this value is empty. Accepted values are: TLS1.1, TLS1.2, TLS1.3.
# This is the minimum TLS version allowed. By default, this value is empty. Accepted values are: TLS1.1 (only for Grafana v10.4 or earlier), TLS1.2, TLS1.3.
min_tls_version = ""
# set to true if you want to skip SSL cert validation
ssl_skip_verify = false

@ -61,7 +61,7 @@ Grafana requires a database to store its configuration data, such as users, data
Grafana supports the following databases:
- [SQLite 3](https://www.sqlite.org/index.html)
- [MySQL 5.7+](https://www.mysql.com/support/supportedplatforms/database.html)
- [MySQL 8.0+](https://www.mysql.com/support/supportedplatforms/database.html)
- [PostgreSQL 12+](https://www.postgresql.org/support/versioning/)
By default Grafana uses an embedded SQLite database, which is stored in the Grafana installation location.

@ -111,7 +111,7 @@ Complete any of the following steps to uninstall Grafana.
To uninstall Grafana, run the following commands in a terminal window:
1. If you configured Grafana to run with systemd, stop the systemd servivce for Grafana server:
1. If you configured Grafana to run with systemd, stop the systemd service for Grafana server:
```shell
sudo systemctl stop grafana-server

@ -390,6 +390,10 @@ The Alerting Provisioning HTTP API can only be used to manage Grafana-managed al
- [cortex-tools](https://github.com/grafana/cortex-tools#cortextool): to interact with the Cortex alertmanager and ruler configuration.
- [lokitool](https://grafana.com/docs/loki/<GRAFANA_VERSION>/alert/#lokitool): to configure the Loki Ruler.
Alternatively, the [Grafana Alerting API](https://editor.swagger.io/?url=https://raw.githubusercontent.com/grafana/grafana/main/pkg/services/ngalert/api/tooling/post.json) can be used to access data from data source-managed alerts. This API is primarily intended for internal usage, with the exception of the `/api/v1/provisioning/` endpoints. It's important to note that internal APIs may undergo changes without prior notice and are not officially supported for user consumption.
For Prometheus, `amtool` can also be used to interact with the [AlertManager API](https://petstore.swagger.io/?url=https://raw.githubusercontent.com/prometheus/alertmanager/main/api/v2/openapi.yaml#/).
## Paths
### <span id="route-delete-alert-rule"></span> Delete a specific alert rule by UID. (_RouteDeleteAlertRule_)

@ -0,0 +1,22 @@
---
description: Guide for upgrading to Grafana v11.2
keywords:
- grafana
- configuration
- documentation
- upgrade
- '11.2'
title: Upgrade to Grafana v11.2
menuTitle: Upgrade to v11.2
weight: 1000
---
# Upgrade to Grafana v11.2
{{< docs/shared lookup="upgrade/intro.md" source="grafana" version="<GRAFANA_VERSION>" >}}
{{< docs/shared lookup="back-up/back-up-grafana.md" source="grafana" version="<GRAFANA_VERSION>" leveloffset="+1" >}}
{{< docs/shared lookup="upgrade/upgrade-common-tasks.md" source="grafana" version="<GRAFANA_VERSION>" >}}
## Technical notes

@ -76,6 +76,7 @@ For a complete list of every change, with links to pull requests and related iss
## Grafana 11
- [What's new in 11.2](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/whatsnew/whats-new-in-v11-2/)
- [What's new in 11.1](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/whatsnew/whats-new-in-v11-1/)
- [What's new in 11.0](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/whatsnew/whats-new-in-v11-0/)

@ -0,0 +1,271 @@
---
description: Feature and improvement highlights for Grafana v11.2
keywords:
- grafana
- new
- documentation
- '11.2'
- release notes
labels:
products:
- cloud
- enterprise
- oss
title: What's new in Grafana v11.2
weight: -44
---
<!-- vale GoogleWe = NO -->
<!-- vale We = NO -->
# What’s new in Grafana v11.2
Welcome to Grafana 11.2! We've made a number of improvements in this release, including a Grafana Cloud Migration Assistant in public preview, several new transformations, and a centralized page for viewing your alert history. We've also released several new data sources to help you visualize data from Zendesk, Catchpoint, and Yugabyte. Read on to learn more about these and all the new features in v11.2.
<!-- {{< youtube id="s6IYpILVDSM" >}} -->
For even more detail about all the changes in this release, refer to the [changelog](https://github.com/grafana/grafana/blob/main/CHANGELOG.md). For the specific steps we recommend when you upgrade to v11.2, check out our [Upgrade Guide](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/upgrade-guide/upgrade-v11.2/).
## Grafana Cloud Migration Assistant is in public preview
<!-- #wg-everyone-to-cloud -->
_Available in public preview in Grafana Open Source and Enterprise_
Migrating from OSS or Enterprise Grafana to Grafana Cloud has traditionally been complex, requiring technical knowledge of Grafana's HTTP API and time-consuming manual processes. The new Grafana Cloud Migration Assistant changes this by providing a user-friendly interface that automates the migration of your resources. No coding required, it securely handles the transfer in just a few easy steps.
This intuitive UI offers real-time updates on your migration status, making your migration journey faster, more efficient, and less error-prone. Initially, the Cloud Migration Assistant supports dashboards, folders, and core data sources, with plans to include alerting, app plugins, and panel plugins in future updates.
Ready to make the move? Explore our [migration guide](https://grafana.com/docs/grafana-cloud/account-management/migration-guide/) to learn more about the Cloud Migration Assistant today and begin your effortless transition to Grafana Cloud.
## Navigation bookmarks
<!-- #grafana-frontend-platform -->
_Available in public preview in all editions of Grafana_
As Grafana keeps growing, we have had feedback that it can be hard to find the pages you are looking for in the navigation. That is why we have added a new section to the navigation called 'Bookmarks', so you can easily access all of your favourite pages at the top of the navigation.
This feature is being rolled out across Grafana Cloud now. To use Bookmarks in self-managed Grafana, turn on the `pinNavItems` [feature toggle](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/feature-toggles/) in Grafana v11.2 or newer.
{{< figure src="/media/docs/grafana/grafana-nav-bookmarks.png" caption="Bookmark pages in the nav bar for quick access." alt="Bookmark pages in the Grafana nav bar for quick access" >}}
## Dashboards and visualizations
### Transformation updates
<!-- Nathan Marrs; #grafana-dataviz -->
_Generally available in all editions of Grafana_
We've made a number of exciting updates to transformations!
**You can now use variables in some transformations**
Template variables are now supported for the **Limit**, **Sort by**, **Filter data by values**, **Grouping to matrix** ([a community contribution](https://github.com/grafana/grafana/pull/88551) ⭐), **Heatmap**, and **Histogram** transformations. This enables dynamic transformation configurations based on panel data and dashboard variables.
**New transpose transformation**
We're excited to announce the new **Transpose** transformation, which allows you to pivot the data frame, converting rows into columns and columns into rows. This feature is particularly useful for data sources that don't support pivot queries, enabling more flexible and insightful data visualizations.
For more information, refer to the [documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/transform-data/#transpose).
{{< figure src="/media/docs/grafana/transformations/screenshot-grafana-11-2-transpose-transformation.png" alt="Transpose transformation in action" >}}
This feature is [a community contribution](https://github.com/grafana/grafana/pull/88963) ❤
**Group to nested tables is now generally available**
We're excited to announce that the **Group to nested tables** transformation is now generally available! Easily group your table data by specified fields and perform calculations on each group. With this transformation, you can enhance the depth and utility of your table visualizations.
See [the documentation for more information](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/transform-data/#group-to-nested-tables).
{{< video-embed src="/media/docs/grafana/screen-recording-10-4-table-group-to-nested-table-transformation.mp4" caption="Group to nested tables transformation" >}}
**Format string is now generally available**
The **Format string** transformation is now generally available! Use this transformation to customize the output of a string field. From formatting your string data to upper, lower, title case, and more, this transformation provides a convenient way to standardize and tailor the presentation of string data for better visualization and analysis. See [the documentation for more information](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/transform-data/#format-string).
**New cumulative and window calculations available in Add field from calculation**
The **Add field from calculation** transformation now supports both cumulative and window calculations. The cumulative function calculates on the current row and all preceding rows. You can calculate the total or the mean of your data up to and including the current row. With the window function you can calculate the mean, standard deviation, or variance on a specified set (window) of your data. The window can either be trailing or centered. With a trailing window the current row will be the last row in the window. With a centered window the window will be centered on the current row.
See [the documentation for more information](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/transform-data/#add-field-from-calculation).
### Improvements in canvas visualizations
<!-- Adela Almasan, Nathan Marrs, #grafana-dataviz -->
_Generally available in all editions of Grafana_
#### Standardized tooltips
As a continuation of our efforts to standardize tooltips across visualizations, we've updated canvas visualization tooltips to be supported for all elements tied to data. Besides the element name and data, the tooltip now also displays the timestamp. This is a step forward from the previous implementation where tooltips were shown only if data links were configured.
#### Data links improvements
We've updated canvas visualizations so that you can add data links to canvas elements without using an override. The **Selected element** configuration now includes a **Data links** section where you can add data links to elements using the same steps as in other visualizations.
Data links in canvas elements can also be configured to open with a single click. To enable this functionality, select **Link** under the one **One-click** section in the **Selected element** data link options. If there are multiple data links for an element, the first link in the list has the one-click functionality.
As part of this improvement, we've also added the ability to control the order in which data links are displayed by dragging and dropping them. This improvement has been added to all visualizations.
{{< video-embed src="/media/docs/grafana/panels-visualizations/canvas-oneclick-tooltips-v4-11.2.mp4" >}}
In future releases, we'll add one-click functionality to data links in other Grafana visualizations.
[Documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/panels-visualizations/visualizations/canvas/#data-links)
### State timeline supports pagination
<!-- #grafana-dataviz -->
_Generally available in all editions of Grafana_
The state timeline visualization now supports pagination. The **Page size** option lets you paginate the state timeline visualization to limit how many series are visible at once. This is useful when you have many series. Previously, all the series in a state timeline were made to fit within the single window of the panel, which could make it hard to read.
{{< video-embed src="/media/docs/grafana/panels-visualizations/screen-recording-grafana-11-2-state-timeline-pagination-dark.mp4" >}}
With paginated results, the visualization displays a subset of all series on each page.
Pagination is especially useful if you're running a query on a dynamic data source. It's also helpful regardless of whether you have many data frames with just two fields (time + value) or few frames with many fields (time + many values).
This feature is [a community contribution](https://github.com/grafana/grafana/pull/89586) ❤
[Documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/panels-visualizations/visualizations/state-timeline/#page-size-enable-pagination)
## Alerting
### Centralized alert history page
<!-- Ryan Kehoe, Sonia Alguilar -->
_Generally available in all editions of Grafana_
View a history of all alert events generated by your Grafana-managed alert rules from one centralized page. This helps you see patterns in your alerts over time, observe trends, make predictions, and even debug alerts that might be firing too often.
An alert event is displayed each time an alert instance changes its state over a period of time. All alert events are displayed regardless of whether silences or mute timings are set, so you’ll see a complete set of your data history even if you’re not necessarily being notified.
For Grafana Enterprise and OSS users:
To try out the new alert history page, enable the `alertingCentralAlertHistory` feature toggle and [configure Loki annotations](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/alerting/set-up/configure-alert-state-history/).
{{< figure src="/media/docs/alerting/alert-state-history.png" alt="Alert state history page" >}}
[Documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/alerting/manage-notifications/view-state-health/)
## Explore
### Logs filtering and pinning in Explore content outline
<!-- Haris Rozajac -->
_Generally available in all editions of Grafana_
Grafana Explore now allows for logs filtering and pinning in content outline.
**Filtering Logs:** All log levels are now automatically available in the content outline. You can filter by log level, currently supported for Elasticsearch and Loki data sources. To select multiple filters, hold down the command key on Mac or the control key on Windows while clicking.
**Pinning Logs:** The new pinning feature allows users to pin logs to the content outline, making them easily accessible for quick reference during investigations. To pin a log, hover over a log in the logs panel and click on the **Pin to content outline** icon in the log row menu. Clicking on a pinned log will open the log context modal, showing the log highlighted in context with other logs. From here, you can also open the log in split mode to preserve the time range in the left pane while having the time range specific to that log in the right pane.
### Forward direction search for Loki
<!-- #observability-logs -->
_Generally available in all editions of Grafana_
Explore now supports forward direction search for Loki logs searches. This allows users to seamlessly browse logs in a time range in forward chronological order (for example, tracing a specific user's actions using logs).
[Documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/datasources/loki/query-editor/)
## Data sources
### Cloudwatch Metric Insights cross account observability support
<!-- #grafana-aws-datasources -->
_Generally available in all editions of Grafana_
We are excited to announce support for cross-account querying in Metric Insights query builder for AWS Cloudwatch Plugin. This enables building SQL queries to monitor across multiple accounts in the same region in AWS Cloudwatch.
This feature introduces an account drop-down for selecting one or all of your source accounts and builds a query that targets them. Furthermore, results can be grouped by account ID by selecting **Account ID** in the **Group By** drop-down.
For more complex queries that are not covered by the options in the builder you can switch to the manual Code editor and edit the query.
To set up cross-account querying for AWS Cloudwatch Plugin, see instructions [here](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/datasources/aws-cloudwatch/query-editor/#cross-account-observability).
### Zendesk data source for Grafana
<!-- #grafana-oss-big-tent -->
_Available in public preview in Grafana Enterprise and Grafana Cloud_
We are excited to announce the release of a new Zendesk data source for Grafana. This addition extends Grafana's capabilities, enabling seamless integration with Zendesk.
You can find out more about the data source in the [Zendesk data source documentation](https://grafana.com/docs/plugins/grafana-zendesk-datasource/latest/).
{{< figure src="/media/docs/zendesk/zendesk_query_editor.png" alt="Zendesk Query Editor" >}}
### Catchpoint Enterprise data source for Grafana
<!-- Taewoo Kim -->
_Available in public preview in Grafana Enterprise and Grafana Cloud_
**Introducing Catchpoint data source plugin.**
The Catchpoint data source plugin allows you to query and visualize `Tests`, `RUM` and `SLO` data from within Grafana.
{{< video-embed src="/media/docs/plugins/Catchpoint.mp4" >}}
### Yugabyte data source for Grafana
<!-- #grafana-oss-big-tent -->
_Available in public preview in all editions of Grafana_
We are excited to announce the release of a new data source for Grafana: **Yugabyte**. This addition extends Grafana's capabilities, enabling seamless integration with YugabyteDB.
You can find out more about the data source in the [Yugabyte data source documentation](https://grafana.com/docs/plugins/grafana-yugabyte-datasource/latest/).
The datasource has some known limitations: ad-hoc filters and TLS/network customization are not yet supported. Improvements and additional supported features are planned for future updates.
{{< figure src="/media/docs/yugabyte/yugabyte_explore_builder.png" alt="Yugabyte query editor" >}}
## Authentication and authorization
### Map org-specific user roles from your OAuth provider
<!-- #identity-access, @Misi -->
_Generally available in Grafana Open Source and Enterprise_
Assign users to particular organizations with a specific role in Grafana, depending on an attribute value obtained from your identity provider.
This is a longstanding feature request from the community. We collaborated with our community to implement the request and have added this capability in Grafana 11.2.0.
For Generic OAuth and Okta, you can configure the claim (using the `org_attribute_path` setting) that contains the organizations which the user belongs to. Other OAuth providers use the same attribute for organization mapping that is used for group mapping: Entra ID (previously Azure AD), GitLab and Google use the current user’s Groups, and GitHub uses the user’s Teams.
To configure organization mapping for your instance, please check the documentation for the OAuth provider you are using in the [Grafana documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-security/configure-authentication/). You can find an example of how to configure organization mapping on each OAuth provider page under the **Org roles mapping example** section.
### Better SAML integration for Azure AD
_Generally available in all editions of Grafana_
<!-- Lino Urdiales -->
When setting up Grafana with Azure AD using the SAML protocol, the Azure AD Graph API sometimes returns a follow-up Graph API call rather than the information itself. This is the case for users who belong to more than 150 groups when using SAML.
With Grafana 11.2, we offer a mechanism for setting up an application as a Service Account in Azure AD and retrieving information from Graph API.
Please refer to our [documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-security/configure-authentication/saml/#configure-a-graph-api-application-in-azure-ad) on how to set up an Azure AD registered application for this setup.
### API support for LDAP configuration
<!-- #proj-grafana-sso-config -->
_Available in public preview in all editions of Grafana_
[The SSO settings API](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/developers/http_api/sso-settings/) has been updated to include support for LDAP settings. This feature is experimental behind the feature flag `ssoSettingsLDAP`.
You will soon be able to configure LDAP from the UI and Terraform.

@ -1,5 +0,0 @@
# Custom plugins
Plugins in this directory will be installed when the e2e [test server](https://github.com/grafana/grafana/blob/main/scripts/grafana-server/start-server) is started. Optionally, you can provision the plugin by adding configuration to the [datasources.yaml](https://github.com/grafana/grafana/blob/extensions/add-e2e-tests/devenv/datasources.yaml) or to the [plugins.yaml](https://github.com/grafana/grafana/blob/extensions/add-e2e-tests/devenv/plugins.yaml).
These plugins are not being built as part of CI. Plugins in this directory are being version controlled, so make sure the bundle size is small. Only use dependencies provided by the runtime (see list of runtime dependencies [here](https://github.com/grafana/plugin-tools/blob/08b67179bdbf8847788c54aadb22654aa1a7c060/packages/create-plugin/templates/common/.config/webpack/webpack.config.ts#L36)).

@ -1,22 +0,0 @@
# App with exposed components
This directory contains two apps - `myorg-componentconsumer-app` and `myorg-componentexposer-app` which is nested inside `myorg-componentconsumer-app`.
`myorg-componentconsumer-app` exposes a simple React component using the [`exposeComponent`](https://grafana.com/developers/plugin-tools/reference/ui-extensions#exposecomponent) api. `myorg-componentconsumer-app` in turn, consumes this compoment using the [`https://grafana.com/developers/plugin-tools/reference/ui-extensions#useplugincomponent`](https://grafana.com/developers/plugin-tools/reference/ui-extensions#useplugincomponent) hook.
To test this app:
```sh
# start e2e test instance (it will install this plugin)
PORT=3000 ./scripts/grafana-server/start-server
# run Playwright tests using Playwright VSCode extension or with the following script
yarn e2e:playwright
```
or
```
PORT=3000 ./scripts/grafana-server/start-server
yarn start
yarn e2e
```

@ -1,28 +0,0 @@
define(['@grafana/data', '@grafana/runtime', 'react'], function (grafanaData, grafanaRuntime, React) {
var AppPlugin = grafanaData.AppPlugin;
var usePluginComponent = grafanaRuntime.usePluginComponent;
var MyComponent = function () {
var plugin = usePluginComponent('myorg-componentexposer-app/reusable-component/v1');
var TestComponent = plugin.component;
var isLoading = plugin.isLoading;
if (!TestComponent) {
return null;
}
return React.createElement(
React.Fragment,
null,
React.createElement('div', null, 'Exposed component:'),
isLoading ? 'Loading..' : React.createElement(TestComponent, { name: 'World' })
);
};
var App = function () {
return React.createElement('div', null, 'Hello Grafana!', React.createElement(MyComponent, null));
};
var plugin = new AppPlugin().setRootPage(App);
return { plugin: plugin };
});

@ -1,35 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/grafana/grafana/main/docs/sources/developers/plugins/plugin.schema.json",
"type": "app",
"name": "Extensions exposed component App",
"id": "myorg-componentconsumer-app",
"preload": true,
"info": {
"keywords": ["app"],
"description": "Example on how to extend grafana ui from a plugin",
"author": {
"name": "Myorg"
},
"logos": {
"small": "img/logo.svg",
"large": "img/logo.svg"
},
"screenshots": [],
"version": "1.0.0",
"updated": "2024-08-09"
},
"includes": [
{
"type": "page",
"name": "Default",
"path": "/a/myorg-componentconsumer-app",
"role": "Admin",
"addToNav": true,
"defaultNav": true
}
],
"dependencies": {
"grafanaDependency": ">=10.3.3",
"plugins": []
}
}

@ -1,14 +0,0 @@
define(['@grafana/data', 'module', 'react'], function (grafanaData, amdModule, React) {
const plugin = new grafanaData.AppPlugin().exposeComponent({
id: 'myorg-componentexposer-app/reusable-component/v1',
title: 'Reusable component',
description: 'A component that can be reused by other app plugins.',
component: function ({ name }) {
return React.createElement('div', { 'data-testid': 'exposed-component' }, 'Hello ', name, '!');
},
});
return {
plugin: plugin,
};
});

@ -1,12 +0,0 @@
# App with extension point
This app was initially copied from the [app-with-extension-point](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/app-with-extension-point) example plugin. The plugin bundle is using AMD, but it's not minified and the plugin feature set is small so it should be possible to make changes in this file if necessary.
To test this app:
```sh
# start e2e test instance (it will install this plugin)
PORT=3000 ./scripts/grafana-server/start-server
# run Playwright tests using Playwright VSCode extension or with the following script
yarn e2e:playwright
```

@ -1,141 +0,0 @@
define(['@grafana/data', 'react', '@grafana/ui', '@grafana/runtime'], function (data, React, UI, runtime) {
'use strict';
const styles = {
container: 'main-app-body',
actions: { button: 'action-button' },
modal: { container: 'container', open: 'open-link' },
appA: { container: 'a-app-body' },
appB: { modal: 'b-app-modal' },
};
function ModalComponent({ onDismiss, title, path }) {
return React.createElement(
UI.Modal,
{ 'data-testid': styles.modal.container, title, isOpen: true, onDismiss },
React.createElement(
UI.VerticalGroup,
{ spacing: 'sm' },
React.createElement('p', null, 'Do you want to proceed in the current tab or open a new tab?')
),
React.createElement(
UI.Modal.ButtonRow,
null,
React.createElement(UI.Button, { onClick: onDismiss, fill: 'outline', variant: 'secondary' }, 'Cancel'),
React.createElement(
UI.Button,
{
type: 'submit',
variant: 'secondary',
onClick: function () {
window.open(data.locationUtil.assureBaseUrl(path), '_blank');
onDismiss();
},
icon: 'external-link-alt',
},
'Open in new tab'
),
React.createElement(
UI.Button,
{
'data-testid': styles.modal.open,
type: 'submit',
variant: 'primary',
onClick: function () {
runtime.locationService.push(path);
},
icon: 'apps',
},
'Open'
)
)
);
}
function ActionComponent({ extensions }) {
const options = React.useMemo(
function () {
return extensions.reduce(function (acc, extension) {
if (runtime.isPluginExtensionLink(extension)) {
acc.push({ label: extension.title, title: extension.title, value: extension });
}
return acc;
}, []);
},
[extensions]
);
const [selected, setSelected] = React.useState();
return options.length === 0
? React.createElement(UI.Button, null, 'Run default action')
: React.createElement(
React.Fragment,
null,
React.createElement(
UI.ButtonGroup,
null,
React.createElement(
UI.ToolbarButton,
{
key: 'default-action',
variant: 'canvas',
onClick: function () {
alert('You triggered the default action');
},
},
'Run default action'
),
React.createElement(UI.ButtonSelect, {
'data-testid': styles.actions.button,
key: 'select-extension',
variant: 'canvas',
options: options,
onChange: function (e) {
const extension = e.value;
if (runtime.isPluginExtensionLink(extension)) {
if (extension.path) setSelected(extension);
if (extension.onClick) extension.onClick();
}
},
})
),
selected &&
selected.path &&
React.createElement(ModalComponent, {
title: selected.title,
path: selected.path,
onDismiss: function () {
setSelected(undefined);
},
})
);
}
class RootComponent extends React.PureComponent {
render() {
const { extensions } = runtime.getPluginExtensions({
extensionPointId: 'plugins/myorg-extensionpoint-app/actions',
context: {},
});
return React.createElement(
'div',
{ 'data-testid': styles.container, style: { marginTop: '5%' } },
React.createElement(
UI.HorizontalGroup,
{ align: 'flex-start', justify: 'center' },
React.createElement(
UI.HorizontalGroup,
null,
React.createElement('span', null, 'Hello Grafana! These are the actions you can trigger from this plugin'),
React.createElement(ActionComponent, { extensions: extensions })
)
)
);
}
}
const plugin = new data.AppPlugin().setRootPage(RootComponent);
return { plugin: plugin };
});

@ -1,36 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/grafana/grafana/main/docs/sources/developers/plugins/plugin.schema.json",
"type": "app",
"name": "Extension Point App",
"id": "myorg-extensionpoint-app",
"preload": true,
"info": {
"keywords": ["app"],
"description": "Show case how to add an extension point to your plugin",
"author": {
"name": "Myorg"
},
"logos": {
"small": "img/logo.svg",
"large": "img/logo.svg"
},
"screenshots": [],
"version": "1.0.0",
"updated": "2024-06-11"
},
"includes": [
{
"type": "page",
"name": "Default",
"path": "/a/myorg-extensionpoint-app",
"role": "Admin",
"addToNav": true,
"defaultNav": true
}
],
"dependencies": {
"grafanaDependency": ">=10.3.3",
"plugins": []
},
"extensions": []
}

@ -1,26 +0,0 @@
define(['@grafana/data', 'react'], function (data, React) {
'use strict';
const styles = {
container: 'a-app-body',
};
class RootComponent extends React.PureComponent {
render() {
return React.createElement(
'div',
{ 'data-testid': styles.container, className: 'page-container' },
'Hello Grafana!'
);
}
}
const plugin = new data.AppPlugin().setRootPage(RootComponent).configureExtensionLink({
title: 'Go to A',
description: 'Navigating to plugin A',
extensionPointId: 'plugins/myorg-extensionpoint-app/actions',
path: '/a/myorg-a-app/',
});
return { plugin: plugin };
});

@ -1,45 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/grafana/grafana/main/docs/sources/developers/plugins/plugin.schema.json",
"type": "app",
"name": "A App",
"id": "myorg-a-app",
"preload": true,
"info": {
"keywords": ["app"],
"description": "Will extend root app with ui extensions",
"author": {
"name": "Myorg"
},
"logos": {
"small": "img/logo.svg",
"large": "img/logo.svg"
},
"screenshots": [],
"version": "%VERSION%",
"updated": "%TODAY%"
},
"includes": [
{
"type": "page",
"name": "Default",
"path": "/a/myorg-a-app",
"role": "Admin",
"addToNav": false,
"defaultNav": false
}
],
"dependencies": {
"grafanaDependency": ">=10.3.3",
"plugins": []
},
"generated": {
"extensions": [
{
"extensionPointId": "plugins/myorg-extensionpoint-app/actions",
"title": "Go to A",
"description": "Navigating to pluging A",
"type": "link"
}
]
}
}

@ -1,27 +0,0 @@
define(['react', '@grafana/data'], function (React, data) {
'use strict';
class RootComponent extends React.PureComponent {
render() {
return React.createElement('div', { className: 'page-container' }, 'Hello Grafana!');
}
}
const modalId = 'b-app-modal';
const plugin = new data.AppPlugin().setRootPage(RootComponent).configureExtensionLink({
title: 'Open from B',
description: 'Open a modal from plugin B',
extensionPointId: 'plugins/myorg-extensionpoint-app/actions',
onClick: function (e, { openModal }) {
openModal({
title: 'Modal from app B',
body: function () {
return React.createElement('div', { 'data-testid': modalId }, 'From plugin B');
},
});
},
});
return { plugin: plugin };
});

@ -1,12 +0,0 @@
# App with extensions
This app was initially copied from the [app-with-extensions](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/app-with-extensions) example plugin. The plugin bundle is using AMD, but it's not minified and the plugin feature set is small so it should be possible to make changes in this file if necessary.
To test this app:
```sh
# start e2e test instance (it will install this plugin)
PORT=3000 ./scripts/grafana-server/start-server
# run Playwright tests using Playwright VSCode extension or with the following script
yarn e2e:playwright
```

@ -1,216 +0,0 @@
define(['react', '@grafana/data', '@grafana/ui', '@grafana/runtime', '@emotion/css', 'rxjs'], function (
React,
data,
ui,
runtime,
css,
rxjs
) {
'use strict';
const styles = {
modalBody: 'ape-modal-body',
mainPageContainer: 'ape-main-page-container',
};
class RootComponent extends React.PureComponent {
render() {
return React.createElement(
'div',
{ 'data-testid': styles.mainPageContainer, className: 'page-container' },
'Hello Grafana!'
);
}
}
const asyncWrapper = (fn) => {
return function () {
const gen = fn.apply(this, arguments);
return new Promise((resolve, reject) => {
function step(key, arg) {
let info, value;
try {
info = gen[key](arg);
value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(next, throw_);
}
}
function next(value) {
step('next', value);
}
function throw_(value) {
step('throw', value);
}
next();
});
};
};
const getStyles = (theme) => ({
colorWeak: css.css`color: ${theme.colors.text.secondary};`,
marginTop: css.css`margin-top: ${theme.spacing(3)};`,
});
const updatePlugin = asyncWrapper(function* (pluginId, settings) {
const response = runtime
.getBackendSrv()
.fetch({ url: `/api/plugins/${pluginId}/settings`, method: 'POST', data: settings });
return rxjs.lastValueFrom(response);
});
const handleUpdate = asyncWrapper(function* (pluginId, settings) {
try {
yield updatePlugin(pluginId, settings);
window.location.reload();
} catch (error) {
console.error('Error while updating the plugin', error);
}
});
const configPageBody = ({ plugin }) => {
const styles = getStyles(ui.useStyles2());
const { enabled, jsonData } = plugin.meta;
return React.createElement(
'div',
null,
React.createElement(ui.Legend, null, 'Enable / Disable '),
!enabled &&
React.createElement(
React.Fragment,
null,
React.createElement('div', { className: styles.colorWeak }, 'The plugin is currently not enabled.'),
React.createElement(
ui.Button,
{
className: styles.marginTop,
variant: 'primary',
onClick: () => handleUpdate(plugin.meta.id, { enabled: true, pinned: true, jsonData: jsonData }),
},
'Enable plugin'
)
),
enabled &&
React.createElement(
React.Fragment,
null,
React.createElement('div', { className: styles.colorWeak }, 'The plugin is currently enabled.'),
React.createElement(
ui.Button,
{
className: styles.marginTop,
variant: 'destructive',
onClick: () => handleUpdate(plugin.meta.id, { enabled: false, pinned: false, jsonData: jsonData }),
},
'Disable plugin'
)
)
);
};
const selectQueryModal = ({ targets = [], onDismiss }) => {
const [selectedQuery, setSelectedQuery] = React.useState(targets[0]);
return React.createElement(
'div',
{ 'data-testid': styles.modalBody },
React.createElement(
'p',
null,
'Please select the query you would like to use to create "something" in the plugin.'
),
React.createElement(
ui.HorizontalGroup,
null,
targets.map((query) =>
React.createElement(ui.FilterPill, {
key: query.refId,
label: query.refId,
selected: query.refId === (selectedQuery ? selectedQuery.refId : null),
onClick: () => setSelectedQuery(query),
})
)
),
React.createElement(
ui.Modal.ButtonRow,
null,
React.createElement(ui.Button, { variant: 'secondary', fill: 'outline', onClick: onDismiss }, 'Cancel'),
React.createElement(
ui.Button,
{
disabled: !Boolean(selectedQuery),
onClick: () => {
onDismiss && onDismiss();
alert(`You selected query "${selectedQuery.refId}"`);
},
},
'OK'
)
)
);
};
const plugin = new data.AppPlugin()
.setRootPage(RootComponent)
.addConfigPage({
title: 'Configuration',
icon: 'cog',
body: configPageBody,
id: 'configuration',
})
.configureExtensionLink({
title: 'Open from time series or pie charts (path)',
description: 'This link will only be visible on time series and pie charts',
extensionPointId: data.PluginExtensionPoints.DashboardPanelMenu,
path: `/a/myorg-extensions-app/`,
configure: (context) => {
if (context.dashboard?.title === 'Link Extensions (path)') {
switch (context.pluginId) {
case 'timeseries':
return {};
case 'piechart':
return { title: `Open from ${context.pluginId}` };
default:
return;
}
}
},
})
.configureExtensionLink({
title: 'Open from time series or pie charts (onClick)',
description: 'This link will only be visible on time series and pie charts',
extensionPointId: data.PluginExtensionPoints.DashboardPanelMenu,
onClick: (_, { context, openModal }) => {
const targets = context?.targets || [];
const title = context?.title;
if (!targets.length) return;
if (targets.length > 1) {
openModal({
title: `Select query from "${title}"`,
body: (props) => React.createElement(selectQueryModal, { ...props, targets: targets }),
});
} else {
alert(`You selected query "${targets[0].refId}"`);
}
},
configure: (context) => {
if (context.dashboard?.title === 'Link Extensions (onClick)') {
switch (context.pluginId) {
case 'timeseries':
return {};
case 'piechart':
return { title: `Open from ${context.pluginId}` };
default:
return;
}
}
},
});
return { plugin: plugin };
});

@ -1,49 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/grafana/grafana/main/docs/sources/developers/plugins/plugin.schema.json",
"type": "app",
"name": "Extensions App",
"id": "myorg-extensions-app",
"preload": true,
"info": {
"keywords": ["app"],
"description": "Example on how to extend grafana ui from a plugin",
"author": {
"name": "Myorg"
},
"logos": {
"small": "img/logo.svg",
"large": "img/logo.svg"
},
"screenshots": [],
"version": "1.0.0",
"updated": "2024-06-11"
},
"includes": [
{
"type": "page",
"name": "Default",
"path": "/a/myorg-extensions-app",
"role": "Admin",
"addToNav": true,
"defaultNav": true
}
],
"dependencies": {
"grafanaDependency": ">=10.3.3",
"plugins": []
},
"extensions": [
{
"extensionPointId": "grafana/dashboard/panel/menu",
"type": "link",
"title": "Open from time series or pie charts (path)",
"description": "This link will only be visible on time series and pie charts"
},
{
"extensionPointId": "grafana/dashboard/panel/menu",
"type": "link",
"title": "Open from time series or pie charts (onClick)",
"description": "This link will only be visible on time series and pie charts"
}
]
}

@ -1,35 +0,0 @@
import { test, expect } from '@grafana/plugin-e2e';
const testIds = {
container: 'main-app-body',
actions: {
button: 'action-button',
},
modal: {
container: 'container',
open: 'open-link',
},
appA: {
container: 'a-app-body',
},
appB: {
modal: 'b-app-modal',
},
};
const pluginId = 'myorg-extensionpoint-app';
test('should extend the actions menu with a link to a-app plugin', async ({ page }) => {
await page.goto(`/a/${pluginId}/one`);
await page.getByTestId(testIds.actions.button).click();
await page.getByTestId(testIds.container).getByText('Go to A').click();
await page.getByTestId(testIds.modal.open).click();
await expect(page.getByTestId(testIds.appA.container)).toBeVisible();
});
test('should extend the actions menu with a command triggered from b-app plugin', async ({ page }) => {
await page.goto(`/a/${pluginId}/one`);
await page.getByTestId(testIds.actions.button).click();
await page.getByTestId(testIds.container).getByText('Open from B').click();
await expect(page.getByTestId(testIds.appB.modal)).toBeVisible();
});

@ -1,38 +0,0 @@
import { expect, test } from '@grafana/plugin-e2e';
const panelTitle = 'Link with defaults';
const extensionTitle = 'Open from time series...';
const testIds = {
modal: {
container: 'ape-modal-body',
},
mainPage: {
container: 'ape-main-page-container',
},
};
const linkOnClickDashboardUid = 'dbfb47c5-e5e5-4d28-8ac7-35f349b95946';
const linkPathDashboardUid = 'd1fbb077-cd44-4738-8c8a-d4e66748b719';
test('should add link extension (path) with defaults to time series panel', async ({ gotoDashboardPage, page }) => {
const dashboardPage = await gotoDashboardPage({ uid: linkPathDashboardUid });
const panel = await dashboardPage.getPanelByTitle(panelTitle);
await panel.clickOnMenuItem(extensionTitle, { parentItem: 'Extensions' });
await expect(page.getByTestId(testIds.mainPage.container)).toBeVisible();
});
test('should add link extension (onclick) with defaults to time series panel', async ({ gotoDashboardPage, page }) => {
const dashboardPage = await gotoDashboardPage({ uid: linkOnClickDashboardUid });
const panel = await dashboardPage.getPanelByTitle(panelTitle);
await panel.clickOnMenuItem(extensionTitle, { parentItem: 'Extensions' });
await expect(page.getByRole('dialog')).toContainText('Select query from "Link with defaults"');
});
test('should add link extension (onclick) with new title to pie chart panel', async ({ gotoDashboardPage, page }) => {
const panelTitle = 'Link with new name';
const extensionTitle = 'Open from piechart';
const dashboardPage = await gotoDashboardPage({ uid: linkOnClickDashboardUid });
const panel = await dashboardPage.getPanelByTitle(panelTitle);
await panel.clickOnMenuItem(extensionTitle, { parentItem: 'Extensions' });
await expect(page.getByRole('dialog')).toContainText('Select query from "Link with new name"');
});

@ -1,9 +0,0 @@
import { test, expect } from '@grafana/plugin-e2e';
const pluginId = 'myorg-componentconsumer-app';
const exposedComponentTestId = 'exposed-component';
test('should display component exposed by another app', async ({ page }) => {
await page.goto(`/a/${pluginId}`);
await expect(await page.getByTestId(exposedComponentTestId)).toHaveText('Hello World!');
});

@ -24,9 +24,7 @@ describe('Dashboards', () => {
e2e.components.Panels.Panel.menuItems('Edit').click();
e2e.components.NavToolbar.editDashboard.backToDashboardButton().click();
// And the last panel should still be visible!
// TODO: investigate scroll to on navigating back
// e2e.components.Panels.Panel.title('Panel #50').should('be.visible');
// e2e.components.Panels.Panel.title('Panel #1').should('be.visible');
// The last panel should still be visible!
e2e.components.Panels.Panel.title('Panel #50').should('be.visible');
});
});

@ -0,0 +1,33 @@
# Test plugins
The [e2e test server](https://github.com/grafana/grafana/blob/main/scripts/grafana-server/start-server) automatically scans and looks for plugins in this directory.
### To add a new test plugin:
1. If provisioning is required you may update the YAML config file in [`/devenv`](https://github.com/grafana/grafana/tree/main/devenv).
2. Add the plugin ID to the `allow_loading_unsigned_plugins` setting in the test server's [configuration file](https://github.com/grafana/grafana/blob/main/scripts/grafana-server/custom.ini).
### Building a test plugin with webpack
If you wish to build a test plugin with webpack, you may take a look at how the [grafana-extensionstest-app](./grafana-extensionstest-app/) is wired. A few things to keep in mind:
- the package name needs to be prefixed with `@test-plugins/`
- extend the webpack config from [`@grafana/plugin-configs`](../../packages/grafana-plugin-configs/) and use custom webpack config to only copy the necessary files (see example [here](./grafana-extensionstest-app/webpack.config.ts))
- keep dependency versions in sync with what's in core
#### Local development
1: Install frontend dependencies:
`yarn install --immutable`
2: Build and watch the core frontend
`yarn start`
3: Build and watch the test plugins
`yarn e2e:plugin:build:dev`
4: Build the backend
`make build-go`
5: Start the Grafana e2e test server with the provisioned test plugin
`PORT=3000 ./scripts/grafana-server/start-server`

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save