mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'main' into drclau/unistor/replace-authenticators-3
This commit is contained in:
commit
6993f108a2
@ -208,7 +208,8 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[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.", "1"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "2"],
|
[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": [
|
"packages/grafana-data/src/types/config.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[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": [
|
"packages/grafana-runtime/src/services/pluginExtensions/usePluginComponent.ts:5381": [
|
||||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
[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": [
|
"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.", "0"]
|
||||||
[0, 0, 0, "Do not use any type assertions.", "1"]
|
|
||||||
],
|
],
|
||||||
"packages/grafana-runtime/src/utils/DataSourceWithBackend.ts:5381": [
|
"packages/grafana-runtime/src/utils/DataSourceWithBackend.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[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.", "6"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "7"]
|
[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": [
|
"public/app/features/alerting/unified/utils/rules.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[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.", "1"],
|
||||||
@ -4991,6 +4991,9 @@ exports[`better eslint`] = {
|
|||||||
"public/app/features/plugins/extensions/getPluginExtensions.test.tsx:5381": [
|
"public/app/features/plugins/extensions/getPluginExtensions.test.tsx:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[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": [
|
"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"]
|
[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"],
|
[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": [
|
"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/angular', // will be removed in Grafana 11
|
||||||
'public/app/plugins/panel/graph', // will be removed alongside angular
|
'public/app/plugins/panel/graph', // will be removed alongside angular
|
||||||
'public/app/plugins/panel/table-old', // 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
|
// 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"
|
@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"
|
@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
|
$(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.
|
@# 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"
|
@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.59.1 "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
@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 := $(GOBIN)/jb-v0.5.1
|
||||||
$(JB): $(BINGO_DIR)/jb.mod
|
$(JB): $(BINGO_DIR)/jb.mod
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
|
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.0/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM=
|
||||||
github.com/Antonboom/testifylint v1.3.1 h1:Uam4q1Q+2b6H7gvk9RQFw6jyVDdpzIirFOOrbs14eG4=
|
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.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 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 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
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 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
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/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 h1:OrFlsDdOj9hW/oBEJBNSuH7QWf+E9WPVHw+x52bXVbk=
|
||||||
github.com/Crocmagnon/fatcontext v0.2.2/go.mod h1:WSn/c/+MMNiD8Pri0ahRj0o9jVpeowzavOQplBJw6u0=
|
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 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM=
|
||||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
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 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c=
|
||||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI=
|
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 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
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/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 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM=
|
||||||
github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo=
|
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 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY=
|
||||||
github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ=
|
github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ=
|
||||||
github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA=
|
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/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/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.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 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo=
|
||||||
github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc=
|
github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc=
|
||||||
github.com/daixiang0/gci v0.12.3 h1:yOZI7VAxAGPQmkb1eqt5g/11SUlwoat1fSblGLmdiQc=
|
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/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 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
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/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/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=
|
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.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 h1:CRRLu1JbhK5avLABFJ/OHVSQ0Ie5c4ulsOId1h3TTks=
|
||||||
github.com/golangci/golangci-lint v1.59.1/go.mod h1:jX5Oif4C7P0j9++YB2MMJmoNrb01NJ8ITqKWNLewThg=
|
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 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g=
|
||||||
github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI=
|
github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI=
|
||||||
github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM=
|
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.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 h1:ZK/wE5Kyi1VX3PJpUO2oEgeoI4FWOUm7Shb2Gbv5obI=
|
||||||
github.com/jjti/go-spancheck v0.6.1/go.mod h1:vF1QkOO159prdo6mHRxak2CpzDpHAfKiPUDP/NeRnX8=
|
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/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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
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/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 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE=
|
||||||
github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA=
|
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 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
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/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 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA=
|
||||||
github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI=
|
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-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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
|
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.1/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs=
|
||||||
github.com/polyfloyd/go-errorlint v1.5.2 h1:SJhVik3Umsjh7mte1vE0fVZ5T1gznasQG3PV7U5xFdA=
|
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.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 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.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
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.1/go.mod h1:DGFHzEhi6iJ0oIDfMuo3TgrS+L9gZvrEfmjjuelnRU0=
|
||||||
github.com/ryancurrah/gomodguard v1.3.2 h1:CuG27ulzEB1Gu5Dk5gP8PFxSOZ3ptSdP5iI/3IXxM18=
|
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.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 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=
|
||||||
github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=
|
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=
|
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.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8=
|
||||||
github.com/sashamelentyev/usestdlibvars v1.26.0 h1:LONR2hNVKxRmzIrZR0PhSF3mhCAzvnr+DcUiHgREfXE=
|
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.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 h1:gl5xMkOI0/E6Hxx0XCY2XujA3V7SNSefA8sC+3f1gnk=
|
||||||
github.com/securego/gosec/v2 v2.19.0/go.mod h1:hOkDcHz9J/XIgIlPDXalxjeVYsHxoWUc5zJSHxcB8YM=
|
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=
|
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/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 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak=
|
||||||
github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg=
|
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 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00=
|
||||||
github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo=
|
github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo=
|
||||||
github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=
|
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/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 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
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 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
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=
|
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/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 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI=
|
||||||
github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k=
|
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 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU=
|
||||||
github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg=
|
github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg=
|
||||||
github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
|
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.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 h1:qlGLiqHbN5islOxjeLXoPtUdZXb669RW+BDQ+xOSNoU=
|
||||||
go-simpler.org/sloglint v0.7.1/go.mod h1:OlaVDRh/FKKd4X4sIMbsz8st97vomydceL146Fthh/c=
|
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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
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.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 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
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.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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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.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 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/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.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
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.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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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.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 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs=
|
||||||
honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
|
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 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo=
|
||||||
mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA=
|
mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA=
|
||||||
mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w=
|
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"
|
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"
|
JB="${GOBIN}/jb-v0.5.1"
|
||||||
|
|
||||||
|
294
.drone.yml
294
.drone.yml
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@ node_modules
|
|||||||
/public/lib/monaco
|
/public/lib/monaco
|
||||||
/scripts/grafana-server/tmp
|
/scripts/grafana-server/tmp
|
||||||
vendor
|
vendor
|
||||||
e2e/custom-plugins
|
e2e/test-plugins
|
||||||
playwright-report
|
playwright-report
|
||||||
|
|
||||||
# TS generate from cue by cuetsy
|
# TS generate from cue by cuetsy
|
||||||
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -209,6 +209,7 @@
|
|||||||
/devenv/docker/blocks/postgres_tests/ @grafana/oss-big-tent
|
/devenv/docker/blocks/postgres_tests/ @grafana/oss-big-tent
|
||||||
/devenv/docker/blocks/prometheus/ @grafana/observability-metrics
|
/devenv/docker/blocks/prometheus/ @grafana/observability-metrics
|
||||||
/devenv/docker/blocks/prometheus_random_data/ @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/pyroscope/ @grafana/observability-traces-and-profiling
|
||||||
/devenv/docker/blocks/redis/ @bergquist
|
/devenv/docker/blocks/redis/ @bergquist
|
||||||
/devenv/docker/blocks/sensugo/ @grafana/grafana-backend-group
|
/devenv/docker/blocks/sensugo/ @grafana/grafana-backend-group
|
||||||
@ -319,6 +320,7 @@
|
|||||||
/e2e/ @grafana/grafana-frontend-platform
|
/e2e/ @grafana/grafana-frontend-platform
|
||||||
/e2e/cloud-plugins-suite/ @grafana/partner-datasources
|
/e2e/cloud-plugins-suite/ @grafana/partner-datasources
|
||||||
/e2e/plugin-e2e/plugin-e2e-api-tests/ @grafana/plugins-platform-frontend
|
/e2e/plugin-e2e/plugin-e2e-api-tests/ @grafana/plugins-platform-frontend
|
||||||
|
/e2e/test-plugins/grafana-extensionstest-app/ @grafana/plugins-platform-frontend
|
||||||
|
|
||||||
# Packages
|
# Packages
|
||||||
/packages/ @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend
|
/packages/ @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend
|
||||||
|
@ -141,6 +141,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
|
workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
|
||||||
service_account: ${{ secrets.LEVITATE_SA }}
|
service_account: ${{ secrets.LEVITATE_SA }}
|
||||||
|
project_id: 'grafanalabs-global'
|
||||||
|
|
||||||
- name: 'Set up Cloud SDK'
|
- name: 'Set up Cloud SDK'
|
||||||
uses: 'google-github-actions/setup-gcloud@v2'
|
uses: 'google-github-actions/setup-gcloud@v2'
|
||||||
@ -149,16 +150,6 @@ jobs:
|
|||||||
project_id: 'grafanalabs-global'
|
project_id: 'grafanalabs-global'
|
||||||
install_components: 'bq'
|
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
|
- name: Get link for the Github Action job
|
||||||
id: job
|
id: job
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
|
2
.github/workflows/go_lint.yml
vendored
2
.github/workflows/go_lint.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: v1.59.1
|
version: v1.60.1
|
||||||
args: |
|
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
|
skip-cache: true
|
||||||
|
2
.github/workflows/pr-go-workspace-check.yml
vendored
2
.github/workflows/pr-go-workspace-check.yml
vendored
@ -31,3 +31,5 @@ jobs:
|
|||||||
echo "If there is a change in enterprise dependencies, please update pkg/extensions/main.go."
|
echo "If there is a change in enterprise dependencies, please update pkg/extensions/main.go."
|
||||||
exit 1
|
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/**/*"
|
"**/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]
|
[linters-settings.gocritic]
|
||||||
enabled-checks = ["ruleguard"]
|
enabled-checks = ["ruleguard"]
|
||||||
[linters-settings.gocritic.settings.ruleguard]
|
[linters-settings.gocritic.settings.ruleguard]
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
ARG BASE_IMAGE=alpine:3.19.1
|
ARG BASE_IMAGE=alpine:3.19.1
|
||||||
ARG JS_IMAGE=node:20-alpine
|
ARG JS_IMAGE=node:20-alpine
|
||||||
ARG JS_PLATFORM=linux/amd64
|
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 GO_SRC=go-builder
|
||||||
ARG JS_SRC=js-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/build/wire/go.* pkg/build/wire/
|
||||||
COPY pkg/promlib/go.* pkg/promlib/
|
COPY pkg/promlib/go.* pkg/promlib/
|
||||||
COPY pkg/storage/unified/resource/go.* pkg/storage/unified/resource/
|
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/semconv/go.* pkg/semconv/
|
||||||
COPY pkg/aggregator/go.* pkg/aggregator/
|
COPY pkg/aggregator/go.* pkg/aggregator/
|
||||||
|
|
||||||
|
16
Makefile
16
Makefile
@ -8,7 +8,7 @@ WIRE_TAGS = "oss"
|
|||||||
include .bingo/Variables.mk
|
include .bingo/Variables.mk
|
||||||
|
|
||||||
GO = go
|
GO = go
|
||||||
GO_VERSION = 1.22.4
|
GO_VERSION = 1.23.0
|
||||||
GO_LINT_FILES ?= $(shell ./scripts/go-workspace/golangci-lint-includes.sh)
|
GO_LINT_FILES ?= $(shell ./scripts/go-workspace/golangci-lint-includes.sh)
|
||||||
GO_TEST_FILES ?= $(shell ./scripts/go-workspace/test-includes.sh)
|
GO_TEST_FILES ?= $(shell ./scripts/go-workspace/test-includes.sh)
|
||||||
SH_FILES ?= $(shell find ./scripts -name *.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 \
|
printf '$(GO_TEST_FILES)' | xargs \
|
||||||
$(GO) test $(GO_RACE_FLAG) -short -covermode=atomic -timeout=30m
|
$(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
|
.PHONY: test-go-integration
|
||||||
test-go-integration: ## Run integration tests for backend with flags.
|
test-go-integration: ## Run integration tests for backend with flags.
|
||||||
@echo "test backend integration tests"
|
@echo "test backend integration tests"
|
||||||
@ -431,6 +439,12 @@ go-race-is-enabled:
|
|||||||
enable-go-race:
|
enable-go-race:
|
||||||
@touch .go-race-enabled-locally
|
@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
|
.PHONY: help
|
||||||
help: ## Display this 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)
|
@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 =
|
hide_angular_deprecation =
|
||||||
# Comma separated list of plugin ids for which environment variables should be forwarded. Used only when feature flag pluginsSkipHostEnvVars is enabled.
|
# 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 =
|
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 ##########################################
|
#################################### Grafana Live ##########################################
|
||||||
[live]
|
[live]
|
||||||
|
@ -2,16 +2,14 @@
|
|||||||
|
|
||||||
<!-- Keep terms in alphabetical order: -->
|
<!-- Keep terms in alphabetical order: -->
|
||||||
|
|
||||||
This document defines technical terms used in Grafana.
|
This glossary defines technical terms used in Grafana.
|
||||||
|
|
||||||
## TLS/SSL
|
## TLS/SSL
|
||||||
|
|
||||||
The acronyms [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security) (Transport Layer Security) and
|
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,
|
[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
|
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).
|
[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.
|
As such, while we use both terms in our codebase and documentation, we generally prefer TLS.
|
||||||
That said however, we have at Grafana Labs decided to use both acronyms in combination when referring to this type of
|
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.
|
||||||
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.
|
|
||||||
|
@ -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.
|
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.
|
||||||
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. 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
|
```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
|
## 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 script above assumes you have Grafana running on `localhost:3000`. You may change this by providing environment variables.
|
The `yarn e2e:playwright` script assumes you have Grafana running on `localhost:3000`. You may change this with an environment variable:
|
||||||
|
|
||||||
`HOST=127.0.0.1 PORT=3001 yarn e2e:playwright`
|
`HOST=127.0.0.1 PORT=3001 yarn e2e:playwright`
|
||||||
|
|
||||||
- `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.
|
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.
|
||||||
|
|
||||||
|
- 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.
|
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 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.
|
- 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).
|
- 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/).
|
- [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
|
## 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
|
- **`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.
|
- **`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 `Selectors` but without `visit` function
|
- **`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 `Pages` that can be reused and shared between tests
|
- **`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
|
## 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" />
|
<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
|
```jsx
|
||||||
<input data-testid="Username input field" className="gf-form-input login-form-input" type="text" />
|
<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
|
```typescript
|
||||||
export const Login = {
|
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
|
```typescript
|
||||||
export const Pages = {
|
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
|
```jsx
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
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 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).
|
- 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).
|
||||||
- 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).
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
describe('Login test', () => {
|
describe('Login test', () => {
|
||||||
@ -83,7 +86,7 @@ describe('Login test', () => {
|
|||||||
|
|
||||||
## Advanced example
|
## 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
|
```jsx
|
||||||
<ul>
|
<ul>
|
||||||
@ -97,7 +100,7 @@ Let's take a look at an example that uses the same `selector` for multiple items
|
|||||||
</ul>
|
</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
|
```typescript
|
||||||
export const DataSources = {
|
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
|
```jsx
|
||||||
<ul>
|
<ul>
|
||||||
@ -126,7 +129,7 @@ The next step is to use the `dataSources` selector function as in our example be
|
|||||||
</ul>
|
</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
|
```html
|
||||||
<div class="card-item-name" data-testid="data-testid Data source list item A">A</div>
|
<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>
|
<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
|
```typescript
|
||||||
describe('List test', () => {
|
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>
|
<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>
|
<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.
|
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>
|
<button data-testid="modal-close-button">Close<button>
|
||||||
```
|
```
|
||||||
|
|
||||||
We have added support for this in our selectors, to use:
|
We have added support for data attributes in our selectors Prefix your selector string with `data-testid`:
|
||||||
|
|
||||||
Prefix your selector string with "data-testid":
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export const Components = {
|
export const Components = {
|
||||||
Login: {
|
Login: {
|
||||||
openButton: 'open-button', // this would look for an aria-label
|
openButton: 'open-button', // this looks for an aria-label
|
||||||
closeButton: 'data-testid modal-close-button', // this would look for a data-testid
|
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}>
|
<button data-testid={Selectors.Components.Login.closeButton}>
|
||||||
|
@ -1,41 +1,6 @@
|
|||||||
# Frontend Style Guide
|
# Frontend style guide
|
||||||
|
|
||||||
Generally we follow the Airbnb [React Style Guide](https://github.com/airbnb/javascript/tree/master/react).
|
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.
|
||||||
|
|
||||||
## 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)
|
|
||||||
|
|
||||||
## Basic rules
|
## 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.
|
- Break large components up into sub-components.
|
||||||
- Use spaces for indentation.
|
- Use spaces for indentation.
|
||||||
|
|
||||||
### Naming conventions
|
## Naming conventions
|
||||||
|
|
||||||
#### Use `PascalCase` for:
|
Follow these guidelines when naming elements of your code.
|
||||||
|
|
||||||
##### Typescript class names
|
### Class names
|
||||||
|
|
||||||
|
Use PascalCase. For example:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// bad
|
// bad
|
||||||
@ -61,7 +28,94 @@ class DataLink {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Types and interfaces
|
### Constants
|
||||||
|
|
||||||
|
Use ALL CAPS for constants. For example:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// bad
|
||||||
|
const constantValue = "This string won't change";
|
||||||
|
// bad
|
||||||
|
const constant_value = "This string won't change";
|
||||||
|
|
||||||
|
// good
|
||||||
|
const CONSTANT_VALUE = "This string won't change";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Emotion class names
|
||||||
|
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
enum buttonVariant {
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
|
||||||
|
// good
|
||||||
|
enum ButtonVariant {
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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).
|
||||||
|
|
||||||
|
- Use `features/new-important-feature/utils.ts`
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
|
Use PascalCase. For example:
|
||||||
|
|
||||||
|
Use camelCase.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// bad
|
||||||
|
const CalculatePercentage = () => { ... }
|
||||||
|
// bad
|
||||||
|
const calculate_percentage = () => { ... }
|
||||||
|
|
||||||
|
// good
|
||||||
|
const calculatePercentage = () => { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interfaces
|
||||||
|
|
||||||
|
Use PascalCase. For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
// bad
|
// bad
|
||||||
@ -91,35 +145,11 @@ type request_info = ...
|
|||||||
type RequestInfo = ...
|
type RequestInfo = ...
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Enums
|
### Methods
|
||||||
|
|
||||||
```
|
Use PascalCase. For example:
|
||||||
// bad
|
|
||||||
enum buttonVariant {
|
|
||||||
//...
|
|
||||||
}
|
|
||||||
|
|
||||||
// good
|
Use camelCase.
|
||||||
enum ButtonVariant {
|
|
||||||
//...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Use `camelCase` for:
|
|
||||||
|
|
||||||
##### Functions
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// bad
|
|
||||||
const CalculatePercentage = () => { ... }
|
|
||||||
// bad
|
|
||||||
const calculate_percentage = () => { ... }
|
|
||||||
|
|
||||||
// good
|
|
||||||
const calculatePercentage = () => { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Methods
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class DateCalculator {
|
class DateCalculator {
|
||||||
@ -137,164 +167,13 @@ class DateCalculator {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Variables
|
### React components
|
||||||
|
|
||||||
```typescript
|
Follow these guidelines for naming React components.
|
||||||
// bad
|
|
||||||
const QueryTargets = [];
|
|
||||||
// bad
|
|
||||||
const query_targets = [];
|
|
||||||
|
|
||||||
// good
|
#### React callback props and handlers
|
||||||
const queryTargets = [];
|
|
||||||
```
|
|
||||||
|
|
||||||
##### React state and properties
|
Name callback props and handlers with an _on_ prefix. For example:
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface ModalState {
|
|
||||||
// bad
|
|
||||||
IsActive: boolean;
|
|
||||||
// bad
|
|
||||||
is_active: boolean;
|
|
||||||
|
|
||||||
// good
|
|
||||||
isActive: boolean;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Emotion class names
|
|
||||||
|
|
||||||
```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,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
#### Use `ALL_CAPS` for constants.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// bad
|
|
||||||
const constantValue = "This string won't change";
|
|
||||||
// bad
|
|
||||||
const constant_value = "This string won't change";
|
|
||||||
|
|
||||||
// good
|
|
||||||
const CONSTANT_VALUE = "This string won't change";
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Use [BEM](http://getbem.com/) convention for SASS styles.
|
|
||||||
|
|
||||||
_SASS styles are deprecated. Please migrate to Emotion whenever you need to modify SASS styles._
|
|
||||||
|
|
||||||
### Typing
|
|
||||||
|
|
||||||
In general, you should let Typescript infer the types so that there's no need to explicitly define 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
|
|
||||||
|
|
||||||
// bad
|
|
||||||
const stringArray = [];
|
|
||||||
|
|
||||||
// good
|
|
||||||
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.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// bad
|
|
||||||
function transform(value?: string) {
|
|
||||||
if (!value) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return applyTransform(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// good
|
|
||||||
function transform(value?: string): TransformedValue | undefined {
|
|
||||||
if (!value) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return applyTransform(value);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### File and directory naming conventions
|
|
||||||
|
|
||||||
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 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 `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.
|
|
||||||
|
|
||||||
- Use kebab case for directory names: lowercase, words delimited by hyphen ( `-` ). For example, `features/new-important-feature/utils.ts`.
|
|
||||||
|
|
||||||
### 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 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.
|
|
||||||
- Component SASS styles should live in the same folder as component code.
|
|
||||||
|
|
||||||
For code that needs to be used by 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
|
|
||||||
|
|
||||||
- Use named exports for all code you want to export from a file.
|
|
||||||
- Use declaration exports (i.e. `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
|
|
||||||
|
|
||||||
- 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.
|
|
||||||
- Please try to follow the [code comment guidelines](./code-comments.md) when adding comments.
|
|
||||||
|
|
||||||
### 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
|
```tsx
|
||||||
// bad
|
// bad
|
||||||
@ -321,17 +200,9 @@ render() {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
##### React Component definitions
|
#### React component constructor
|
||||||
|
|
||||||
```jsx
|
Use the following convention when implementing these React components:
|
||||||
// bad
|
|
||||||
export class YourClass extends PureComponent { ... }
|
|
||||||
|
|
||||||
// good
|
|
||||||
export class YourClass extends PureComponent<{},{}> { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
##### React Component constructor
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// bad
|
// bad
|
||||||
@ -341,7 +212,9 @@ constructor(props) {...}
|
|||||||
constructor(props: Props) {...}
|
constructor(props: Props) {...}
|
||||||
```
|
```
|
||||||
|
|
||||||
##### React Component defaultProps
|
#### React component defaultProps
|
||||||
|
|
||||||
|
Use the following convention when implementing these React components:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// bad
|
// bad
|
||||||
@ -351,9 +224,131 @@ static defaultProps = { ... }
|
|||||||
static defaultProps: Partial<Props> = { ... }
|
static defaultProps: Partial<Props> = { ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
### How to declare functional components
|
#### React component definitions
|
||||||
|
|
||||||
We prefer using function declarations over function expressions when creating a new react functional component.
|
Use the following convention when implementing these React components:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// bad
|
||||||
|
export class YourClass extends PureComponent { ... }
|
||||||
|
|
||||||
|
// good
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SASS
|
||||||
|
|
||||||
|
SASS styles are deprecated. You should migrate to Emotion whenever you need to modify SASS styles.
|
||||||
|
|
||||||
|
### Types
|
||||||
|
|
||||||
|
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 the type of arrays or objects; otherwise, it infers type as an array of any
|
||||||
|
|
||||||
|
// bad
|
||||||
|
const stringArray = [];
|
||||||
|
|
||||||
|
// good
|
||||||
|
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:** Linting is not enabled for this issue because there is old code that needs to be fixed first.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// bad
|
||||||
|
function transform(value?: string) {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return applyTransform(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// good
|
||||||
|
function transform(value?: string): TransformedValue | undefined {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return applyTransform(value);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variables
|
||||||
|
|
||||||
|
Use PascalCase. For example:
|
||||||
|
|
||||||
|
Use camelCase.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// bad
|
||||||
|
const QueryTargets = [];
|
||||||
|
// bad
|
||||||
|
const query_targets = [];
|
||||||
|
|
||||||
|
// good
|
||||||
|
const queryTargets = [];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code organization
|
||||||
|
|
||||||
|
Organize your code in a directory that encloses feature code:
|
||||||
|
|
||||||
|
- 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 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 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
|
||||||
|
|
||||||
|
- Use named exports for all code you want to export from a file.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
- Please try to follow the [code comment guidelines](./code-comments.md) when adding comments.
|
||||||
|
|
||||||
|
## Linting
|
||||||
|
|
||||||
|
Linting is performed using [@grafana/eslint-config](https://github.com/grafana/eslint-config-grafana).
|
||||||
|
|
||||||
|
## Functional components
|
||||||
|
|
||||||
|
Use function declarations instead of function expressions when creating a new React functional component. For example:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// bad
|
// bad
|
||||||
@ -366,13 +361,6 @@ export const Component: React.FC<Props> = (props) => { ... }
|
|||||||
export function Component(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
|
## State management
|
||||||
|
|
||||||
- Don't mutate state in reducers or thunks.
|
- 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.
|
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
|
## Test functionality
|
||||||
|
|
||||||
|
Here's how to test the functioning of your Redux reducers.
|
||||||
|
|
||||||
### reducerTester
|
### reducerTester
|
||||||
|
|
||||||
Fluent API that simplifies the testing of reducers
|
Use the Fluent API framework to simplify the testing of reducers.
|
||||||
|
|
||||||
#### Usage
|
#### Usage
|
||||||
|
|
||||||
|
Example of `reducerTester` in use:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
reducerTester()
|
reducerTester()
|
||||||
.givenReducer(someReducer, initialState)
|
.givenReducer(someReducer, initialState)
|
||||||
@ -21,9 +25,9 @@ reducerTester()
|
|||||||
|
|
||||||
#### Complex usage
|
#### 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
|
```typescript
|
||||||
reducerTester()
|
reducerTester()
|
||||||
@ -37,10 +41,12 @@ reducerTester()
|
|||||||
|
|
||||||
### thunkTester
|
### thunkTester
|
||||||
|
|
||||||
Fluent API that simplifies the testing of thunks.
|
Here's a Fluent API function that simplifies the testing of thunks.
|
||||||
|
|
||||||
#### Usage
|
#### Usage
|
||||||
|
|
||||||
|
Example of `thunkTester` in use:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const dispatchedActions = await thunkTester(initialState).givenThunk(someThunk).whenThunkIsDispatched(arg1, arg2, arg3);
|
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
|
## 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
|
```typescript
|
||||||
import { connect, ConnectedProps } from 'react-redux';
|
import { connect, ConnectedProps } from 'react-redux';
|
||||||
@ -80,4 +86,4 @@ class PanelEditorUnconnected extends PureComponent<Props> {}
|
|||||||
export const PanelEditor = connector(PanelEditorUnconnected);
|
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
|
||||||
|
|
||||||
[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:
|
Storybook is:
|
||||||
|
|
||||||
@ -10,16 +12,22 @@ Storybook is:
|
|||||||
|
|
||||||
## How to create stories
|
## 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
|
### 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:
|
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
|
- **`title`**: Where the component is going to live in the hierarchy
|
||||||
- `decorators`: A list which can contain wrappers or provide context, such as theming
|
- **`decorators`**: A list which can contain wrappers or provide context, such as theming
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
// In MyComponent.story.tsx
|
// 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
|
```jsx
|
||||||
// Will produce a story name “some story”
|
// Will produce a story name “some story”
|
||||||
export const someStory = () => <MyComponent />;
|
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
|
### Categorization
|
||||||
|
|
||||||
We currently have these categories:
|
We have these categories of components:
|
||||||
|
|
||||||
- **Docs Overview** - Guidelines and information regarding the design system
|
- **Docs Overview** - Guidelines and information regarding the design system
|
||||||
- **Forms** - Components commonly used in forms such as different kind of inputs
|
- **Forms** - Components commonly used in forms such as different kind of inputs
|
||||||
@ -55,7 +63,7 @@ We currently have these categories:
|
|||||||
|
|
||||||
## Writing MDX documentation
|
## 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
|
### Link the MDX file to a component’s stories
|
||||||
|
|
||||||
@ -83,7 +91,7 @@ export default {
|
|||||||
|
|
||||||
### MDX file structure
|
### 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
|
- When and why the component should be used
|
||||||
- Best practices - dos and don’ts for the component
|
- 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
|
### 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`
|
- 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:
|
- 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
|
```jsx
|
||||||
<Meta title="Docs Overview/Color Palettes" parameters={{ options: { isToolshown: false }}}/>
|
<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
|
## 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
|
### 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
|
```jsx
|
||||||
interface MyProps {
|
interface MyProps {
|
||||||
@ -143,25 +153,28 @@ interface MyProps {
|
|||||||
|
|
||||||
### Controls
|
### 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
|
#### 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.
|
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`
|
`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'`
|
`import { Props } from './Component'`
|
||||||
|
|
||||||
4. Add the Story type to all stories in the file, then replace the props sent to the component
|
4. Add the `Story` type to all stories in the file, then replace the props sent to the component and remove any knobs:
|
||||||
and remove any knobs.
|
|
||||||
|
|
||||||
Before
|
Before:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
export const Simple = () => {
|
export const Simple = () => {
|
||||||
@ -172,7 +185,7 @@ As a test, we migrated the [button story](https://github.com/grafana/grafana/blo
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
After
|
After:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
export const Simple: Story<Props> = ({ prop1, prop2 }) => {
|
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
|
```tsx
|
||||||
Simple.args = {
|
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
|
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:
|
||||||
specify these in an `argTypes`. This is done in the default export of the story.
|
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
export default {
|
export default {
|
||||||
@ -204,6 +216,6 @@ As a test, we migrated the [button story](https://github.com/grafana/grafana/blo
|
|||||||
|
|
||||||
## Best practices
|
## 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.
|
- 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
|
# 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
|
## Usage
|
||||||
|
|
||||||
@ -8,9 +8,9 @@ For styling components, use [Emotion's `css` function](https://emotion.sh/docs/e
|
|||||||
|
|
||||||
### Basic styling
|
### 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
|
```tsx
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
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
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
```tsx
|
||||||
import { css, cx } from '@emotion/css';
|
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.
|
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
|
## 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`:
|
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.
|
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
|
```tsx
|
||||||
import { render, screen } from '@testing-library/react';
|
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:
|
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.
|
- [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 which queries work with specific HTML elements.
|
- [Testing Playground](https://testing-playground.com/) - An interactive sandbox that allows testing of 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.
|
- [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.
|
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
|
### 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
|
```tsx
|
||||||
describe('OrgRolePicker', () => {
|
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.
|
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
|
```tsx
|
||||||
let windowSpy: jest.SpyInstance;
|
let windowSpy: jest.SpyInstance;
|
||||||
@ -156,7 +157,7 @@ it('should test with window', function () {
|
|||||||
|
|
||||||
### Mocking getBackendSrv()
|
### 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
|
```tsx
|
||||||
jest.mock('@grafana/runtime', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
@ -169,9 +170,9 @@ jest.mock('@grafana/runtime', () => ({
|
|||||||
|
|
||||||
#### Mocking getBackendSrv for AsyncSelect
|
#### 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
|
```tsx
|
||||||
import { screen, render } from '@testing-library/react';
|
import { screen, render } from '@testing-library/react';
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
"editable": true,
|
"editable": true,
|
||||||
"fiscalYearStartMonth": 0,
|
"fiscalYearStartMonth": 0,
|
||||||
"graphTooltip": 0,
|
"graphTooltip": 0,
|
||||||
"id": 508,
|
|
||||||
"links": [],
|
"links": [],
|
||||||
"liveNow": false,
|
"liveNow": false,
|
||||||
"panels": [
|
"panels": [
|
||||||
@ -88,6 +87,7 @@
|
|||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -102,7 +102,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__house_locations",
|
"alias": "__house_locations",
|
||||||
@ -174,6 +174,7 @@
|
|||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -188,7 +189,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__house_locations",
|
"alias": "__house_locations",
|
||||||
@ -260,6 +261,7 @@
|
|||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -274,7 +276,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__house_locations",
|
"alias": "__house_locations",
|
||||||
@ -346,6 +348,7 @@
|
|||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "horizontal",
|
"orientation": "horizontal",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -360,7 +363,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__server_names",
|
"alias": "__server_names",
|
||||||
@ -431,6 +434,7 @@
|
|||||||
"graphMode": "line",
|
"graphMode": "line",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -445,7 +449,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"datasource": {
|
"datasource": {
|
||||||
@ -560,6 +564,7 @@
|
|||||||
"graphMode": "line",
|
"graphMode": "line",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "horizontal",
|
"orientation": "horizontal",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -574,7 +579,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"datasource": {
|
"datasource": {
|
||||||
@ -691,6 +696,7 @@
|
|||||||
"graphMode": "none",
|
"graphMode": "none",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "horizontal",
|
"orientation": "horizontal",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -705,7 +711,7 @@
|
|||||||
"textMode": "name",
|
"textMode": "name",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__server_names",
|
"alias": "__server_names",
|
||||||
@ -781,6 +787,7 @@
|
|||||||
"graphMode": "none",
|
"graphMode": "none",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -795,7 +802,7 @@
|
|||||||
"textMode": "value",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__server_names",
|
"alias": "__server_names",
|
||||||
@ -871,6 +878,7 @@
|
|||||||
"graphMode": "none",
|
"graphMode": "none",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -885,7 +893,7 @@
|
|||||||
"textMode": "none",
|
"textMode": "none",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__server_names",
|
"alias": "__server_names",
|
||||||
@ -972,6 +980,7 @@
|
|||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -986,7 +995,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__house_locations",
|
"alias": "__house_locations",
|
||||||
@ -1058,6 +1067,7 @@
|
|||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -1072,7 +1082,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__house_locations",
|
"alias": "__house_locations",
|
||||||
@ -1144,6 +1154,7 @@
|
|||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -1158,7 +1169,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__house_locations",
|
"alias": "__house_locations",
|
||||||
@ -1230,6 +1241,7 @@
|
|||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "horizontal",
|
"orientation": "horizontal",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -1244,7 +1256,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__server_names",
|
"alias": "__server_names",
|
||||||
@ -1315,6 +1327,7 @@
|
|||||||
"graphMode": "line",
|
"graphMode": "line",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -1329,7 +1342,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"datasource": {
|
"datasource": {
|
||||||
@ -1444,6 +1457,7 @@
|
|||||||
"graphMode": "line",
|
"graphMode": "line",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "horizontal",
|
"orientation": "horizontal",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -1458,7 +1472,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"datasource": {
|
"datasource": {
|
||||||
@ -1575,6 +1589,7 @@
|
|||||||
"graphMode": "none",
|
"graphMode": "none",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "horizontal",
|
"orientation": "horizontal",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -1589,7 +1604,7 @@
|
|||||||
"textMode": "name",
|
"textMode": "name",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__server_names",
|
"alias": "__server_names",
|
||||||
@ -1665,6 +1680,7 @@
|
|||||||
"graphMode": "none",
|
"graphMode": "none",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -1679,7 +1695,7 @@
|
|||||||
"textMode": "value",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__server_names",
|
"alias": "__server_names",
|
||||||
@ -1755,6 +1771,7 @@
|
|||||||
"graphMode": "none",
|
"graphMode": "none",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"mean"
|
"mean"
|
||||||
@ -1769,7 +1786,7 @@
|
|||||||
"textMode": "none",
|
"textMode": "none",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "__server_names",
|
"alias": "__server_names",
|
||||||
@ -1819,7 +1836,7 @@
|
|||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 8,
|
"h": 8,
|
||||||
"w": 8,
|
"w": 4,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 78
|
"y": 78
|
||||||
},
|
},
|
||||||
@ -1829,6 +1846,7 @@
|
|||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"lastNotNull"
|
"lastNotNull"
|
||||||
@ -1840,7 +1858,7 @@
|
|||||||
"textMode": "value",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"csvContent": "time, value\n2023-12-13T00:00:00Z, 0\n2023-12-13T00:00:00Z, 100",
|
"csvContent": "time, value\n2023-12-13T00:00:00Z, 0\n2023-12-13T00:00:00Z, 100",
|
||||||
@ -1855,6 +1873,73 @@
|
|||||||
"title": "Infinity Percent Change",
|
"title": "Infinity Percent Change",
|
||||||
"type": "stat"
|
"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": {
|
"datasource": {
|
||||||
"type": "grafana-testdata-datasource",
|
"type": "grafana-testdata-datasource",
|
||||||
@ -1894,6 +1979,7 @@
|
|||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"lastNotNull"
|
"lastNotNull"
|
||||||
@ -1905,7 +1991,7 @@
|
|||||||
"textMode": "value",
|
"textMode": "value",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"csvContent": "time, value\n2023-12-13T00:00:00Z, 0\n2023-12-13T00:00:00Z, 0",
|
"csvContent": "time, value\n2023-12-13T00:00:00Z, 0\n2023-12-13T00:00:00Z, 0",
|
||||||
@ -1959,6 +2045,7 @@
|
|||||||
"graphMode": "area",
|
"graphMode": "area",
|
||||||
"justifyMode": "auto",
|
"justifyMode": "auto",
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
"reduceOptions": {
|
"reduceOptions": {
|
||||||
"calcs": [
|
"calcs": [
|
||||||
"lastNotNull"
|
"lastNotNull"
|
||||||
@ -1970,7 +2057,7 @@
|
|||||||
"textMode": "auto",
|
"textMode": "auto",
|
||||||
"wideLayout": true
|
"wideLayout": true
|
||||||
},
|
},
|
||||||
"pluginVersion": "10.3.0-pre",
|
"pluginVersion": "11.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"csvContent": "Name, value\nName1, 10\nName2, 20",
|
"csvContent": "Name, value\nName1, 10\nName2, 20",
|
||||||
@ -2017,6 +2104,6 @@
|
|||||||
"timezone": "",
|
"timezone": "",
|
||||||
"title": "Panel Tests - Stat",
|
"title": "Panel Tests - Stat",
|
||||||
"uid": "EJ8_d9jZk",
|
"uid": "EJ8_d9jZk",
|
||||||
"version": 11,
|
"version": 15,
|
||||||
"weekStart": ""
|
"weekStart": ""
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
mimir_backend:
|
mimir_backend:
|
||||||
image: grafana/mimir-alpine:r295-a23e559
|
image: grafana/mimir-alpine:r304-3872ccb
|
||||||
container_name: mimir_backend
|
container_name: mimir_backend
|
||||||
command:
|
command:
|
||||||
- -target=backend
|
- -target=backend
|
||||||
|
@ -37,3 +37,8 @@
|
|||||||
build: docker/blocks/prometheus_random_data
|
build: docker/blocks/prometheus_random_data
|
||||||
ports:
|
ports:
|
||||||
- "8081:8080"
|
- "8081:8080"
|
||||||
|
|
||||||
|
prometheus-high-card:
|
||||||
|
build: docker/blocks/prometheus_high_card
|
||||||
|
ports:
|
||||||
|
- "9111:9111"
|
||||||
|
@ -38,6 +38,10 @@ scrape_configs:
|
|||||||
static_configs:
|
static_configs:
|
||||||
- targets: ['prometheus-random-data:8080']
|
- targets: ['prometheus-random-data:8080']
|
||||||
|
|
||||||
|
- job_name: 'prometheus-high-card'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['prometheus-high-card:9111']
|
||||||
|
|
||||||
- job_name: 'mysql'
|
- job_name: 'mysql'
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ['mysql-exporter:9104']
|
- targets: ['mysql-exporter:9104']
|
||||||
|
15
devenv/docker/blocks/prometheus_high_card/Dockerfile
Normal file
15
devenv/docker/blocks/prometheus_high_card/Dockerfile
Normal file
@ -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"
|
20
devenv/docker/blocks/prometheus_high_card/go.mod
Normal file
20
devenv/docker/blocks/prometheus_high_card/go.mod
Normal file
@ -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
|
||||||
|
)
|
26
devenv/docker/blocks/prometheus_high_card/go.sum
Normal file
26
devenv/docker/blocks/prometheus_high_card/go.sum
Normal file
@ -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=
|
123
devenv/docker/blocks/prometheus_high_card/main.go
Normal file
123
devenv/docker/blocks/prometheus_high_card/main.go
Normal file
@ -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
|
apiVersion: 1
|
||||||
|
|
||||||
apps:
|
apps:
|
||||||
- type: myorg-extensions-app
|
- type: grafana-extensionstest-app
|
||||||
org_id: 1
|
org_id: 1
|
||||||
org_name: Main Org.
|
org_name: Main Org.
|
||||||
disabled: false
|
disabled: false
|
||||||
- type: myorg-a-app
|
jsonData:
|
||||||
|
apiUrl: http://default-url.com
|
||||||
|
secureJsonData:
|
||||||
|
apiKey: secret-key
|
||||||
|
- type: grafana-extensionexample1-app
|
||||||
org_id: 1
|
org_id: 1
|
||||||
org_name: Main Org.
|
org_name: Main Org.
|
||||||
disabled: false
|
disabled: false
|
||||||
- type: myorg-b-app
|
- type: grafana-extensionexample2-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
|
|
||||||
org_id: 1
|
org_id: 1
|
||||||
org_name: Main Org.
|
org_name: Main Org.
|
||||||
disabled: false
|
disabled: false
|
||||||
|
@ -6,6 +6,15 @@
|
|||||||
# [Semantic versioning](https://semver.org/) is used to help the reader identify the significance of changes.
|
# [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.
|
# 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)
|
# ## 8.0.1 (2024-07-01)
|
||||||
#
|
#
|
||||||
# ### Fixed
|
# ### Fixed
|
||||||
@ -727,6 +736,9 @@ POSIX_HERESTRING
|
|||||||
|
|
||||||
_repo="$(repo_path website)"
|
_repo="$(repo_path website)"
|
||||||
volumes="--volume=${_repo}/config:/hugo/config:z"
|
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}/layouts:/hugo/layouts:z"
|
||||||
volumes="${volumes} --volume=${_repo}/scripts:/hugo/scripts:z"
|
volumes="${volumes} --volume=${_repo}/scripts:/hugo/scripts:z"
|
||||||
fi
|
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.
|
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
|
### 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.
|
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 |
|
| token | yes |
|
||||||
|
|
||||||
|
#### Alert notification `MQTT`
|
||||||
|
|
||||||
|
| Name | Secure setting |
|
||||||
|
| ------------------ | -------------- |
|
||||||
|
| brokerUrl | |
|
||||||
|
| clientId | |
|
||||||
|
| topic | |
|
||||||
|
| messageFormat |
|
||||||
|
| username | |
|
||||||
|
| password | yes |
|
||||||
|
| insecureSkipVerify | |
|
||||||
|
|
||||||
#### Alert notification `pagerduty`
|
#### Alert notification `pagerduty`
|
||||||
|
|
||||||
| Name | Secure setting |
|
| Name | Secure setting |
|
||||||
|
@ -10,6 +10,7 @@ labels:
|
|||||||
products:
|
products:
|
||||||
- enterprise
|
- enterprise
|
||||||
- oss
|
- oss
|
||||||
|
- cloud
|
||||||
title: Roles and permissions
|
title: Roles and permissions
|
||||||
weight: 300
|
weight: 300
|
||||||
---
|
---
|
||||||
|
@ -12,6 +12,7 @@ labels:
|
|||||||
products:
|
products:
|
||||||
- enterprise
|
- enterprise
|
||||||
- oss
|
- oss
|
||||||
|
- cloud
|
||||||
menuTitle: Service accounts
|
menuTitle: Service accounts
|
||||||
title: Service accounts
|
title: Service accounts
|
||||||
weight: 800
|
weight: 800
|
||||||
|
45
docs/sources/alerting/alerting-rules/create-alerts-panels.md
Normal file
45
docs/sources/alerting/alerting-rules/create-alerts-panels.md
Normal file
@ -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,10 +83,7 @@ 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).
|
Multiple alert instances can be created as a result of one alert rule (also known as a multi-dimensional alerting).
|
||||||
|
|
||||||
{{% admonition type="note" %}}
|
{{% admonition type="note" %}}
|
||||||
For Grafana Cloud, there are limits on how many Grafana-managed alert rules you can create. These are as follows:
|
For Grafana Cloud, you can create 100 free Grafana-managed alert rules.
|
||||||
|
|
||||||
- Free: 100 alert rules
|
|
||||||
- Paid: 2000 alert rules
|
|
||||||
{{% /admonition %}}
|
{{% /admonition %}}
|
||||||
|
|
||||||
Grafana managed alert rules can only be edited or deleted by users with Edit permissions for the folder storing the rules.
|
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 custom annotation
|
||||||
1. Optional: Add a **dashboard and panel link**.
|
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" %}}
|
{{% 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 %}}
|
{{% /admonition %}}
|
||||||
|
|
||||||
{{< docs/play title="visualizations with linked alerts in Grafana" url="https://play.grafana.org/d/000000074/" >}}
|
|
||||||
|
|
||||||
1. Click **Save rule**.
|
1. Click **Save rule**.
|
||||||
|
|
||||||
## Configure no data and error handling
|
## 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). |
|
| 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.
|
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.
|
Links alerts to panels in a dashboard.
|
||||||
|
|
||||||
{{% admonition type="note" %}}
|
{{% 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 %}}
|
{{% /admonition %}}
|
||||||
|
|
||||||
{{< docs/play title="visualizations with linked alerts in Grafana" url="https://play.grafana.org/d/000000074/" >}}
|
|
||||||
|
|
||||||
1. Click **Save rule**.
|
1. Click **Save rule**.
|
||||||
|
@ -76,6 +76,11 @@ refs:
|
|||||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/set-up/configure-alertmanager/
|
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/set-up/configure-alertmanager/
|
||||||
- pattern: /docs/grafana-cloud/
|
- pattern: /docs/grafana-cloud/
|
||||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/set-up/configure-alertmanager/
|
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
|
# Configure contact points
|
||||||
@ -164,6 +169,7 @@ The following table lists the contact point integrations supported by Grafana.
|
|||||||
| Google Chat | `googlechat` |
|
| Google Chat | `googlechat` |
|
||||||
| [Grafana Oncall](ref:oncall) | `oncall` |
|
| [Grafana Oncall](ref:oncall) | `oncall` |
|
||||||
| Kafka REST Proxy | `kafka` |
|
| Kafka REST Proxy | `kafka` |
|
||||||
|
| [MQTT](ref:mqtt) | `mqtt` |
|
||||||
| Line | `line` |
|
| Line | `line` |
|
||||||
| [Microsoft Teams](ref:teams) | `teams` |
|
| [Microsoft Teams](ref:teams) | `teams` |
|
||||||
| [Opsgenie](ref:opsgenie) | `opsgenie` |
|
| [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
|
- oss
|
||||||
menuTitle: Discord
|
menuTitle: Discord
|
||||||
title: Configure Discord for Alerting
|
title: Configure Discord for Alerting
|
||||||
weight: 10
|
weight: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configure Discord for Alerting
|
# Configure Discord for Alerting
|
||||||
|
@ -13,7 +13,7 @@ labels:
|
|||||||
- oss
|
- oss
|
||||||
menuTitle: Email
|
menuTitle: Email
|
||||||
title: Configure email for Alerting
|
title: Configure email for Alerting
|
||||||
weight: 20
|
weight: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configure email for Alerting
|
# Configure email for Alerting
|
||||||
|
@ -13,7 +13,7 @@ labels:
|
|||||||
- oss
|
- oss
|
||||||
menuTitle: Google Chat
|
menuTitle: Google Chat
|
||||||
title: Configure Google Chat for Alerting
|
title: Configure Google Chat for Alerting
|
||||||
weight: 30
|
weight: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configure Google Chat for Alerting
|
# 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
|
- oss
|
||||||
menuTitle: Grafana OnCall
|
menuTitle: Grafana OnCall
|
||||||
title: Configure Grafana OnCall for Alerting
|
title: Configure Grafana OnCall for Alerting
|
||||||
weight: 40
|
weight: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configure Grafana OnCall for Alerting
|
# Configure Grafana OnCall for Alerting
|
||||||
|
@ -13,7 +13,7 @@ labels:
|
|||||||
- oss
|
- oss
|
||||||
menuTitle: Opsgenie
|
menuTitle: Opsgenie
|
||||||
title: Configure Opsgenie for Alerting
|
title: Configure Opsgenie for Alerting
|
||||||
weight: 60
|
weight: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configure Opsgenie for Alerting
|
# Configure Opsgenie for Alerting
|
||||||
|
@ -13,7 +13,7 @@ labels:
|
|||||||
- oss
|
- oss
|
||||||
menuTitle: Slack
|
menuTitle: Slack
|
||||||
title: Configure Slack for Alerting
|
title: Configure Slack for Alerting
|
||||||
weight: 80
|
weight: 0
|
||||||
refs:
|
refs:
|
||||||
nested-policy:
|
nested-policy:
|
||||||
- pattern: /docs/grafana/
|
- pattern: /docs/grafana/
|
||||||
|
@ -13,7 +13,7 @@ labels:
|
|||||||
- oss
|
- oss
|
||||||
menuTitle: Microsoft Teams
|
menuTitle: Microsoft Teams
|
||||||
title: Configure Microsoft Teams for Alerting
|
title: Configure Microsoft Teams for Alerting
|
||||||
weight: 50
|
weight: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configure Microsoft Teams for Alerting
|
# Configure Microsoft Teams for Alerting
|
||||||
|
@ -13,7 +13,7 @@ labels:
|
|||||||
- oss
|
- oss
|
||||||
menuTitle: Telegram
|
menuTitle: Telegram
|
||||||
title: Configure Telegram for Alerting
|
title: Configure Telegram for Alerting
|
||||||
weight: 90
|
weight: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configure Telegram for Alerting
|
# Configure Telegram for Alerting
|
||||||
|
@ -14,7 +14,7 @@ labels:
|
|||||||
- oss
|
- oss
|
||||||
menuTitle: PagerDuty
|
menuTitle: PagerDuty
|
||||||
title: Configure PagerDuty for Alerting
|
title: Configure PagerDuty for Alerting
|
||||||
weight: 70
|
weight: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configure PagerDuty for Alerting
|
# Configure PagerDuty for Alerting
|
||||||
|
@ -21,7 +21,7 @@ labels:
|
|||||||
- oss
|
- oss
|
||||||
menuTitle: Webhook notifier
|
menuTitle: Webhook notifier
|
||||||
title: Configure the webhook notifier for Alerting
|
title: Configure the webhook notifier for Alerting
|
||||||
weight: 100
|
weight: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configure the webhook notifier for Alerting
|
# 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 |
|
| Google Chat | No | Yes |
|
||||||
| Kafka | No | No |
|
| Kafka | No | No |
|
||||||
| Line | No | No |
|
| Line | No | No |
|
||||||
|
| MQTT | No | No |
|
||||||
| Microsoft Teams | No | Yes |
|
| Microsoft Teams | No | Yes |
|
||||||
| Opsgenie | No | Yes |
|
| Opsgenie | No | Yes |
|
||||||
| Pagerduty | 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" %}}
|
{{% admonition type="note" %}}
|
||||||
For Grafana Enterprise and OSS users:
|
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/).
|
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).
|
Users can only see the history and transitions of alert rules they have access to (RBAC).
|
||||||
|
@ -18,6 +18,17 @@ labels:
|
|||||||
- oss
|
- oss
|
||||||
title: Configure high availability
|
title: Configure high availability
|
||||||
weight: 600
|
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
|
# 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.
|
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" %}}
|
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.
|
||||||
|
|
||||||
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 %}}
|
|
||||||
|
|
||||||
## Enable alerting high availability using Memberlist
|
## 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`).
|
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.
|
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
|
## 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
|
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_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).
|
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:
|
For a demo, see this [example using Docker Compose](https://github.com/grafana/alerting-ha-docker-examples/tree/main/redis).
|
||||||
|
|
||||||
| 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. |
|
|
||||||
|
|
||||||
## Enable alerting high availability using Kubernetes
|
## 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_peer_timeout = 15s
|
||||||
ha_reconnect_timeout = 2m
|
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.
|
Details of the fixed roles and the access they provide for Grafana Alerting are below.
|
||||||
|
|
||||||
| Fixed role | Permissions | Description |
|
| Display name in UI / 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. |
|
| 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. |
|
||||||
| `fixed:alerting.instances:reader` | `alert.instances:read` for organization scope <br> `alert.instances.external:read` for scope `datasources:*` | Read all alerts and silences. |
|
| 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. |
|
||||||
| `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. |
|
| 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. |
|
||||||
| `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. |
|
| 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. |
|
||||||
| `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 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. |
|
||||||
| `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. |
|
| 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. |
|
||||||
| `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. |
|
| 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. |
|
||||||
| `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. |
|
| 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. |
|
||||||
| `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. |
|
| 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. |
|
||||||
| `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. |
|
| 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. |
|
||||||
| `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. |
|
| 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
|
## Create custom roles
|
||||||
|
|
||||||
|
@ -343,6 +343,31 @@ settings:
|
|||||||
|
|
||||||
{{< /collapse >}}
|
{{< /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" >}}
|
{{< collapse title="Microsoft Teams" >}}
|
||||||
|
|
||||||
#### Microsoft Teams
|
#### Microsoft Teams
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
---
|
---
|
||||||
aliases:
|
|
||||||
- ../features/dashboard/dashboards/
|
|
||||||
- dashboard-manage/
|
|
||||||
labels:
|
labels:
|
||||||
products:
|
products:
|
||||||
- cloud
|
- cloud
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
aliases:
|
aliases:
|
||||||
- ../features/dashboard/dashboards/
|
|
||||||
- ../panels/working-with-panels/organize-dashboard/
|
- ../panels/working-with-panels/organize-dashboard/
|
||||||
- ../reference/dashboard_folders/
|
- ../reference/dashboard_folders/
|
||||||
- dashboard-folders/
|
- dashboard-folders/
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
aliases:
|
aliases:
|
||||||
- ../features/dashboard/dashboards/
|
|
||||||
- ../reference/search/
|
- ../reference/search/
|
||||||
- dashboard-ui/
|
- dashboard-ui/
|
||||||
- dashboard-ui/dashboard-header/
|
- 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.
|
_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" %}}
|
{{% 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 %}}
|
{{% /admonition %}}
|
||||||
|
|
||||||
1. [Enter general options](#enter-general-options).
|
1. [Enter general options](#enter-general-options).
|
||||||
|
@ -247,6 +247,10 @@ The resulting table panel:
|
|||||||
|
|
||||||
## Use time series queries
|
## 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.
|
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.
|
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.
|
- [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.
|
- [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",
|
"targetUID": "PDDA8E780A17E7EF1",
|
||||||
"label": "My Label",
|
"label": "My Label",
|
||||||
"description": "Logs to Traces",
|
"description": "Logs to Traces",
|
||||||
"config": {
|
|
||||||
"type": "query",
|
"type": "query",
|
||||||
|
"config": {
|
||||||
"field": "message",
|
"field": "message",
|
||||||
"target": {},
|
"target": {},
|
||||||
}
|
}
|
||||||
@ -65,8 +65,8 @@ Content-Type: application/json
|
|||||||
"sourceUID": "uyBf2637k",
|
"sourceUID": "uyBf2637k",
|
||||||
"targetUID": "PDDA8E780A17E7EF1",
|
"targetUID": "PDDA8E780A17E7EF1",
|
||||||
"uid": "50xhMlg9k",
|
"uid": "50xhMlg9k",
|
||||||
"config": {
|
|
||||||
"type": "query",
|
"type": "query",
|
||||||
|
"config": {
|
||||||
"field": "message",
|
"field": "message",
|
||||||
"target": {},
|
"target": {},
|
||||||
}
|
}
|
||||||
@ -153,8 +153,8 @@ Content-Type: application/json
|
|||||||
"sourceUID": "uyBf2637k",
|
"sourceUID": "uyBf2637k",
|
||||||
"targetUID": "PDDA8E780A17E7EF1",
|
"targetUID": "PDDA8E780A17E7EF1",
|
||||||
"uid": "J6gn7d31L",
|
"uid": "J6gn7d31L",
|
||||||
"config": {
|
|
||||||
"type": "query",
|
"type": "query",
|
||||||
|
"config": {
|
||||||
"field": "message",
|
"field": "message",
|
||||||
"target": {}
|
"target": {}
|
||||||
}
|
}
|
||||||
@ -197,8 +197,8 @@ Content-Type: application/json
|
|||||||
"targetUID": "PDDA8E780A17E7EF1",
|
"targetUID": "PDDA8E780A17E7EF1",
|
||||||
"uid": "J6gn7d31L",
|
"uid": "J6gn7d31L",
|
||||||
"provisioned": false,
|
"provisioned": false,
|
||||||
"config": {
|
|
||||||
"type": "query",
|
"type": "query",
|
||||||
|
"config": {
|
||||||
"field": "message",
|
"field": "message",
|
||||||
"target": {},
|
"target": {},
|
||||||
}
|
}
|
||||||
@ -239,8 +239,8 @@ Content-Type: application/json
|
|||||||
"targetUID": "PDDA8E780A17E7EF1",
|
"targetUID": "PDDA8E780A17E7EF1",
|
||||||
"uid": "J6gn7d31L",
|
"uid": "J6gn7d31L",
|
||||||
"provisioned": false,
|
"provisioned": false,
|
||||||
"config": {
|
|
||||||
"type": "query",
|
"type": "query",
|
||||||
|
"config": {
|
||||||
"field": "message",
|
"field": "message",
|
||||||
"target": {},
|
"target": {},
|
||||||
}
|
}
|
||||||
@ -252,8 +252,8 @@ Content-Type: application/json
|
|||||||
"targetUID": "P15396BDD62B2BE29",
|
"targetUID": "P15396BDD62B2BE29",
|
||||||
"uid": "uWCpURgVk",
|
"uid": "uWCpURgVk",
|
||||||
"provisioned": false,
|
"provisioned": false,
|
||||||
"config": {
|
|
||||||
"type": "query",
|
"type": "query",
|
||||||
|
"config": {
|
||||||
"field": "message",
|
"field": "message",
|
||||||
"target": {},
|
"target": {},
|
||||||
}
|
}
|
||||||
@ -301,8 +301,8 @@ Content-Type: application/json
|
|||||||
"targetUID": "PDDA8E780A17E7EF1",
|
"targetUID": "PDDA8E780A17E7EF1",
|
||||||
"uid": "J6gn7d31L",
|
"uid": "J6gn7d31L",
|
||||||
"provisioned": false,
|
"provisioned": false,
|
||||||
"config": {
|
|
||||||
"type": "query",
|
"type": "query",
|
||||||
|
"config": {
|
||||||
"field": "message",
|
"field": "message",
|
||||||
"target": {},
|
"target": {},
|
||||||
}
|
}
|
||||||
@ -314,8 +314,8 @@ Content-Type: application/json
|
|||||||
"targetUID": "P15396BDD62B2BE29",
|
"targetUID": "P15396BDD62B2BE29",
|
||||||
"uid": "uWCpURgVk",
|
"uid": "uWCpURgVk",
|
||||||
"provisioned": false,
|
"provisioned": false,
|
||||||
"config": {
|
|
||||||
"type": "query",
|
"type": "query",
|
||||||
|
"config": {
|
||||||
"field": "message",
|
"field": "message",
|
||||||
"target": {},
|
"target": {},
|
||||||
}
|
}
|
||||||
|
@ -563,7 +563,7 @@
|
|||||||
},
|
},
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"type": "array",
|
"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": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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)
|
- [Adobe Analytics](/grafana/plugins/grafana-adobeanalytics-datasource)
|
||||||
- [AppDynamics](/grafana/plugins/dlopes7-appdynamics-datasource)
|
- [AppDynamics](/grafana/plugins/dlopes7-appdynamics-datasource)
|
||||||
- [Atlassian Statuspage](/grafana/plugins/grafana-atlassianstatuspage-datasource)
|
- [Atlassian Statuspage](/grafana/plugins/grafana-atlassianstatuspage-datasource)
|
||||||
|
- [Aurora](/grafana/plugins/grafana-aurora-datasource)
|
||||||
- [Azure CosmosDB](/grafana/plugins/grafana-azurecosmosdb-datasource)
|
- [Azure CosmosDB](/grafana/plugins/grafana-azurecosmosdb-datasource)
|
||||||
- [Azure Devops](/grafana/plugins/grafana-azuredevops-datasource)
|
- [Azure Devops](/grafana/plugins/grafana-azuredevops-datasource)
|
||||||
- [Catchpoint](/grafana/plugins/grafana-catchpoint-datasource)
|
- [Catchpoint](/grafana/plugins/grafana-catchpoint-datasource)
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
aliases:
|
aliases:
|
||||||
- ../dashboards/add-organize-panels/
|
- ../dashboards/add-organize-panels/
|
||||||
- ../dashboards/dashboard-create/
|
- ../dashboards/dashboard-create/
|
||||||
- ../features/dashboard/dashboards/
|
|
||||||
- ../panels/add-panels-dynamically/about-repeating-panels-rows/
|
- ../panels/add-panels-dynamically/about-repeating-panels-rows/
|
||||||
- ../panels/add-panels-dynamically/configure-repeating-panels/
|
- ../panels/add-panels-dynamically/configure-repeating-panels/
|
||||||
- ../panels/add-panels-dynamically/configure-repeating-rows/
|
- ../panels/add-panels-dynamically/configure-repeating-rows/
|
||||||
|
@ -54,13 +54,36 @@ refs:
|
|||||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-overrides/
|
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-overrides/
|
||||||
- pattern: /docs/grafana-cloud/
|
- pattern: /docs/grafana-cloud/
|
||||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-overrides/
|
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
|
# 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.
|
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:
|
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.
|
Annotations and alerts are not currently supported for tables.
|
||||||
{{< /admonition >}}
|
{{< /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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Sort column
|
## 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.
|
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
|
### 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" %}}
|
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 cell display mode is set to Auto, Color text, Color background or JSON View.
|
|
||||||
{{% /admonition %}}
|
Cell value inspection is only available when the **Cell type** selection is **Auto**, **Colored text**, **Colored background**, or **JSON View**.
|
||||||
|
|
||||||
## Turn on column filtering
|
## Turn on column filtering
|
||||||
|
|
||||||
|
@ -64,11 +64,16 @@ refs:
|
|||||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/panel-editor-overview/#data-section
|
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/panel-editor-overview/#data-section
|
||||||
- pattern: /docs/grafana-cloud/
|
- pattern: /docs/grafana-cloud/
|
||||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/panel-editor-overview/#data-section
|
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
|
||||||
|
|
||||||
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" >}}
|
{{< 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.
|
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 >}}
|
{{< /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
|
## 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:
|
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
|
## Supported data formats
|
||||||
|
|
||||||
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/).
|
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 |
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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 |
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 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 |
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
## Alert rules
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ 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.
|
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 |
|
| Feature toggle name | Description | Enabled by default |
|
||||||
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
||||||
| `disableEnvelopeEncryption` | Disable envelope encryption (emergency only) | |
|
| `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 |
|
| `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 | |
|
| `featureHighlights` | Highlight Grafana Enterprise features | |
|
||||||
@ -34,7 +34,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
|
|||||||
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | 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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | Yes |
|
||||||
@ -67,6 +66,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
|
|||||||
| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes |
|
| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes |
|
||||||
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
|
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
|
||||||
| `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | |
|
| `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 |
|
| `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes |
|
||||||
|
|
||||||
## Public preview feature toggles
|
## 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 |
|
| `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. |
|
| `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 |
|
| `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 |
|
| `cloudwatchMetricInsightsCrossAccount` | Enables cross account observability for Cloudwatch Metric Insights query builder |
|
||||||
|
|
||||||
## Experimental feature toggles
|
## Experimental feature toggles
|
||||||
@ -121,7 +120,6 @@ Experimental features might be changed or removed without prior notice.
|
|||||||
| `canvasPanelNesting` | Allow elements nesting |
|
| `canvasPanelNesting` | Allow elements nesting |
|
||||||
| `disableSecretsCompatibility` | Disable duplicated secret storage in legacy tables |
|
| `disableSecretsCompatibility` | Disable duplicated secret storage in legacy tables |
|
||||||
| `logRequestsInstrumentedAsUnknown` | Logs the path for requests that are instrumented as unknown |
|
| `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 |
|
| `showDashboardValidationWarnings` | Show warnings when dashboards do not validate against the schema |
|
||||||
| `mysqlAnsiQuotes` | Use double quotes to escape keyword in a MySQL query |
|
| `mysqlAnsiQuotes` | Use double quotes to escape keyword in a MySQL query |
|
||||||
| `alertingBacktesting` | Rule backtesting API for alerting |
|
| `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 |
|
| `wargamesTesting` | Placeholder feature flag for internal testing |
|
||||||
| `externalCorePlugins` | Allow core plugins to be loaded as external |
|
| `externalCorePlugins` | Allow core plugins to be loaded as external |
|
||||||
| `pluginsAPIMetrics` | Sends metrics of public grafana packages usage by plugins |
|
| `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 |
|
| `enableNativeHTTPHistogram` | Enables native HTTP Histograms |
|
||||||
| `disableClassicHTTPHistogram` | Disables classic HTTP Histogram (use with enableNativeHTTPHistogram) |
|
| `disableClassicHTTPHistogram` | Disables classic HTTP Histogram (use with enableNativeHTTPHistogram) |
|
||||||
| `kubernetesSnapshots` | Routes snapshot requests from /api to the /apis endpoint |
|
| `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 |
|
| `backgroundPluginInstaller` | Enable background plugin installer |
|
||||||
| `dataplaneAggregator` | Enable grafana dataplane aggregator |
|
| `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. |
|
| `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
|
## Development feature toggles
|
||||||
|
|
||||||
|
@ -88,10 +88,11 @@ port = 636
|
|||||||
use_ssl = true
|
use_ssl = true
|
||||||
# If set to true, use LDAP with STARTTLS instead of LDAPS
|
# If set to true, use LDAP with STARTTLS instead of LDAPS
|
||||||
start_tls = false
|
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
|
# 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 = []
|
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 = ""
|
min_tls_version = ""
|
||||||
# set to true if you want to skip SSL cert validation
|
# set to true if you want to skip SSL cert validation
|
||||||
ssl_skip_verify = false
|
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:
|
Grafana supports the following databases:
|
||||||
|
|
||||||
- [SQLite 3](https://www.sqlite.org/index.html)
|
- [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/)
|
- [PostgreSQL 12+](https://www.postgresql.org/support/versioning/)
|
||||||
|
|
||||||
By default Grafana uses an embedded SQLite database, which is stored in the Grafana installation location.
|
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:
|
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
|
```shell
|
||||||
sudo systemctl stop grafana-server
|
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.
|
- [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.
|
- [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
|
## Paths
|
||||||
|
|
||||||
### <span id="route-delete-alert-rule"></span> Delete a specific alert rule by UID. (_RouteDeleteAlertRule_)
|
### <span id="route-delete-alert-rule"></span> Delete a specific alert rule by UID. (_RouteDeleteAlertRule_)
|
||||||
|
22
docs/sources/upgrade-guide/upgrade-v11.2/index.md
Normal file
22
docs/sources/upgrade-guide/upgrade-v11.2/index.md
Normal file
@ -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
|
## 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.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/)
|
- [What's new in 11.0](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/whatsnew/whats-new-in-v11-0/)
|
||||||
|
|
||||||
|
271
docs/sources/whatsnew/whats-new-in-v11-2.md
Normal file
271
docs/sources/whatsnew/whats-new-in-v11-2.md
Normal file
@ -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.Panels.Panel.menuItems('Edit').click();
|
||||||
e2e.components.NavToolbar.editDashboard.backToDashboardButton().click();
|
e2e.components.NavToolbar.editDashboard.backToDashboardButton().click();
|
||||||
|
|
||||||
// And the last panel should still be visible!
|
// 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 #50').should('be.visible');
|
|
||||||
// e2e.components.Panels.Panel.title('Panel #1').should('be.visible');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
33
e2e/test-plugins/README.md
Normal file
33
e2e/test-plugins/README.md
Normal file
@ -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`
|
Before Width: | Height: | Size: 100 B After Width: | Height: | Size: 100 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user