Compare commits
87 Commits
release-11
...
pkg/promli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e96b5a68c4 | ||
|
|
c0b3932168 | ||
|
|
1eb45dd0f8 | ||
|
|
cff07c9fb6 | ||
|
|
106af5d0ee | ||
|
|
029edcb6be | ||
|
|
4386cfc984 | ||
|
|
019ee9c2d4 | ||
|
|
b532df36c4 | ||
|
|
a32eed1d13 | ||
|
|
3df1fa86ae | ||
|
|
d95484fba6 | ||
|
|
31deddafb6 | ||
|
|
4c0fa629da | ||
|
|
862c0ce9b5 | ||
|
|
70ddf9cb76 | ||
|
|
98e9f3a534 | ||
|
|
4936c53072 | ||
|
|
a3cdad25a3 | ||
|
|
2187a66f2b | ||
|
|
ee4016f4bc | ||
|
|
77f7ab27be | ||
|
|
4fcd529d0e | ||
|
|
76a7987427 | ||
|
|
7da6e48036 | ||
|
|
3641b28e84 | ||
|
|
d025523a8b | ||
|
|
cd46f1ddb9 | ||
|
|
f9f341e9c9 | ||
|
|
bab55a4cb8 | ||
|
|
fc90a446c6 | ||
|
|
b6fc695598 | ||
|
|
5d45af1110 | ||
|
|
d00592ffa0 | ||
|
|
8415089534 | ||
|
|
125fdc8f21 | ||
|
|
f142f12887 | ||
|
|
c03586dfe8 | ||
|
|
38927f0719 | ||
|
|
a290db6a7e | ||
|
|
e3e580edfa | ||
|
|
1dcff0a71f | ||
|
|
dfe0712955 | ||
|
|
79fc26ea87 | ||
|
|
ea6cb8f139 | ||
|
|
5aeaccadff | ||
|
|
7f04f66137 | ||
|
|
32790c6918 | ||
|
|
bbade6b011 | ||
|
|
a6eb8abd05 | ||
|
|
3f71a72c1a | ||
|
|
0d302a161a | ||
|
|
0fce8799eb | ||
|
|
2a08c9ed82 | ||
|
|
dbfc412ed8 | ||
|
|
e15fc984c3 | ||
|
|
69da0bb22c | ||
|
|
c5111b8132 | ||
|
|
3b8477dcda | ||
|
|
feb334cdbb | ||
|
|
f7e0710f53 | ||
|
|
e38bab43db | ||
|
|
1ff4053f03 | ||
|
|
ce0d986673 | ||
|
|
7151ea6abc | ||
|
|
b96a752db3 | ||
|
|
125a11ca99 | ||
|
|
3884c0e896 | ||
|
|
037570b9c8 | ||
|
|
7c87ff1b84 | ||
|
|
2594b4f7af | ||
|
|
0032e839ce | ||
|
|
6b5146651f | ||
|
|
7771768363 | ||
|
|
0b5b21548b | ||
|
|
70fb7b9545 | ||
|
|
ff032a61d5 | ||
|
|
5570a7e42e | ||
|
|
e45eb95812 | ||
|
|
33a91f22c0 | ||
|
|
dbd3bb7667 | ||
|
|
740cd22fe5 | ||
|
|
64617886d9 | ||
|
|
1281b0f094 | ||
|
|
5c49b6cf73 | ||
|
|
65f5a176a8 | ||
|
|
36d6fad421 |
@@ -86,6 +86,10 @@ module.exports = [
|
||||
importNames: ['Layout', 'HorizontalGroup', 'VerticalGroup'],
|
||||
message: 'Use Stack component instead.',
|
||||
},
|
||||
{
|
||||
group: ['@grafana/ui/src/*', '@grafana/runtime/src/*', '@grafana/data/src/*'],
|
||||
message: 'Import from the public export instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
1274
.betterer.results
1274
.betterer.results
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,12 @@ $(BRA): $(BINGO_DIR)/bra.mod
|
||||
@echo "(re)installing $(GOBIN)/bra-v0.0.0-20200517080246-1e3013ecaff8"
|
||||
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=bra.mod -o=$(GOBIN)/bra-v0.0.0-20200517080246-1e3013ecaff8 "github.com/unknwon/bra"
|
||||
|
||||
COG := $(GOBIN)/cog-v0.0.15
|
||||
$(COG): $(BINGO_DIR)/cog.mod
|
||||
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
|
||||
@echo "(re)installing $(GOBIN)/cog-v0.0.15"
|
||||
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=cog.mod -o=$(GOBIN)/cog-v0.0.15 "github.com/grafana/cog/cmd/cli"
|
||||
|
||||
CUE := $(GOBIN)/cue-v0.5.0
|
||||
$(CUE): $(BINGO_DIR)/cue.mod
|
||||
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
|
||||
|
||||
5
.bingo/cog.mod
Normal file
5
.bingo/cog.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
|
||||
|
||||
go 1.23.4
|
||||
|
||||
require github.com/grafana/cog v0.0.15 // cmd/cli
|
||||
@@ -1,11 +1,10 @@
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 h1:R5wwEcbEZSBmeyg91MJZTxfd7WpBo2jPof3AYjRbxwY=
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg=
|
||||
cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0=
|
||||
cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0=
|
||||
cuelang.org/go v0.11.0 h1:2af2nhipqlUHtXk2dtOP5xnMm1ObGvKqIsJUJL1sRE4=
|
||||
cuelang.org/go v0.11.0/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0=
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg=
|
||||
github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY=
|
||||
github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI=
|
||||
@@ -16,18 +15,16 @@ github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk=
|
||||
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
|
||||
github.com/grafana/cog v0.0.5 h1:BCa+10i3KvV+KMSQuxlN1DS9cZEwN+EAFc7ZmXqHxQE=
|
||||
github.com/grafana/cog v0.0.5/go.mod h1:lzetOuhGUl/JaSACiJoHvBokf9/fS6PEFaWZvnQu2vs=
|
||||
github.com/grafana/cog v0.0.14 h1:sBK89oSu9BK4S9l3G9ewVJnGYnNQJTHFBC/01DZDRZs=
|
||||
github.com/grafana/cog v0.0.14/go.mod h1:HwJbc60fZ+viayROClLGdDwO5w/JjBOpO9wjGnAfMLc=
|
||||
github.com/grafana/cog v0.0.15 h1:e2pMY+Hf2nS22HcKJuguEzl0BVmV9DSINwCfWt+dFZQ=
|
||||
github.com/grafana/cog v0.0.15/go.mod h1:jrS9indvWuDs60RHEZpLaAkmZdgyoLKMOEUT0jiB1t0=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -35,18 +32,12 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
||||
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
@@ -61,36 +52,35 @@ github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNH
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
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/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY=
|
||||
github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -10,6 +10,8 @@ fi
|
||||
|
||||
BRA="${GOBIN}/bra-v0.0.0-20200517080246-1e3013ecaff8"
|
||||
|
||||
COG="${GOBIN}/cog-v0.0.15"
|
||||
|
||||
CUE="${GOBIN}/cue-v0.5.0"
|
||||
|
||||
DRONE="${GOBIN}/drone-v1.5.0"
|
||||
|
||||
12
.drone.yml
12
.drone.yml
@@ -1273,11 +1273,6 @@ steps:
|
||||
depends_on: []
|
||||
image: node:22.11.0-alpine
|
||||
name: yarn-install
|
||||
- commands:
|
||||
- pip3 install codespell
|
||||
- codespell -I docs/.codespellignore docs/
|
||||
image: python:3.8
|
||||
name: codespell
|
||||
- commands:
|
||||
- yarn run prettier:checkDocs
|
||||
depends_on:
|
||||
@@ -1635,11 +1630,6 @@ steps:
|
||||
depends_on: []
|
||||
image: node:22.11.0-alpine
|
||||
name: yarn-install
|
||||
- commands:
|
||||
- pip3 install codespell
|
||||
- codespell -I docs/.codespellignore docs/
|
||||
image: python:3.8
|
||||
name: codespell
|
||||
- commands:
|
||||
- yarn run prettier:checkDocs
|
||||
depends_on:
|
||||
@@ -5579,6 +5569,6 @@ kind: secret
|
||||
name: gcr_credentials
|
||||
---
|
||||
kind: signature
|
||||
hmac: bdf4cd3767ad13e5a8d7221731676e8ee1991dae0da9ae2cee92087d5c660ece
|
||||
hmac: be82e983dfa15f85f82935674ec109056f519ff99c7ab1f4be71f9ce62b571a7
|
||||
|
||||
...
|
||||
|
||||
13
.github/CODEOWNERS
vendored
13
.github/CODEOWNERS
vendored
@@ -32,10 +32,10 @@
|
||||
/devenv/README.md @grafana/docs-grafana
|
||||
|
||||
# START Technical documentation
|
||||
/.vale.ini @grafana/docs-tooling
|
||||
# `make docs` procedure and related workflows are owned @grafana/docs-tooling. Slack #docs.
|
||||
/docs/ @grafana/docs-tooling
|
||||
|
||||
/docs/.codespellignore @grafana/docs-tooling
|
||||
/docs/sources/ @irenerl24
|
||||
|
||||
/docs/sources/alerting/ @brendamuir
|
||||
@@ -422,8 +422,6 @@
|
||||
/packages/grafana-ui/src/graveyard/TimeSeries/ @grafana/dataviz-squad
|
||||
/packages/grafana-ui/src/utils/storybook/ @grafana/plugins-platform-frontend
|
||||
|
||||
/plugins-bundled/ @grafana/plugins-platform-frontend
|
||||
|
||||
# root files, mostly frontend
|
||||
/.browserslistrc @grafana/frontend-ops
|
||||
/package.json @grafana/frontend-ops
|
||||
@@ -759,6 +757,7 @@ embed.go @grafana/grafana-as-code
|
||||
/.github/workflows/commands.yml @torkelo
|
||||
/.github/workflows/community-release.yml @grafana/grafana-developer-enablement-squad
|
||||
/.github/workflows/detect-breaking-changes-* @grafana/plugins-platform-frontend
|
||||
/.github/workflows/documentation-ci.yml @grafana/docs-tooling
|
||||
/.github/workflows/doc-validator.yml @grafana/docs-tooling
|
||||
/.github/workflows/deploy-pr-preview.yml @grafana/docs-tooling
|
||||
/.github/workflows/epic-add-to-platform-ux-parent-project.yml @meanmina
|
||||
@@ -781,10 +780,10 @@ embed.go @grafana/grafana-as-code
|
||||
/.github/workflows/stale.yml @grafana/grafana-developer-enablement-squad
|
||||
/.github/workflows/update-changelog.yml @grafana/grafana-developer-enablement-squad
|
||||
/.github/workflows/update-make-docs.yml @grafana/docs-tooling
|
||||
/.github/workflows/scripts/kinds/verify-kinds.go @grafana/platform-cat
|
||||
/.github/workflows/publish-kinds-next.yml @grafana/platform-cat
|
||||
/.github/workflows/publish-kinds-release.yml @grafana/platform-cat
|
||||
/.github/workflows/verify-kinds.yml @grafana/platform-cat
|
||||
/.github/workflows/scripts/kinds/verify-kinds.go @grafana/platform-monitoring
|
||||
/.github/workflows/publish-kinds-next.yml @grafana/platform-monitoring
|
||||
/.github/workflows/publish-kinds-release.yml @grafana/platform-monitoring
|
||||
/.github/workflows/verify-kinds.yml @grafana/platform-monitoring
|
||||
/.github/workflows/dashboards-issue-add-label.yml @grafana/dashboards-squad
|
||||
/.github/workflows/ephemeral-instances-pr-comment.yml @grafana/grafana-backend-services-squad
|
||||
/.github/workflows/create-security-patch-from-security-mirror.yml @grafana/grafana-developer-enablement-squad
|
||||
|
||||
3
.github/pr-commands.json
vendored
3
.github/pr-commands.json
vendored
@@ -14,7 +14,6 @@
|
||||
"public/**/*",
|
||||
"packages/**/*",
|
||||
"e2e/**/*",
|
||||
"plugins-bundled/**/*",
|
||||
"scripts/build/release-packages.sh",
|
||||
"scripts/circle-release-next-packages.sh",
|
||||
"scripts/ci-frontend-metrics.sh",
|
||||
@@ -437,4 +436,4 @@
|
||||
"action": "updateLabel",
|
||||
"addLabel": "area/panel/table"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
2
.github/renovate.json5
vendored
2
.github/renovate.json5
vendored
@@ -19,7 +19,7 @@
|
||||
"nx"
|
||||
],
|
||||
includePaths: ["package.json", "packages/**", "public/app/plugins/**"],
|
||||
ignorePaths: ["emails/**", "plugins-bundled/**", "**/mocks/**"],
|
||||
ignorePaths: ["emails/**", "**/mocks/**"],
|
||||
labels: ["area/frontend", "dependencies", "no-changelog"],
|
||||
postUpdateOptions: ["yarnDedupeHighest"],
|
||||
packageRules: [
|
||||
|
||||
18
.github/workflows/documentation-ci.yml
vendored
Normal file
18
.github/workflows/documentation-ci.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Documentation CI
|
||||
on:
|
||||
pull_request:
|
||||
paths: ["docs/sources/**"]
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
vale:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: grafana/vale:latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: grafana/writers-toolkit/vale-action@13205961f20ad13843505a9b84fdf032f911a3f4 # vale-action/v1.1.0
|
||||
with:
|
||||
filter: '.Name in ["Grafana.WordList", "Grafana.Spelling", "Grafana.ProductPossessives"]'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
5
.vale.ini
Normal file
5
.vale.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
MinAlertLevel = warning
|
||||
|
||||
[*]
|
||||
BasedOnStyles = Grafana
|
||||
TokenIgnores = (<http[^\n]+>+?), \*\*[^\n]+\*\*
|
||||
19
.vscode/launch.json
vendored
19
.vscode/launch.json
vendored
@@ -9,12 +9,7 @@
|
||||
"program": "${workspaceFolder}/pkg/cmd/grafana/",
|
||||
"env": {},
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": [
|
||||
"server",
|
||||
"--homepath", "${workspaceFolder}",
|
||||
"--packaging", "dev",
|
||||
"cfg:app_mode=development",
|
||||
]
|
||||
"args": ["server", "--homepath", "${workspaceFolder}", "--packaging", "dev", "cfg:app_mode=development"]
|
||||
},
|
||||
{
|
||||
"name": "Attach to Test Process",
|
||||
@@ -23,7 +18,7 @@
|
||||
"mode": "remote",
|
||||
"host": "127.0.0.1",
|
||||
"port": 50480,
|
||||
"apiVersion": 2,
|
||||
"apiVersion": 2
|
||||
},
|
||||
{
|
||||
"name": "Run API Server (testdata)",
|
||||
@@ -72,6 +67,16 @@
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"]
|
||||
},
|
||||
{
|
||||
"name": "Run Authz server",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/pkg/cmd/grafana/",
|
||||
"env": { "GF_DEFAULT_TARGET": "zanzana-server", "GF_SERVER_HTTP_PORT": "3001" },
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"]
|
||||
},
|
||||
{
|
||||
"name": "Attach to Chrome",
|
||||
"port": 9222,
|
||||
|
||||
@@ -13,10 +13,6 @@ packageExtensions:
|
||||
doctrine@3.0.0:
|
||||
dependencies:
|
||||
assert: 2.0.0
|
||||
rc-time-picker@3.7.3:
|
||||
peerDependencies:
|
||||
react: 17.0.1
|
||||
react-dom: 17.0.1
|
||||
rc-trigger@2.6.5:
|
||||
peerDependencies:
|
||||
react: 17.0.1
|
||||
|
||||
@@ -16,7 +16,6 @@ WORKDIR /tmp/grafana
|
||||
COPY package.json project.json nx.json yarn.lock .yarnrc.yml ./
|
||||
COPY .yarn .yarn
|
||||
COPY packages packages
|
||||
COPY plugins-bundled plugins-bundled
|
||||
COPY public public
|
||||
COPY LICENSE ./
|
||||
COPY conf/defaults.ini ./conf/defaults.ini
|
||||
|
||||
3
Makefile
3
Makefile
@@ -149,7 +149,7 @@ gen-cue: ## Do all CUE/Thema code generation
|
||||
.PHONY: gen-cuev2
|
||||
gen-cuev2: ## Do all CUE code generation
|
||||
@echo "generate code from .cue files (v2)"
|
||||
go generate ./kindsv2/gen.go
|
||||
@$(MAKE) -C ./kindsv2 all
|
||||
|
||||
.PHONY: gen-feature-toggles
|
||||
gen-feature-toggles:
|
||||
@@ -211,7 +211,6 @@ build-cli: ## Build Grafana CLI application.
|
||||
build-js: ## Build frontend assets.
|
||||
@echo "build frontend"
|
||||
yarn run build
|
||||
yarn run plugins:build-bundled
|
||||
|
||||
PLUGIN_ID ?=
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/grafana/grafana => ../../..
|
||||
|
||||
require (
|
||||
github.com/grafana/grafana v0.0.0-00010101000000-000000000000
|
||||
github.com/grafana/grafana-app-sdk v0.23.1
|
||||
github.com/grafana/grafana-app-sdk v0.29.0
|
||||
k8s.io/apimachinery v0.32.0
|
||||
k8s.io/apiserver v0.32.0
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f
|
||||
|
||||
@@ -71,8 +71,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/grafana-app-sdk v0.23.1 h1:BRpUG0bA0oVxjthkmO2thuJBo3nbjaRSSmZJHw+mA8I=
|
||||
github.com/grafana/grafana-app-sdk v0.23.1/go.mod h1:KzgPnTJfMeckGmMctv6CJb8Jr/o/5rwARDyjXoeR0Fc=
|
||||
github.com/grafana/grafana-app-sdk v0.29.0 h1:LMSm/+0LOBPd13fe1bs/4sKJmuLiixYUX9T0oqDqp4I=
|
||||
github.com/grafana/grafana-app-sdk v0.29.0/go.mod h1:XLt308EmK6kvqPlzjUyXxbwZKEk2vur/eiypUNDay5I=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da h1:2E3c/I3ayAy4Z1GwIPqXNZcpUccRapE1aBXA1ho4g7o=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da/go.mod h1:p09fvU5ujNL/Ig8HB7g4f+S0zyYbQq3x/f0jA4ujVOM=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module github.com/grafana/grafana/apps/investigation
|
||||
|
||||
go 1.23.1
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
github.com/grafana/grafana-app-sdk v0.23.1
|
||||
github.com/grafana/grafana-app-sdk v0.29.0
|
||||
k8s.io/apimachinery v0.32.0
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f
|
||||
@@ -29,7 +29,9 @@ require (
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/grafana-app-sdk/logging v0.29.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
@@ -61,10 +63,12 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.25.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241216192217-9240e9c98484 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect
|
||||
|
||||
@@ -29,6 +29,8 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
@@ -43,10 +45,14 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grafana/grafana-app-sdk v0.23.1 h1:BRpUG0bA0oVxjthkmO2thuJBo3nbjaRSSmZJHw+mA8I=
|
||||
github.com/grafana/grafana-app-sdk v0.23.1/go.mod h1:KzgPnTJfMeckGmMctv6CJb8Jr/o/5rwARDyjXoeR0Fc=
|
||||
github.com/grafana/grafana-app-sdk v0.29.0 h1:LMSm/+0LOBPd13fe1bs/4sKJmuLiixYUX9T0oqDqp4I=
|
||||
github.com/grafana/grafana-app-sdk v0.29.0/go.mod h1:XLt308EmK6kvqPlzjUyXxbwZKEk2vur/eiypUNDay5I=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.29.0 h1:mgbXaAf33aFwqwGVeaX30l8rkeAJH0iACgX5Rn6YkN4=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.29.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -81,6 +87,10 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
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/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -150,6 +160,8 @@ golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -167,6 +179,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module github.com/grafana/grafana/apps/playlist
|
||||
|
||||
go 1.23.1
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
github.com/grafana/grafana-app-sdk v0.23.1
|
||||
github.com/grafana/grafana-app-sdk v0.29.0
|
||||
k8s.io/apimachinery v0.32.0
|
||||
k8s.io/client-go v0.32.0
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
@@ -30,7 +30,9 @@ require (
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/grafana-app-sdk/logging v0.29.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
@@ -62,10 +64,12 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.25.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241216192217-9240e9c98484 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect
|
||||
|
||||
@@ -29,6 +29,8 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
@@ -43,10 +45,14 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grafana/grafana-app-sdk v0.23.1 h1:BRpUG0bA0oVxjthkmO2thuJBo3nbjaRSSmZJHw+mA8I=
|
||||
github.com/grafana/grafana-app-sdk v0.23.1/go.mod h1:KzgPnTJfMeckGmMctv6CJb8Jr/o/5rwARDyjXoeR0Fc=
|
||||
github.com/grafana/grafana-app-sdk v0.29.0 h1:LMSm/+0LOBPd13fe1bs/4sKJmuLiixYUX9T0oqDqp4I=
|
||||
github.com/grafana/grafana-app-sdk v0.29.0/go.mod h1:XLt308EmK6kvqPlzjUyXxbwZKEk2vur/eiypUNDay5I=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.29.0 h1:mgbXaAf33aFwqwGVeaX30l8rkeAJH0iACgX5Rn6YkN4=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.29.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -81,6 +87,10 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
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/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -150,6 +160,8 @@ golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -167,6 +179,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -60,6 +60,14 @@ func main() {
|
||||
label: "label.with.spaß",
|
||||
getNextValue: staticList([]string{"this_is_fun"}),
|
||||
},
|
||||
{
|
||||
label: "instance",
|
||||
getNextValue: staticList([]string{"instance"}),
|
||||
},
|
||||
{
|
||||
label: "job",
|
||||
getNextValue: staticList([]string{"job"}),
|
||||
},
|
||||
{
|
||||
label: "site",
|
||||
getNextValue: staticList([]string{"LA-EPI"}),
|
||||
@@ -85,6 +93,12 @@ func main() {
|
||||
Help: "a metric with utf8 labels",
|
||||
}, dimensions)
|
||||
|
||||
target_info := promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "target_info",
|
||||
Help: "an info metric model for otel",
|
||||
ConstLabels: map[string]string{"job": "job", "instance": "instance", "resource 1": "1", "resource 2": "2", "resource ę": "e", "deployment_environment": "prod"},
|
||||
})
|
||||
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@@ -101,6 +115,7 @@ func main() {
|
||||
|
||||
utf8Metric.WithLabelValues(labels...).Inc()
|
||||
opsProcessed.WithLabelValues(labels...).Inc()
|
||||
target_info.Set(1)
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
aks
|
||||
eror
|
||||
geomap
|
||||
Geomap
|
||||
grafanalib
|
||||
grafonnet
|
||||
iam
|
||||
Jsonnet
|
||||
[Operato Windrose](https://grafana.com/grafana/plugins/operato-windrose-panel/)
|
||||
runbook
|
||||
sergent
|
||||
sparkline
|
||||
wan
|
||||
@@ -20,6 +20,11 @@ labels:
|
||||
title: Configure data source-managed alert rules
|
||||
weight: 200
|
||||
refs:
|
||||
shared-configure-prometheus-data-source-alerting:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/prometheus/configure-prometheus-data-source/#alerting
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/prometheus/configure-prometheus-data-source/#alerting
|
||||
configure-grafana-managed-rules:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/alerting-rules/create-grafana-managed-rule/
|
||||
@@ -74,17 +79,19 @@ refs:
|
||||
|
||||
# Configure data source-managed alert rules
|
||||
|
||||
Data source-managed alert rules can only query Prometheus-based data sources, such as Prometheus, Grafana Mimir, or Grafana Loki. They are one of the two [alert rule types](ref:alert-rules) supported in Grafana.
|
||||
Data source-managed alert rules can only be created using Grafana Mimir or Grafana Loki data sources.
|
||||
|
||||
Data source-managed alert rules are stored within the data source. In a distributed architecture, they can scale horizontally to provide high-availability.
|
||||
The rules are stored within the data source. In a distributed architecture, they can scale horizontally to provide high-availability. For more details, refer to [alert rule types](ref:alert-rules).
|
||||
|
||||
We recommend using [Grafana-managed alert rules](ref:configure-grafana-managed-rules) whenever possible and opting for data source-managed alert rules when scaling your alerting setup is necessary.
|
||||
|
||||
{{< docs/shared lookup="alerts/note-prometheus-ds-rules.md" source="grafana" version="<GRAFANA_VERSION>" >}}
|
||||
|
||||
To create or edit data source-managed alert rules, follow these instructions.
|
||||
|
||||
## Before you begin
|
||||
|
||||
Verify that you have write permission to the Prometheus, Mimir, or Loki data source. Otherwise, you cannot create or update data source-managed alert rules.
|
||||
Verify that you have write permission to the Mimir or Loki data source. Otherwise, you cannot create or update data source-managed alert rules.
|
||||
|
||||
### Enable the Ruler API
|
||||
|
||||
@@ -96,7 +103,7 @@ For more information, refer to the [Mimir Ruler API](/docs/mimir/latest/referenc
|
||||
|
||||
### Permissions
|
||||
|
||||
Alert rules for Prometheus, Mimir, or Loki instances can be edited or deleted by users with **Editor** or **Admin** roles.
|
||||
Alert rules for Mimir or Loki instances can be edited or deleted by users with **Editor** or **Admin** roles.
|
||||
|
||||
If you do not want to manage alert rules for a particular data source, go to its settings and clear the **Manage alerts via Alerting UI** checkbox.
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@ labels:
|
||||
title: Alert rules
|
||||
weight: 100
|
||||
refs:
|
||||
shared-configure-prometheus-data-source-alerting:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/prometheus/configure-prometheus-data-source/#alerting
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/prometheus/configure-prometheus-data-source/#alerting
|
||||
queries-and-conditions:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/fundamentals/alert-rules/queries-conditions/
|
||||
@@ -72,8 +77,9 @@ Grafana-managed alert rules are the most flexible alert rule type. They allow yo
|
||||
|
||||
{{< figure src="/media/docs/alerting/grafana-managed-alerting-architecture.png" max-width="750px" caption="How Grafana-managed alerting works by default" >}}
|
||||
|
||||
1. Alert rules are created within Grafana and query one or more data sources.
|
||||
1. Alert rules are evaluated by the Alert Rule Evaluation Engine from within Grafana.
|
||||
1. Alert rules are created and stored within Grafana.
|
||||
1. Alert rules can query one or more supported data sources.
|
||||
1. Alert rules are evaluated by the Alert Rule Evaluation Engine within Grafana.
|
||||
1. Firing and resolved alert instances are forwarded to [handle their notifications](ref:notifications).
|
||||
|
||||
### Supported data sources
|
||||
@@ -84,17 +90,17 @@ Find the public data sources supporting Alerting in the [Grafana Plugins directo
|
||||
|
||||
## Data source-managed alert rules
|
||||
|
||||
Data source-managed alert rules can only query Prometheus-based data sources, such as Prometheus, Grafana Mimir, or Grafana Loki.
|
||||
|
||||
Alert rules are stored within the data source. In this distributed architecture, the separation of components can provide high-availability and fault tolerance, enabling the scaling of your alerting setup.
|
||||
Data source-managed alert rules can only be created using Grafana Mimir or Grafana Loki data sources. Both data source backends can provide high availability and fault tolerance, enabling you to scale your alerting setup.
|
||||
|
||||
{{< figure src="/media/docs/alerting/mimir-managed-alerting-architecture-v2.png" max-width="750px" caption="Mimir-managed alerting architecture" >}}
|
||||
|
||||
1. Alert rules are created and stored within the data source itself.
|
||||
1. Alert rules can only query Prometheus-based data.
|
||||
1. Alert rules are evaluated by the Alert Rule Evaluation Engine.
|
||||
1. Alert rules are stored within the Mimir or Loki data source.
|
||||
1. Alert rules can query only their specific data source.
|
||||
1. Alert rules are evaluated by the Alert Rule Evaluation Engine within the data source.
|
||||
1. Firing and resolved alert instances are forwarded to [handle their notifications](ref:notifications).
|
||||
|
||||
{{< docs/shared lookup="alerts/note-prometheus-ds-rules.md" source="grafana" version="<GRAFANA_VERSION>" >}}
|
||||
|
||||
## Comparison between alert rule types
|
||||
|
||||
We recommend using Grafana-managed alert rules whenever possible, and opting for data source-managed alert rules when you need to scale your alerting setup.
|
||||
@@ -103,7 +109,7 @@ The table below compares Grafana-managed and data source-managed alert rules.
|
||||
|
||||
| <div style="width:200px">Feature</div> | <div style="width:200px">Grafana-managed alert rule</div> | <div style="width:200px">Data source-managed alert rule |
|
||||
| ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| Create alert rules<wbr /> that query [data sources supporting Alerting](#supported-data-sources) | Yes | No. Only query Prometheus-based data sources. |
|
||||
| Create alert rules<wbr /> that query [data sources supporting Alerting](#supported-data-sources) | Yes | Only supports creating rules for Mimir and Loki. |
|
||||
| Mix and match data sources | Yes | No |
|
||||
| Add [expressions](ref:expression-queries) to transform<wbr /> your data and set [alert conditions](ref:alert-condition) | Yes | No |
|
||||
| Use [images in alert notifications](ref:notification-images) | Yes | No |
|
||||
|
||||
@@ -17,7 +17,9 @@ weight: 400
|
||||
|
||||
# Grafana CLI
|
||||
|
||||
Grafana CLI is a small executable that is bundled with Grafana server. It can be executed on the same machine Grafana server is running on. Grafana CLI has `plugins` and `admin` commands, as well as global options.
|
||||
Grafana CLI is a small executable that's bundled with Grafana server.
|
||||
You can run it on the same machine Grafana server is running on.
|
||||
Grafana CLI has `plugins` and `admin` commands, as well as global options.
|
||||
|
||||
To list all commands and options:
|
||||
|
||||
@@ -25,13 +27,17 @@ To list all commands and options:
|
||||
grafana cli -h
|
||||
```
|
||||
|
||||
## Invoking Grafana CLI
|
||||
## Run Grafana CLI
|
||||
|
||||
To invoke Grafana CLI, add the path to the grafana binaries in your `PATH` environment variable. Alternately, if your current directory is the `bin` directory, use `./grafana cli`. Otherwise, you can specify full path to the CLI. For example, on Linux `/usr/share/grafana/bin/grafana` and on Windows `C:\Program Files\GrafanaLabs\grafana\bin\grafana.exe`, and invoke it with `grafana cli`.
|
||||
To run Grafana CLI, add the path to the Grafana binaries in your `PATH` environment variable.
|
||||
Alternately, if your current directory is the `bin` directory, run `./grafana cli`.
|
||||
Otherwise, you can specify full path to the binary.
|
||||
For example, on Linux `/usr/share/grafana/bin/grafana` and on Windows `C:\Program Files\GrafanaLabs\grafana\bin\grafana.exe`, and run it with `grafana cli`.
|
||||
|
||||
{{% admonition type="note" %}}
|
||||
Some commands, such as installing or removing plugins, require `sudo` on Linux. If you are on Windows, run Windows PowerShell as Administrator.
|
||||
{{% /admonition %}}
|
||||
{{< admonition type="note" >}}
|
||||
Some commands, such as installing or removing plugins, require `sudo` on Linux.
|
||||
If you're on Windows, run Windows PowerShell as Administrator.
|
||||
{{< /admonition >}}
|
||||
|
||||
## Grafana CLI command syntax
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ When sharing URLs with ad hoc filters, remember to encode the URL. In the preced
|
||||
|
||||
### Example
|
||||
|
||||
[This dashboard in Grafana Play](https://play.grafana.org/d/000000002/influxdb-templated?orgId=1&var-datacenter=America&var-host=All&var-summarize=1m&var-adhoc=datacenter%7C%3D%7CAmerica) passes the ad hoc filter variable `adhoc` with the filter value `datacenter = America`.
|
||||
[This dashboard in Grafana Play](https://play.grafana.org/d/p-k6QtkGz/template-redux?var-interval=$__auto&orgId=1&from=now-5m&to=now&timezone=utc&var-query=$__all&var-query2=$__all&var-query3=$__all&var-Filters=job%7C%3D%7Cmetrictank%2Ftsdb-gw&var-textbox=foo&var-custom=lisa&var-datasource=grafanacloud-demoinfra-prom) passes the ad hoc filter variable `Filters` with the filter value `job = metrictank/tsdb-gw`.
|
||||
|
||||
## Time range control using the URL
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ You can create a variety of queries to visualize logs or metrics stored in Elast
|
||||
For instructions on how to add a data source to Grafana, refer to the [administration documentation](ref:administration-documentation).
|
||||
|
||||
Only users with the organization `administrator` role can add data sources.
|
||||
Administrators can also [configure the data source via YAML](#provision-the-data-source) with Grafana's provisioning system.
|
||||
Administrators can also [configure the data source via YAML](ref:provisioning-data-sources) with Grafana's provisioning system.
|
||||
|
||||
## Configuring permissions
|
||||
|
||||
|
||||
@@ -31,6 +31,11 @@ refs:
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-data-links/#value-variables
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-data-links/#value-variables
|
||||
alerting-alert-rules:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/fundamentals/alert-rules/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/alert-rules/
|
||||
---
|
||||
|
||||
# Configure Prometheus
|
||||
@@ -115,7 +120,7 @@ Following are additional configuration options.
|
||||
|
||||
### Alerting
|
||||
|
||||
- **Manage alerts via Alerting UI** - Toggle to enable `Alertmanager` integration for this data source.
|
||||
- **Manage alerts via Alerting UI** - Toggle to enable [data source-managed rules in Grafana Alerting](ref:alerting-alert-rules) for this data source. For `Mimir`, it enables managing data source-managed rules and alerts. For `Prometheus`, it only supports viewing existing rules and alerts, which are displayed as data source-managed.
|
||||
|
||||
{{% admonition type="note" %}}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ In order to access Explore, you must have either the `editor` or `administrator`
|
||||
Refer to [Role-based access control (RBAC)](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/) in Grafana Enterprise to understand how you can manage Explore with role-based permissions.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
If you are using Grafana Cloud, open a [support ticket in the Cloud Portal](/https://grafana.com/auth/sign-in) to enable the `viewers_can_edit` option.
|
||||
If you are using Grafana Cloud, open a [support ticket in the Cloud Portal](https://grafana.com/auth/sign-in) to enable the `viewers_can_edit` option.
|
||||
{{< /admonition >}}
|
||||
|
||||
## Explore elements
|
||||
|
||||
@@ -51,7 +51,7 @@ refs:
|
||||
# Traces
|
||||
|
||||
Traces visualizations let you follow a request as it traverses the services in your infrastructure.
|
||||
The traces visualization displays traces data in a diagram that allows you to easily interpret it.
|
||||
The traces visualization displays traces data in a diagram that allows you to easily interpret it. Traces visualizations currently render one trace traversal based on the traceID used in TraceQL or using a variable.
|
||||
|
||||
For more information about traces and how to use them, refer to the following documentation:
|
||||
|
||||
|
||||
@@ -232,6 +232,8 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `k8SFolderCounts` | Enable folder's api server counts |
|
||||
| `k8SFolderMove` | Enable folder's api server move |
|
||||
| `teamHttpHeadersMimir` | Enables LBAC for datasources for Mimir to apply LBAC filtering of metrics to the client requests for users in teams |
|
||||
| `queryLibraryDashboards` | Enables Query Library feature in Dashboards |
|
||||
| `elasticsearchImprovedParsing` | Enables less memory intensive Elasticsearch result parsing |
|
||||
|
||||
## Development feature toggles
|
||||
|
||||
|
||||
10
docs/sources/shared/alerts/note-prometheus-ds-rules.md
Normal file
10
docs/sources/shared/alerts/note-prometheus-ds-rules.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
labels:
|
||||
products:
|
||||
- oss
|
||||
title: 'Note Prometheus data source-managed rules'
|
||||
---
|
||||
|
||||
> Rules from a Prometheus data source appear in the **Data source-managed** section of the **Alert rules** page when [Manage alerts via Alerting UI](ref:shared-configure-prometheus-data-source-alerting) is enabled.
|
||||
>
|
||||
> However, Grafana can only create and edit data source-managed rules for Mimir and Loki, not for a Prometheus instance.
|
||||
@@ -10,10 +10,10 @@ labels:
|
||||
- cloud
|
||||
tags:
|
||||
- beginner
|
||||
title: Get started with Grafana Alerting - Part 2 of 3
|
||||
title: Get started with Grafana Alerting - Part 2 of 4
|
||||
weight: 50
|
||||
killercoda:
|
||||
title: Get started with Grafana Alerting - Part 2 of 3
|
||||
title: Get started with Grafana Alerting - Part 2 of 4
|
||||
description: Learn to use alert instances and route notifications by labels to contacts, building on your alerting skills in Grafana for more advanced workflows — Part 2.
|
||||
backend:
|
||||
imageid: ubuntu
|
||||
@@ -21,7 +21,7 @@ killercoda:
|
||||
|
||||
<!-- INTERACTIVE page intro.md START -->
|
||||
|
||||
# Get started with Grafana Alerting - Part 2 of 3
|
||||
# Get started with Grafana Alerting - Part 2 of 4
|
||||
|
||||
The Get started with Grafana Alerting tutorial Part 2 is a continuation of [Get started with Grafana Alerting tutorial Part 1](http://www.grafana.com/tutorials/alerting-get-started/).
|
||||
|
||||
@@ -222,7 +222,7 @@ The alert rule that you are about to create is meant to monitor web traffic page
|
||||
|
||||
### Create an alert rule
|
||||
|
||||
1. Navigate to **Alerting > Alert rules**.
|
||||
1. Navigate to **Alerts & IRM > Alerting > Alert rules**.
|
||||
1. Click **New alert rule**.
|
||||
|
||||
### Enter an alert rule name
|
||||
@@ -260,18 +260,21 @@ It should return two series.`desktop` in Firing state, and `mobile` in Normal st
|
||||
<!-- INTERACTIVE page step6.md END -->
|
||||
<!-- INTERACTIVE page step7.md START -->
|
||||
|
||||
### Add folders and labels
|
||||
|
||||
1. In **Folder**, click **+ New folder** and enter a name. For example: `web-traffic-alerts` . This folder contains our alert rules.
|
||||
|
||||
### Set evaluation behavior
|
||||
|
||||
In the [life cycle](http://grafana.com/docs/grafana/next/alerting/fundamentals/alert-rule-evaluation/) of alert instances, when an alert condition (threshold) is not met, the alert instance state is **Normal**. Similarly, when the condition is breached (for longer than the pending period, which in this tutorial will be 0), the alert instance state switches back to **Alerting**, which means that the alert rule state is **Firing**, and a notification is sent.
|
||||
|
||||
To set up evaluation behavior:
|
||||
|
||||
1. In **Folder**, click **+ New folder** and enter a name. For example: `web-traffic-alerts`. This folder contains our alert rules.
|
||||
1. In **Evaluation group**, repeat the above step to create a new evaluation group. Name it `1m` (referring to “1 minute”).
|
||||
1. Choose an Evaluation interval (how often the alert will be evaluated). Choose `1m`.
|
||||
1. Set the pending period to `0s` (zero seconds), so the alert rule fires the moment the condition is met.
|
||||
1. In **Evaluation group and interval**, repeat the above step to create a new evaluation group. Name it `1m` (referring to “1 minute”).
|
||||
1. Choose an **Evaluation interval** (how often the alert will be evaluated). Choose `1m`.
|
||||
1. Set the **pending period** to `0s` (zero seconds), so the alert rule fires the moment the condition is met.
|
||||
|
||||
### Configure labels and notifications
|
||||
### Configure notifications
|
||||
|
||||
In this section, you can select how you want to route your alert instances. Since we want to route by notification policy, we need to ensure that the labels match the alert instance.
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ labels:
|
||||
- cloud
|
||||
tags:
|
||||
- intermediate
|
||||
title: Get started with Grafana Alerting - Part 3
|
||||
title: Get started with Grafana Alerting - Part 3 of 4
|
||||
weight: 60
|
||||
killercoda:
|
||||
title: Get started with Grafana Alerting - Part 3
|
||||
title: Get started with Grafana Alerting - Part 3 of 4
|
||||
description: Learn how to group alert notifications effectively to reduce noise and streamline communication in Grafana Alerting — Part 3.
|
||||
backend:
|
||||
imageid: ubuntu
|
||||
@@ -37,7 +37,7 @@ refs:
|
||||
|
||||
<!-- INTERACTIVE page intro.md START -->
|
||||
|
||||
# Get started with Grafana Alerting - Part 3
|
||||
# Get started with Grafana Alerting - Part 3 of 4
|
||||
|
||||
The Get started with Grafana Alerting tutorial Part 3 is a continuation of [Get started with Grafana Alerting tutorial Part 2](http://www.grafana.com/tutorials/alerting-get-started-pt2/).
|
||||
|
||||
@@ -338,7 +338,7 @@ Following the above example, [notification policies](ref:notification-policies)
|
||||
|
||||
In this section we configure an alert rule based on our application monitoring example.
|
||||
|
||||
1. Navigate to **Alerting > Alert rules**.
|
||||
1. Navigate to **Alerts & IRM > Alerting > Alert rules**.
|
||||
2. Click **New alert rule**.
|
||||
|
||||
### Enter an alert rule name
|
||||
@@ -383,16 +383,19 @@ Grafana includes a [test data source](https://grafana.com/docs/grafana/latest/da
|
||||
|
||||
{{< figure src="/media/docs/alerting/regions-alert-instance-preview.png" max-width="750px" alt="Preview of a query returning alert instances." >}}
|
||||
|
||||
### Add folders and labels
|
||||
|
||||
1. In **Folder**, click **+ New folder** and enter a name. For example: `Multi-region alerts` . This folder contains our alert rules.
|
||||
|
||||
### Set evaluation behavior
|
||||
|
||||
Every alert rule is assigned to an evaluation group. You can assign the alert rule to an existing evaluation group or create a new one.
|
||||
|
||||
1. In **Folder**, click **+ New folder** and enter a name. For example: `Multi-region alerts`. This folder contains our alert rules.
|
||||
1. In the **Evaluation group**, repeat the above step to create a new evaluation group. Name it `Multi-region group`.
|
||||
1. In the **Evaluation group and interval**, repeat the above step to create a new evaluation group. Name it `Multi-region group`.
|
||||
1. Choose an **Evaluation interval** (how often the alert are evaluated). Choose `1m`.
|
||||
1. Set the pending period to `0s` (zero seconds), so the alert rule fires the moment the condition is met (this minimizes the waiting time for the demonstration).
|
||||
1. Set the **pending period** to `0s` (zero seconds), so the alert rule fires the moment the condition is met (this minimizes the waiting time for the demonstration).
|
||||
|
||||
### Configure labels and notifications
|
||||
### Configure notifications
|
||||
|
||||
Select who should receive a notification when an alert rule fires.
|
||||
|
||||
@@ -508,4 +511,24 @@ _Detail of memory alert instances grouped into a separate notification for us-ea
|
||||
|
||||
By configuring **notification policies** and using **labels** (such as _region_), you can group alert notifications based on specific criteria and route them to the appropriate teams. Fine-tuning **timing options**—including group wait, group interval, and repeat interval—further can reduce noise and ensures notifications remain actionable without overwhelming on-call engineers.
|
||||
|
||||
## Learn more in [Grafana Alerting Part 4](http://www.grafana.com/tutorials/alerting-get-started-pt4/)
|
||||
|
||||
<!-- INTERACTIVE ignore START -->
|
||||
|
||||
{{< admonition type="tip" >}}
|
||||
|
||||
In [Get started with Grafana Alerting - Part 4](http://www.grafana.com/tutorials/alerting-get-started-pt4/) you learn how to use templates to create customized and concise notifications.
|
||||
|
||||
{{< /admonition >}}
|
||||
|
||||
<!-- INTERACTIVE ignore END -->
|
||||
|
||||
{{< docs/ignore >}}
|
||||
|
||||
In [Get started with Grafana Alerting - Part 4](http://www.grafana.com/tutorials/alerting-get-started-pt4/) you learn how to use templates to create customized and concise notifications.
|
||||
|
||||
{{< /docs/ignore >}}
|
||||
|
||||
<!-- INTERACTIVE page finish.md END -->
|
||||
|
||||
<!-- INTERACTIVE page finish.md END -->
|
||||
|
||||
421
docs/sources/tutorials/alerting-get-started-pt4/index.md
Normal file
421
docs/sources/tutorials/alerting-get-started-pt4/index.md
Normal file
@@ -0,0 +1,421 @@
|
||||
---
|
||||
Feedback Link: https://github.com/grafana/tutorials/issues/new
|
||||
categories:
|
||||
- alerting
|
||||
description: Learn how to use templates to create customized and concise notifications — Part 4
|
||||
labels:
|
||||
products:
|
||||
- enterprise
|
||||
- oss
|
||||
- cloud
|
||||
tags:
|
||||
- intermediate
|
||||
title: Get started with Grafana Alerting - Part 4
|
||||
weight: 60
|
||||
killercoda:
|
||||
title: Get started with Grafana Alerting - Part 4
|
||||
description: Learn how to use templates to create customized and concise notifications — Part 4.
|
||||
backend:
|
||||
imageid: ubuntu
|
||||
refs:
|
||||
alert-labels:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/fundamentals/alert-rules/annotation-label/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/alert-rules/annotation-label/
|
||||
template-labels-annotations:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/alerting-rules/templates/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/
|
||||
template-labels-annotations-ref:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/alerting-rules/templates/reference/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/reference/
|
||||
template-labels-annotations-ref-labels-variable:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/alerting-rules/templates/reference/#labels
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/reference/#labels
|
||||
template-labels-annotations-ref-values-variable:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/alerting-rules/templates/reference/#values
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/reference/#values
|
||||
template-labels-annotations-lang:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/alerting-rules/templates/language/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/language/
|
||||
template-notifications:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/configure-notifications/template-notifications/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/template-notifications/
|
||||
template-notifications-ref:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/configure-notifications/template-notifications/reference/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/template-notifications/reference/
|
||||
template-notifications-lang:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/configure-notifications/template-notifications/language/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/template-notifications/language/
|
||||
templates:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/fundamentals/templates/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/templates/
|
||||
---
|
||||
|
||||
<!-- INTERACTIVE page intro.md START -->
|
||||
|
||||
# Get started with Grafana Alerting - Part 4
|
||||
|
||||
The Get started with Grafana Alerting tutorial Part 4 is a continuation of [Get started with Grafana Alerting tutorial Part 3](http://www.grafana.com/tutorials/alerting-get-started-pt3/).
|
||||
|
||||
In this tutorial, you will learn:
|
||||
|
||||
- The two types of templates in Grafana Alerting: labels and annotations and notification templates.
|
||||
- How to configure alert rules with summary and description annotations.
|
||||
- How to create a notification template that integrates with alert rule annotations.
|
||||
- How to use a built-in notification template to group and format multiple alert instances.
|
||||
- How to preview alert notifications by leveraging alert instances in the notification template payload.
|
||||
|
||||
<!-- INTERACTIVE page intro.md END -->
|
||||
<!-- INTERACTIVE page step1.md START -->
|
||||
|
||||
<!-- INTERACTIVE ignore START -->
|
||||
|
||||
{{< docs/ignore >}}
|
||||
|
||||
## Set up the Grafana stack
|
||||
|
||||
{{< /docs/ignore >}}
|
||||
|
||||
## Before you begin
|
||||
|
||||
There are different ways you can follow along with this tutorial.
|
||||
|
||||
- **Grafana Cloud**
|
||||
|
||||
- As a Grafana Cloud user, you don't have to install anything. [Create your free account](http://www.grafana.com/auth/sign-up/create-user).
|
||||
|
||||
Continue to [how templating works](#how-templating-works).
|
||||
|
||||
- **Interactive learning environment**
|
||||
|
||||
- Alternatively, you can try out this example in our interactive learning environment: [Get started with Grafana Alerting - Part 4](https://killercoda.com/grafana-labs/course/grafana/alerting-get-started-pt4/). It's a fully configured environment with all the dependencies already installed.
|
||||
|
||||
- **Grafana OSS**
|
||||
|
||||
- If you opt to run a Grafana stack locally, ensure you have the following applications installed:
|
||||
|
||||
- [Docker Compose](https://docs.docker.com/get-docker/) (included in Docker for Desktop for macOS and Windows)
|
||||
- [Git](https://git-scm.com/)
|
||||
|
||||
### Set up the Grafana stack (OSS users)
|
||||
|
||||
<!-- INTERACTIVE ignore END -->
|
||||
|
||||
To demonstrate the observation of data using the Grafana stack, download and run the following files.
|
||||
|
||||
1. Clone the [tutorial environment repository](https://www.github.com/grafana/tutorial-environment).
|
||||
|
||||
<!-- INTERACTIVE exec START -->
|
||||
|
||||
```
|
||||
git clone https://github.com/grafana/tutorial-environment.git
|
||||
```
|
||||
|
||||
<!-- INTERACTIVE exec END -->
|
||||
|
||||
1. Change to the directory where you cloned the repository:
|
||||
|
||||
<!-- INTERACTIVE exec START -->
|
||||
|
||||
```
|
||||
cd tutorial-environment
|
||||
```
|
||||
|
||||
<!-- INTERACTIVE exec END -->
|
||||
|
||||
1. Run the Grafana stack:
|
||||
|
||||
<!-- INTERACTIVE ignore START -->
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
<!-- INTERACTIVE ignore END -->
|
||||
|
||||
{{< docs/ignore >}}
|
||||
|
||||
<!-- INTERACTIVE exec START -->
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
<!-- INTERACTIVE exec END -->
|
||||
|
||||
{{< /docs/ignore >}}
|
||||
|
||||
The first time you run `docker compose up -d`, Docker downloads all the necessary resources for the tutorial. This might take a few minutes, depending on your internet connection.
|
||||
|
||||
<!-- INTERACTIVE ignore START -->
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
If you already have Grafana, Loki, or Prometheus running on your system, you might see errors, because the Docker image is trying to use ports that your local installations are already using. If this is the case, stop the services, then run the command again.
|
||||
{{< /admonition >}}
|
||||
|
||||
<!-- INTERACTIVE ignore END -->
|
||||
|
||||
{{< docs/ignore >}}
|
||||
|
||||
NOTE:
|
||||
|
||||
If you already have Grafana, Loki, or Prometheus running on your system, you might see errors, because the Docker image is trying to use ports that your local installations are already using. If this is the case, stop the services, then run the command again.
|
||||
|
||||
{{< /docs/ignore >}}
|
||||
|
||||
<!-- INTERACTIVE page step1.md END -->
|
||||
<!-- INTERACTIVE page step2.md START -->
|
||||
|
||||
## How templating works
|
||||
|
||||
In Grafana, you can use [templates](https://grafana.com/docs/grafana/latest/alerting/fundamentals/templates/) to dynamically pull in specific data about the alert rule. This results in more flexible and informative alert notification messages. You can template either alert rule labels and annotations, or notification templates. Both use the Go template language.
|
||||
|
||||
{{< figure src="/media/docs/alerting/how-notification-templates-works.png" max-width="1200px" caption="How templating works" >}}
|
||||
|
||||
### Templating alert rule labels and annotations
|
||||
|
||||
[Labels and annotations](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/templates/) are key fields where templates are applied. One of the main advantages of using templating in annotations is the ability to incorporate dynamic data from queries, allowing alerts to reflect real-time information relevant to the triggered condition. By using templating in annotations, you can customize the content of each alert instance, such as including instance names and metric values, so the notification becomes more informative.
|
||||
|
||||
### Notification templates
|
||||
|
||||
The real power of templating lies in how it helps you format notifications with dynamic alert data. [Notification templates](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/template-notifications/) let you pull in details from annotations to create clear and consistent messages. They also make it simple to reuse the same format across different contact points, saving time and effort.
|
||||
|
||||
Notification templates allow you to customize how information is presented in each notification. For example, you can use templates to organize and format details about firing or resolved alerts, making it easier for recipients to understand the status of each alert at a glance—all within a single notification.
|
||||
|
||||
This particular notification template pulls in summary and description annotations for each alert instance and organizes them into separate sections, such as "firing" and "resolved." This way, instead of getting a long list of individual alert notifications, users can receive one well-structured message with all the relevant details grouped together.
|
||||
|
||||
This approach is helpful when you want to reduce notification noise, especially in situations where multiple instances of an alert are firing at the same time (e.g., high CPU usage across several instances). You can leverage templates to create a unified, easy-to-read notification that includes all the pertinent details.
|
||||
|
||||
<!-- INTERACTIVE page step2.md END -->
|
||||
<!-- INTERACTIVE page step3.md START -->
|
||||
|
||||
## Step 1: Template labels and annotations
|
||||
|
||||
Now that we've introduced how templating works, let’s move on to the next step. We guide you through creating an alert rule with a summary and description annotation. In doing so, we incorporate CPU usage and instance names, which we later use in our notification template.
|
||||
|
||||
### Create an alert rule
|
||||
|
||||
1. Sign in to Grafana:
|
||||
|
||||
- **Grafana Cloud** users: Log in via Grafana Cloud.
|
||||
- **OSS users**: Go to [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
1. Create an alert rule that includes a summary and description annotation:
|
||||
- Navigate to **Alerts & IRM > Alerting > Alert rules**.
|
||||
- Click **+ New alert rule**.
|
||||
- Enter an **alert rule name**. Name it `High CPU usage`
|
||||
1. **Define query an alert condition** section:
|
||||
|
||||
- Select TestData data source from the drop-down menu.
|
||||
|
||||
[TestData](https://grafana.com/docs/grafana/latest/datasources/testdata/) is included in the demo environment. If you’re working in Grafana Cloud or your own local Grafana instance, you can add the data source through the Connections menu.
|
||||
|
||||
- From **Scenario** select **CSV Content**.
|
||||
- Copy in the following CSV data:
|
||||
|
||||
```
|
||||
region,cpu-usage,service,instance
|
||||
us-west,88,web-server-1,server-01
|
||||
us-west,81,web-server-1,server-02
|
||||
us-east,79,web-server-2,server-03
|
||||
us-east,52,web-server-2,server-04
|
||||
```
|
||||
|
||||
This dataset simulates a data source returning multiple time series, with each time series generating a separate alert instance.
|
||||
|
||||
1. **Alert condition** section:
|
||||
|
||||
- Keep Last as the value for the reducer function (`WHEN`), and `75` as the threshold value, representing CPU usage above 75% .This is the value above which the alert rule should trigger.
|
||||
- Click **Preview alert rule condition** to run the queries.
|
||||
|
||||
It should return 3 series in Firing state, and 1 in Normal state.
|
||||
|
||||
{{< figure src="/media/docs/alerting/part-4-firing-instances-preview.png" max-width="1200px" caption="Preview of a query returning alert instances" >}}
|
||||
|
||||
1. Add folders and labels section:
|
||||
|
||||
- In **Folder**, click **+ New folder** and enter a name. For example: `System metrics` . This folder contains our alert rules.
|
||||
|
||||
Note: while it's possible to template labels here, in this tutorial, we focus on templating the summary and annotations fields instead.
|
||||
|
||||
1. **Set evaluation behaviour** section:
|
||||
- In the **Evaluation group and interval**, repeat the above step to create a new evaluation group. Name it `High usage`.
|
||||
- Choose an **Evaluation interval** (how often the alert will be evaluated). Choose `1m`.
|
||||
- Set the **pending period** to 0s (zero seconds), so the alert rule fires the moment the condition is met (this minimizes the waiting time for the demonstration.).
|
||||
1. **Configure notifications** section:
|
||||
|
||||
Select who should receive a notification when an alert rule fires.
|
||||
|
||||
- Select a **Contact point**. If you don’t have any contact points, click _View or create contact points_.
|
||||
|
||||
1. **Configure notification message** section:
|
||||
|
||||
In this step, you’ll configure the **summary** and **description** annotations to make your alert notifications informative and easy to understand. These annotations use templates to dynamically include key information about the alert.
|
||||
|
||||
- **Summary** annotation: Enter the following code as the value for the annotation.:
|
||||
|
||||
```go
|
||||
{{- "\n" -}}
|
||||
Instance: {{ index $labels "instance" }}
|
||||
{{- "\t" -}} Usage: {{ index $values "A"}}%{{- "\n" -}}
|
||||
```
|
||||
|
||||
This template automatically adds the instance name (from the [$labels](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/templates/reference/#labels) data) and its current CPU usage (from [$values["A"]](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/templates/reference/#values)) into the alert summary. `\t`: Adds a tab space between the instance name and the value. And, `\n`: Inserts a new line after the value.
|
||||
|
||||
Output example:
|
||||
|
||||
```
|
||||
server-01 88
|
||||
```
|
||||
|
||||
This output helps you quickly see which instance is affected and its usage level.
|
||||
|
||||
1. Optional: Add a description to help the on-call engineer to better understand what the alert rule is about. Eg. This alert monitors CPU usage across instances and triggers if any instance exceeds a usage threshold of 75%.
|
||||
1. Click **Save rule and exit**.
|
||||
|
||||
Now that we’ve configured an alert rule with dynamic templates for the **summary** annotation, the next step is to customize the alert notifications themselves. While the default notification message includes the summary annotation and works well, it can often be too verbose.
|
||||
|
||||
{{< figure src="/media/docs/alerting/templated-annotation-alert.png" max-width="1200px" caption="Default email alert notification with templated annotation" >}}
|
||||
|
||||
To make our alert notifications more concise and tailored to our needs, we’ll create a custom **notification template** that references the summary annotation we just set up. Notification templates are especially useful because they can be reused across multiple contact points, ensuring consistent alert messages.
|
||||
|
||||
<!-- INTERACTIVE page step3.md END -->
|
||||
<!-- INTERACTIVE page step4.md START -->
|
||||
|
||||
## Step 2: Template notifications
|
||||
|
||||
In this step, we use a built-in notification template to format alert notifications in a clear and organized way. Notification templates allow us to customize the structure of alert messages, making them easier to read and more relevant.
|
||||
|
||||
Without a notification template, the alert messages would include the default Grafana formatting (`default.message`, see image above).
|
||||
|
||||
### Adding a notification template:
|
||||
|
||||
1. Navigate to **Alerts & IRM** > **Alerting** > **Contact point**s.
|
||||
1. Select the **Notification Templates** tab.
|
||||
1. Click **+ Add notification template group**.
|
||||
1. Enter a name. E.g `instance-cpu-summary`.
|
||||
1. From the **Add example** dropdown menu, choose `Print firing and resolved alerts`.
|
||||
|
||||
This template prints out alert instances into two sections: **firing alerts** and **resolved alerts**, and includes only the key details for each. In addition, it adds our summary and description annotations.
|
||||
|
||||
```
|
||||
{{- /* Example displaying firing and resolved alerts separately in the notification. */ -}}
|
||||
{{- /* Edit the template name and template content as needed. */ -}}
|
||||
{{ define "custom.firing_and_resolved_alerts" -}}
|
||||
{{ len .Alerts.Resolved }} resolved alert(s)
|
||||
{{ range .Alerts.Resolved -}}
|
||||
{{ template "alert.summary_and_description" . -}}
|
||||
{{ end }}
|
||||
{{ len .Alerts.Firing }} firing alert(s)
|
||||
{{ range .Alerts.Firing -}}
|
||||
{{ template "alert.summary_and_description" . -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ define "alert.summary_and_description" }}
|
||||
Summary: {{.Annotations.summary}}
|
||||
Status: {{ .Status }}
|
||||
Description: {{.Annotations.description}}
|
||||
{{ end -}}
|
||||
```
|
||||
|
||||
{{< docs/ignore >}}
|
||||
Note: Your notification template name (`{{define "<NAME>"}}`) must be unique. You cannot have two templates with the same name in the same notification template group or in different notification template groups.
|
||||
{{< /docs/ignore >}}
|
||||
|
||||
<!-- INTERACTIVE ignore START -->
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
Your notification template name (`{{define "<NAME>"}}`) must be unique. You cannot have two templates with the same name in the same notification template group or in different notification template groups.
|
||||
{{< /admonition >}}
|
||||
|
||||
<!-- INTERACTIVE ignore END -->
|
||||
|
||||
Here’s a breakdown of the template:
|
||||
|
||||
- `{{ define "custom.firing_and_resolved_alerts" -}}` section: Displays the number of resolved alerts and their summaries, using the `alert.summary_and_description` template to include the summary, status, and description for each alert.
|
||||
- `.Alerts.Firing` section: Similarly lists the number of firing alert instances and their details.
|
||||
- `alert.summary_and_description`: This sub-template pulls the summary annotation you configured earlier.
|
||||
|
||||
In the **Preview** area, you can see a sample of how the notification would look. Since we’ve already created our alert rule, you can take it a step further by previewing how an actual alert instance from your rule would appear in the notification.
|
||||
|
||||
1. Click **Edit payload**.
|
||||
1. Click **Use existing alert instance**.
|
||||
|
||||
You should see our alert rule listed on the left.
|
||||
|
||||
1. Click the alert rule.
|
||||
1. Select an instance.
|
||||
1. Click **Add alert data to payload**.
|
||||
|
||||
The alert instance is added to the bottom of the preview.
|
||||
|
||||
{{< figure src="/media/docs/alerting/alert-instance-preview-in-template.png" max-width="1200px" caption="Preview of an alert instance in a notification template" >}}
|
||||
|
||||
1. Click **Save**.
|
||||
|
||||
With the notification template ready, the next step is to apply it to your contact point to see it in action.
|
||||
|
||||
<!-- INTERACTIVE page step4.md END -->
|
||||
<!-- INTERACTIVE page step5.md START -->
|
||||
|
||||
### Apply the template to your contact point
|
||||
|
||||
1. Apply the template to your contact point.
|
||||
- Navigate to **Alerts & IRM** > **Alerting** > **Contact points**.
|
||||
- Edit your contact point.
|
||||
1. **Optional** [email] **settings** section:
|
||||
- Click **Edit Message**.
|
||||
- Under **Select notification template**, search `custom.firing_and_resolved_alerts`.
|
||||
- Click **Save**.
|
||||
1. Save your contact point.
|
||||
|
||||
<!-- INTERACTIVE page step5.md END -->
|
||||
<!-- INTERACTIVE page step6.md START -->
|
||||
|
||||
### Receiving notifications
|
||||
|
||||
Now that the template has been applied to the contact point, you should receive notifications in the specified contact point.
|
||||
|
||||
Note: you might need to pause the alert rule evaluation and resume it to trigger the notification.
|
||||
|
||||
{{< figure src="/media/docs/alerting/templated-notification-cpu.png" max-width="1200px" caption="Templated email notification for CPU and memory usage" >}}
|
||||
|
||||
In the screen capture, you can see how the notification template groups the alert instances into two sections: **firing alerts** and **resolved alerts**. Each section includes only the key details for each alert, ensuring the message remains concise and focused. Additionally, the summary and description annotations we created earlier are included, providing affected instance and CPU usage.
|
||||
|
||||
<!-- INTERACTIVE page step6.md END -->
|
||||
<!-- INTERACTIVE page finish.md START -->
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this tutorial, we learned how to use templating in Grafana Alerting to create dynamic and actionable alert notifications. We explored how to configure alert rules with annotations, design custom notification templates, and apply them to contact points to enhance the clarity and efficiency of alert messages. By organizing alert instances into concise notifications, you can reduce noise and ensure on-call engineers quickly understand and address critical issues.
|
||||
|
||||
To deepen your understanding of Grafana’s templating, explore the following resources:
|
||||
|
||||
- **Overview of the functions and operators used in templates**:
|
||||
|
||||
- [Notification template language](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/template-notifications/language/)
|
||||
- [Alert rule template language](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/templates/language/)
|
||||
|
||||
- [**Notification template reference**](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/template-notifications/reference/): Lists the data available for use in notification templates and explores specific functions.
|
||||
- [**Alert rule template reference**](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/templates/reference/): Covers the specifics of creating dynamic labels and annotations for alert rules using elements such as variables and functions.
|
||||
|
||||
<!-- INTERACTIVE page finish.md END -->
|
||||
@@ -11,10 +11,10 @@ labels:
|
||||
- cloud
|
||||
tags:
|
||||
- beginner
|
||||
title: Get started with Grafana Alerting - Part 1 of 3
|
||||
title: Get started with Grafana Alerting - Part 1 of 4
|
||||
weight: 50
|
||||
killercoda:
|
||||
title: Get started with Grafana Alerting - Part 1 of 3
|
||||
title: Get started with Grafana Alerting - Part 1 of 4
|
||||
description: Get started with Grafana Alerting by creating your first alert rule, sending notifications to a webhook, and generating data to test it live — Part 1.
|
||||
backend:
|
||||
imageid: ubuntu
|
||||
@@ -22,7 +22,7 @@ killercoda:
|
||||
|
||||
<!-- INTERACTIVE page intro.md START -->
|
||||
|
||||
# Get started with Grafana Alerting - Part 1 of 3
|
||||
# Get started with Grafana Alerting - Part 1 of 4
|
||||
|
||||
In this guide, we walk you through the process of setting up your first alert in just a few minutes. You'll witness your alert in action with real-time data, as well as sending alert notifications.
|
||||
|
||||
@@ -181,7 +181,7 @@ We have created a dummy Webhook endpoint and created a new Alerting contact poin
|
||||
|
||||
Next, we establish an [alert rule](https://grafana.com/docs/grafana/latest/alerting/alerting-rules/create-grafana-managed-rule/) within Grafana Alerting to notify us whenever alert rules are triggered and resolved.
|
||||
|
||||
1. In Grafana, **navigate to Alerting** > **Alert rules**. Click on **New alert rule**.
|
||||
1. In Grafana, navigate to **Alerts & IRM > Alerting > Alert rules**. Click on **New alert rule**.
|
||||
|
||||
1. Enter alert rule name for your alert rule. Make it short and descriptive as this appears in your alert notification. For instance, **database-metrics**
|
||||
|
||||
@@ -203,6 +203,10 @@ Grafana includes a [test data source](https://grafana.com/docs/grafana/latest/da
|
||||
|
||||
{{< figure src="/media/docs/alerting/random-walk-firing-alert-rule.png" max-width="1200px" caption="A preview of a firing alert" >}}
|
||||
|
||||
### Add folders and labels
|
||||
|
||||
1. In **Folder**, click **+ New folder** and enter a name. For example: `metric-alerts` . This folder contains our alert rules.
|
||||
|
||||
### Set evaluation behavior
|
||||
|
||||
The [alert rule evaluation](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/rule-evaluation/) defines the conditions under which an alert rule triggers, based on the following settings:
|
||||
@@ -213,13 +217,12 @@ The [alert rule evaluation](https://grafana.com/docs/grafana/latest/alerting/fun
|
||||
|
||||
To set up the evaluation:
|
||||
|
||||
1. In **Folder**, click **+ New folder** and enter a name. For example: _metric-alerts_. This folder contains our alerts.
|
||||
1. In the **Evaluation group**, repeat the above step to create a new evaluation group. Name it _1m-evaluation_.
|
||||
1. In the **Evaluation group and interval**, repeat the above step to create a new evaluation group. Name it _1m-evaluation_.
|
||||
1. Choose an **Evaluation interval** (how often the alert are evaluated).
|
||||
For example, every `1m` (1 minute).
|
||||
1. Set the pending period to, `0s` (zero seconds), so the alert rule fires the moment the condition is met.
|
||||
1. Set the **pending period** to, `0s` (zero seconds), so the alert rule fires the moment the condition is met.
|
||||
|
||||
### Configure labels and notifications
|
||||
### Configure notifications
|
||||
|
||||
Choose the contact point where you want to receive your alert notifications.
|
||||
|
||||
|
||||
208
go.mod
208
go.mod
@@ -11,12 +11,14 @@ require (
|
||||
cuelang.org/go v0.11.1 // @grafana/grafana-as-code
|
||||
filippo.io/age v1.2.1 // @grafana/identity-access-team
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // @grafana/partner-datasources
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // @grafana/identity-access-team
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // @grafana/grafana-backend-group
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // @grafana/grafana-backend-group
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0 // @grafana/grafana-backend-group
|
||||
github.com/Azure/go-autorest/autorest v0.11.29 // @grafana/grafana-backend-group
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.24 // @grafana/grafana-backend-group
|
||||
github.com/BurntSushi/toml v1.4.0 // @grafana/identity-access-team
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 // @grafana/grafana-search-and-storage
|
||||
github.com/Masterminds/semver v1.5.0 // @grafana/grafana-backend-group
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // @grafana/grafana-developer-enablement-squad
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // @grafana/grafana-backend-group
|
||||
@@ -41,6 +43,7 @@ require (
|
||||
github.com/fatih/color v1.17.0 // @grafana/grafana-backend-group
|
||||
github.com/fullstorydev/grpchan v1.1.1 // @grafana/grafana-backend-group
|
||||
github.com/gchaincl/sqlhooks v1.3.0 // @grafana/grafana-search-and-storage
|
||||
github.com/getkin/kin-openapi v0.128.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/go-jose/go-jose/v3 v3.0.3 // @grafana/identity-access-team
|
||||
github.com/go-kit/log v0.2.1 // @grafana/grafana-backend-group
|
||||
github.com/go-ldap/ldap/v3 v3.4.4 // @grafana/identity-access-team
|
||||
@@ -59,6 +62,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.4 // @grafana/grafana-backend-group
|
||||
github.com/golang/snappy v0.0.4 // @grafana/alerting-backend
|
||||
github.com/google/go-cmp v0.6.0 // @grafana/grafana-backend-group
|
||||
github.com/google/go-querystring v1.1.0 // indirect; @grafana/oss-big-tent
|
||||
github.com/google/uuid v1.6.0 // @grafana/grafana-backend-group
|
||||
github.com/google/wire v0.6.0 // @grafana/grafana-backend-group
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 // @grafana/grafana-backend-group
|
||||
@@ -70,29 +74,25 @@ require (
|
||||
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
|
||||
github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics
|
||||
github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // @grafana/grafana-backend-group
|
||||
github.com/grafana/e2e v0.1.1 // @grafana-app-platform-squad
|
||||
github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad
|
||||
github.com/grafana/gomemcache v0.0.0-20240805133030-fdaf6a95408e // @grafana/grafana-operator-experience-squad
|
||||
github.com/grafana/grafana-app-sdk v0.23.1 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana-app-sdk v0.29.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana-app-sdk/logging v0.29.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana-aws-sdk v0.31.5 // @grafana/aws-datasources
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.2 // @grafana/partner-datasources
|
||||
github.com/grafana/grafana-cloud-migration-snapshot v1.6.0 // @grafana/grafana-operator-experience-squad
|
||||
github.com/grafana/grafana-google-sdk-go v0.2.1 // @grafana/partner-datasources
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.261.0 // @grafana/plugins-platform-backend
|
||||
github.com/grafana/grafana/pkg/aggregator v0.0.0-20240813192817-1b0e6b5c09b2 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/apiserver v0.0.0-20240821155123-6891eb1d35da // @grafana/grafana-app-platform-squad
|
||||
// This needs to be here for other projects that import grafana/grafana
|
||||
// For local development grafana/grafana will always use the local files
|
||||
// Check go.work file for details
|
||||
github.com/grafana/grafana/pkg/promlib v0.0.7 // @grafana/oss-big-tent
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.262.0 // @grafana/plugins-platform-backend
|
||||
github.com/grafana/loki/v3 v3.2.1 // @grafana/observability-logs
|
||||
github.com/grafana/otel-profiling-go v0.5.1 // @grafana/grafana-backend-group
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // @grafana/observability-traces-and-profiling
|
||||
github.com/grafana/pyroscope/api v1.0.0 // @grafana/observability-traces-and-profiling
|
||||
github.com/grafana/tempo v1.5.1-0.20241001135150-ed943d7a56b2 // @grafana/observability-traces-and-profiling
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // @grafana/plugins-platform-backend
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 // @grafana/grafana-backend-group
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // @grafana/identity-access-team
|
||||
github.com/hashicorp/go-hclog v1.6.3 // @grafana/plugins-platform-backend
|
||||
github.com/hashicorp/go-multierror v1.1.1 // @grafana/alerting-squad
|
||||
github.com/hashicorp/go-plugin v1.6.2 // @grafana/plugins-platform-backend
|
||||
@@ -101,7 +101,10 @@ require (
|
||||
github.com/hashicorp/hcl/v2 v2.17.0 // @grafana/alerting-backend
|
||||
github.com/huandu/xstrings v1.5.0 // @grafana/partner-datasources
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.13.0 // @grafana/partner-datasources
|
||||
github.com/influxdata/influxql v1.4.0 // @grafana/partner-datasources
|
||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // @grafana/grafana-app-platform-squad
|
||||
github.com/jeremywohl/flatten v1.0.1 // @grafana/grafana-app-platform-squad
|
||||
github.com/jmespath-community/go-jmespath v1.1.1 // @grafana/identity-access-team
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect; @grafana/grafana-backend-group
|
||||
github.com/jmoiron/sqlx v1.3.5 // @grafana/grafana-backend-group
|
||||
github.com/json-iterator/go v1.1.12 // @grafana/grafana-backend-group
|
||||
@@ -123,7 +126,9 @@ require (
|
||||
github.com/openfga/api/proto v0.0.0-20240906203051-102620ef2a66 // @grafana/identity-access-team
|
||||
github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20240926131254-992b301a003f // @grafana/identity-access-team
|
||||
github.com/openfga/openfga v1.6.2 // @grafana/identity-access-team
|
||||
github.com/openzipkin/zipkin-go v0.4.3 // @grafana/oss-big-tent
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // @grafana/alerting-backend
|
||||
github.com/phpdave11/gofpdi v1.0.13 // @grafana/sharing-squad
|
||||
github.com/prometheus/alertmanager v0.27.0 // @grafana/alerting-backend
|
||||
github.com/prometheus/client_golang v1.20.5 // @grafana/alerting-backend
|
||||
github.com/prometheus/client_model v0.6.1 // @grafana/grafana-backend-group
|
||||
@@ -131,6 +136,7 @@ require (
|
||||
github.com/prometheus/prometheus v0.301.0 // @grafana/alerting-backend
|
||||
github.com/redis/go-redis/v9 v9.6.1 // @grafana/alerting-backend
|
||||
github.com/robfig/cron/v3 v3.0.1 // @grafana/grafana-backend-group
|
||||
github.com/rs/cors v1.11.1 // @grafana/identity-access-team
|
||||
github.com/russellhaering/goxmldsig v1.4.0 // @grafana/grafana-backend-group
|
||||
github.com/spf13/cobra v1.8.1 // @grafana/grafana-app-platform-squad
|
||||
github.com/spf13/pflag v1.0.5 // @grafana-app-platform-squad
|
||||
@@ -157,6 +163,7 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.33.0 // @grafana/grafana-backend-group
|
||||
go.uber.org/atomic v1.11.0 // @grafana/alerting-backend
|
||||
go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage
|
||||
go.uber.org/zap v1.27.0 // @grafana/identity-access-team
|
||||
gocloud.dev v0.40.0 // @grafana/grafana-app-platform-squad
|
||||
golang.org/x/crypto v0.32.0 // @grafana/grafana-backend-group
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // @grafana/alerting-backend
|
||||
@@ -190,13 +197,33 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20241209165425-c324376999f7 // @grafana/alerting-backend
|
||||
github.com/grafana/grafana/apps/investigation v0.0.0-20241218083103-f46c07aba7b6 // @fcjack @matryer
|
||||
github.com/grafana/grafana/apps/playlist v0.0.0-20241105090059-facca37f4d1f // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/aggregator v0.0.0-20240813192817-1b0e6b5c09b2 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/apiserver v0.0.0-20240821155123-6891eb1d35da // @grafana/grafana-app-platform-squad
|
||||
|
||||
// This needs to be here for other projects that import grafana/grafana
|
||||
// For local development grafana/grafana will always use the local files
|
||||
// Check go.work file for details
|
||||
github.com/grafana/grafana/pkg/promlib v0.0.7 // @grafana/oss-big-tent
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20240821183201-2f012860344d // @grafana/grafana-search-and-storage
|
||||
github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240821161612-71f0dae39e9d // @grafana/grafana-search-and-storage
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.18.0 // indirect
|
||||
cloud.google.com/go v0.116.0 // indirect
|
||||
cloud.google.com/go/auth v0.13.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
cloud.google.com/go/iam v1.2.1 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.1 // indirect
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // @grafana/identity-access-team
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
@@ -207,8 +234,8 @@ require (
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 // @grafana/grafana-search-and-storage
|
||||
github.com/FZambia/eagle v0.1.0 // indirect
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
@@ -221,25 +248,42 @@ require (
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/at-wat/mqtt-go v0.19.4 // indirect
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.12.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.1.12 // indirect
|
||||
github.com/blevesearch/geo v0.1.20 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.23 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.2.16 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
github.com/blevesearch/vellum v1.0.10 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.3.16 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.1.8 // indirect
|
||||
github.com/blugelabs/ice v1.0.0 // indirect
|
||||
github.com/blugelabs/ice/v2 v2.0.1 // indirect
|
||||
github.com/bufbuild/protocompile v0.4.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 // indirect
|
||||
github.com/caio/go-tdigest v3.1.0+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/centrifugal/protocol v0.13.4 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
@@ -248,18 +292,22 @@ require (
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/edsrzf/mmap-go v1.2.0 // indirect
|
||||
github.com/elazarl/goproxy v1.2.6 // indirect
|
||||
github.com/elazarl/goproxy v1.3.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/emicklei/proto v1.13.2 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
|
||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/gammazero/deque v0.2.1 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect; @grafana/grafana-app-platform-squad
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.0 // indirect
|
||||
@@ -275,6 +323,7 @@ require (
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/cel-go v0.22.0 // indirect
|
||||
@@ -283,23 +332,27 @@ require (
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20240821183201-2f012860344d // @grafana/grafana-search-and-storage
|
||||
github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240821161612-71f0dae39e9d // @grafana/grafana-search-and-storage
|
||||
github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32 // indirect
|
||||
github.com/grafana/loki/pkg/push v0.0.0-20231124142027-e52380921608 // indirect
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
||||
github.com/grafana/sqlds/v4 v4.1.3 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect; @grafana/plugins-platform-backend
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // @grafana/identity-access-team
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
|
||||
github.com/hashicorp/consul/api v1.30.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-msgpack v1.1.5 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.6 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/memberlist v0.5.0 // indirect
|
||||
github.com/hashicorp/serf v0.10.1 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/invopop/jsonschema v0.12.0 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/invopop/yaml v0.3.1 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
@@ -311,13 +364,13 @@ require (
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||
github.com/jeremywohl/flatten v1.0.1 // @grafana/grafana-app-platform-squad
|
||||
github.com/jessevdk/go-flags v1.5.0 // indirect
|
||||
github.com/jhump/protoreflect v1.15.1 // indirect
|
||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect
|
||||
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||
github.com/karlseguin/ccache/v3 v3.0.5 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
@@ -331,6 +384,9 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.12 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/maypok86/otter v1.2.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/mdlayher/vsock v1.2.1 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/miekg/dns v1.1.62 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
@@ -353,34 +409,44 @@ require (
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/oklog/ulid/v2 v2.1.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e // indirect
|
||||
github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/pressly/goose/v3 v3.22.1 // indirect
|
||||
github.com/prometheus/common/sigv4 v0.1.0 // indirect
|
||||
github.com/prometheus/exporter-toolkit v0.13.2 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/prometheus/sigv4 v0.1.0 // indirect
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d // indirect
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
|
||||
github.com/redis/rueidis v1.0.45 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rs/cors v1.11.1 // @grafana/identity-access-team
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/segmentio/encoding v0.4.0 // indirect
|
||||
github.com/sercand/kuberesolver/v5 v5.1.1 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||
github.com/shadowspore/fossil-delta v0.0.0-20240102155221-e3a8590b820b // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
|
||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
|
||||
github.com/sony/gobreaker v0.5.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
@@ -395,34 +461,41 @@ require (
|
||||
github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
||||
github.com/yudai/pp v2.0.1+incompatible // indirect
|
||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||
github.com/zclconf/go-cty v1.13.0 // indirect
|
||||
github.com/zeebo/xxh3 v1.0.2 // indirect
|
||||
go.etcd.io/bbolt v1.3.11 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.16 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.16 // indirect
|
||||
go.mongodb.org/mongo-driver v1.16.1 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.33.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // @grafana/identity-access-team
|
||||
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect; @grafana/grafana-backend-group
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241216192217-9240e9c98484 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect; @grafana/plugins-platform-backend
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.32.0 // indirect
|
||||
k8s.io/kms v0.32.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.55.3 // indirect
|
||||
@@ -433,92 +506,7 @@ require (
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect; @grafana-app-platform-squad
|
||||
)
|
||||
|
||||
require github.com/phpdave11/gofpdi v1.0.13 // @grafana/sharing-squad
|
||||
|
||||
require (
|
||||
github.com/google/go-querystring v1.1.0 // indirect; @grafana/oss-big-tent
|
||||
github.com/grafana/e2e v0.1.1 // @grafana-app-platform-squad
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect; indirect0.0.0-20240809095826-8eb5495c0b2a
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/getkin/kin-openapi v0.128.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/apps/playlist v0.0.0-20241105090059-facca37f4d1f // @grafana/grafana-app-platform-squad
|
||||
github.com/influxdata/influxql v1.4.0 // @grafana/partner-datasources
|
||||
)
|
||||
|
||||
require github.com/jmespath-community/go-jmespath v1.1.1 // @grafana/identity-access-team
|
||||
|
||||
require github.com/grafana/loki/v3 v3.2.1 // @grafana/observability-logs
|
||||
|
||||
require github.com/openzipkin/zipkin-go v0.4.3 // @grafana/oss-big-tent
|
||||
|
||||
require github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20241209165425-c324376999f7 // @grafana/alerting-backend
|
||||
|
||||
require github.com/grafana/grafana/apps/investigation v0.0.0-20241218083103-f46c07aba7b6 // @fcjack @matryer
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.18.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.1 // indirect
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
|
||||
github.com/at-wat/mqtt-go v0.19.4 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.1.12 // indirect
|
||||
github.com/blevesearch/geo v0.1.20 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.23 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.2.16 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.3.16 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.1.8 // indirect
|
||||
github.com/blugelabs/ice/v2 v2.0.1 // indirect
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/gammazero/deque v0.2.1 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32 // indirect
|
||||
github.com/grafana/loki/pkg/push v0.0.0-20231124142027-e52380921608 // indirect
|
||||
github.com/grafana/sqlds/v4 v4.1.3 // indirect
|
||||
github.com/hashicorp/consul/api v1.30.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/serf v0.10.1 // indirect
|
||||
github.com/maypok86/otter v1.2.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/mdlayher/vsock v1.2.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/prometheus/sigv4 v0.1.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/sercand/kuberesolver/v5 v5.1.1 // indirect
|
||||
github.com/shadowspore/fossil-delta v0.0.0-20240102155221-e3a8590b820b // indirect
|
||||
github.com/sony/gobreaker v0.5.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.11 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 // indirect
|
||||
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.32.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
// Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream
|
||||
|
||||
21
go.sum
21
go.sum
@@ -1067,8 +1067,8 @@ github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8E
|
||||
github.com/efficientgo/core v1.0.0-rc.3 h1:X6CdgycYWDcbYiJr1H1+lQGzx13o7bq3EUkbB9DsSPc=
|
||||
github.com/efficientgo/core v1.0.0-rc.3/go.mod h1:FfGdkzWarkuzOlY04VY+bGfb1lWrjaL6x/GLcQ4vJps=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elazarl/goproxy v1.2.6 h1:jk1cwYYz96HhB985lQ1FFv7UcYVQHV84w8lWtpxW7WE=
|
||||
github.com/elazarl/goproxy v1.2.6/go.mod h1:yBhqz1/IaNA5tCayHGVfFmuzyanF6YeDNGIwPhfvtp8=
|
||||
github.com/elazarl/goproxy v1.3.0 h1:hpDH1r1qJgM3eusz7lP+BiMPnLiWPa6hDjIFF5WVCjE=
|
||||
github.com/elazarl/goproxy v1.3.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
@@ -1507,8 +1507,10 @@ github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 h1:jxJJ5z0GxqhWFbQU
|
||||
github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447/go.mod h1:IxsY6mns6Q5sAnWcrptrgUrSglTZJXH/kXr9nbpb/9I=
|
||||
github.com/grafana/gomemcache v0.0.0-20240805133030-fdaf6a95408e h1:UlEET0InuoFautfaFp8lDrNF7rPHYXuBMrzwWx9XqFY=
|
||||
github.com/grafana/gomemcache v0.0.0-20240805133030-fdaf6a95408e/go.mod h1:IGRj8oOoxwJbHBYl1+OhS9UjQR0dv6SQOep7HqmtyFU=
|
||||
github.com/grafana/grafana-app-sdk v0.23.1 h1:BRpUG0bA0oVxjthkmO2thuJBo3nbjaRSSmZJHw+mA8I=
|
||||
github.com/grafana/grafana-app-sdk v0.23.1/go.mod h1:KzgPnTJfMeckGmMctv6CJb8Jr/o/5rwARDyjXoeR0Fc=
|
||||
github.com/grafana/grafana-app-sdk v0.29.0 h1:LMSm/+0LOBPd13fe1bs/4sKJmuLiixYUX9T0oqDqp4I=
|
||||
github.com/grafana/grafana-app-sdk v0.29.0/go.mod h1:XLt308EmK6kvqPlzjUyXxbwZKEk2vur/eiypUNDay5I=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.29.0 h1:mgbXaAf33aFwqwGVeaX30l8rkeAJH0iACgX5Rn6YkN4=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.29.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM=
|
||||
github.com/grafana/grafana-aws-sdk v0.31.5 h1:4HpMQx7n4Qqoi7Bgu8KHQ2QKT9fYYdHilX/Gh3FZKBE=
|
||||
github.com/grafana/grafana-aws-sdk v0.31.5/go.mod h1:5p4Cjyr5ZiR6/RT2nFWkJ8XpIKgX4lAUmUMu70m2yCM=
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.2 h1:fV6IgVtViXcYZ4VqTAMuVBTLuGAnI27HhQkaLttzbPE=
|
||||
@@ -1519,8 +1521,8 @@ github.com/grafana/grafana-google-sdk-go v0.2.1 h1:XeFdKnkXBjOJjXc1gf4iMx4h5aCHT
|
||||
github.com/grafana/grafana-google-sdk-go v0.2.1/go.mod h1:RiITSHwBhqVTTd3se3HQq5Ncs/wzzhTB9OK5N0J0PEU=
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs=
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.261.0 h1:pGGpPbKRWZcLxwNATEiVDhILbYGYwlWOEXFLhmUkBMo=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.261.0/go.mod h1:QsLK0kAbmDXuX/QncFBTETPHCzw5g9hZnzqOPkoB3Yo=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.262.0 h1:R2DV6lwBQE5zaogxX3PorD9Seo8CXA8YuStf84oqwkk=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.262.0/go.mod h1:U43Cnrj/9DNYyvFcNdeUWNjMXTKNB0jcTcQGpWKd2gw=
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20241209165425-c324376999f7 h1:JFB5dvs0XwBh/RiDNA5OrqcF3eWCQmTYBm6Hy79PDMQ=
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20241209165425-c324376999f7/go.mod h1:AVvGgNqHsruJINRjKkhhY5NZMh5ke6Ei2bywuQ4Uuus=
|
||||
github.com/grafana/grafana/apps/investigation v0.0.0-20241218083103-f46c07aba7b6 h1:KsHIuuPGww1U0G2CB1JYTasDaboJn0Cq91Je1aluEuc=
|
||||
@@ -1690,8 +1692,8 @@ github.com/influxdata/influxql v1.4.0 h1:Lf62rbAF8KWQf+4Djqf4hVXgmQuGozUoSD6kNWj
|
||||
github.com/influxdata/influxql v1.4.0/go.mod h1:VqxAKyQz5p8GzgGsxWalCWYGxEqw6kvJo2IickMQiQk=
|
||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU=
|
||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||
github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
|
||||
github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
||||
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
||||
github.com/ionos-cloud/sdk-go/v6 v6.3.0 h1:/lTieTH9Mo/CWm3cTlFLnK10jgxjUGkAqRffGqvPteY=
|
||||
@@ -1758,8 +1760,9 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ=
|
||||
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
|
||||
1
go.work
1
go.work
@@ -8,7 +8,6 @@ use (
|
||||
./apps/alerting/notifications
|
||||
./apps/investigation
|
||||
./apps/playlist
|
||||
./kindsv2
|
||||
./pkg/aggregator
|
||||
./pkg/apimachinery
|
||||
./pkg/apiserver
|
||||
|
||||
449
go.work.sum
449
go.work.sum
File diff suppressed because it is too large
Load Diff
8
kindsv2/Makefile
Normal file
8
kindsv2/Makefile
Normal file
@@ -0,0 +1,8 @@
|
||||
include ../.bingo/Variables.mk
|
||||
|
||||
.PHONY: all
|
||||
all: dashboards
|
||||
|
||||
.PHONY: dashboards
|
||||
dashboards: $(COG) ## Dashboards – Typescript
|
||||
@$(COG) generate --config ./dashboard-ts.yaml
|
||||
22
kindsv2/dashboard-ts.yaml
Normal file
22
kindsv2/dashboard-ts.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/grafana/cog/main/schemas/pipeline.json
|
||||
|
||||
inputs:
|
||||
- cue:
|
||||
entrypoint: '%__config_dir%/../packages/grafana-schema/src/schema/dashboard/v2alpha0'
|
||||
metadata:
|
||||
kind: core
|
||||
cue_imports:
|
||||
- '%__config_dir%/../packages/grafana-schema/src/common:github.com/grafana/grafana/packages/grafana-schema/src/common'
|
||||
|
||||
output:
|
||||
directory: '%__config_dir%/../packages/grafana-schema/src/schema/dashboard/'
|
||||
|
||||
types: true
|
||||
|
||||
languages:
|
||||
- typescript:
|
||||
skip_runtime: true
|
||||
enums_as_union_types: true
|
||||
path_prefix: ""
|
||||
packages_import_map:
|
||||
common: '@grafana/schema'
|
||||
@@ -1,53 +0,0 @@
|
||||
//go:generate go run gen.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/grafana/cog"
|
||||
)
|
||||
|
||||
type codegenTargets struct {
|
||||
modulePath string
|
||||
outputPath string
|
||||
cueImportsMap map[string]string
|
||||
packagesImportMap map[string]string
|
||||
}
|
||||
|
||||
func main() {
|
||||
targets := []codegenTargets{
|
||||
{
|
||||
modulePath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/",
|
||||
outputPath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts",
|
||||
cueImportsMap: map[string]string{
|
||||
"github.com/grafana/grafana/packages/grafana-schema/src/common": "../packages/grafana-schema/src/common",
|
||||
},
|
||||
packagesImportMap: map[string]string{
|
||||
"common": "@grafana/schema",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
codegenPipeline := cog.TypesFromSchema().
|
||||
CUEModule(
|
||||
target.modulePath,
|
||||
cog.CUEImports(target.cueImportsMap),
|
||||
).
|
||||
Typescript(cog.TypescriptConfig{
|
||||
ImportsMap: target.packagesImportMap,
|
||||
EnumsAsUnionTypes: true,
|
||||
})
|
||||
|
||||
files, err := codegenPipeline.Run(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(target.outputPath, files[0].Data, 0644); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
module github.com/grafana/grafana/kindsv2
|
||||
|
||||
go 1.23.1
|
||||
|
||||
require github.com/grafana/cog v0.0.5
|
||||
|
||||
require (
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 // indirect
|
||||
cuelang.org/go v0.11.1 // indirect
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/proto v1.13.2 // indirect
|
||||
github.com/expr-lang/expr v1.16.9 // indirect
|
||||
github.com/getkin/kin-openapi v0.128.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/invopop/yaml v0.3.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/yalue/merged_fs v1.3.0 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.24.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
20
package.json
20
package.json
@@ -50,7 +50,7 @@
|
||||
"themes-generate": "esbuild --target=es6 ./scripts/cli/generateSassVariableFiles.ts --bundle --platform=node --tsconfig=./scripts/cli/tsconfig.json | node",
|
||||
"themes:usage": "eslint . --ignore-pattern '*.test.ts*' --ignore-pattern '*.spec.ts*' --cache --plugin '@grafana' --rule '{ @grafana/theme-token-usage: \"error\" }'",
|
||||
"typecheck": "tsc --noEmit && yarn run packages:typecheck",
|
||||
"plugins:build-bundled": "find plugins-bundled -name package.json -not -path '*/node_modules/*' -execdir yarn build \\;",
|
||||
"plugins:build-bundled": "echo 'bundled plugins are no longer supported'",
|
||||
"watch": "yarn start -d watch,start core:start --watchTheme",
|
||||
"ci:test-frontend": "yarn run test:ci",
|
||||
"i18n:stats": "node ./scripts/cli/reportI18nStats.mjs",
|
||||
@@ -123,7 +123,7 @@
|
||||
"@types/lodash": "4.17.14",
|
||||
"@types/logfmt": "^1.2.3",
|
||||
"@types/lucene": "^2",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/node": "22.10.6",
|
||||
"@types/node-forge": "^1",
|
||||
"@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.2.4",
|
||||
"@types/pluralize": "^0.0.33",
|
||||
@@ -213,7 +213,7 @@
|
||||
"ngtemplate-loader": "2.1.0",
|
||||
"node-notifier": "10.0.1",
|
||||
"nx": "19.8.2",
|
||||
"postcss": "8.4.49",
|
||||
"postcss": "8.5.1",
|
||||
"postcss-loader": "8.1.1",
|
||||
"postcss-reporter": "7.1.0",
|
||||
"postcss-scss": "4.0.9",
|
||||
@@ -224,7 +224,7 @@
|
||||
"redux-mock-store": "1.5.5",
|
||||
"rimraf": "6.0.1",
|
||||
"rudder-sdk-js": "2.48.43",
|
||||
"sass": "1.83.1",
|
||||
"sass": "1.83.4",
|
||||
"sass-loader": "16.0.4",
|
||||
"smtp-tester": "^2.1.0",
|
||||
"style-loader": "4.0.0",
|
||||
@@ -265,14 +265,14 @@
|
||||
"@grafana/faro-web-tracing": "^1.8.2",
|
||||
"@grafana/flamegraph": "workspace:*",
|
||||
"@grafana/google-sdk": "0.1.2",
|
||||
"@grafana/lezer-logql": "0.2.6",
|
||||
"@grafana/monaco-logql": "^0.0.7",
|
||||
"@grafana/lezer-logql": "0.2.7",
|
||||
"@grafana/monaco-logql": "^0.0.8",
|
||||
"@grafana/o11y-ds-frontend": "workspace:*",
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/saga-icons": "workspace:*",
|
||||
"@grafana/scenes": "5.36.4",
|
||||
"@grafana/scenes-react": "5.36.4",
|
||||
"@grafana/scenes": "5.37.0",
|
||||
"@grafana/scenes-react": "5.37.0",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
@@ -359,8 +359,7 @@
|
||||
"pluralize": "^8.0.0",
|
||||
"prismjs": "1.29.0",
|
||||
"rc-slider": "11.1.8",
|
||||
"rc-time-picker": "3.7.3",
|
||||
"rc-tree": "5.11.0",
|
||||
"rc-tree": "5.13.0",
|
||||
"re-resizable": "6.10.3",
|
||||
"react": "18.2.0",
|
||||
"react-diff-viewer-continued": "^3.4.0",
|
||||
@@ -437,7 +436,6 @@
|
||||
"packages": [
|
||||
"packages/*",
|
||||
"packages/!(grafana-icons)/**",
|
||||
"plugins-bundled/internal/*",
|
||||
"public/app/plugins/*/*",
|
||||
"e2e/test-plugins/*"
|
||||
]
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"moment": "2.30.1",
|
||||
"moment-timezone": "0.5.46",
|
||||
"ol": "7.4.0",
|
||||
"papaparse": "5.4.1",
|
||||
"papaparse": "5.5.1",
|
||||
"react-use": "17.6.0",
|
||||
"rxjs": "7.8.1",
|
||||
"string-hash": "^1.1.3",
|
||||
@@ -65,7 +65,7 @@
|
||||
"@rollup/plugin-node-resolve": "16.0.0",
|
||||
"@types/history": "4.7.11",
|
||||
"@types/lodash": "4.17.14",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/node": "22.10.6",
|
||||
"@types/papaparse": "5.3.15",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.2.25",
|
||||
|
||||
@@ -254,6 +254,29 @@ describe('Stats Calculators', () => {
|
||||
expect(reduce(someNulls, ReducerID.count)).toEqual(4);
|
||||
});
|
||||
|
||||
it('median should ignoreNulls by default', () => {
|
||||
const someNulls = createField('y', [3, null, 2, 1, 4]);
|
||||
expect(reduce(someNulls, ReducerID.median)).toEqual(2.5);
|
||||
});
|
||||
|
||||
it('median should use fieldConfig nullValueMode.Ignore and not count nulls', () => {
|
||||
const someNulls = createField('y', [3, null, 2, 1, 4]);
|
||||
someNulls.config.nullValueMode = NullValueMode.Ignore;
|
||||
expect(reduce(someNulls, ReducerID.median)).toEqual(2.5);
|
||||
});
|
||||
|
||||
it('median should use fieldConfig nullValueMode.Null and count nulls', () => {
|
||||
const someNulls = createField('y', [3, null, 2, 1, 4]);
|
||||
someNulls.config.nullValueMode = NullValueMode.Null;
|
||||
expect(reduce(someNulls, ReducerID.median)).toEqual(2);
|
||||
});
|
||||
|
||||
it('median should use fieldConfig nullValueMode.AsZero and count nulls as zero', () => {
|
||||
const someNulls = createField('y', [3, null, 2, 1, 4]);
|
||||
someNulls.config.nullValueMode = NullValueMode.AsZero;
|
||||
expect(reduce(someNulls, ReducerID.median)).toEqual(2);
|
||||
});
|
||||
|
||||
it('can reduce to percentiles', () => {
|
||||
// This `Array.from` will build an array of elements from 1 to 99
|
||||
const percentiles = [...Array.from({ length: 99 }, (_, i) => i + 1)];
|
||||
|
||||
@@ -283,7 +283,8 @@ export const fieldReducers = new Registry<FieldReducerInfo>(() => [
|
||||
id: ReducerID.median,
|
||||
name: 'Median',
|
||||
description: 'Median Value',
|
||||
standard: true,
|
||||
standard: false,
|
||||
reduce: calculateMedian,
|
||||
aliasIds: ['median'],
|
||||
preservesUnits: true,
|
||||
},
|
||||
@@ -584,6 +585,7 @@ export function doStandardCalcs(field: Field, ignoreNulls: boolean, nullAsZero:
|
||||
if (isNumber(calcs.firstNotNull) && isNumber(calcs.diff)) {
|
||||
calcs.diffperc = (calcs.diff / calcs.firstNotNull) * 100;
|
||||
}
|
||||
|
||||
return calcs;
|
||||
}
|
||||
|
||||
@@ -703,3 +705,32 @@ function calculatePercentile(field: Field, percentile: number, ignoreNulls: bool
|
||||
const index = Math.round((sorted.length - 1) * percentile);
|
||||
return sorted[index];
|
||||
}
|
||||
|
||||
function calculateMedian(field: Field<number>, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs {
|
||||
const numbers: number[] = [];
|
||||
|
||||
for (let i = 0; i < field.values.length; i++) {
|
||||
let currentValue = field.values[i];
|
||||
|
||||
if (currentValue == null) {
|
||||
if (ignoreNulls) {
|
||||
continue;
|
||||
}
|
||||
if (nullAsZero) {
|
||||
currentValue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
numbers.push(currentValue);
|
||||
}
|
||||
|
||||
numbers.sort((a, b) => a - b);
|
||||
|
||||
const mid = Math.floor(numbers.length / 2);
|
||||
|
||||
if (numbers.length % 2 === 0) {
|
||||
return { median: (numbers[mid - 1] + numbers[mid]) / 2 };
|
||||
} else {
|
||||
return { median: numbers[mid] };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,10 @@ describe('ensureColumns transformer', () => {
|
||||
options: {},
|
||||
};
|
||||
|
||||
const data = [seriesA, seriesBC];
|
||||
const data = [
|
||||
{ refId: 'A', ...seriesA },
|
||||
{ refId: 'B', ...seriesBC },
|
||||
];
|
||||
|
||||
await expect(transformDataFrame([cfg], data)).toEmitValuesWith((received) => {
|
||||
const filtered = received[0];
|
||||
@@ -109,6 +112,7 @@ describe('ensureColumns transformer', () => {
|
||||
},
|
||||
],
|
||||
"length": 2,
|
||||
"refId": "joinByField-A-B",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -592,5 +592,6 @@ export function histogramFieldsToFrame(info: HistogramFields, theme?: GrafanaThe
|
||||
type: DataFrameType.Histogram,
|
||||
},
|
||||
fields: [info.xMin, info.xMax, ...info.counts],
|
||||
refId: `${DataTransformerID.histogram}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export const joinByFieldTransformer: SynchronousDataTransformerInfo<JoinByFieldO
|
||||
}
|
||||
const joined = joinDataFrames({ frames: data, joinBy, mode: options.mode });
|
||||
if (joined) {
|
||||
joined.refId = `${DataTransformerID.joinByField}-${data.map((frame) => frame.refId).join('-')}`;
|
||||
return [joined];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,10 @@ export const mergeTransformer: DataTransformerInfo<MergeTransformerOptions> = {
|
||||
const fieldNames = new Set<string>();
|
||||
const fieldIndexByName: Record<string, Record<number, number>> = {};
|
||||
const fieldNamesForKey: string[] = [];
|
||||
const dataFrame = new MutableDataFrame();
|
||||
const dataFrame = new MutableDataFrame({
|
||||
refId: `${DataTransformerID.merge}-${data.map((frame) => frame.refId).join('-')}`,
|
||||
fields: [],
|
||||
});
|
||||
|
||||
for (let frameIndex = 0; frameIndex < data.length; frameIndex++) {
|
||||
const frame = data[frameIndex];
|
||||
|
||||
@@ -56,7 +56,9 @@ export const reduceTransformer: DataTransformerInfo<ReduceTransformerOptions> =
|
||||
|
||||
// Add a row for each series
|
||||
const res = reduceSeriesToRows(data, matcher, options.reducers, options.labelsToFields);
|
||||
return res ? [res] : [];
|
||||
return res
|
||||
? [{ ...res, refId: `${DataTransformerID.reduce}-${data.map((frame) => frame.refId).join('-')}` }]
|
||||
: [];
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
@@ -37,7 +37,10 @@ export const seriesToRowsTransformer: DataTransformerInfo<SeriesToRowsTransforme
|
||||
|
||||
const timeFieldByIndex: Record<number, number> = {};
|
||||
const targetFields = new Set<string>();
|
||||
const dataFrame = new MutableDataFrame();
|
||||
const dataFrame = new MutableDataFrame({
|
||||
refId: `${DataTransformerID.seriesToRows}-${data.map((frame) => frame.refId).join('-')}`,
|
||||
fields: [],
|
||||
});
|
||||
const metricField: Field = {
|
||||
name: TIME_SERIES_METRIC_FIELD_NAME,
|
||||
values: [],
|
||||
|
||||
@@ -80,6 +80,7 @@ function transposeDataFrame(options: TransposeTransformerOptions, data: DataFram
|
||||
...frame,
|
||||
fields: newFields,
|
||||
length: Math.max(...newFields.map((field) => field.values.length)),
|
||||
refId: `${DataTransformerID.transpose}-${frame.refId}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -252,4 +252,6 @@ export interface FeatureToggles {
|
||||
teamHttpHeadersMimir?: boolean;
|
||||
ABTestFeatureToggleA?: boolean;
|
||||
ABTestFeatureToggleB?: boolean;
|
||||
queryLibraryDashboards?: boolean;
|
||||
elasticsearchImprovedParsing?: boolean;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "16.0.0",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/node": "22.10.6",
|
||||
"@types/semver": "7.5.8",
|
||||
"esbuild": "0.24.2",
|
||||
"rimraf": "6.0.1",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"@types/d3": "^7",
|
||||
"@types/jest": "^29.5.4",
|
||||
"@types/lodash": "4.17.14",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/node": "22.10.6",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-virtualized-auto-sizer": "1.0.4",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"@svgr/plugin-prettier": "^8.1.0",
|
||||
"@svgr/plugin-svgo": "^8.1.0",
|
||||
"@types/babel__core": "^7",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/node": "22.10.6",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.2.25",
|
||||
"esbuild": "0.24.2",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"@testing-library/react": "16.1.0",
|
||||
"@testing-library/user-event": "14.5.2",
|
||||
"@types/jest": "^29.5.4",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/node": "22.10.6",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/systemjs": "6.15.1",
|
||||
"jest": "^29.6.4",
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/jquery": "3.5.32",
|
||||
"@types/lodash": "4.17.14",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/node": "22.10.6",
|
||||
"@types/pluralize": "^0.0.33",
|
||||
"@types/prismjs": "1.26.5",
|
||||
"@types/react": "18.3.3",
|
||||
@@ -129,7 +129,7 @@
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-esbuild": "6.1.1",
|
||||
"rollup-plugin-node-externals": "^8.0.0",
|
||||
"sass": "1.83.1",
|
||||
"sass": "1.83.4",
|
||||
"sass-loader": "16.0.4",
|
||||
"style-loader": "4.0.0",
|
||||
"testing-library-selector": "0.3.1",
|
||||
|
||||
@@ -112,4 +112,24 @@ describe('addLabelToQuery()', () => {
|
||||
it('should not add ad-hoc filter bool operator', () => {
|
||||
expect(addLabelToQuery('ALERTS < bool 1', 'bar', 'baz')).toBe('ALERTS{bar="baz"} < bool 1');
|
||||
});
|
||||
|
||||
it('should add a utf8 label', () => {
|
||||
expect(addLabelToQuery('{"metric.name"}', 'cenk.erdem', 'muhabbet')).toBe(
|
||||
'{"metric.name", "cenk.erdem"="muhabbet"}'
|
||||
);
|
||||
|
||||
expect(addLabelToQuery('metric{label="val"}', 'cenk.erdem', 'muhabbet')).toBe(
|
||||
'metric{label="val", "cenk.erdem"="muhabbet"}'
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add a utf8 label when it is already applied', () => {
|
||||
expect(addLabelToQuery('{"metric.name", "cenk.erdem"="muhabbet"}', 'cenk.erdem', 'muhabbet')).toBe(
|
||||
'{"metric.name", "cenk.erdem"="muhabbet"}'
|
||||
);
|
||||
|
||||
expect(addLabelToQuery('metric{label="val", "cenk.erdem"="muhabbet"}', 'cenk.erdem', 'muhabbet')).toBe(
|
||||
'metric{label="val", "cenk.erdem"="muhabbet"}'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,6 +54,49 @@ describe('buildSelector()', () => {
|
||||
];
|
||||
expect(buildSelector(labels)).toEqual('foo{bar="baz"}');
|
||||
});
|
||||
|
||||
describe('utf8 support', () => {
|
||||
it('metric selector with utf8 metric', () => {
|
||||
const labels: SelectableLabel[] = [
|
||||
{ name: '__name__', selected: true, values: [{ name: 'utf8.metric', selected: true }] },
|
||||
];
|
||||
expect(buildSelector(labels)).toEqual('{"utf8.metric"}');
|
||||
});
|
||||
|
||||
it('metric selector with utf8 labels', () => {
|
||||
const labels: SelectableLabel[] = [
|
||||
{ name: '__name__', selected: true, values: [{ name: 'foo', selected: true }] },
|
||||
{ name: 'utf8.label', selected: true, values: [{ name: 'baz', selected: true }] },
|
||||
];
|
||||
expect(buildSelector(labels)).toEqual('foo{"utf8.label"="baz"}');
|
||||
});
|
||||
|
||||
it('metric selector with utf8 labels and metrics', () => {
|
||||
const labels: SelectableLabel[] = [
|
||||
{ name: '__name__', selected: true, values: [{ name: 'utf8.metric', selected: true }] },
|
||||
{ name: 'utf8.label', selected: true, values: [{ name: 'baz', selected: true }] },
|
||||
];
|
||||
expect(buildSelector(labels)).toEqual('{"utf8.metric","utf8.label"="baz"}');
|
||||
});
|
||||
|
||||
it('metric selector with utf8 metric and with utf8/non-utf8 labels', () => {
|
||||
const labels: SelectableLabel[] = [
|
||||
{ name: '__name__', selected: true, values: [{ name: 'utf8.metric', selected: true }] },
|
||||
{ name: 'utf8.label', selected: true, values: [{ name: 'uuu', selected: true }] },
|
||||
{ name: 'bar', selected: true, values: [{ name: 'baz', selected: true }] },
|
||||
];
|
||||
expect(buildSelector(labels)).toEqual('{"utf8.metric","utf8.label"="uuu",bar="baz"}');
|
||||
});
|
||||
|
||||
it('metric selector with non-utf8 metric with utf8/non-utf8 labels', () => {
|
||||
const labels: SelectableLabel[] = [
|
||||
{ name: '__name__', selected: true, values: [{ name: 'foo', selected: true }] },
|
||||
{ name: 'utf8.label', selected: true, values: [{ name: 'uuu', selected: true }] },
|
||||
{ name: 'bar', selected: true, values: [{ name: 'baz', selected: true }] },
|
||||
];
|
||||
expect(buildSelector(labels)).toEqual('foo{"utf8.label"="uuu",bar="baz"}');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('facetLabels()', () => {
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
|
||||
import PromQlLanguageProvider from '../language_provider';
|
||||
import { escapeLabelValueInExactSelector, escapeLabelValueInRegexSelector } from '../language_utils';
|
||||
import { isValidLegacyName, utf8Support } from '../utf8_support';
|
||||
|
||||
// Hard limit on labels to render
|
||||
const EMPTY_SELECTOR = '{}';
|
||||
@@ -68,22 +69,38 @@ export interface SelectableLabel {
|
||||
|
||||
export function buildSelector(labels: SelectableLabel[]): string {
|
||||
let singleMetric = '';
|
||||
const selectedLabels = [];
|
||||
const selectedLabels: string[] = [];
|
||||
for (const label of labels) {
|
||||
if ((label.name === METRIC_LABEL || label.selected) && label.values && label.values.length > 0) {
|
||||
const selectedValues = label.values.filter((value) => value.selected).map((value) => value.name);
|
||||
if (selectedValues.length > 1) {
|
||||
selectedLabels.push(`${label.name}=~"${selectedValues.map(escapeLabelValueInRegexSelector).join('|')}"`);
|
||||
selectedLabels.push(
|
||||
`${utf8Support(label.name)}=~"${selectedValues.map(escapeLabelValueInRegexSelector).join('|')}"`
|
||||
);
|
||||
} else if (selectedValues.length === 1) {
|
||||
if (label.name === METRIC_LABEL) {
|
||||
singleMetric = selectedValues[0];
|
||||
} else {
|
||||
selectedLabels.push(`${label.name}="${escapeLabelValueInExactSelector(selectedValues[0])}"`);
|
||||
selectedLabels.push(`${utf8Support(label.name)}="${escapeLabelValueInExactSelector(selectedValues[0])}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [singleMetric, '{', selectedLabels.join(','), '}'].join('');
|
||||
|
||||
const selectorParts: string[] = [];
|
||||
const isLegacyName = singleMetric === '' || isValidLegacyName(singleMetric);
|
||||
|
||||
if (isLegacyName) {
|
||||
selectorParts.push(singleMetric, '{');
|
||||
} else {
|
||||
selectorParts.push('{', `"${singleMetric}"`);
|
||||
if (selectedLabels.length > 0) {
|
||||
selectorParts.push(',');
|
||||
}
|
||||
}
|
||||
|
||||
selectorParts.push(selectedLabels.join(','), '}');
|
||||
return selectorParts.join('');
|
||||
}
|
||||
|
||||
export function facetLabels(
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// Core grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/completions.ts
|
||||
import UFuzzy from '@leeoniya/ufuzzy';
|
||||
import { languages } from 'monaco-editor';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { prometheusRegularEscape } from '../../../datasource';
|
||||
import { escapeLabelValueInExactSelector } from '../../../language_utils';
|
||||
import { FUNCTIONS } from '../../../promql';
|
||||
import { isValidLegacyName } from '../../../utf8_support';
|
||||
|
||||
import { DataProvider } from './data_provider';
|
||||
import type { Label, Situation } from './situation';
|
||||
@@ -14,10 +16,16 @@ import { NeverCaseError } from './util';
|
||||
|
||||
export type CompletionType = 'HISTORY' | 'FUNCTION' | 'METRIC_NAME' | 'DURATION' | 'LABEL_NAME' | 'LABEL_VALUE';
|
||||
|
||||
// We cannot use languages.CompletionItemInsertTextRule.InsertAsSnippet because grafana-prometheus package isn't compatible
|
||||
// It should first change the moduleResolution to bundler for TS to correctly resolve the types
|
||||
// https://github.com/grafana/grafana/pull/96450
|
||||
const InsertAsSnippet = 4;
|
||||
|
||||
type Completion = {
|
||||
type: CompletionType;
|
||||
label: string;
|
||||
insertText: string;
|
||||
insertTextRules?: languages.CompletionItemInsertTextRule;
|
||||
detail?: string;
|
||||
documentation?: string;
|
||||
triggerOnInsert?: boolean;
|
||||
@@ -29,6 +37,11 @@ const metricNamesSearch = {
|
||||
singleError: new UFuzzy({ intraMode: 1 }),
|
||||
};
|
||||
|
||||
// Snippet Marker is telling monaco where to show the cursor and maybe a help text
|
||||
// With help text example: ${1:labelName}
|
||||
// labelName will be shown as selected. So user would know what to type next
|
||||
const snippetMarker = '${1:}';
|
||||
|
||||
interface MetricFilterOptions {
|
||||
metricNames: string[];
|
||||
inputText: string;
|
||||
@@ -74,9 +87,16 @@ function getAllMetricNamesCompletions(dataProvider: DataProvider): Completion[]
|
||||
return dataProvider.metricNamesToMetrics(metricNames).map((metric) => ({
|
||||
type: 'METRIC_NAME',
|
||||
label: metric.name,
|
||||
insertText: metric.name,
|
||||
detail: `${metric.name} : ${metric.type}`,
|
||||
documentation: metric.help,
|
||||
...(metric.isUtf8
|
||||
? {
|
||||
insertText: `{"${metric.name}"${snippetMarker}}`,
|
||||
insertTextRules: InsertAsSnippet,
|
||||
}
|
||||
: {
|
||||
insertText: metric.name,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -159,12 +179,22 @@ async function getLabelNamesForCompletions(
|
||||
dataProvider: DataProvider
|
||||
): Promise<Completion[]> {
|
||||
const labelNames = await getLabelNames(metric, otherLabels, dataProvider);
|
||||
return labelNames.map((text) => ({
|
||||
type: 'LABEL_NAME',
|
||||
label: text,
|
||||
insertText: `${text}${suffix}`,
|
||||
triggerOnInsert,
|
||||
}));
|
||||
return labelNames.map((text) => {
|
||||
const isUtf8 = !isValidLegacyName(text);
|
||||
return {
|
||||
type: 'LABEL_NAME',
|
||||
label: text,
|
||||
...(isUtf8
|
||||
? {
|
||||
insertText: `"${text}"${suffix}`,
|
||||
insertTextRules: InsertAsSnippet,
|
||||
}
|
||||
: {
|
||||
insertText: `${text}${suffix}`,
|
||||
}),
|
||||
triggerOnInsert,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function getLabelNamesForSelectorCompletions(
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Monaco } from '@grafana/ui'; // used in TSDoc `@link` below
|
||||
|
||||
import PromQlLanguageProvider from '../../../language_provider';
|
||||
import { PromQuery } from '../../../types';
|
||||
import { isValidLegacyName } from '../../../utf8_support';
|
||||
|
||||
export const CODE_MODE_SUGGESTIONS_INCOMPLETE_EVENT = 'codeModeSuggestionsIncomplete';
|
||||
|
||||
@@ -26,6 +27,7 @@ interface Metric {
|
||||
name: string;
|
||||
help: string;
|
||||
type: string;
|
||||
isUtf8?: boolean;
|
||||
}
|
||||
|
||||
export interface DataProviderParams {
|
||||
@@ -78,6 +80,7 @@ export class DataProvider {
|
||||
name: m,
|
||||
help: metaItem?.help ?? '',
|
||||
type: metaItem?.type ?? '',
|
||||
isUtf8: !isValidLegacyName(m),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ export function getCompletionProvider(
|
||||
kind: getMonacoCompletionItemKind(item.type, monaco),
|
||||
label: item.label,
|
||||
insertText: item.insertText,
|
||||
insertTextRules: item.insertTextRules,
|
||||
detail: item.detail,
|
||||
documentation: item.documentation,
|
||||
sortText: index.toString().padStart(maxIndexDigits, '0'), // to force the order we have
|
||||
|
||||
@@ -56,6 +56,7 @@ describe('situation', () => {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
metricName: 'something',
|
||||
otherLabels: [],
|
||||
betweenQuotes: false,
|
||||
});
|
||||
|
||||
assertSituation('sum(something) by (^)', {
|
||||
@@ -79,34 +80,157 @@ describe('situation', () => {
|
||||
{ name: 'three', value: 'val3', op: '=~' },
|
||||
{ name: 'four', value: 'val4', op: '!~' },
|
||||
],
|
||||
betweenQuotes: false,
|
||||
});
|
||||
|
||||
assertSituation('{^}', {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
otherLabels: [],
|
||||
betweenQuotes: false,
|
||||
});
|
||||
|
||||
assertSituation('{one="val1",^}', {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
otherLabels: [{ name: 'one', value: 'val1', op: '=' }],
|
||||
betweenQuotes: false,
|
||||
});
|
||||
|
||||
// single-quoted label-values with escape
|
||||
assertSituation("{one='val\\'1',^}", {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
otherLabels: [{ name: 'one', value: "val'1", op: '=' }],
|
||||
betweenQuotes: false,
|
||||
});
|
||||
|
||||
// double-quoted label-values with escape
|
||||
assertSituation('{one="val\\"1",^}', {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
otherLabels: [{ name: 'one', value: 'val"1', op: '=' }],
|
||||
betweenQuotes: false,
|
||||
});
|
||||
|
||||
// backticked label-values with escape (the escape should not be interpreted)
|
||||
assertSituation('{one=`val\\"1`,^}', {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
otherLabels: [{ name: 'one', value: 'val\\"1', op: '=' }],
|
||||
betweenQuotes: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe('utf-8 metric name support', () => {
|
||||
it('with utf8 metric name no label and no comma', () => {
|
||||
assertSituation(`{"metric.name"^}`, null);
|
||||
});
|
||||
|
||||
it('with utf8 metric name no label', () => {
|
||||
assertSituation(`{"metric.name", ^}`, {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
metricName: 'metric.name',
|
||||
otherLabels: [],
|
||||
betweenQuotes: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('with utf8 metric name requesting utf8 labels in quotes', () => {
|
||||
assertSituation(`{"metric.name", "^"}`, {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
metricName: 'metric.name',
|
||||
otherLabels: [],
|
||||
betweenQuotes: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('with utf8 metric name with a legacy label', () => {
|
||||
assertSituation(`{"metric.name", label1="val", ^}`, {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
metricName: 'metric.name',
|
||||
otherLabels: [{ name: 'label1', value: 'val', op: '=' }],
|
||||
betweenQuotes: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('with utf8 metric name with a legacy label and no value', () => {
|
||||
assertSituation(`{"metric.name", label1="^"}`, {
|
||||
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
||||
metricName: 'metric.name',
|
||||
labelName: 'label1',
|
||||
betweenQuotes: true,
|
||||
otherLabels: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('with utf8 metric name with a utf8 label and no value', () => {
|
||||
assertSituation(`{"metric.name", "utf8.label"="^"}`, {
|
||||
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
||||
metricName: 'metric.name',
|
||||
labelName: '"utf8.label"',
|
||||
betweenQuotes: true,
|
||||
otherLabels: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('with utf8 metric name with a legacy label and utf8 label', () => {
|
||||
assertSituation(`{"metric.name", label1="val", "utf8.label"="^"}`, {
|
||||
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
||||
metricName: 'metric.name',
|
||||
labelName: `"utf8.label"`,
|
||||
betweenQuotes: true,
|
||||
otherLabels: [{ name: 'label1', value: 'val', op: '=' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('with utf8 metric name with a utf8 label and legacy label', () => {
|
||||
assertSituation(`{"metric.name", "utf8.label"="val", label1="^"}`, {
|
||||
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
||||
metricName: 'metric.name',
|
||||
labelName: `label1`,
|
||||
betweenQuotes: true,
|
||||
otherLabels: [{ name: '"utf8.label"', value: 'val', op: '=' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('with utf8 metric name with grouping', () => {
|
||||
assertSituation(`sum by (^)(rate({"metric.name", label1="val"}[1m]))`, {
|
||||
type: 'IN_GROUPING',
|
||||
metricName: 'metric.name',
|
||||
otherLabels: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('utf-8 label support', () => {
|
||||
assertSituation(`metric{"label": "^"}`, null);
|
||||
|
||||
assertSituation(`metric{"label with space": "^"}`, null);
|
||||
|
||||
assertSituation(`metric{"label_🤖": "^"}`, null);
|
||||
|
||||
assertSituation(`metric{"Spaß": "^"}`, null);
|
||||
|
||||
assertSituation(`{"metric", "Spaß": "^"}`, null);
|
||||
|
||||
assertSituation('something{"job"=^}', {
|
||||
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
||||
metricName: 'something',
|
||||
labelName: '"job"',
|
||||
betweenQuotes: false,
|
||||
otherLabels: [],
|
||||
});
|
||||
|
||||
assertSituation('something{"job📈"=^}', {
|
||||
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
||||
metricName: 'something',
|
||||
labelName: '"job📈"',
|
||||
betweenQuotes: false,
|
||||
otherLabels: [],
|
||||
});
|
||||
|
||||
assertSituation('something{"job with space"=^,host="h1"}', {
|
||||
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
||||
metricName: 'something',
|
||||
labelName: '"job with space"',
|
||||
betweenQuotes: false,
|
||||
otherLabels: [{ name: 'host', value: 'h1', op: '=' }],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -194,6 +318,7 @@ describe('situation', () => {
|
||||
{ name: 'three', value: 'val3', op: '=~' },
|
||||
{ name: 'four', value: 'val4', op: '!~' },
|
||||
],
|
||||
betweenQuotes: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
NumberDurationLiteralInDurationContext,
|
||||
parser,
|
||||
PromQL,
|
||||
QuotedLabelMatcher,
|
||||
QuotedLabelName,
|
||||
StringLiteral,
|
||||
UnquotedLabelMatcher,
|
||||
VectorSelector,
|
||||
@@ -35,8 +37,10 @@ type NodeTypeId =
|
||||
| typeof GroupingLabels
|
||||
| typeof Identifier
|
||||
| typeof UnquotedLabelMatcher
|
||||
| typeof QuotedLabelMatcher
|
||||
| typeof LabelMatchers
|
||||
| typeof LabelName
|
||||
| typeof QuotedLabelName
|
||||
| typeof PromQL
|
||||
| typeof StringLiteral
|
||||
| typeof VectorSelector
|
||||
@@ -80,8 +84,10 @@ function walk(node: SyntaxNode, path: Path): SyntaxNode | null {
|
||||
return current;
|
||||
}
|
||||
|
||||
function getNodeText(node: SyntaxNode, text: string): string {
|
||||
return text.slice(node.from, node.to);
|
||||
function getNodeText(node: SyntaxNode, text: string, utf8?: boolean): string {
|
||||
const nodeFrom = utf8 ? node.from + 1 : node.from;
|
||||
const nodeTo = utf8 ? node.to - 1 : node.to;
|
||||
return text.slice(nodeFrom, nodeTo);
|
||||
}
|
||||
|
||||
function parsePromQLStringLiteral(text: string): string {
|
||||
@@ -140,6 +146,8 @@ export type Situation =
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME';
|
||||
metricName?: string;
|
||||
otherLabels: Label[];
|
||||
// utf8 labels must be in quotes
|
||||
betweenQuotes: boolean;
|
||||
}
|
||||
| {
|
||||
type: 'IN_GROUPING';
|
||||
@@ -170,6 +178,10 @@ const RESOLVERS: Resolver[] = [
|
||||
path: [LabelMatchers, VectorSelector],
|
||||
fun: resolveLabelKeysWithEquals,
|
||||
},
|
||||
{
|
||||
path: [StringLiteral, QuotedLabelName, LabelMatchers, VectorSelector],
|
||||
fun: resolveUtf8LabelKeysWithEquals,
|
||||
},
|
||||
{
|
||||
path: [PromQL],
|
||||
fun: resolveTopLevel,
|
||||
@@ -182,6 +194,10 @@ const RESOLVERS: Resolver[] = [
|
||||
path: [StringLiteral, UnquotedLabelMatcher],
|
||||
fun: resolveLabelMatcher,
|
||||
},
|
||||
{
|
||||
path: [StringLiteral, QuotedLabelMatcher],
|
||||
fun: resolveQuotedLabelMatcher,
|
||||
},
|
||||
{
|
||||
path: [ERROR_NODE_NAME, BinaryExpr, PromQL],
|
||||
fun: resolveTopLevel,
|
||||
@@ -190,6 +206,10 @@ const RESOLVERS: Resolver[] = [
|
||||
path: [ERROR_NODE_NAME, UnquotedLabelMatcher],
|
||||
fun: resolveLabelMatcher,
|
||||
},
|
||||
{
|
||||
path: [ERROR_NODE_NAME, QuotedLabelMatcher],
|
||||
fun: resolveQuotedLabelMatcher,
|
||||
},
|
||||
{
|
||||
path: [ERROR_NODE_NAME, NumberDurationLiteralInDurationContext, MatrixSelector],
|
||||
fun: resolveDurations,
|
||||
@@ -217,11 +237,13 @@ function getLabelOp(opNode: SyntaxNode): LabelOperator | null {
|
||||
}
|
||||
|
||||
function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null {
|
||||
if (labelMatcherNode.type.id !== UnquotedLabelMatcher) {
|
||||
const allowedMatchers = new Set([UnquotedLabelMatcher, QuotedLabelMatcher]);
|
||||
if (!allowedMatchers.has(labelMatcherNode.type.id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nameNode = walk(labelMatcherNode, [['firstChild', LabelName]]);
|
||||
const nameNode =
|
||||
walk(labelMatcherNode, [['firstChild', LabelName]]) ?? walk(labelMatcherNode, [['firstChild', QuotedLabelName]]);
|
||||
|
||||
if (nameNode === null) {
|
||||
return null;
|
||||
@@ -254,8 +276,17 @@ function getLabels(labelMatchersNode: SyntaxNode, text: string): Label[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
const labelNodes = labelMatchersNode.getChildren(UnquotedLabelMatcher);
|
||||
return labelNodes.map((ln) => getLabel(ln, text)).filter(notEmpty);
|
||||
const matchers = [UnquotedLabelMatcher, QuotedLabelMatcher];
|
||||
|
||||
return matchers.reduce<Label[]>((acc, matcher) => {
|
||||
labelMatchersNode.getChildren(matcher).forEach((ln) => {
|
||||
const label = getLabel(ln, text);
|
||||
if (notEmpty(label)) {
|
||||
acc.push(label);
|
||||
}
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function getNodeChildren(node: SyntaxNode): SyntaxNode[] {
|
||||
@@ -299,12 +330,20 @@ function resolveLabelsForGrouping(node: SyntaxNode, text: string, pos: number):
|
||||
return null;
|
||||
}
|
||||
|
||||
const metricIdNode = getNodeInSubtree(bodyNode, Identifier);
|
||||
if (metricIdNode === null) {
|
||||
const metricIdNode = getNodeInSubtree(bodyNode, Identifier) ?? getNodeInSubtree(bodyNode, StringLiteral);
|
||||
|
||||
if (!metricIdNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const metricName = getNodeText(metricIdNode, text);
|
||||
// Let's check whether it's a utf8 metric.
|
||||
// A utf8 metric must be a StringLiteral and its parent must be a QuotedLabelName
|
||||
if (metricIdNode.type.id === StringLiteral && metricIdNode.parent?.type.id !== QuotedLabelName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const metricName = getNodeText(metricIdNode, text, metricIdNode.type.id === StringLiteral);
|
||||
|
||||
return {
|
||||
type: 'IN_GROUPING',
|
||||
metricName,
|
||||
@@ -341,29 +380,54 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa
|
||||
// we need to remove "our" label from all-labels, if it is in there
|
||||
const otherLabels = allLabels.filter((label) => label.name !== labelName);
|
||||
|
||||
const metricNameNode = walk(labelMatchersNode, [
|
||||
['parent', VectorSelector],
|
||||
['firstChild', Identifier],
|
||||
]);
|
||||
|
||||
if (metricNameNode === null) {
|
||||
// we are probably in a situation without a metric name
|
||||
return {
|
||||
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
||||
labelName,
|
||||
betweenQuotes: inStringNode,
|
||||
otherLabels,
|
||||
};
|
||||
}
|
||||
|
||||
const metricName = getNodeText(metricNameNode, text);
|
||||
const metricName = getMetricName(labelMatchersNode, text);
|
||||
|
||||
// we are probably in a situation without a metric name
|
||||
return {
|
||||
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
||||
metricName,
|
||||
labelName,
|
||||
betweenQuotes: inStringNode,
|
||||
otherLabels,
|
||||
...(metricName ? { metricName } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveQuotedLabelMatcher(node: SyntaxNode, text: string, pos: number): Situation | null {
|
||||
// we can arrive here in two situation. `node` is either:
|
||||
// - a StringNode (like in `{"job"="^"}`)
|
||||
// - or an error node (like in `{"job"=^}`)
|
||||
const inStringNode = !node.type.isError;
|
||||
|
||||
const parent = walk(node, [['parent', QuotedLabelMatcher]]);
|
||||
if (parent === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const labelNameNode = walk(parent, [['firstChild', QuotedLabelName]]);
|
||||
if (labelNameNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const labelName = getNodeText(labelNameNode, text);
|
||||
|
||||
const labelMatchersNode = walk(parent, [['parent', LabelMatchers]]);
|
||||
if (labelMatchersNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// now we need to find the other names
|
||||
const allLabels = getLabels(labelMatchersNode, text);
|
||||
|
||||
// we need to remove "our" label from all-labels, if it is in there
|
||||
const otherLabels = allLabels.filter((label) => label.name !== labelName);
|
||||
const metricName = getMetricName(parent.parent!, text);
|
||||
|
||||
return {
|
||||
type: 'IN_LABEL_SELECTOR_WITH_LABEL_NAME',
|
||||
labelName,
|
||||
betweenQuotes: inStringNode,
|
||||
otherLabels,
|
||||
...(metricName ? { metricName } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -388,7 +452,7 @@ function resolveDurations(node: SyntaxNode, text: string, pos: number): Situatio
|
||||
function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number): Situation | null {
|
||||
// next false positive:
|
||||
// `something{a="1"^}`
|
||||
const child = walk(node, [['firstChild', UnquotedLabelMatcher]]);
|
||||
let child = walk(node, [['firstChild', UnquotedLabelMatcher]]);
|
||||
if (child !== null) {
|
||||
// means the label-matching part contains at least one label already.
|
||||
//
|
||||
@@ -403,28 +467,72 @@ function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number)
|
||||
}
|
||||
}
|
||||
|
||||
const metricNameNode = walk(node, [
|
||||
// next false positive:
|
||||
// `{"utf8.metric"^}`
|
||||
child = walk(node, [['firstChild', QuotedLabelName]]);
|
||||
if (child !== null) {
|
||||
// means the label-matching part contains a utf8 metric.
|
||||
//
|
||||
// in this case, we will need to have a `,` character at the end,
|
||||
// to be able to suggest adding the next label.
|
||||
// the area between the end-of-the-child-node and the cursor-pos
|
||||
// must contain a `,` in this case.
|
||||
const textToCheck = text.slice(child.to, pos);
|
||||
|
||||
if (!textToCheck.includes(',')) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const otherLabels = getLabels(node, text);
|
||||
const metricName = getMetricName(node, text);
|
||||
|
||||
return {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
otherLabels,
|
||||
betweenQuotes: false,
|
||||
...(metricName ? { metricName } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveUtf8LabelKeysWithEquals(node: SyntaxNode, text: string, pos: number): Situation | null {
|
||||
const otherLabels = getLabels(node, text);
|
||||
const metricName = node.parent?.parent ? getMetricName(node.parent.parent, text) : null;
|
||||
|
||||
return {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
otherLabels,
|
||||
betweenQuotes: true,
|
||||
...(metricName ? { metricName } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function getMetricName(node: SyntaxNode, text: string): string | null {
|
||||
// Legacy Metric metric_name{label="value"}
|
||||
const legacyMetricNameNode = walk(node, [
|
||||
['parent', VectorSelector],
|
||||
['firstChild', Identifier],
|
||||
]);
|
||||
|
||||
const otherLabels = getLabels(node, text);
|
||||
|
||||
if (metricNameNode === null) {
|
||||
// we are probably in a situation without a metric name.
|
||||
return {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
otherLabels,
|
||||
};
|
||||
if (legacyMetricNameNode) {
|
||||
return getNodeText(legacyMetricNameNode, text);
|
||||
}
|
||||
|
||||
const metricName = getNodeText(metricNameNode, text);
|
||||
// check for a utf-8 metric
|
||||
// utf-8 metric {"metric.name", label="value"}
|
||||
const utf8MetricNameNode = walk(node, [
|
||||
['parent', VectorSelector],
|
||||
['firstChild', LabelMatchers],
|
||||
['firstChild', QuotedLabelName],
|
||||
['firstChild', StringLiteral],
|
||||
]);
|
||||
|
||||
return {
|
||||
type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
|
||||
metricName,
|
||||
otherLabels,
|
||||
};
|
||||
if (utf8MetricNameNode) {
|
||||
return getNodeText(utf8MetricNameNode, text, true);
|
||||
}
|
||||
|
||||
// no metric name
|
||||
return null;
|
||||
}
|
||||
|
||||
// we find the first error-node in the tree that is at the cursor-position.
|
||||
@@ -461,10 +569,10 @@ export function getSituation(text: string, pos: number): Situation | null {
|
||||
}
|
||||
|
||||
/**
|
||||
PromQL
|
||||
Expr
|
||||
VectorSelector
|
||||
LabelMatchers
|
||||
PromQL
|
||||
Expr
|
||||
VectorSelector
|
||||
LabelMatchers
|
||||
*/
|
||||
const tree = parser.parse(text);
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ import {
|
||||
RawRecordingRules,
|
||||
RuleQueryMapping,
|
||||
} from './types';
|
||||
import { utf8Support, wrapUtf8Filters } from './utf8_support';
|
||||
import { PrometheusVariableSupport } from './variables';
|
||||
|
||||
const ANNOTATION_QUERY_STEP_DEFAULT = '60s';
|
||||
@@ -925,7 +926,21 @@ export class PrometheusDatasource
|
||||
|
||||
// We need a first replace to evaluate variables before applying adhoc filters
|
||||
// This is required for an expression like `metric > $VAR` where $VAR is a float to which we must not add adhoc filters
|
||||
const expr = this.templateSrv.replace(target.expr, variables, this.interpolateQueryExpr);
|
||||
const expr = this.templateSrv.replace(
|
||||
target.expr,
|
||||
variables,
|
||||
(value: string | string[] = [], variable: QueryVariableModel | CustomVariableModel) => {
|
||||
if (typeof value === 'string' && target.fromExploreMetrics) {
|
||||
if (variable.name === 'filters') {
|
||||
return wrapUtf8Filters(value);
|
||||
}
|
||||
if (variable.name === 'groupby') {
|
||||
return utf8Support(value);
|
||||
}
|
||||
}
|
||||
return this.interpolateQueryExpr(value, variable);
|
||||
}
|
||||
);
|
||||
|
||||
// Apply ad-hoc filters
|
||||
// When ad-hoc filters are applied, we replace again the variables in case the ad-hoc filters also reference a variable
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AbstractLabelOperator, dateTime, TimeRange } from '@grafana/data';
|
||||
import { DEFAULT_SERIES_LIMIT } from './components/PrometheusMetricsBrowser';
|
||||
import { Label } from './components/monaco-query-field/monaco-completion-provider/situation';
|
||||
import { PrometheusDatasource } from './datasource';
|
||||
import LanguageProvider from './language_provider';
|
||||
import LanguageProvider, { removeQuotesIfExist } from './language_provider';
|
||||
import { getClientCacheDurationInMinutes, getPrometheusTime, getRangeSnapInterval } from './language_utils';
|
||||
import { PrometheusCacheLevel, PromQuery } from './types';
|
||||
|
||||
@@ -120,7 +120,13 @@ describe('Language completion provider', () => {
|
||||
|
||||
const labelName = 'job';
|
||||
const labelValue = 'grafana';
|
||||
getSeriesLabels(`{${labelName}="${labelValue}"}`, [{ name: labelName, value: labelValue, op: '=' }] as Label[]);
|
||||
getSeriesLabels(`{${labelName}="${labelValue}"}`, [
|
||||
{
|
||||
name: labelName,
|
||||
value: labelValue,
|
||||
op: '=',
|
||||
},
|
||||
] as Label[]);
|
||||
expect(requestSpy).toHaveBeenCalled();
|
||||
expect(requestSpy).toHaveBeenCalledWith(
|
||||
`/api/v1/labels`,
|
||||
@@ -145,7 +151,13 @@ describe('Language completion provider', () => {
|
||||
|
||||
const labelName = 'job';
|
||||
const labelValue = 'grafana';
|
||||
getSeriesLabels(`{${labelName}="${labelValue}"}`, [{ name: labelName, value: labelValue, op: '=' }] as Label[]);
|
||||
getSeriesLabels(`{${labelName}="${labelValue}"}`, [
|
||||
{
|
||||
name: labelName,
|
||||
value: labelValue,
|
||||
op: '=',
|
||||
},
|
||||
] as Label[]);
|
||||
expect(requestSpy).toHaveBeenCalled();
|
||||
expect(requestSpy).toHaveBeenCalledWith(
|
||||
'/api/v1/series',
|
||||
@@ -174,7 +186,13 @@ describe('Language completion provider', () => {
|
||||
|
||||
const labelName = 'job';
|
||||
const labelValue = 'grafana';
|
||||
getSeriesLabels(`{${labelName}="${labelValue}"}`, [{ name: labelName, value: labelValue, op: '=' }] as Label[]);
|
||||
getSeriesLabels(`{${labelName}="${labelValue}"}`, [
|
||||
{
|
||||
name: labelName,
|
||||
value: labelValue,
|
||||
op: '=',
|
||||
},
|
||||
] as Label[]);
|
||||
expect(requestSpy).toHaveBeenCalled();
|
||||
expect(requestSpy).toHaveBeenCalledWith(
|
||||
`/api/v1/labels`,
|
||||
@@ -569,14 +587,14 @@ describe('Language completion provider', () => {
|
||||
it('should interpolate variable in series', () => {
|
||||
const languageProvider = new LanguageProvider({
|
||||
...defaultDatasource,
|
||||
interpolateString: (string: string) => string.replace(/\$/, 'interpolated-'),
|
||||
interpolateString: (string: string) => string.replace(/\$/g, 'interpolated_'),
|
||||
} as PrometheusDatasource);
|
||||
const fetchLabelValues = languageProvider.fetchLabelValues;
|
||||
const requestSpy = jest.spyOn(languageProvider, 'request');
|
||||
fetchLabelValues('$job');
|
||||
expect(requestSpy).toHaveBeenCalled();
|
||||
expect(requestSpy).toHaveBeenCalledWith(
|
||||
'/api/v1/label/interpolated-job/values',
|
||||
'/api/v1/label/interpolated_job/values',
|
||||
[],
|
||||
{
|
||||
end: toPrometheusTimeString,
|
||||
@@ -585,6 +603,69 @@ describe('Language completion provider', () => {
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it('should fetch with encoded utf8 label', () => {
|
||||
const languageProvider = new LanguageProvider({
|
||||
...defaultDatasource,
|
||||
interpolateString: (string: string) => string.replace(/\$/g, 'http.status:sum'),
|
||||
} as PrometheusDatasource);
|
||||
const fetchLabelValues = languageProvider.fetchLabelValues;
|
||||
const requestSpy = jest.spyOn(languageProvider, 'request');
|
||||
fetchLabelValues('"http.status:sum"');
|
||||
expect(requestSpy).toHaveBeenCalled();
|
||||
expect(requestSpy).toHaveBeenCalledWith(
|
||||
'/api/v1/label/U__http_2e_status:sum/values',
|
||||
[],
|
||||
{
|
||||
end: toPrometheusTimeString,
|
||||
start: fromPrometheusTimeString,
|
||||
},
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchSeriesValuesWithMatch', () => {
|
||||
it('should fetch with encoded utf8 label', () => {
|
||||
const languageProvider = new LanguageProvider({
|
||||
...defaultDatasource,
|
||||
interpolateString: (string: string) => string.replace(/\$/g, 'http.status:sum'),
|
||||
} as PrometheusDatasource);
|
||||
const fetchSeriesValuesWithMatch = languageProvider.fetchSeriesValuesWithMatch;
|
||||
const requestSpy = jest.spyOn(languageProvider, 'request');
|
||||
fetchSeriesValuesWithMatch('"http.status:sum"', '{__name__="a_utf8_http_requests_total"}');
|
||||
expect(requestSpy).toHaveBeenCalled();
|
||||
expect(requestSpy).toHaveBeenCalledWith(
|
||||
'/api/v1/label/U__http_2e_status:sum/values',
|
||||
[],
|
||||
{
|
||||
end: toPrometheusTimeString,
|
||||
start: fromPrometheusTimeString,
|
||||
'match[]': '{__name__="a_utf8_http_requests_total"}',
|
||||
},
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it('should fetch without encoding for standard prometheus labels', () => {
|
||||
const languageProvider = new LanguageProvider({
|
||||
...defaultDatasource,
|
||||
} as PrometheusDatasource);
|
||||
const fetchSeriesValuesWithMatch = languageProvider.fetchSeriesValuesWithMatch;
|
||||
const requestSpy = jest.spyOn(languageProvider, 'request');
|
||||
fetchSeriesValuesWithMatch('"http_status_sum"', '{__name__="a_utf8_http_requests_total"}');
|
||||
expect(requestSpy).toHaveBeenCalled();
|
||||
expect(requestSpy).toHaveBeenCalledWith(
|
||||
'/api/v1/label/http_status_sum/values',
|
||||
[],
|
||||
{
|
||||
end: toPrometheusTimeString,
|
||||
start: fromPrometheusTimeString,
|
||||
'match[]': '{__name__="a_utf8_http_requests_total"}',
|
||||
},
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabled metrics lookup', () => {
|
||||
@@ -650,3 +731,59 @@ describe('Language completion provider', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeQuotesIfExist', () => {
|
||||
it('removes quotes from a string with double quotes', () => {
|
||||
const input = '"hello"';
|
||||
const result = removeQuotesIfExist(input);
|
||||
expect(result).toBe('hello');
|
||||
});
|
||||
|
||||
it('returns the original string if it does not start and end with quotes', () => {
|
||||
const input = 'hello';
|
||||
const result = removeQuotesIfExist(input);
|
||||
expect(result).toBe('hello');
|
||||
});
|
||||
|
||||
it('returns the original string if it has mismatched quotes', () => {
|
||||
const input = '"hello';
|
||||
const result = removeQuotesIfExist(input);
|
||||
expect(result).toBe('"hello');
|
||||
});
|
||||
|
||||
it('removes quotes for strings with special characters inside quotes', () => {
|
||||
const input = '"hello, world!"';
|
||||
const result = removeQuotesIfExist(input);
|
||||
expect(result).toBe('hello, world!');
|
||||
});
|
||||
|
||||
it('removes quotes for strings with spaces inside quotes', () => {
|
||||
const input = '" "';
|
||||
const result = removeQuotesIfExist(input);
|
||||
expect(result).toBe(' ');
|
||||
});
|
||||
|
||||
it('returns the original string for an empty string', () => {
|
||||
const input = '';
|
||||
const result = removeQuotesIfExist(input);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('returns the original string if the string only has a single quote character', () => {
|
||||
const input = '"';
|
||||
const result = removeQuotesIfExist(input);
|
||||
expect(result).toBe('"');
|
||||
});
|
||||
|
||||
it('handles strings with nested quotes correctly', () => {
|
||||
const input = '"nested \"quotes\""';
|
||||
const result = removeQuotesIfExist(input);
|
||||
expect(result).toBe('nested \"quotes\"');
|
||||
});
|
||||
|
||||
it('removes quotes from a numeric string wrapped in quotes', () => {
|
||||
const input = '"12345"';
|
||||
const result = removeQuotesIfExist(input);
|
||||
expect(result).toBe('12345');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import PromqlSyntax from './promql';
|
||||
import { buildVisualQueryFromString } from './querybuilder/parsing';
|
||||
import { PrometheusCacheLevel, PromMetricsMetadata, PromQuery } from './types';
|
||||
import { escapeForUtf8Support, isValidLegacyName } from './utf8_support';
|
||||
|
||||
const DEFAULT_KEYS = ['job', 'instance'];
|
||||
const EMPTY_SELECTOR = '{}';
|
||||
@@ -208,7 +209,8 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
||||
fetchLabelValues = async (key: string): Promise<string[]> => {
|
||||
const params = this.datasource.getAdjustedInterval(this.timeRange);
|
||||
const interpolatedName = this.datasource.interpolateString(key);
|
||||
const url = `/api/v1/label/${interpolatedName}/values`;
|
||||
const interpolatedAndEscapedName = escapeForUtf8Support(removeQuotesIfExist(interpolatedName));
|
||||
const url = `/api/v1/label/${interpolatedAndEscapedName}/values`;
|
||||
const value = await this.request(url, [], params, this.getDefaultCacheHeaders());
|
||||
return value ?? [];
|
||||
};
|
||||
@@ -232,10 +234,11 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
||||
queries?.forEach((q) => {
|
||||
const visualQuery = buildVisualQueryFromString(q.expr);
|
||||
if (visualQuery.query.metric !== '') {
|
||||
searchParams.append('match[]', visualQuery.query.metric);
|
||||
const isUtf8Metric = !isValidLegacyName(visualQuery.query.metric);
|
||||
searchParams.append('match[]', isUtf8Metric ? `{"${visualQuery.query.metric}"}` : visualQuery.query.metric);
|
||||
if (visualQuery.query.binaryQueries) {
|
||||
visualQuery.query.binaryQueries.forEach((bq) => {
|
||||
searchParams.append('match[]', bq.query.metric);
|
||||
searchParams.append('match[]', isUtf8Metric ? `{"${bq.query.metric}"}` : bq.query.metric);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -263,7 +266,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
||||
getSeriesValues = async (labelName: string, selector: string): Promise<string[]> => {
|
||||
if (!this.datasource.hasLabelsMatchAPISupport()) {
|
||||
const data = await this.getSeries(selector);
|
||||
return data[labelName] ?? [];
|
||||
return data[removeQuotesIfExist(labelName)] ?? [];
|
||||
}
|
||||
return await this.fetchSeriesValuesWithMatch(labelName, selector);
|
||||
};
|
||||
@@ -297,7 +300,14 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
||||
requestOptions = undefined;
|
||||
}
|
||||
|
||||
const value = await this.request(`/api/v1/label/${interpolatedName}/values`, [], urlParams, requestOptions);
|
||||
const interpolatedAndEscapedName = escapeForUtf8Support(removeQuotesIfExist(interpolatedName ?? ''));
|
||||
|
||||
const value = await this.request(
|
||||
`/api/v1/label/${interpolatedAndEscapedName}/values`,
|
||||
[],
|
||||
urlParams,
|
||||
requestOptions
|
||||
);
|
||||
return value ?? [];
|
||||
};
|
||||
|
||||
@@ -493,3 +503,10 @@ function isCancelledError(error: unknown): error is {
|
||||
} {
|
||||
return typeof error === 'object' && error !== null && 'cancelled' in error && error.cancelled === true;
|
||||
}
|
||||
|
||||
// For utf8 labels we use quotes around the label
|
||||
// While requesting the label values we must remove the quotes
|
||||
export function removeQuotesIfExist(input: string): string {
|
||||
const match = input.match(/^"(.*)"$/); // extract the content inside the quotes
|
||||
return match?.[1] ?? input;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getPrometheusTime,
|
||||
getRangeSnapInterval,
|
||||
parseSelector,
|
||||
processLabels,
|
||||
toPromLikeQuery,
|
||||
truncateResult,
|
||||
} from './language_utils';
|
||||
@@ -565,3 +566,54 @@ describe('truncateResult', () => {
|
||||
expect(array[999]).toBe(999);
|
||||
});
|
||||
});
|
||||
|
||||
describe('processLabels', () => {
|
||||
it('export abstract query to expr', () => {
|
||||
const labels: Array<{ [key: string]: string }> = [
|
||||
{ label1: 'value1' },
|
||||
{ label2: 'value2' },
|
||||
{ label3: 'value3' },
|
||||
{ label1: 'value1' },
|
||||
{ label1: 'value1b' },
|
||||
];
|
||||
|
||||
expect(processLabels(labels)).toEqual({
|
||||
keys: ['label1', 'label2', 'label3'],
|
||||
values: { label1: ['value1', 'value1b'], label2: ['value2'], label3: ['value3'] },
|
||||
});
|
||||
});
|
||||
|
||||
it('dont wrap utf8 label values with quotes', () => {
|
||||
const labels: Array<{ [key: string]: string }> = [
|
||||
{ label1: 'value1' },
|
||||
{ label2: 'value2' },
|
||||
{ label3: 'value3 with space' },
|
||||
{ label4: 'value4.with.dot' },
|
||||
];
|
||||
|
||||
expect(processLabels(labels)).toEqual({
|
||||
keys: ['label1', 'label2', 'label3', 'label4'],
|
||||
values: {
|
||||
label1: ['value1'],
|
||||
label2: ['value2'],
|
||||
label3: [`value3 with space`],
|
||||
label4: [`value4.with.dot`],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('dont wrap utf8 labels with quotes', () => {
|
||||
const labels: Array<{ [key: string]: string }> = [
|
||||
{ 'label1 with space': 'value1' },
|
||||
{ 'label2.with.dot': 'value2' },
|
||||
];
|
||||
|
||||
expect(processLabels(labels)).toEqual({
|
||||
keys: ['label1 with space', 'label2.with.dot'],
|
||||
values: {
|
||||
'label1 with space': ['value1'],
|
||||
'label2.with.dot': ['value2'],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -478,7 +478,10 @@ export function extractLabelMatchers(tokens: Array<string | Token>): AbstractLab
|
||||
export function getRangeSnapInterval(
|
||||
cacheLevel: PrometheusCacheLevel,
|
||||
range: TimeRange
|
||||
): { start: string; end: string } {
|
||||
): {
|
||||
start: string;
|
||||
end: string;
|
||||
} {
|
||||
// Don't round the range if we're not caching
|
||||
if (cacheLevel === PrometheusCacheLevel.None) {
|
||||
return {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { PrometheusDatasource } from './datasource';
|
||||
import { getPrometheusTime } from './language_utils';
|
||||
import { PrometheusMetricFindQuery } from './metric_find_query';
|
||||
import { PromApplication, PromOptions } from './types';
|
||||
import { escapeForUtf8Support } from './utf8_support';
|
||||
|
||||
const fetchMock = jest.fn((options: BackendSrvRequest): Observable<FetchResponse<BackendDataSourceResponse>> => {
|
||||
return of({} as unknown as FetchResponse);
|
||||
@@ -413,5 +414,51 @@ describe('PrometheusMetricFindQuery', () => {
|
||||
});
|
||||
});
|
||||
// </ ModernPrometheus>
|
||||
describe('utf8 metric and label support', () => {
|
||||
it('utf8 label - label_values(a_utf8_http_requests_total,instance.test) should generate label values query', () => {
|
||||
const metricName = 'a_utf8_http_requests_total';
|
||||
const label = 'instance.test';
|
||||
const query = `label_values(${metricName},${label})`;
|
||||
const metricFindQuery = new PrometheusMetricFindQuery(prometheusDatasource, query);
|
||||
metricFindQuery.process(raw);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(fetchMock).toHaveBeenCalledWith({
|
||||
method: 'GET',
|
||||
url: `/api/datasources/uid/ABCDEF/resources/api/v1/label/${escapeForUtf8Support(label)}/values?match%5B%5D=${metricName}&start=1524650400&end=1524654000`,
|
||||
hideFromInspector: true,
|
||||
headers: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('utf8 metric - label_values(utf8.http_requests_total,instance_test) should generate label values query', () => {
|
||||
const metricName = 'utf8.http_requests_total';
|
||||
const label = 'instance_test';
|
||||
const query = `label_values(${metricName},${label})`;
|
||||
const metricFindQuery = new PrometheusMetricFindQuery(prometheusDatasource, query);
|
||||
metricFindQuery.process(raw);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(fetchMock).toHaveBeenCalledWith({
|
||||
method: 'GET',
|
||||
url: `/api/datasources/uid/ABCDEF/resources/api/v1/label/${label}/values?match%5B%5D=${metricName}&start=1524650400&end=1524654000`,
|
||||
hideFromInspector: true,
|
||||
headers: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('utf8 metric and label - label_values(utf8.http_requests_total,instance.test) should generate label values query', () => {
|
||||
const metricName = 'utf8.http_requests_total';
|
||||
const label = 'instance.test';
|
||||
const query = `label_values(${metricName},${label})`;
|
||||
const metricFindQuery = new PrometheusMetricFindQuery(prometheusDatasource, query);
|
||||
metricFindQuery.process(raw);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(fetchMock).toHaveBeenCalledWith({
|
||||
method: 'GET',
|
||||
url: `/api/datasources/uid/ABCDEF/resources/api/v1/label/${escapeForUtf8Support(label)}/values?match%5B%5D=${metricName}&start=1524650400&end=1524654000`,
|
||||
hideFromInspector: true,
|
||||
headers: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,9 +8,11 @@ import { getPrometheusTime } from './language_utils';
|
||||
import {
|
||||
PrometheusLabelNamesRegex,
|
||||
PrometheusLabelNamesRegexWithMatch,
|
||||
PrometheusLabelValuesRegex,
|
||||
PrometheusMetricNamesRegex,
|
||||
PrometheusQueryResultRegex,
|
||||
} from './migrations/variableMigration';
|
||||
import { escapeForUtf8Support, isValidLegacyName } from './utf8_support';
|
||||
|
||||
export class PrometheusMetricFindQuery {
|
||||
range: TimeRange;
|
||||
@@ -28,7 +30,7 @@ export class PrometheusMetricFindQuery {
|
||||
this.range = timeRange;
|
||||
const labelNamesRegex = PrometheusLabelNamesRegex;
|
||||
const labelNamesRegexWithMatch = PrometheusLabelNamesRegexWithMatch;
|
||||
const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/;
|
||||
const labelValuesRegex = PrometheusLabelValuesRegex;
|
||||
const metricNamesRegex = PrometheusMetricNamesRegex;
|
||||
const queryResultRegex = PrometheusQueryResultRegex;
|
||||
const labelNamesQuery = this.query.match(labelNamesRegex);
|
||||
@@ -83,8 +85,13 @@ export class PrometheusMetricFindQuery {
|
||||
const end = getPrometheusTime(this.range.to, true);
|
||||
const params = { ...(metric && { 'match[]': metric }), start: start.toString(), end: end.toString() };
|
||||
|
||||
let escapedLabel = label;
|
||||
if (!isValidLegacyName(label)) {
|
||||
escapedLabel = escapeForUtf8Support(label);
|
||||
}
|
||||
|
||||
if (!metric || this.datasource.hasLabelsMatchAPISupport()) {
|
||||
const url = `/api/v1/label/${label}/values`;
|
||||
const url = `/api/v1/label/${escapedLabel}/values`;
|
||||
|
||||
return this.datasource.metadataRequest(url, params).then((result) => {
|
||||
return _map(result.data.data, (value) => {
|
||||
|
||||
@@ -4,8 +4,7 @@ import { buildVisualQueryFromString } from '../querybuilder/parsing';
|
||||
import { PromVariableQuery, PromVariableQueryType as QueryType } from '../types';
|
||||
|
||||
export const PrometheusLabelNamesRegex = /^label_names\(\)\s*$/;
|
||||
// Note that this regex is different from the one in metric_find_query.ts because this is used pre-interpolation
|
||||
export const PrometheusLabelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_$][a-zA-Z0-9_]*)\)\s*$/;
|
||||
export const PrometheusLabelValuesRegex = /^label_values\((?:(.+),\s*)?(.+)\)\s*$/;
|
||||
export const PrometheusMetricNamesRegex = /^metrics\((.+)\)\s*$/;
|
||||
export const PrometheusQueryResultRegex = /^query_result\((.+)\)\s*$/;
|
||||
export const PrometheusLabelNamesRegexWithMatch = /^label_names\((.+)\)\s*$/;
|
||||
@@ -97,7 +96,7 @@ export function migrateVariableQueryToEditor(rawQuery: string | PromVariableQuer
|
||||
return queryBase;
|
||||
}
|
||||
|
||||
// migrate it back to a string with the correct varialbes in place
|
||||
// migrate it back to a string with the correct variables in place
|
||||
export function migrateVariableEditorBackToVariableSupport(QueryVariable: PromVariableQuery): string {
|
||||
switch (QueryVariable.qryType) {
|
||||
case QueryType.LabelNames:
|
||||
|
||||
@@ -334,3 +334,82 @@ describe('PromQueryModeller', () => {
|
||||
).toBe('cluster_namespace_slug_dialer_name <= bool 2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PromQueryModeller with utf8 support', () => {
|
||||
const modeller = new PromQueryModeller();
|
||||
|
||||
it('should render nothing if there is nothing', () => {
|
||||
expect(
|
||||
modeller.renderQuery({
|
||||
metric: undefined,
|
||||
labels: [],
|
||||
operations: [],
|
||||
})
|
||||
).toBe('');
|
||||
|
||||
expect(
|
||||
modeller.renderQuery({
|
||||
metric: '',
|
||||
labels: [],
|
||||
operations: [],
|
||||
})
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
it('should render legacy metric name as usual', () => {
|
||||
expect(
|
||||
modeller.renderQuery({
|
||||
metric: 'not_a_utf8_metric',
|
||||
labels: [],
|
||||
operations: [],
|
||||
})
|
||||
).toBe('not_a_utf8_metric');
|
||||
});
|
||||
|
||||
it('can render utf8 metric name in curly braces', () => {
|
||||
expect(
|
||||
modeller.renderQuery({
|
||||
metric: 'a.utf8.metric',
|
||||
labels: [],
|
||||
operations: [],
|
||||
})
|
||||
).toBe('{"a.utf8.metric"}');
|
||||
});
|
||||
|
||||
it('can render utf8 metric name in curly braces with legacy labels', () => {
|
||||
expect(
|
||||
modeller.renderQuery({
|
||||
metric: 'a.utf8.metric',
|
||||
labels: [
|
||||
{
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
op: '=',
|
||||
},
|
||||
],
|
||||
operations: [],
|
||||
})
|
||||
).toBe('{"a.utf8.metric", label="value"}');
|
||||
});
|
||||
|
||||
it('can render utf8 metric name in curly braces with legacy and utf8 labels', () => {
|
||||
expect(
|
||||
modeller.renderQuery({
|
||||
metric: 'a.utf8.metric',
|
||||
labels: [
|
||||
{
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
op: '=',
|
||||
},
|
||||
{
|
||||
label: 'utf8.label',
|
||||
value: 'value',
|
||||
op: '=',
|
||||
},
|
||||
],
|
||||
operations: [],
|
||||
})
|
||||
).toBe('{"a.utf8.metric", label="value", "utf8.label"="value"}');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useCallback, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorFieldGroup, InputGroup } from '@grafana/experimental';
|
||||
import { Button, InlineField, InlineFieldRow } from '@grafana/ui';
|
||||
import { Combobox, ComboboxOption } from '@grafana/ui/src/components/Combobox/Combobox';
|
||||
import { Button, InlineField, InlineFieldRow, Combobox, ComboboxOption } from '@grafana/ui';
|
||||
|
||||
import { PrometheusDatasource } from '../../datasource';
|
||||
import { regexifyLabelValuesQueryString } from '../parsingUtils';
|
||||
|
||||
@@ -93,7 +93,7 @@ export const PromQueryBuilder = memo<PromQueryBuilderProps>((props) => {
|
||||
{showExplain && (
|
||||
<OperationExplainedBox
|
||||
stepNumber={1}
|
||||
title={<RawQuery query={`${query.metric} ${promQueryModeller.renderLabels(query.labels)}`} lang={lang} />}
|
||||
title={<RawQuery query={`${promQueryModeller.renderQuery(query)}`} lang={lang} />}
|
||||
>
|
||||
{EXPLAIN_LABEL_FILTER_CONTENT}
|
||||
</OperationExplainedBox>
|
||||
|
||||
@@ -25,7 +25,7 @@ export const PromQueryBuilderExplained = memo<PromQueryBuilderExplainedProps>(({
|
||||
<Stack gap={0.5} direction="column">
|
||||
<OperationExplainedBox
|
||||
stepNumber={1}
|
||||
title={<RawQuery query={`${visQuery.metric} ${promQueryModeller.renderLabels(visQuery.labels)}`} lang={lang} />}
|
||||
title={<RawQuery query={`${promQueryModeller.renderQuery(visQuery)}`} lang={lang} />}
|
||||
>
|
||||
{EXPLAIN_LABEL_FILTER_CONTENT}
|
||||
</OperationExplainedBox>
|
||||
|
||||
@@ -3,6 +3,80 @@ import { buildVisualQueryFromString } from './parsing';
|
||||
import { PromOperationId, PromVisualQuery } from './types';
|
||||
|
||||
describe('buildVisualQueryFromString', () => {
|
||||
describe('utf8 support', () => {
|
||||
it('supports uts-8 label names', () => {
|
||||
expect(buildVisualQueryFromString('{"glück:🍀.dot"="luck"} == 11')).toEqual({
|
||||
query: {
|
||||
labels: [
|
||||
{
|
||||
label: 'glück:🍀.dot',
|
||||
op: '=',
|
||||
value: 'luck',
|
||||
},
|
||||
],
|
||||
metric: '',
|
||||
operations: [
|
||||
{
|
||||
id: PromOperationId.EqualTo,
|
||||
params: [11, false],
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('supports uts-8 metric names', () => {
|
||||
expect(buildVisualQueryFromString('{"I am a metric"}')).toEqual({
|
||||
query: {
|
||||
labels: [],
|
||||
metric: 'I am a metric',
|
||||
operations: [],
|
||||
},
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('supports uts-8 metric names with labels', () => {
|
||||
expect(buildVisualQueryFromString('{"metric.name", label_field="label value"}')).toEqual({
|
||||
query: {
|
||||
labels: [
|
||||
{
|
||||
label: 'label_field',
|
||||
op: '=',
|
||||
value: 'label value',
|
||||
},
|
||||
],
|
||||
metric: 'metric.name',
|
||||
operations: [],
|
||||
},
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('supports uts-8 metric names with utf8 labels', () => {
|
||||
expect(buildVisualQueryFromString('{"metric.name", "glück:🍀.dot"="luck"} == 11')).toEqual({
|
||||
query: {
|
||||
labels: [
|
||||
{
|
||||
label: 'glück:🍀.dot',
|
||||
op: '=',
|
||||
value: 'luck',
|
||||
},
|
||||
],
|
||||
metric: 'metric.name',
|
||||
operations: [
|
||||
{
|
||||
id: PromOperationId.EqualTo,
|
||||
params: [11, false],
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('creates no errors for empty query', () => {
|
||||
expect(buildVisualQueryFromString('')).toEqual(
|
||||
noErrors({
|
||||
@@ -246,6 +320,31 @@ describe('buildVisualQueryFromString', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('parses query with aggregation by utf8 labels', () => {
|
||||
const visQuery = {
|
||||
metric: 'metric_name',
|
||||
labels: [
|
||||
{
|
||||
label: 'instance',
|
||||
op: '=',
|
||||
value: 'internal:3000',
|
||||
},
|
||||
],
|
||||
operations: [
|
||||
{
|
||||
id: '__sum_by',
|
||||
params: ['cluster', '"app.version"'],
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(
|
||||
buildVisualQueryFromString('sum(metric_name{instance="internal:3000"}) by ("app.version", cluster)')
|
||||
).toEqual(noErrors(visQuery));
|
||||
expect(
|
||||
buildVisualQueryFromString('sum by ("app.version", cluster)(metric_name{instance="internal:3000"})')
|
||||
).toEqual(noErrors(visQuery));
|
||||
});
|
||||
|
||||
it('parses aggregation with params', () => {
|
||||
expect(buildVisualQueryFromString('topk(5, http_requests_total)')).toEqual(
|
||||
noErrors({
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
GroupingLabels,
|
||||
Identifier,
|
||||
LabelName,
|
||||
QuotedLabelName,
|
||||
MatchingModifierClause,
|
||||
MatchOp,
|
||||
NumberDurationLiteral,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
ParenExpr,
|
||||
parser,
|
||||
StringLiteral,
|
||||
QuotedLabelMatcher,
|
||||
UnquotedLabelMatcher,
|
||||
VectorSelector,
|
||||
Without,
|
||||
@@ -145,9 +147,33 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex
|
||||
break;
|
||||
}
|
||||
|
||||
case QuotedLabelName: {
|
||||
// Usually we got the metric name above in the Identifier case.
|
||||
// If we didn't get the name that's potentially we have it in curly braces as quoted string.
|
||||
// It must be quoted because that's how utf8 metric names should be defined
|
||||
// See proposal https://github.com/prometheus/proposals/blob/main/proposals/2023-08-21-utf8.md
|
||||
if (visQuery.metric === '') {
|
||||
const strLiteral = node.getChild(StringLiteral);
|
||||
const quotedMetric = getString(expr, strLiteral);
|
||||
visQuery.metric = quotedMetric.slice(1, -1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case QuotedLabelMatcher: {
|
||||
const quotedLabel = getLabel(expr, node, QuotedLabelName);
|
||||
quotedLabel.label = quotedLabel.label.slice(1, -1);
|
||||
visQuery.labels.push(quotedLabel);
|
||||
const err = node.getChild(ErrorId);
|
||||
if (err) {
|
||||
context.errors.push(makeError(expr, err));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case UnquotedLabelMatcher: {
|
||||
// Same as MetricIdentifier should be just one per query.
|
||||
visQuery.labels.push(getLabel(expr, node));
|
||||
visQuery.labels.push(getLabel(expr, node, LabelName));
|
||||
const err = node.getChild(ErrorId);
|
||||
if (err) {
|
||||
context.errors.push(makeError(expr, err));
|
||||
@@ -202,8 +228,12 @@ function isIntervalVariableError(node: SyntaxNode) {
|
||||
return node.prevSibling?.firstChild?.type.id === VectorSelector;
|
||||
}
|
||||
|
||||
function getLabel(expr: string, node: SyntaxNode): QueryBuilderLabelFilter {
|
||||
const label = getString(expr, node.getChild(LabelName));
|
||||
function getLabel(
|
||||
expr: string,
|
||||
node: SyntaxNode,
|
||||
labelType: typeof LabelName | typeof QuotedLabelName
|
||||
): QueryBuilderLabelFilter {
|
||||
const label = getString(expr, node.getChild(labelType));
|
||||
const op = getString(expr, node.getChild(MatchOp));
|
||||
const value = getString(expr, node.getChild(StringLiteral)).replace(/^["'`]|["'`]$/g, '');
|
||||
return {
|
||||
@@ -281,7 +311,7 @@ function handleAggregation(expr: string, node: SyntaxNode, context: Context) {
|
||||
funcName = `__${funcName}_without`;
|
||||
}
|
||||
|
||||
labels.push(...getAllByType(expr, modifier, LabelName));
|
||||
labels.push(...getAllByType(expr, modifier, LabelName), ...getAllByType(expr, modifier, QuotedLabelName));
|
||||
}
|
||||
|
||||
const body = node.getChild(FunctionCallBody);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Registry } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { prometheusRegularEscape } from '../../datasource';
|
||||
import { isValidLegacyName, utf8Support } from '../../utf8_support';
|
||||
import { PromVisualQueryOperationCategory } from '../types';
|
||||
|
||||
import { QueryBuilderLabelFilter, QueryBuilderOperation, QueryBuilderOperationDef, VisualQueryModeller } from './types';
|
||||
@@ -97,14 +98,28 @@ export abstract class LokiAndPromQueryModellerBase implements VisualQueryModelle
|
||||
if (config.featureToggles.prometheusSpecialCharsInLabelValues && !usingRegexOperator) {
|
||||
labelValue = prometheusRegularEscape(labelValue);
|
||||
}
|
||||
expr += `${filter.label}${filter.op}"${labelValue}"`;
|
||||
expr += `${utf8Support(filter.label)}${filter.op}"${labelValue}"`;
|
||||
}
|
||||
|
||||
return expr + `}`;
|
||||
}
|
||||
|
||||
renderQuery(query: PromLokiVisualQuery, nested?: boolean) {
|
||||
let queryString = `${query.metric ?? ''}${this.renderLabels(query.labels)}`;
|
||||
let queryString = '';
|
||||
const labels = this.renderLabels(query.labels);
|
||||
if (query.metric) {
|
||||
if (isValidLegacyName(query.metric)) {
|
||||
// This is a legacy metric, put outside the curl legacy_query{label="value"}
|
||||
queryString = `${query.metric}${labels}`;
|
||||
} else {
|
||||
// This is a utf8 metric, put inside the curly and quotes {"utf8.metric", label="value"}
|
||||
queryString = `{"${query.metric}"${labels.length > 0 ? `, ${labels.substring(1)}` : `}`}`;
|
||||
}
|
||||
} else {
|
||||
// No metric just use labels {label="value"}
|
||||
queryString = labels;
|
||||
}
|
||||
|
||||
queryString = this.renderOperations(queryString, query.operations);
|
||||
|
||||
if (!nested && this.hasBinaryOp(query) && Boolean(query.binaryQueries?.length)) {
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface PromQuery extends GenPromQuery, DataQuery {
|
||||
disableTextWrap?: boolean;
|
||||
fullMetaSearch?: boolean;
|
||||
includeNullMetadata?: boolean;
|
||||
fromExploreMetrics?: boolean;
|
||||
}
|
||||
|
||||
export enum PrometheusCacheLevel {
|
||||
|
||||
127
packages/grafana-prometheus/src/utf8_support.test.ts
Normal file
127
packages/grafana-prometheus/src/utf8_support.test.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { escapeForUtf8Support, utf8Support, wrapUtf8Filters } from './utf8_support';
|
||||
|
||||
describe('utf8 support', () => {
|
||||
it('should return utf8 labels wrapped in quotes', () => {
|
||||
const labels = ['valid:label', 'metric_label', 'utf8 label with space 🤘', ''];
|
||||
const expected = ['valid:label', 'metric_label', `"utf8 label with space 🤘"`, ''];
|
||||
const supportedLabels = labels.map(utf8Support);
|
||||
expect(supportedLabels).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyValueEncodingEscaping', () => {
|
||||
it('should return utf8 labels wrapped in quotes', () => {
|
||||
const labels = [
|
||||
'no:escaping_required',
|
||||
'mysystem.prod.west.cpu.load',
|
||||
'mysystem.prod.west.cpu.load_total',
|
||||
'http.status:sum',
|
||||
'my lovely_http.status:sum',
|
||||
'花火',
|
||||
'label with 😱',
|
||||
];
|
||||
const expected = [
|
||||
'no:escaping_required',
|
||||
'U__mysystem_2e_prod_2e_west_2e_cpu_2e_load',
|
||||
'U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total',
|
||||
'U__http_2e_status:sum',
|
||||
'U__my_20_lovely__http_2e_status:sum',
|
||||
'U___82b1__706b_',
|
||||
'U__label_20_with_20__1f631_',
|
||||
];
|
||||
const excapedLabels = labels.map(escapeForUtf8Support);
|
||||
expect(excapedLabels).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrapUtf8Filters', () => {
|
||||
it('should correctly wrap UTF-8 labels and values for multiple key-value pairs', () => {
|
||||
const result = wrapUtf8Filters('label.with.spaß="this_is_fun",instance="localhost:9112"');
|
||||
const expected = '"label.with.spaß"="this_is_fun",instance="localhost:9112"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should correctly wrap UTF-8 labels and values for a single key-value pair', () => {
|
||||
const result = wrapUtf8Filters('label.with.spaß="this_is_fun"');
|
||||
const expected = '"label.with.spaß"="this_is_fun"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should correctly handle commas within values', () => {
|
||||
const result = wrapUtf8Filters('label.with.spaß="this,is,fun",instance="localhost:9112"');
|
||||
const expected = '"label.with.spaß"="this,is,fun",instance="localhost:9112"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should correctly handle escaped quotes within values', () => {
|
||||
const result = wrapUtf8Filters(`label.with.spaß="this_is_\\"fun\\"",instance="localhost:9112"`);
|
||||
const expected = `"label.with.spaß"="this_is_\\"fun\\"",instance="localhost:9112"`;
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should correctly handle spaces within keys', () => {
|
||||
const result = wrapUtf8Filters('label with space="value with space",instance="localhost:9112"');
|
||||
const expected = '"label with space"="value with space",instance="localhost:9112"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should correctly process mixed inputs with various formats', () => {
|
||||
const result = wrapUtf8Filters('key1="value1",key2="value,with,comma",key3="val3"');
|
||||
const expected = 'key1="value1",key2="value,with,comma",key3="val3"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should correctly handle empty values', () => {
|
||||
const result = wrapUtf8Filters('key1="",key2="value2"');
|
||||
const expected = 'key1="",key2="value2"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle an empty input string', () => {
|
||||
const result = wrapUtf8Filters('');
|
||||
const expected = '';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle a single key with an empty value', () => {
|
||||
const result = wrapUtf8Filters('key1=""');
|
||||
const expected = 'key1=""';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle multiple consecutive commas in a value', () => {
|
||||
const result = wrapUtf8Filters('key1="value1,,value2",key2="value3"');
|
||||
const expected = 'key1="value1,,value2",key2="value3"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle a key-value pair with special characters in the key', () => {
|
||||
const result = wrapUtf8Filters('special@key#="value1",key2="value2"');
|
||||
const expected = '"special@key#"="value1",key2="value2"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle a key-value pair with special characters in the value', () => {
|
||||
const result = wrapUtf8Filters('key1="value@#&*",key2="value2"');
|
||||
const expected = 'key1="value@#&*",key2="value2"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should correctly process keys without special characters', () => {
|
||||
const result = wrapUtf8Filters('key1="value1",key2="value2"');
|
||||
const expected = 'key1="value1",key2="value2"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle nested escaped quotes correctly', () => {
|
||||
const result = wrapUtf8Filters('key1="nested \\"escaped\\" quotes",key2="value2"');
|
||||
const expected = 'key1="nested \\"escaped\\" quotes",key2="value2"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle escaped quotes correctly', () => {
|
||||
const result = wrapUtf8Filters('key1="nested \\"escaped\\" quotes",key2="value with \\"escaped\\" quotes"');
|
||||
const expected = 'key1="nested \\"escaped\\" quotes",key2="value with \\"escaped\\" quotes"';
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
113
packages/grafana-prometheus/src/utf8_support.ts
Normal file
113
packages/grafana-prometheus/src/utf8_support.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
export const utf8Support = (value: string) => {
|
||||
if (value === '') {
|
||||
return value;
|
||||
}
|
||||
const isLegacyLabel = isValidLegacyName(value);
|
||||
if (isLegacyLabel) {
|
||||
return value;
|
||||
}
|
||||
return `"${value}"`;
|
||||
};
|
||||
|
||||
export const escapeForUtf8Support = (value: string) => {
|
||||
const isLegacyLabel = isValidLegacyName(value);
|
||||
if (isLegacyLabel) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let escaped = 'U__';
|
||||
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const char = value[i];
|
||||
const codePoint = value.codePointAt(i);
|
||||
|
||||
if (char === '_') {
|
||||
escaped += '__';
|
||||
} else if (codePoint !== undefined && isValidLegacyRune(char, i)) {
|
||||
escaped += char;
|
||||
} else if (codePoint === undefined || !isValidCodePoint(codePoint)) {
|
||||
escaped += '_FFFD_';
|
||||
} else {
|
||||
escaped += '_';
|
||||
escaped += codePoint.toString(16); // Convert code point to hexadecimal
|
||||
escaped += '_';
|
||||
}
|
||||
|
||||
// Handle surrogate pairs for characters outside the Basic Multilingual Plane
|
||||
if (codePoint !== undefined && codePoint > 0xffff) {
|
||||
i++; // Skip the second half of the surrogate pair
|
||||
}
|
||||
}
|
||||
|
||||
return escaped;
|
||||
};
|
||||
|
||||
export const isValidLegacyName = (name: string): boolean => {
|
||||
if (name.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
const char = name[i];
|
||||
if (!isValidLegacyRune(char, i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// const labelNamePriorToUtf8Support = /^[a-zA-Z_:][a-zA-Z0-9_:]*$/;
|
||||
// instead of regex we use rune check (converted from prometheus code)
|
||||
// https://github.com/prometheus/common/blob/main/model/metric.go#L426-L428
|
||||
const isValidLegacyRune = (char: string, index: number): boolean => {
|
||||
const codePoint = char.codePointAt(0);
|
||||
if (codePoint === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
(codePoint >= 97 && codePoint <= 122) || // 'a' to 'z'
|
||||
(codePoint >= 65 && codePoint <= 90) || // 'A' to 'Z'
|
||||
codePoint === 95 || // '_'
|
||||
codePoint === 58 || // ':'
|
||||
(codePoint >= 48 && codePoint <= 57 && index > 0) // '0' to '9', but not at the start
|
||||
);
|
||||
};
|
||||
|
||||
const isValidCodePoint = (codePoint: number): boolean => {
|
||||
// Validate the code point for UTF-8 compliance if needed.
|
||||
return codePoint >= 0 && codePoint <= 0x10ffff;
|
||||
};
|
||||
|
||||
export const wrapUtf8Filters = (filterStr: string): string => {
|
||||
const resultArray: string[] = [];
|
||||
let currentKey = '';
|
||||
let currentValue = '';
|
||||
let inQuotes = false;
|
||||
let temp = '';
|
||||
|
||||
for (const char of filterStr) {
|
||||
if (char === '"' && temp[temp.length - 1] !== '\\') {
|
||||
// Toggle inQuotes when an unescaped quote is found
|
||||
inQuotes = !inQuotes;
|
||||
temp += char;
|
||||
} else if (char === ',' && !inQuotes) {
|
||||
// When outside quotes and encountering ',', finalize the current pair
|
||||
[currentKey, currentValue] = temp.split('=');
|
||||
resultArray.push(`${utf8Support(currentKey.trim())}="${currentValue.slice(1, -1)}"`);
|
||||
temp = ''; // Reset for the next pair
|
||||
} else {
|
||||
// Collect characters
|
||||
temp += char;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the last key-value pair
|
||||
if (temp) {
|
||||
[currentKey, currentValue] = temp.split('=');
|
||||
resultArray.push(`${utf8Support(currentKey.trim())}="${currentValue.slice(1, -1)}"`);
|
||||
}
|
||||
|
||||
return resultArray.join(',');
|
||||
};
|
||||
@@ -57,3 +57,12 @@ export { hasPermission, hasPermissionInMetadata, hasAllPermissions, hasAnyPermis
|
||||
export { QueryEditorWithMigration } from './components/QueryEditorWithMigration';
|
||||
export { type MigrationHandler, isMigrationHandler, migrateQuery, migrateRequest } from './utils/migrationHandler';
|
||||
export { usePluginUserStorage } from './utils/userStorage';
|
||||
export {
|
||||
type CorrelationsService,
|
||||
type CorrelationData,
|
||||
type CorrelationsData,
|
||||
type CorrelationExternal,
|
||||
type CorrelationQuery,
|
||||
getCorrelationsService,
|
||||
setCorrelationsService,
|
||||
} from './services/CorrelationsService';
|
||||
|
||||
125
packages/grafana-runtime/src/services/CorrelationsService.ts
Normal file
125
packages/grafana-runtime/src/services/CorrelationsService.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
DataFrame,
|
||||
DataLinkPostProcessor,
|
||||
DataLinkTransformationConfig,
|
||||
DataSourceInstanceSettings,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
|
||||
export type CorrelationConfigQuery = {
|
||||
field: string;
|
||||
target: object; // for queries, this contains anything that would go in the query editor, so any extension off DataQuery a datasource would have, and needs to be generic.
|
||||
transformations?: DataLinkTransformationConfig[];
|
||||
};
|
||||
|
||||
export type CorrelationConfigExternal = {
|
||||
field: string;
|
||||
target: {
|
||||
url: string; // For external, this simply contains a URL
|
||||
};
|
||||
transformations?: DataLinkTransformationConfig[];
|
||||
};
|
||||
|
||||
type CorrelationBase = {
|
||||
uid: string;
|
||||
sourceUID: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
provisioned: boolean;
|
||||
orgId?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export type CorrelationExternal = CorrelationBase & {
|
||||
type: 'external';
|
||||
config: CorrelationConfigExternal;
|
||||
};
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export type CorrelationQuery = CorrelationBase & {
|
||||
type: 'query';
|
||||
config: CorrelationConfigQuery;
|
||||
targetUID: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export type CorrelationData =
|
||||
| (Omit<CorrelationExternal, 'sourceUID'> & {
|
||||
source: DataSourceInstanceSettings;
|
||||
})
|
||||
| (Omit<CorrelationQuery, 'sourceUID' | 'targetUID'> & {
|
||||
source: DataSourceInstanceSettings;
|
||||
target: DataSourceInstanceSettings;
|
||||
});
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface CorrelationsData {
|
||||
correlations: CorrelationData[];
|
||||
page: number;
|
||||
limit: number;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to work with user defined correlations.
|
||||
* Should be accessed via {@link getCorrelationsService} function.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface CorrelationsService {
|
||||
/**
|
||||
* Creates data links in data frames from provided correlations
|
||||
*
|
||||
* @param dataFrames list of data frames to be processed
|
||||
* @param correlations list of of possible correlations that can be applied
|
||||
* @param dataFrameRefIdToDataSourceUid a map that for provided refId references corresponding data source ui
|
||||
*/
|
||||
attachCorrelationsToDataFrames: (
|
||||
dataFrames: DataFrame[],
|
||||
correlations: CorrelationData[],
|
||||
dataFrameRefIdToDataSourceUid: Record<string, string>
|
||||
) => DataFrame[];
|
||||
|
||||
/**
|
||||
* Creates a link post processor function that handles correlation transformations
|
||||
*
|
||||
* @param timeRange The current time range
|
||||
*/
|
||||
correlationsDataLinkPostProcessorFactory: (timeRange: TimeRange) => DataLinkPostProcessor;
|
||||
|
||||
/**
|
||||
* Loads all the correlations defined for the given data sources.
|
||||
*
|
||||
* @param sourceUIDs Data source UIDs
|
||||
*/
|
||||
getCorrelationsBySourceUIDs: (sourceUIDs: string[]) => Promise<CorrelationsData>;
|
||||
}
|
||||
|
||||
let singletonInstance: CorrelationsService;
|
||||
|
||||
/**
|
||||
* Used during startup by Grafana to set the CorrelationsService so it is available
|
||||
* via {@link getCorrelationsService} to the rest of the application.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function setCorrelationsService(instance: CorrelationsService) {
|
||||
singletonInstance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to retrieve the {@link CorrelationsService}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export function getCorrelationsService(): CorrelationsService {
|
||||
return singletonInstance;
|
||||
}
|
||||
@@ -70,6 +70,12 @@ export type BackendSrvRequest = {
|
||||
*/
|
||||
responseType?: 'json' | 'text' | 'arraybuffer' | 'blob';
|
||||
|
||||
/**
|
||||
* Used to cancel an open connection
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/AbortController
|
||||
*/
|
||||
abortSignal?: AbortSignal;
|
||||
|
||||
/**
|
||||
* The credentials read-only property of the Request interface indicates whether the user agent should send cookies from the other domain in the case of cross-origin requests.
|
||||
*/
|
||||
@@ -173,6 +179,14 @@ export interface BackendSrv {
|
||||
* Observable http request interface
|
||||
*/
|
||||
fetch<T>(options: BackendSrvRequest): Observable<FetchResponse<T>>;
|
||||
|
||||
/**
|
||||
* Observe each raw chunk in the response. This is useful when reading values from
|
||||
* a long living HTTP connection like the kubernetes WATCH command.
|
||||
*
|
||||
* Each chunk includes the full response headers and the `data` property is filled with the chunk.
|
||||
*/
|
||||
chunked(options: BackendSrvRequest): Observable<FetchResponse<Uint8Array | undefined>>;
|
||||
}
|
||||
|
||||
let singletonInstance: BackendSrv;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DashboardV2Spec } from './dashboard.gen';
|
||||
import { DashboardV2Spec } from './types.gen';
|
||||
|
||||
export const handyTestingSchema: DashboardV2Spec = {
|
||||
id: 1,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
export * from './types.gen';
|
||||
export type * from './types.gen';
|
||||
@@ -41,7 +41,7 @@
|
||||
"@testing-library/user-event": "14.5.2",
|
||||
"@types/jest": "^29.5.4",
|
||||
"@types/lodash": "4.17.14",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/node": "22.10.6",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.2.25",
|
||||
"@types/react-virtualized-auto-sizer": "1.0.4",
|
||||
|
||||
@@ -83,11 +83,11 @@
|
||||
"monaco-editor": "0.34.1",
|
||||
"ol": "7.4.0",
|
||||
"prismjs": "1.29.0",
|
||||
"rc-cascader": "3.31.0",
|
||||
"rc-cascader": "3.33.0",
|
||||
"rc-drawer": "7.2.0",
|
||||
"rc-picker": "4.9.2",
|
||||
"rc-slider": "11.1.8",
|
||||
"rc-time-picker": "^3.7.3",
|
||||
"rc-tooltip": "6.3.2",
|
||||
"rc-tooltip": "6.4.0",
|
||||
"react-calendar": "^5.1.0",
|
||||
"react-colorful": "5.6.1",
|
||||
"react-custom-scrollbars-2": "4.5.0",
|
||||
@@ -145,7 +145,7 @@
|
||||
"@types/is-hotkey": "0.1.10",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/mock-raf": "1.0.6",
|
||||
"@types/node": "22.10.5",
|
||||
"@types/node": "22.10.6",
|
||||
"@types/prismjs": "1.26.5",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-color": "3.0.13",
|
||||
|
||||
@@ -26,6 +26,7 @@ export const getComboboxStyles = (theme: GrafanaTheme2) => {
|
||||
zIndex: theme.zIndex.dropdown,
|
||||
position: 'relative',
|
||||
borderRadius: theme.shape.radius.default,
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
menuUlContainer: css({
|
||||
label: 'combobox-menu-ul-container',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { dateTime, dateTimeAsMoment, dateTimeForTimeZone, getTimeZone, setTimeZoneResolver } from '@grafana/data';
|
||||
@@ -86,23 +86,16 @@ describe('Date time picker', () => {
|
||||
|
||||
// open the time of day overlay
|
||||
await userEvent.click(screen.getAllByRole('textbox')[1]);
|
||||
const hourList = screen.getAllByRole('list')[0];
|
||||
|
||||
// change the hour
|
||||
await userEvent.click(
|
||||
screen.getAllByRole('button', {
|
||||
name: '00',
|
||||
})[0]
|
||||
);
|
||||
await userEvent.click(within(hourList).getByText('00'));
|
||||
|
||||
// Check the active day is the 5th
|
||||
expect(screen.getByRole('button', { name: 'May 5, 2021' })).toHaveClass('react-calendar__tile--active');
|
||||
|
||||
// change the hour
|
||||
await userEvent.click(
|
||||
screen.getAllByRole('button', {
|
||||
name: '23',
|
||||
})[0]
|
||||
);
|
||||
await userEvent.click(within(hourList).getByText('23'));
|
||||
|
||||
// Check the active day is the 5th
|
||||
expect(screen.getByRole('button', { name: 'May 5, 2021' })).toHaveClass('react-calendar__tile--active');
|
||||
@@ -123,7 +116,6 @@ describe('Date time picker', () => {
|
||||
await userEvent.click(screen.getByRole('button', { name: 'May 15, 2021' }));
|
||||
|
||||
const timeInput = screen.getAllByRole('textbox')[1];
|
||||
expect(timeInput).toHaveClass('rc-time-picker-input');
|
||||
expect(timeInput).not.toHaveDisplayValue('00:00:00');
|
||||
}
|
||||
);
|
||||
@@ -216,9 +208,7 @@ describe('Date time picker', () => {
|
||||
await userEvent.click(screen.getAllByRole('textbox')[1]);
|
||||
|
||||
// check the hour element is visible
|
||||
const hourElement = screen.getAllByRole('button', {
|
||||
name: '00',
|
||||
})[0];
|
||||
const hourElement = screen.getAllByText('00')[0];
|
||||
expect(hourElement).toBeVisible();
|
||||
|
||||
// select the hour value and check it's still visible
|
||||
@@ -227,11 +217,7 @@ describe('Date time picker', () => {
|
||||
|
||||
// click outside the overlay and check the hour element is no longer visible
|
||||
await userEvent.click(document.body);
|
||||
expect(
|
||||
screen.queryByRole('button', {
|
||||
name: '00',
|
||||
})
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('00')).not.toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export const Basic: StoryFn<typeof TimeOfDayPicker> = (args) => {
|
||||
return (
|
||||
<TimeOfDayPicker
|
||||
{...args}
|
||||
onChange={(newValue) => {
|
||||
onChange={(newValue?) => {
|
||||
action('on selected')(newValue);
|
||||
updateArgs({ value: newValue });
|
||||
}}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user