diff --git a/.betterer.results b/.betterer.results
index 2af5d4872d0..e1996888277 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -1615,21 +1615,8 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"]
],
- "public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx:5381": [
- [0, 0, 0, "No untranslated strings. Wrap text with ", "0"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "1"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "2"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "3"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "4"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "5"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "6"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "7"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "8"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "9"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "10"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "11"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "12"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "13"]
+ "public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx:5381": [
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
],
"public/app/features/alerting/unified/components/rule-editor/NeedHelpInfo.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
@@ -1682,9 +1669,6 @@ exports[`better eslint`] = {
"public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
],
- "public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx:5381": [
- [0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
- ],
"public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "1"],
@@ -2434,6 +2418,14 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "7"],
[0, 0, 0, "Do not use any type assertions.", "8"]
],
+ "public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts:5381": [
+ [0, 0, 0, "Do not use any type assertions.", "0"],
+ [0, 0, 0, "Do not use any type assertions.", "1"],
+ [0, 0, 0, "Unexpected any. Specify a different type.", "2"],
+ [0, 0, 0, "Do not use any type assertions.", "3"],
+ [0, 0, 0, "Do not use any type assertions.", "4"],
+ [0, 0, 0, "Unexpected any. Specify a different type.", "5"]
+ ],
"public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "1"],
@@ -2564,9 +2556,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryButtons\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./VersionHistoryComparison\`)", "4"]
],
- "public/app/features/dashboard-scene/sharing/ShareExportTab.tsx:5381": [
- [0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
- ],
"public/app/features/dashboard-scene/sharing/public-dashboards/ConfigPublicDashboard.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
],
@@ -5637,7 +5626,8 @@ exports[`better eslint`] = {
],
"public/swagger/SwaggerPage.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "1"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "1"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "2"]
],
"public/swagger/index.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 8b4c204f87a..99a3971e3e4 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -40,7 +40,6 @@
/docs/sources/alerting/ @brendamuir
/docs/sources/dashboards/ @imatwawana
-/docs/sources/explore/ @grafana/observability-traces-and-profiling @lwandz13
/docs/sources/panels-visualizations/ @imatwawana
/docs/sources/release-notes/ @irenerl24 @GrafanaWriter
/docs/sources/upgrade-guide/ @imatwawana
@@ -673,6 +672,7 @@ playwright.config.ts @grafana/plugins-platform-frontend
# Kind definitions
/kinds/dashboard @grafana/dashboards-squad
/kinds/ @grafana/grafana-as-code
+kindsv2/ @grafana/dashboards-squad
# Kind system and code generation
embed.go @grafana/grafana-as-code
diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml
index a46a12134bf..8cbe3030ffe 100644
--- a/.github/workflows/github-release.yml
+++ b/.github/workflows/github-release.yml
@@ -8,7 +8,7 @@ on:
type: string
latest:
required: false
- default: false
+ default: "0"
description: Mark this release as latest (`1`) or not (`0`, default)
type: string
dry_run:
@@ -23,6 +23,7 @@ on:
type: string
latest:
required: false
+ default: "0"
description: Mark this release as latest (`1`) or not (`0`, default)
type: string
dry_run:
diff --git a/.github/workflows/release-comms.yml b/.github/workflows/release-comms.yml
index dc7355ef0b8..02d53057184 100644
--- a/.github/workflows/release-comms.yml
+++ b/.github/workflows/release-comms.yml
@@ -8,10 +8,11 @@ on:
dry_run:
required: false
default: true
+ type: boolean
version:
required: true
latest:
- type: bool
+ type: boolean
default: false
pull_request:
types:
@@ -30,17 +31,18 @@ jobs:
latest: ${{ steps.output.outputs.latest }}
runs-on: ubuntu-latest
steps:
+ # The github-release action expects a `LATEST` value of a string of either '1' or '0'
- if: ${{ github.event_name == 'workflow_dispatch' }}
run: |
echo setting up GITHUB_ENV for ${{ github.event_name }}
echo "VERSION=${{ inputs.version }}" >> $GITHUB_ENV
echo "DRY_RUN=${{ inputs.dry_run }}" >> $GITHUB_ENV
- echo "LATEST=${{ inputs.latest }}" >> $GITHUB_ENV
+ echo "LATEST=${{ inputs.latest && '1' || '0' }}" >> $GITHUB_ENV
- if: ${{ github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/') }}
run: |
echo "VERSION=$(echo ${{ github.head_ref }} | sed -e 's/release\/.*\///g')" >> $GITHUB_ENV
echo "DRY_RUN=${{ contains(github.event.pull_request.labels.*.name, 'release/dry-run') }}" >> $GITHUB_ENV
- echo "LATEST=${{ contains(github.event.pull_request.labels.*.name, 'release/latest') }}" >> $GITHUB_ENV
+ echo "LATEST=${{ contains(github.event.pull_request.labels.*.name, 'release/latest') && '1' || '0' }}" >> $GITHUB_ENV
- id: output
run: |
echo "dry_run: $DRY_RUN"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index af808ed98a1..4469fb9a34d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,114 @@
+
+
+# 11.3.1 (2024-11-19)
+
+### Features and enhancements
+
+- **Alerting:** Make context deadline on AlertNG service startup configurable [#96135](https://github.com/grafana/grafana/pull/96135), [@fayzal-g](https://github.com/fayzal-g)
+- **MigrationAssistant:** Restrict dashboards, folders and datasources by the org id of the signed in user [#96345](https://github.com/grafana/grafana/pull/96345), [@leandro-deveikis](https://github.com/leandro-deveikis)
+- **User:** Check SignedInUser OrgID in RevokeInvite [#95490](https://github.com/grafana/grafana/pull/95490), [@mgyongyosi](https://github.com/mgyongyosi)
+
+### Bug fixes
+
+- **Alerting:** Fix escaping of silence matchers in utf8 mode [#95347](https://github.com/grafana/grafana/pull/95347), [@tomratcliffe](https://github.com/tomratcliffe)
+- **Alerting:** Fix overflow for long receiver names [#95133](https://github.com/grafana/grafana/pull/95133), [@gillesdemey](https://github.com/gillesdemey)
+- **Alerting:** Fix saving advanced mode toggle state in the alert rule editor [#95981](https://github.com/grafana/grafana/pull/95981), [@alexander-akhmetov](https://github.com/alexander-akhmetov)
+- **Alerting:** Fix setting datasource uid, when datasource is string in old version [#96273](https://github.com/grafana/grafana/pull/96273), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron)
+- **Alerting:** Force refetch prom rules when refreshing panel [#96125](https://github.com/grafana/grafana/pull/96125), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron)
+- **Anonymous User:** Adds validator service for anonymous users [#94994](https://github.com/grafana/grafana/pull/94994), [@leandro-deveikis](https://github.com/leandro-deveikis)
+- **Anonymous User:** Adds validator service for anonymous users (Enterprise)
+- **Azure Monitor:** Support metric namespaces fallback [#95155](https://github.com/grafana/grafana/pull/95155), [@aangelisc](https://github.com/aangelisc)
+- **Azure:** Fix duplicated traces in multi-resource trace query [#95247](https://github.com/grafana/grafana/pull/95247), [@aangelisc](https://github.com/aangelisc)
+- **Azure:** Handle namespace request rejection [#95909](https://github.com/grafana/grafana/pull/95909), [@aangelisc](https://github.com/aangelisc)
+- **CloudWatch:** Interpolate region in log context query [#94990](https://github.com/grafana/grafana/pull/94990), [@iwysiu](https://github.com/iwysiu)
+- **Dashboard datasource:** Return annotations as series when query topic is "annotations" [#95971](https://github.com/grafana/grafana/pull/95971), [@kaydelaney](https://github.com/kaydelaney)
+- **Dashboard:** Append orgId to URL [#95963](https://github.com/grafana/grafana/pull/95963), [@bfmatei](https://github.com/bfmatei)
+- **Dashboards:** Fixes performance issue expanding a row [#95321](https://github.com/grafana/grafana/pull/95321), [@torkelo](https://github.com/torkelo)
+- **Flame Graph:** Fix crash when it receives empty data [#96211](https://github.com/grafana/grafana/pull/96211), [@yincongcyincong](https://github.com/yincongcyincong)
+- **Folders:** Add admin permissions upon creation of a folder w. SA [#95365](https://github.com/grafana/grafana/pull/95365), [@eleijonmarck](https://github.com/eleijonmarck)
+- **Folders:** Don't show error pop-up if the user can't fetch the root folder [#95600](https://github.com/grafana/grafana/pull/95600), [@IevaVasiljeva](https://github.com/IevaVasiljeva)
+- **Migration:** Remove table aliasing in delete statement to make it work for mariadb [#95232](https://github.com/grafana/grafana/pull/95232), [@kalleep](https://github.com/kalleep)
+- **ServerLock:** Fix pg concurrency/locking issue [#95935](https://github.com/grafana/grafana/pull/95935), [@mgyongyosi](https://github.com/mgyongyosi)
+- **Service Accounts:** Run service account creation in transaction [#94803](https://github.com/grafana/grafana/pull/94803), [@IevaVasiljeva](https://github.com/IevaVasiljeva)
+- **Table:** Fix text wrapping applying to wrong field [#95425](https://github.com/grafana/grafana/pull/95425), [@codeincarnate](https://github.com/codeincarnate)
+- **Unified Storage:** Use ssl_mode instead of sslmode [#95662](https://github.com/grafana/grafana/pull/95662), [@chaudyg](https://github.com/chaudyg)
+
+
+
+
+# 11.2.4 (2024-11-19)
+
+### Features and enhancements
+
+- **Alerting:** Make context deadline on AlertNG service startup configurable [#96133](https://github.com/grafana/grafana/pull/96133), [@fayzal-g](https://github.com/fayzal-g)
+- **MigrationAssistant:** Restrict dashboards, folders and datasources by the org id of the signed in user [#96344](https://github.com/grafana/grafana/pull/96344), [@leandro-deveikis](https://github.com/leandro-deveikis)
+- **Transformations:** Add 'transpose' transform [#95076](https://github.com/grafana/grafana/pull/95076), [@jmdane](https://github.com/jmdane)
+- **User:** Check SignedInUser OrgID in RevokeInvite [#95489](https://github.com/grafana/grafana/pull/95489), [@mgyongyosi](https://github.com/mgyongyosi)
+
+### Bug fixes
+
+- **Alerting:** Force refetch prom rules when refreshing panel [#96124](https://github.com/grafana/grafana/pull/96124), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron)
+- **Anonymous User:** Adds validator service for anonymous users [#94993](https://github.com/grafana/grafana/pull/94993), [@leandro-deveikis](https://github.com/leandro-deveikis)
+- **Anonymous User:** Adds validator service for anonymous users (Enterprise)
+- **Azure Monitor:** Support metric namespaces fallback [#95154](https://github.com/grafana/grafana/pull/95154), [@aangelisc](https://github.com/aangelisc)
+- **Azure:** Fix duplicated traces in multi-resource trace query [#95246](https://github.com/grafana/grafana/pull/95246), [@aangelisc](https://github.com/aangelisc)
+- **Azure:** Handle namespace request rejection [#95908](https://github.com/grafana/grafana/pull/95908), [@aangelisc](https://github.com/aangelisc)
+- **Folders:** Add admin permissions upon creation of a folder w. SA [#95416](https://github.com/grafana/grafana/pull/95416), [@eleijonmarck](https://github.com/eleijonmarck)
+- **Migration:** Remove table aliasing in delete statement to make it work for mariadb [#95231](https://github.com/grafana/grafana/pull/95231), [@kalleep](https://github.com/kalleep)
+- **ServerLock:** Fix pg concurrency/locking issue [#95934](https://github.com/grafana/grafana/pull/95934), [@mgyongyosi](https://github.com/mgyongyosi)
+- **ServerSideExpressions:** Disable SQL Expressions to prevent RCE and LFI vulnerability [#94959](https://github.com/grafana/grafana/pull/94959), [@samjewell](https://github.com/samjewell)
+
+
+
+
+# 11.1.9 (2024-11-19)
+
+### Features and enhancements
+
+- **Alerting:** Make context deadline on AlertNG service startup configurable [#96132](https://github.com/grafana/grafana/pull/96132), [@fayzal-g](https://github.com/fayzal-g)
+- **User:** Check SignedInUser OrgID in RevokeInvite [#95488](https://github.com/grafana/grafana/pull/95488), [@mgyongyosi](https://github.com/mgyongyosi)
+
+### Bug fixes
+
+- **Alerting:** Force refetch prom rules when refreshing panel [#96123](https://github.com/grafana/grafana/pull/96123), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron)
+- **Anonymous User:** Adds validator service for anonymous users [#94992](https://github.com/grafana/grafana/pull/94992), [@leandro-deveikis](https://github.com/leandro-deveikis)
+- **Anonymous User:** Adds validator service for anonymous users (Enterprise)
+- **Azure Monitor:** Support metric namespaces fallback [#95153](https://github.com/grafana/grafana/pull/95153), [@aangelisc](https://github.com/aangelisc)
+- **Azure:** Fix duplicated traces in multi-resource trace query [#95245](https://github.com/grafana/grafana/pull/95245), [@aangelisc](https://github.com/aangelisc)
+- **Azure:** Handle namespace request rejection [#95907](https://github.com/grafana/grafana/pull/95907), [@aangelisc](https://github.com/aangelisc)
+- **Migration:** Remove table aliasing in delete statement to make it work for mariadb [#95230](https://github.com/grafana/grafana/pull/95230), [@kalleep](https://github.com/kalleep)
+- **Prometheus:** Fix interpolating adhoc filters with template variables [#95977](https://github.com/grafana/grafana/pull/95977), [@cazeaux](https://github.com/cazeaux)
+- **ServerLock:** Fix pg concurrency/locking issue [#95933](https://github.com/grafana/grafana/pull/95933), [@mgyongyosi](https://github.com/mgyongyosi)
+- **ServerSideExpressions:** Disable SQL Expressions to prevent RCE and LFI vulnerability [#94969](https://github.com/grafana/grafana/pull/94969), [@scottlepp](https://github.com/scottlepp)
+
+
+
+
+# 11.0.8 (2024-11-19)
+
+### Features and enhancements
+
+- **Alerting:** Make context deadline on AlertNG service startup configurable [#96131](https://github.com/grafana/grafana/pull/96131), [@fayzal-g](https://github.com/fayzal-g)
+- **User:** Check SignedInUser OrgID in RevokeInvite [#95487](https://github.com/grafana/grafana/pull/95487), [@mgyongyosi](https://github.com/mgyongyosi)
+
+### Bug fixes
+
+- **Anonymous User:** Adds validator service for anonymous users [#95151](https://github.com/grafana/grafana/pull/95151), [@leandro-deveikis](https://github.com/leandro-deveikis)
+- **Anonymous User:** Adds validator service for anonymous users (Enterprise)
+- **Azure Monitor:** Support metric namespaces fallback [#95152](https://github.com/grafana/grafana/pull/95152), [@aangelisc](https://github.com/aangelisc)
+- **Azure:** Fix duplicated traces in multi-resource trace query [#95244](https://github.com/grafana/grafana/pull/95244), [@aangelisc](https://github.com/aangelisc)
+- **Azure:** Handle namespace request rejection [#95906](https://github.com/grafana/grafana/pull/95906), [@aangelisc](https://github.com/aangelisc)
+- **Migration:** Remove table aliasing in delete statement to make it work for mariadb [#95229](https://github.com/grafana/grafana/pull/95229), [@kalleep](https://github.com/kalleep)
+- **Prometheus:** Fix interpolating adhoc filters with template variables [#95986](https://github.com/grafana/grafana/pull/95986), [@cazeaux](https://github.com/cazeaux)
+- **ServerLock:** Fix pg concurrency/locking issue [#95932](https://github.com/grafana/grafana/pull/95932), [@mgyongyosi](https://github.com/mgyongyosi)
+- **ServerSideExpressions:** Disable SQL Expressions to prevent RCE and LFI vulnerability [#94971](https://github.com/grafana/grafana/pull/94971), [@samjewell](https://github.com/samjewell)
+
+
+
+
+# 10.4.13 (2024-11-19)
+
+
# 11.3.0+security-01 (2024-11-12)
diff --git a/Dockerfile b/Dockerfile
index f66b04bd747..9c71569fc58 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -30,7 +30,7 @@ COPY tsconfig.json .eslintrc .editorconfig .browserslistrc .prettierrc.js ./
COPY scripts scripts
COPY emails emails
-ENV NODE_ENV production
+ENV NODE_ENV=production
RUN yarn build
FROM ${GO_IMAGE} as go-builder
@@ -77,6 +77,7 @@ RUN if [[ "$BINGO" = "true" ]]; then \
COPY embed.go Makefile build.go package.json ./
COPY cue.mod cue.mod
COPY kinds kinds
+COPY kindsv2 kindsv2
COPY local local
COPY packages/grafana-schema packages/grafana-schema
COPY public/app/plugins public/app/plugins
diff --git a/Makefile b/Makefile
index 6929b8167d4..0298bb55d83 100644
--- a/Makefile
+++ b/Makefile
@@ -146,6 +146,11 @@ gen-cue: ## Do all CUE/Thema code generation
go generate ./kinds/gen.go
go generate ./public/app/plugins/gen.go
+.PHONY: gen-cuev2
+gen-cuev2: ## Do all CUE code generation
+ @echo "generate code from .cue files (v2)"
+ go generate ./kindsv2/gen.go
+
.PHONY: gen-feature-toggles
gen-feature-toggles:
## First go test run fails because it will re-generate the feature toggles.
diff --git a/devenv/docker/blocks/collectd/Dockerfile b/devenv/docker/blocks/collectd/Dockerfile
index ea046659bd1..00762cd0149 100644
--- a/devenv/docker/blocks/collectd/Dockerfile
+++ b/devenv/docker/blocks/collectd/Dockerfile
@@ -1,6 +1,6 @@
FROM ubuntu:xenial
-ENV DEBIAN_FRONTEND noninteractive
+ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update
RUN apt-get -y install collectd curl python-pip
diff --git a/devenv/docker/buildcontainer/Dockerfile b/devenv/docker/buildcontainer/Dockerfile
index ae85c8fbad2..a6a636439c3 100644
--- a/devenv/docker/buildcontainer/Dockerfile
+++ b/devenv/docker/buildcontainer/Dockerfile
@@ -2,18 +2,18 @@ FROM centos:6.6
RUN yum install -y initscripts curl tar gcc libc6-dev git
-ENV GOLANG_VERSION 1.4.2
+ENV GOLANG_VERSION=1.4.2
RUN curl -sSL https://golang.org/dl/go$GOLANG_VERSION.src.tar.gz \
| tar -v -C /usr/src -xz
RUN cd /usr/src/go/src && ./make.bash --no-clean 2>&1
-ENV PATH /usr/src/go/bin:$PATH
+ENV PATH=/usr/src/go/bin:$PATH
RUN mkdir -p /go/src /go/bin && chmod -R 777 /go
-ENV GOPATH /go
-ENV PATH /go/bin:$PATH
+ENV GOPATH=/go
+ENV PATH=/go/bin:$PATH
WORKDIR /go/src/github.com/grafana/grafana
diff --git a/devenv/docker/ha-test-unified-alerting/Dockerfile b/devenv/docker/ha-test-unified-alerting/Dockerfile
index cc9b14e2e4e..38965c12636 100644
--- a/devenv/docker/ha-test-unified-alerting/Dockerfile
+++ b/devenv/docker/ha-test-unified-alerting/Dockerfile
@@ -7,6 +7,6 @@ WORKDIR /go/src/webhook
RUN mkdir /tmp/logs
RUN go build -o /bin webhook-listener.go
-ENV PORT 8080
+ENV PORT=8080
ENTRYPOINT [ "/bin/webhook-listener" ]
diff --git a/docs/sources/datasources/loki/configure-loki-data-source.md b/docs/sources/datasources/loki/configure-loki-data-source.md
index b667c8e1bf8..8dc6eb1670c 100644
--- a/docs/sources/datasources/loki/configure-loki-data-source.md
+++ b/docs/sources/datasources/loki/configure-loki-data-source.md
@@ -114,9 +114,13 @@ Each derived field consists of the following:
- **Type** - Defines the type of the derived field. It can be either:
- - **Regex**: A regular expression to parse a part of the log message and capture it as the value of the new field. Can contain only one capture group.
+{{% admonition type="caution" %}}
+Using complex regular expressions in either type can impact browser performance when processing large volumes of logs. Consider using simpler patterns when possible.
+{{% /admonition %}}
- - **Label**: A label from the selected log line. This can be any type of label - indexed, parsed or structured metadata. The label's value will be used as the value of the derived field.
+- **Regex**: A regular expression to parse a part of the log message and capture it as the value of the new field. Can contain only one capture group.
+
+- **Label**: A label from the selected log line. This can be any type of label - indexed, parsed or structured metadata. When using this type, the input will match as a regular expression against label keys, allowing you to match variations like `traceid` and `trace_id` with a single regex pattern like `trace[_]?id`. The value of the matched label will be used as the value of the derived field.
- **URL/query** Sets the full link URL if the link is external, or a query for the target data source if the link is internal. You can interpolate the value from the field with the `${__value.raw}` macro.
diff --git a/docs/sources/datasources/prometheus/_index.md b/docs/sources/datasources/prometheus/_index.md
index 1482e497ed2..cd5cbb04ac1 100644
--- a/docs/sources/datasources/prometheus/_index.md
+++ b/docs/sources/datasources/prometheus/_index.md
@@ -163,17 +163,7 @@ For details about these metrics, refer to [Internal Grafana metrics](ref:set-up-
## Amazon Managed Service for Prometheus
-The Prometheus data source works with Amazon Managed Service for Prometheus.
-
-If you use an AWS Identity and Access Management (IAM) policy to control access to your Amazon Elasticsearch Service domain, you must use AWS Signature Version 4 (AWS SigV4) to sign all requests to that domain.
-
-For details on AWS SigV4, refer to the [AWS documentation](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html).
-
-### AWS Signature Version 4 authentication
-
-To connect the Prometheus data source to Amazon Managed Service for Prometheus using SigV4 authentication, refer to the AWS guide to [Set up Grafana open source or Grafana Enterprise for use with AMP](https://docs.aws.amazon.com/prometheus/latest/userguide/AMP-onboard-query-standalone-grafana.html).
-
-If you run Grafana in an Amazon EKS cluster, follow the AWS guide to [Query using Grafana running in an Amazon EKS cluster](https://docs.aws.amazon.com/prometheus/latest/userguide/AMP-onboard-query-grafana-7.3.html).
+The Prometheus data source with Amazon Managed Service for Prometheus is deprecated. Please use the [Amazon Managed service for Prometheus data source](https://grafana.com/grafana/plugins/grafana-amazonprometheus-datasource/). Migrations steps are detailed in the link.
## Azure authentication settings
diff --git a/docs/sources/introduction/grafana-enterprise.md b/docs/sources/introduction/grafana-enterprise.md
index 0a9a17ca79b..ccd311d14fe 100644
--- a/docs/sources/introduction/grafana-enterprise.md
+++ b/docs/sources/introduction/grafana-enterprise.md
@@ -34,12 +34,14 @@ Grafana Enterprise includes integrations with more ways to authenticate your use
Supported auth providers:
- [Auth Proxy]({{< relref "../setup-grafana/configure-security/configure-authentication/auth-proxy#team-sync-enterprise-only" >}})
-- [Azure AD OAuth]({{< relref "../setup-grafana/configure-security/configure-authentication/azuread#team-sync-enterprise-only" >}})
-- [GitHub OAuth]({{< relref "../setup-grafana/configure-security/configure-authentication/github#configure-team-synchronization" >}})
-- [GitLab OAuth]({{< relref "../setup-grafana/configure-security/configure-authentication/gitlab#configure-team-synchronization" >}})
-- [LDAP]({{< relref "../setup-grafana/configure-security/configure-authentication/enhanced-ldap#ldap-group-synchronization-for-teams" >}})
-- [Okta]({{< relref "../setup-grafana/configure-security/configure-authentication/okta#configure-team-synchronization-enterprise-only" >}})
-- [SAML]({{< relref "../setup-grafana/configure-security/configure-authentication/saml#configure-team-sync" >}})
+- [Azure AD](https://grafana.com/docs/grafana//configure-security/configure-authentication/azuread#group-sync-enterprise-only)
+- [Generic OAuth integration](https://grafana.com/docs/grafana//configure-security/configure-authentication/generic-oauth#configure-group-synchronization" >}})
+- [GitHub OAuth](https://grafana.com/docs/grafana//configure-security/configure-authentication/github#configure-group-synchronization)
+- [GitLab OAuth](https://grafana.com/docs/grafana//configure-security/configure-authentication/gitlab#configure-group-synchronization)
+- [Google OAuth](https://grafana.com/docs/grafana//configure-security/configure-authentication/google#configure-group-synchronization)
+- [LDAP](https://grafana.com/docs/grafana//configure-security/configure-authentication/enhanced-ldap#ldap-group-synchronization)
+- [Okta](https://grafana.com/docs/grafana//configure-security/configure-authentication/okta#configure-group-synchronization-enterprise-only)
+- [SAML](https://grafana.com/docs/grafana//configure-security/configure-authentication/saml#configure-group-synchronization)
### Enhanced LDAP integration
diff --git a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md b/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md
index 553f08b452e..d0ac307a1bc 100644
--- a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md
+++ b/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md
@@ -338,6 +338,7 @@ Use this transformation to select a source of data and extract content from it i
- **Format** - Choose one of the following:
- **JSON** - Parse JSON content from the source.
- **Key+value pairs** - Parse content in the format 'a=b' or 'c:d' from the source.
+ - **RegExp** - Parse content using a regular expression with [named capturing group(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Named_capturing_group).
- **Auto** - Discover fields automatically.
- **Replace All Fields** - (Optional) Select this option to hide all other fields and display only your calculated field in the visualization.
- **Keep Time** - (Optional) Available only if **Replace All Fields** is true. Keeps the time field in the output.
diff --git a/docs/sources/panels-visualizations/visualizations/node-graph/index.md b/docs/sources/panels-visualizations/visualizations/node-graph/index.md
index f5860c717c0..e7e2b7c9dcc 100644
--- a/docs/sources/panels-visualizations/visualizations/node-graph/index.md
+++ b/docs/sources/panels-visualizations/visualizations/node-graph/index.md
@@ -25,7 +25,7 @@ Node graphs are useful when you need to visualize elements that are related to e
Node graphs display useful information about each node, as well as the relationships between them, allowing you to visualize complex infrastructure maps, hierarchies, or execution diagrams.
-
+
The appearance of nodes and edges can also be customized in several ways including color, borders, and line style.
@@ -79,36 +79,7 @@ Similar to the nodes dataset, the edges dataset needs one unique ID field for ea
If a node lacks edge connections, it’s displayed on its own outside of the network.
-## Panel options
-
-{{< docs/shared lookup="visualizations/panel-options.md" source="grafana" version="" >}}
-
-## Node graph options
-
-Use the following options to refine your node graph visualization.
-
-- **Zoom mode** - Choose how the node graph should handle zoom and scroll events.
-
-## Nodes options
-
-The **Nodes** options section provides configurations for node behaviors.
-
-- **Main stat unit** - Choose which unit the main stat displays in the graph's nodes.
-- **Secondary stat unit** - Choose which unit the secondary stat displays in the graph's nodes.
-- **Arc sections** - Configure which fields define the size of the colored circle around the node and select a color for each. You can add multiple fields.
-
-{{< admonition type="note" >}}
-Defining arc sections overrides the automatic detection of `arc__*` and `color` fields described in the **Optional fields** section of [Nodes data frame structure](#nodes-data-frame-structure).
-{{< /admonition >}}
-
-## Edges options
-
-The **Edges** options section provides configurations for node edges behaviors.
-
-- **Main stat unit** - Choose which unit the main stat displays in the graph's edges.
-- **Secondary stat unit** - Choose which unit the secondary stat displays in the graph's edges.
-
-## Data requirements
+### Data requirements
A node graph requires a specific shape of the data to be able to display its nodes and edges. This means not every data source or query can be visualized with this graph. If you want to use this as a data source developer see the section about data API.
@@ -119,7 +90,7 @@ A node graph consists of _nodes_ and _edges_.
Both nodes and edges can have associated metadata or statistics. The data source defines what information and values is shown, so different data sources can show different type of values or not show some values.
-### Nodes
+#### Nodes
{{% admonition type="note" %}}
Node graphs can show only 1,500 nodes. If this limit is crossed a warning will be visible in upper right corner, and some nodes will be hidden. You can expand hidden parts of the graph by clicking on the "Hidden nodes" markers in the graph.
@@ -131,47 +102,88 @@ For example, you can have the percentage of errors represented by a red portion
Additional details can be displayed in a context menu which is displayed when you click on the node.
There also can be additional links in the context menu that can target either other parts of Grafana or any external link.
-
+
-### Edges
+#### Edges
Edges can also show statistics when you hover over the edge. Similar to nodes, you can open a context menu with additional details and links by clicking on the edge.
The first data source supporting this visualization is X-Ray data source for its Service map feature. For more information, refer to the [X-Ray plugin documentation](https://grafana.com/grafana/plugins/grafana-x-ray-datasource).
+## Configuration options
+
+{{< docs/shared lookup="visualizations/config-options-intro.md" source="grafana" version="" >}}
+
+### Panel options
+
+{{< docs/shared lookup="visualizations/panel-options.md" source="grafana" version="" >}}
+
+### Node graph options
+
+Use the following options to refine your node graph visualization.
+
+- **Zoom mode** - Choose how the node graph should handle zoom and scroll events.
+
+### Nodes options
+
+The **Nodes** options section provides configurations for node behaviors.
+
+- **Main stat unit** - Choose which unit the main stat displays in the graph's nodes.
+- **Secondary stat unit** - Choose which unit the secondary stat displays in the graph's nodes.
+- **Arc sections** - Configure which fields define the size of the colored circle around the node and select a color for each. You can add multiple fields.
+
+{{< admonition type="note" >}}
+Defining arc sections overrides the automatic detection of `arc__*` and `color` fields described in the **Optional fields** section of [Nodes data frame structure](#nodes-data-frame-structure).
+{{< /admonition >}}
+
+### Edges options
+
+The **Edges** options section provides configurations for node edges behaviors.
+
+- **Main stat unit** - Choose which unit the main stat displays in the graph's edges.
+- **Secondary stat unit** - Choose which unit the secondary stat displays in the graph's edges.
+
+### Data links
+
+{{< docs/shared lookup="visualizations/datalink-options.md" source="grafana" version="" >}}
+
+In node graphs, some data fields may have pre-configured data links. To add a different data link in those cases, use a [field override](#field-overrides).
+
+### Field overrides
+
+{{< docs/shared lookup="visualizations/overrides-options.md" source="grafana" version="" >}}
+
## Navigating the node graph
-You can pan and zoom in or out a node graph.
+You can use pan, zoom, and other functions to navigate a node graph.
### Pan
You can pan the view by clicking outside any node or edge and dragging your mouse.
-### Zoom in or out
+### Zoom
-Use the buttons in the upper left corner or use the mouse wheel, touchpad scroll, together with either Ctrl or Cmd key to zoom in or out.
+Use the buttons in the lower right corner to zoom in or out. You can also use the mouse wheel or touchpad scroll, together with either Ctrl or Cmd key to do so.
-### Explore hidden nodes
+### Hidden nodes
The number of nodes shown at a given time is limited to maintain a reasonable visualization performance. Nodes that are not currently visible are hidden behind clickable markers that show an approximate number of hidden nodes that are connected by a particular edge. You can click on the marker to expand the graph around that node.
-
+
### Grid view
You can switch to the grid view to have a better overview of the most interesting nodes in the graph. Grid view shows nodes in a grid without edges and can be sorted by stats shown inside the node or by stats represented by the a colored border of the nodes.
-
-
-
+
To sort the nodes, click on the stats inside the legend. The marker next to the stat name shows which stat is currently used for sorting and sorting direction.
-
+
Click on the node and select "Show in Graph layout" option to switch back to graph layout and focus on the selected node, to show it in context of the full graph.
-
+
## Data API
@@ -217,15 +229,15 @@ Required fields:
Optional fields:
-| Field name | Type | Description |
-| ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| title | string | Name of the node visible in just under the node. |
-| subtitle | string | Additional, name, type or other identifier shown under the title. |
-| mainstat | string/number | First stat shown inside the node itself. It can either be a string showing the value as is or a number. If it is a number, any unit associated with that field is also shown. |
-| secondarystat | string/number | Same as mainStat, but shown under it inside the node. |
-| arc\_\_\* | number | Any field prefixed with `arc__` will be used to create the color circle around the node. All values in these fields should add up to 1. You can specify color using `config.color.fixedColor`. |
-| detail\_\_\* | string/number | Any field prefixed with `detail__` will be shown in the header of context menu when clicked on the node. Use `config.displayName` for more human readable label. |
-| color | string/number | Can be used to specify a single color instead of using the `arc__` fields to specify color sections. It can be either a string which should then be an acceptable HTML color string or it can be a number in which case the behaviour depends on `field.config.color.mode` setting. This can be for example used to create gradient colors controlled by the field value. |
-| icon | string | Name of the icon to show inside the node instead of the default stats. Only Grafana built in icons are allowed (see the available icons [here](https://developers.grafana.com/ui/latest/index.html?path=/story/docs-overview-icon--icons-overview)). |
-| nodeRadius | number | Radius value in pixels. Used to manage node size. |
-| highlighted | boolean | Sets whether the node should be highlighted. Useful for example to represent a specific path in the graph by highlighting several nodes and edges. Default: `false` |
+| Field name | Type | Description |
+| ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| title | string | Name of the node visible in just under the node. |
+| subtitle | string | Additional, name, type or other identifier shown under the title. |
+| mainstat | string/number | First stat shown inside the node itself. It can either be a string showing the value as is or a number. If it is a number, any unit associated with that field is also shown. |
+| secondarystat | string/number | Same as mainStat, but shown under it inside the node. |
+| arc\_\_\* | number | Any field prefixed with `arc__` will be used to create the color circle around the node. All values in these fields should add up to 1. You can specify color using `config.color.fixedColor`. |
+| detail\_\_\* | string/number | Any field prefixed with `detail__` will be shown in the header of context menu when clicked on the node. Use `config.displayName` for more human readable label. |
+| color | string/number | Can be used to specify a single color instead of using the `arc__` fields to specify color sections. It can be either a string which should then be an acceptable HTML color string or it can be a number in which case the behavior depends on `field.config.color.mode` setting. This can be for example used to create gradient colors controlled by the field value. |
+| icon | string | Name of the icon to show inside the node instead of the default stats. Only Grafana [built in icons](https://developers.grafana.com/ui/latest/index.html?path=/story/docs-overview-icon--icons-overview)) are allowed. |
+| nodeRadius | number | Radius value in pixels. Used to manage node size. |
+| highlighted | boolean | Sets whether the node should be highlighted. Useful for example to represent a specific path in the graph by highlighting several nodes and edges. Default: `false` |
diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
index 15d4cb8adce..5537f1d1e73 100644
--- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
+++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
@@ -78,6 +78,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `openSearchBackendFlowEnabled` | Enables the backend query flow for Open Search datasource plugin | Yes |
| `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes |
| `cloudwatchMetricInsightsCrossAccount` | Enables cross account observability for Cloudwatch Metric Insights query builder | Yes |
+| `singleTopNav` | Unifies the top search bar and breadcrumb bar into one | Yes |
| `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | |
| `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes |
| `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes |
@@ -115,7 +116,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `ssoSettingsSAML` | Use the new SSO Settings API to configure the SAML connector |
| `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars |
| `ssoSettingsLDAP` | Use the new SSO Settings API to configure LDAP |
-| `singleTopNav` | Unifies the top search bar and breadcrumb bar into one |
| `useSessionStorageForRedirection` | Use session storage for handling the redirection after login |
| `reportingUseRawTimeRange` | Uses the original report or dashboard time range instead of making an absolute transformation |
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md
index dfbea92ba5f..4c4d8d6e81e 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md
@@ -353,14 +353,14 @@ This setting is ignored if multiple auth providers are configured to use auto lo
auto_login = true
```
-### Team Sync (Enterprise only)
+### Group sync (Enterprise only)
-With Team Sync you can map your Entra ID groups to teams in Grafana so that your users will automatically be added to
-the correct teams.
+With group sync you can map your Entra ID groups to teams and roles in Grafana. This allows users to automatically be added to
+the correct teams and be granted the correct roles in Grafana.
You can reference Entra ID groups by group object ID, like `8bab1c86-8fba-33e5-2089-1d1c80ec267d`.
-To learn more, refer to the [Team Sync]({{< relref "../../configure-team-sync" >}}) documentation.
+To learn more about group synchronization, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync).
## Common troubleshooting
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/enhanced-ldap/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/enhanced-ldap/index.md
index edc4057ec5f..a8992e015d6 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/enhanced-ldap/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/enhanced-ldap/index.md
@@ -28,18 +28,16 @@ The enhanced LDAP integration adds additional functionality on top of the [LDAP
> To control user access with role-based permissions, refer to [role-based access control]({{< relref "../../../../administration/roles-and-permissions/access-control" >}}).
-## LDAP group synchronization for teams
+## LDAP group synchronization
-With enhanced LDAP integration, you can set up synchronization between LDAP groups and teams. This enables LDAP users that are members
-of certain LDAP groups to automatically be added or removed as members to certain teams in Grafana.
+With enhanced LDAP integration, you can set up synchronization between LDAP groups and Grafana teams and roles. This enables users that are members
+of certain LDAP groups to automatically be added to teams and gain roles in Grafana.
+
+The below example shows an LDAP group member mapped to a Grafana team.

-Grafana keeps track of all synchronized users in teams, and you can see which users have been synchronized from LDAP in the team members list, see `LDAP` label in screenshot.
-This mechanism allows Grafana to remove an existing synchronized user from a team when its LDAP group membership changes. This mechanism also allows you to manually add
-a user as member of a team, and it will not be removed when the user signs in. This gives you flexibility to combine LDAP group memberships and Grafana team memberships.
-
-[Learn more about team sync.]({{< relref "../../configure-team-sync" >}})
+To learn more about group synchronization, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync).
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md
index 13fce031d97..2d6eb5fd39d 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md
@@ -127,7 +127,7 @@ To integrate your OAuth2 provider with Grafana using our Generic OAuth authentic
c. Enable the refresh token on the provider if required.
1. [Configure role mapping]({{< relref "#configure-role-mapping" >}}).
-1. Optional: [Configure team synchronization]({{< relref "#configure-team-synchronization" >}}).
+1. Optional: [Configure group synchronization]({{< relref "#configure-group-synchronization" >}}).
1. Restart Grafana.
You should now see a Generic OAuth login button on the login page and be able to log in or sign up with your OAuth2 provider.
@@ -328,19 +328,21 @@ org_attribute_path = info.roles
org_mapping = org_foo:org_foo:Viewer org_bar:org_bar:Editor *:org_baz:Editor
```
-### Configure team synchronization
+## Configure group synchronization
-> **Note:** Available in [Grafana Enterprise]({{< relref "../../../../introduction/grafana-enterprise" >}}) and [Grafana Cloud](/docs/grafana-cloud/).
+{{< admonition type="note" >}}
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
+{{< /admonition >}}
-By using Team Sync, you can link your OAuth2 groups to teams within Grafana. This will automatically assign users to the appropriate teams.
-Teams for each user are synchronized when the user logs in.
+Grafana supports synchronization of OAuth2 groups with Grafana teams and roles. This allows automatically assigning users to the appropriate teams or automatically granting them the mapped roles.
+Teams and roles get synchronized when the user logs in.
Generic OAuth groups can be referenced by group ID, such as `8bab1c86-8fba-33e5-2089-1d1c80ec267d` or `myteam`.
For information on configuring OAuth2 groups with Grafana using the `groups_attribute_path` configuration option, refer to [configuration options]({{< relref "#configuration-options" >}}).
-To learn more about Team Sync, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}).
+To learn more about group synchronization, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync).
-#### Team synchronization example
+#### Group attribute synchronization example
Configuration:
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md
index 600cc9f26e4..5792c3a4a70 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md
@@ -110,7 +110,7 @@ To configure GitHub authentication with Grafana, follow these steps:
Review the list of other GitHub [configuration options]({{< relref "#configuration-options" >}}) and complete them, as necessary.
1. [Configure role mapping]({{< relref "#configure-role-mapping" >}}).
-1. Optional: [Configure team synchronization]({{< relref "#configure-team-synchronization" >}}).
+1. Optional: [Configure group synchronization]({{< relref "#configure-group-synchronization" >}}).
1. Restart Grafana.
You should now see a GitHub login button on the login page and be able to log in or sign up with your GitHub accounts.
@@ -216,14 +216,14 @@ allowed_domains = mycompany.com mycompany.org
role_attribute_path = [login=='octocat'][0] && 'GrafanaAdmin' || 'Viewer'
```
-## Configure team synchronization
+## Configure group synchronization
{{< admonition type="note" >}}
-Available in [Grafana Enterprise]({{< relref "../../../../introduction/grafana-enterprise" >}}) and Grafana Cloud.
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
{{< /admonition >}}
-By using Team Sync, you can map teams from your GitHub organization to teams within Grafana. This will automatically assign users to the appropriate teams.
-Teams for each user are synchronized when the user logs in.
+Grafana supports synchronization of teams from your GitHub organization with Grafana teams and roles. This allows automatically assigning users to the appropriate teams or granting them the mapped roles.
+Teams and roles get synchronized when the user logs in.
GitHub teams can be referenced in two ways:
@@ -232,7 +232,7 @@ GitHub teams can be referenced in two ways:
Examples: `https://github.com/orgs/grafana/teams/developers` or `@grafana/developers`.
-To learn more about Team Sync, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}).
+To learn more about group synchronization, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync).
## Configuration options
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md
index 075047b7c49..d1503808910 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md
@@ -119,7 +119,7 @@ To configure GitLab authentication with Grafana, follow these steps:
a. Set `use_refresh_token` to `true` in `[auth.gitlab]` section in Grafana configuration file.
1. [Configure role mapping]({{< relref "#configure-role-mapping" >}}).
-1. Optional: [Configure team synchronization]({{< relref "#configure-team-synchronization" >}}).
+1. Optional: [Configure group synchronization]({{< relref "#configure-group-synchronization" >}}).
1. Restart Grafana.
You should now see a GitLab login button on the login page and be able to log in or sign up with your GitLab accounts.
@@ -242,20 +242,20 @@ use_pkce = true
use_refresh_token = true
```
-## Configure team synchronization
+## Configure group synchronization
-{{% admonition type="note" %}}
-Available in [Grafana Enterprise]({{< relref "../../../../introduction/grafana-enterprise" >}}) and [Grafana Cloud](/docs/grafana-cloud/).
-{{% /admonition %}}
+{{< admonition type="note" >}}
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
+{{< /admonition >}}
-By using Team Sync, you can map GitLab groups to teams within Grafana. This will automatically assign users to the appropriate teams.
-Teams for each user are synchronized when the user logs in.
+Grafana supports synchronization of GitLab groups with Grafana teams and roles. This allows automatically assigning users to the appropriate teams or granting them the mapped roles.
+Teams and roles get synchronized when the user logs in.
GitLab groups are referenced by the group name. For example, `developers`. To reference a subgroup `frontend`, use `developers/frontend`.
Note that in GitLab, the group or subgroup name does not always match its display name, especially if the display name contains spaces or special characters.
Make sure you always use the group or subgroup name as it appears in the URL of the group or subgroup.
-To learn more about Team Sync, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}).
+To learn more about group synchronization, refer to [Configure team sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync).
## Configuration options
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md
index fed9af7ebd9..9e4740c6914 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md
@@ -165,9 +165,15 @@ This setting is ignored if multiple auth providers are configured to use auto lo
auto_login = true
```
-### Configure team sync for Google OAuth
+### Configure group synchronization
-With team sync, you can easily add users to teams by utilizing their Google groups. To set up team sync for Google OAuth, refer to the following example.
+{{< admonition type="note" >}}
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
+{{< /admonition >}}
+
+Grafana supports syncing users to teams and roles based on their Google groups.
+
+To set up group sync for Google OAuth:
1. Enable the Google Cloud Identity API on your [organization's dashboard](https://console.cloud.google.com/apis/api/cloudidentity.googleapis.com/).
@@ -181,10 +187,9 @@ With team sync, you can easily add users to teams by utilizing their Google grou
scopes = openid email profile https://www.googleapis.com/auth/cloud-identity.groups.readonly
```
-1. Configure team sync in your Grafana team's `External group sync` tab.
- The external group ID for a Google group is the group's email address, such as `dev@grafana.com`.
+The external group ID for a Google group is the group's email address, such as `dev@grafana.com`.
-To learn more about Team Sync, refer to [Configure Team Sync]({{< relref "../../configure-team-sync" >}}).
+To learn more about how to configure group synchronization, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation.
#### Configure allowed groups
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/jwt/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/jwt/index.md
index c8ed2b24a89..beffabd14f7 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/jwt/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/jwt/index.md
@@ -23,6 +23,10 @@ This method of authentication is useful for integrating with other systems that
use JWKS but can't directly integrate with Grafana or if you want to use pass-through
authentication in an app embedding Grafana.
+{{% admonition type="note" %}}
+Grafana does not currently support refresh tokens.
+{{% /admonition %}}
+
## Enable JWT
To use JWT authentication:
@@ -183,7 +187,8 @@ key_id = my-key-id
By default, only `"exp"`, `"nbf"` and `"iat"` claims are validated.
-You might also want to validate that other claims are really what you expect them to be.
+Consider validating that other claims match your expectations by using the `expect_claims` configuration option.
+Token claims must match exactly the values set here.
```ini
# This can be seen as a required "subset" of a JWT Claims Set.
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md
index 0759f30428c..323e6e1614d 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md
@@ -94,7 +94,7 @@ roles
```
{{% admonition type="warning" %}}
-these scopes do not add group claims to the id_token. Without group claims, teamsync will not work. Teamsync is covered further down in this document.
+These scopes do not add group claims to the id_token. Without group claims, group synchronization will not work. Group synchronization is covered further down in this document.
{{% /admonition %}}
3. For role mapping to work with the example configuration above,
@@ -106,16 +106,18 @@ editor
viewer
```
-## Teamsync
+## Group synchronization
-{{% admonition type="note" %}}
-Available in [Grafana Enterprise]({{< relref "../../../../introduction/grafana-enterprise" >}}) and [Grafana Cloud](/docs/grafana-cloud/).
-{{% /admonition %}}
+{{< admonition type="note" >}}
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
+{{< /admonition >}}
-[Teamsync]({{< relref "../../configure-team-sync" >}}) is a feature that allows you to map groups from your identity provider to Grafana teams. This is useful if you want to give your users access to specific dashboards or folders based on their group membership.
+By using group synchronization, you can link your Keycloak groups to teams and roles within Grafana. This allows automatically assigning users to the appropriate teams or granting them the mapped roles.
+This is useful if you want to give your users access to specific resources based on their group membership.
+Teams and roles get synchronized when the user logs in.
-To enable teamsync, you need to add a `groups` mapper to the client configuration in Keycloak.
-This will add the `groups` claim to the id_token. You can then use the `groups` claim to map groups to teams in Grafana.
+To enable group synchronization, you need to add a `groups` mapper to the client configuration in Keycloak.
+This will add the `groups` claim to the id_token. You can then use the `groups` claim to map groups to teams and roles in Grafana.
1. In the client configuration, head to `Mappers` and create a mapper with the following settings:
@@ -141,6 +143,8 @@ If you use nested groups containing special characters such as quotes or colons,
groups_attribute_path = reverse("Global:department")
```
+To learn more about how to configure group synchronization, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation.
+
## Enable Single Logout
To enable Single Logout, you need to add the following option to the configuration of Grafana:
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md
index acf3e070cdd..39d77ca3f7f 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md
@@ -169,7 +169,7 @@ To integrate your Okta OIDC provider with Grafana using our Okta OIDC integratio
1. Optional: [Configure a refresh token]({{< relref "#configure-a-refresh-token" >}}).
1. [Configure role mapping]({{< relref "#configure-role-mapping" >}}).
-1. Optional: [Configure team synchronization]({{< relref "#configure-team-synchronization-enterprise-only" >}}).
+1. Optional: [Configure group synchronization]({{< relref "#configure-group-synchronization-enterprise-only" >}}).
1. Restart Grafana.
You should now see a Okta OIDC login button on the login page and be able to log in or sign up with your OIDC provider.
@@ -243,20 +243,18 @@ org_attribute_path = groups
org_mapping = ["Group 1:org_foo:Viewer", "Group 2:org_bar:Editor", "*:3:Editor"]
```
-### Configure team synchronization (Enterprise only)
+### Configure group synchronization (Enterprise only)
-{{% admonition type="note" %}}
-Available in [Grafana Enterprise]({{< relref "../../../../introduction/grafana-enterprise" >}}) and [Grafana Cloud]({{< relref "../../../../introduction/grafana-cloud" >}}).
-{{% /admonition %}}
+{{< admonition type="note" >}}
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
+{{< /admonition >}}
-By using Team Sync, you can link your Okta groups to teams within Grafana. This will automatically assign users to the appropriate teams.
-
-Map your Okta groups to teams in Grafana so that your users will automatically be added to
-the correct teams.
+By using group synchronization, you can link your Okta groups to teams and roles within Grafana. This allows automatically assigning users to the appropriate teams or granting them the mapped roles.
+Teams and roles get synchronized when the user logs in.
Okta groups can be referenced by group names, like `Admins` or `Editors`.
-To learn more about Team Sync, refer to [Configure Team Sync]({{< relref "../../configure-team-sync" >}}).
+To learn more about how to configure group synchronization, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation.
## Configuration options
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md
index 187fd6356dc..1be5e2d993e 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml-ui/index.md
@@ -113,8 +113,8 @@ Sign in to Grafana and navigate to **Administration > Authentication > Configure
1. If you wish to [map user information from SAML assertions]({{< relref "../saml#assertion-mapping" >}}), complete the **Assertion attributes mappings** section.
- You also need to configure the **Groups attribute** field if you want to use team sync. Team sync automatically maps users to Grafana teams based on their SAML group membership.
- Learn more about [team sync]({{< relref "../../configure-team-sync" >}}) and [configuring team sync for SAML]({{< relref "../saml#configure-team-sync" >}}).
+ You also need to configure the **Groups attribute** field if you want to use group synchronization. Group sync allows you to automatically map users to Grafana teams or role-based access control roles based on their SAML group membership.
+ To learn more about how to configure group synchronization, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation.
1. If you want to automatically assign users' roles based on their SAML roles, complete the **Role mapping** section.
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md
index 08e01f5dcc2..230edd20cb4 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md
@@ -410,12 +410,15 @@ This setting is ignored if multiple auth providers are configured to use auto lo
auto_login = true
```
-### Configure team sync
+### Configure group synchronization
-To use SAML Team sync, set [`assertion_attribute_groups`]({{< relref "../../../configure-grafana/enterprise-configuration#assertion_attribute_groups" >}}) to the attribute name where you store user groups. Then Grafana will use attribute values extracted from SAML assertion to add user into the groups with the same name configured on the External group sync tab.
+Group synchronization allows you to map user groups from an identity provider to Grafana teams and roles.
+
+To use SAML group synchronization, set [`assertion_attribute_groups`]({{< relref "../../../configure-grafana/enterprise-configuration#assertion_attribute_groups" >}}) to the attribute name where you store user groups.
+Then Grafana will use attribute values extracted from SAML assertion to add user to Grafana teams and grant them roles.
{{% admonition type="note" %}}
-Teamsync allows you sync users from SAML to Grafana teams. It does not automatically create teams in Grafana. You need to create teams in Grafana before you can use this feature.
+Team sync allows you sync users from SAML to Grafana teams. It does not automatically create teams in Grafana. You need to create teams in Grafana before you can use this feature.
{{% /admonition %}}
Given the following partial SAML assertion:
@@ -445,12 +448,12 @@ The configuration would look like this:
assertion_attribute_groups = groups
```
-The following `External Group ID`s would be valid for input in the desired team's _External group sync_ tab:
+The following `External Group ID`s would be valid for configuring team sync or role sync in Grafana:
- `admins_group`
- `division_1`
-[Learn more about Team Sync]({{< relref "../../configure-team-sync" >}})
+To learn more about how to configure group synchronization, refer to [Configure team sync]({{< relref "../../configure-team-sync" >}}) and [Configure group attribute sync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-group-attribute-sync) documentation.
### Configure role sync
diff --git a/docs/sources/setup-grafana/configure-security/configure-group-attribute-sync.md b/docs/sources/setup-grafana/configure-security/configure-group-attribute-sync.md
index 1377f885397..0ebe9f3db6c 100644
--- a/docs/sources/setup-grafana/configure-security/configure-group-attribute-sync.md
+++ b/docs/sources/setup-grafana/configure-security/configure-group-attribute-sync.md
@@ -26,6 +26,17 @@ If a group mapping is removed, the role assignment to users for the group mappin
Role mappings are tied to organizations, so you can have different mappings for different organizations.
+## Supported providers
+
+- [Azure AD](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/azuread#group-sync-enterprise-only)
+- [Generic OAuth integration](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/generic-oauth#configure-group-synchronization)
+- [GitHub OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/github#configure-group-synchronization)
+- [GitLab OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/gitlab#configure-group-synchronization)
+- [Google OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/google#configure-group-synchronization)
+- [LDAP](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/enhanced-ldap#ldap-group-synchronization)
+- [Okta](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/okta#configure-group-synchronization-enterprise-only)
+- [SAML](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/saml#configure-group-synchronization)
+
## Create role mappings for a new group
For information about creating group mappings via the API, refer to [create group mappings reference](https://grafana.com/docs/grafana//developers/http_api/group_attribute_sync#create-group-mappings).
diff --git a/docs/sources/setup-grafana/configure-security/configure-team-sync.md b/docs/sources/setup-grafana/configure-security/configure-team-sync.md
index ad3fde7ab92..f5d50cff311 100644
--- a/docs/sources/setup-grafana/configure-security/configure-team-sync.md
+++ b/docs/sources/setup-grafana/configure-security/configure-team-sync.md
@@ -28,14 +28,15 @@ This mechanism allows Grafana to remove an existing synchronized user from a tea
## Supported providers
- [Auth Proxy]({{< relref "./configure-authentication/auth-proxy#team-sync-enterprise-only" >}})
-- [Azure AD]({{< relref "./configure-authentication/azuread#team-sync-enterprise-only" >}})
-- [Generic OAuth integration]({{< relref "./configure-authentication/generic-oauth#configure-team-synchronization" >}})
-- [GitHub OAuth]({{< relref "./configure-authentication/github#configure-team-synchronization" >}})
-- [GitLab OAuth]({{< relref "./configure-authentication/gitlab#configure-team-synchronization" >}})
-- [Google OAuth]({{< relref "./configure-authentication/google#configure-team-sync-for-google-oauth" >}})
-- [LDAP]({{< relref "./configure-authentication/enhanced-ldap#ldap-group-synchronization-for-teams" >}})
-- [Okta]({{< relref "./configure-authentication/okta#configure-team-synchronization-enterprise-only" >}})
-- [SAML]({{< relref "./configure-authentication/saml#configure-team-sync" >}})
+- [Azure AD](https://grafana.com/docs/grafana//configure-authentication/azuread#group-sync-enterprise-only)
+- [Azure AD](https://grafana.com/docs/grafana//configure-security/configure-authentication/azuread#group-sync-enterprise-only)
+- [Generic OAuth integration](https://grafana.com/docs/grafana//configure-security/configure-authentication/generic-oauth#configure-group-synchronization)
+- [GitHub OAuth](https://grafana.com/docs/grafana//configure-security/configure-authentication/github#configure-group-synchronization)
+- [GitLab OAuth](https://grafana.com/docs/grafana//configure-security/configure-authentication/gitlab#configure-group-synchronization)
+- [Google OAuth](https://grafana.com/docs/grafana//configure-security/configure-authentication/google#configure-group-synchronization)
+- [LDAP](https://grafana.com/docs/grafana//configure-security/configure-authentication/enhanced-ldap#ldap-group-synchronization)
+- [Okta](https://grafana.com/docs/grafana//configure-security/configure-authentication/okta#configure-group-synchronization-enterprise-only)
+- [SAML](https://grafana.com/docs/grafana//configure-security/configure-authentication/saml#configure-group-synchronization)
## Synchronize a Grafana team with an external group
diff --git a/docs/sources/tutorials/create-alerts-with-logs/index.md b/docs/sources/tutorials/create-alerts-with-logs/index.md
index 1e1de3f9646..39de7c380c2 100644
--- a/docs/sources/tutorials/create-alerts-with-logs/index.md
+++ b/docs/sources/tutorials/create-alerts-with-logs/index.md
@@ -82,7 +82,7 @@ To demonstrate the observation of data using the Grafana stack, download and run
1. Download and save a Docker compose file to run Grafana, Loki and Promtail.
```bash
- wget https://raw.githubusercontent.com/grafana/loki/v2.8.0/production/docker-compose.yaml -O docker-compose.yaml
+ wget https://raw.githubusercontent.com/grafana/loki/refs/heads/main/production/docker-compose.yaml -O docker-compose.yaml
```
2. Run the Grafana stack.
@@ -234,7 +234,7 @@ In this section, we use the default options for Grafana-managed alert rule creat
It should return alert instances from log lines with a status code that is not 200 (OK), and that has met the alert condition. The condition for the alert rule to fire is any occurrence that goes over the threshold of `0`. Since the Loki query has returned more than zero alert instances, the alert rule is `Firing`.
- {{< figure src="/media/docs/alerting/expression-loki-alert.png" max-width="1200px" caption="Preview of a firing alert instances" >}}
+ {{< figure src="/media/docs/alerting/firing-loki-alert-rule.png" max-width="1200px" caption="Preview of a firing alert instances" >}}
### Set evaluation behavior
diff --git a/docs/sources/upgrade-guide/when-to-upgrade/index.md b/docs/sources/upgrade-guide/when-to-upgrade/index.md
index 2c56ec67047..e3ee3e3bd12 100644
--- a/docs/sources/upgrade-guide/when-to-upgrade/index.md
+++ b/docs/sources/upgrade-guide/when-to-upgrade/index.md
@@ -82,7 +82,11 @@ During these times, no scheduled releases will be executed. However, this does n
We've enhanced our naming convention for security release versions to make it easier to clearly identify our security releases from our standard patching releases.
-In the past, critical vulnerabilities triggered unscheduled releases that incremented the patch version (e.g., 10.3.0 to 10.3.1). However, we found that the naming convention for these releases didn't clearly communicate the nature of the update. For example, if there was a version change from 11.3.0 to 11.3.1, there was no indication whether it was a security fix, a bug fix, or a minor feature update. This lack of clarity led to confusion about the urgency and nature of the update.
+In the past, critical vulnerabilities triggered unscheduled releases that incremented the patch version (e.g., 10.3.0 to 10.3.1). However, we found that the naming convention for these releases didn't clearly communicate the nature of the update. For example, if there was a version change from 11.3.0 to 11.3.1, there was no indication whether it was a security fix, a bug fix, or a minor feature update. This lack of clarity led to confusion about the urgency and nature of the update.
+
+{{< admonition type="note" >}}
+Docker does not allow the plus sign (`+`) in image tag names. A plus sign (`+`) will be a rendered as a dash (`-`) in the docker tag.
+{{< /admonition >}}
Our new approach directly addresses this issue. Going forward, security releases will be appended with "+security" to indicate that the release is the indicated version PLUS the security fix.
diff --git a/e2e/various-suite/verify-i18n.spec.ts b/e2e/various-suite/verify-i18n.spec.ts
new file mode 100644
index 00000000000..3de37d5d863
--- /dev/null
+++ b/e2e/various-suite/verify-i18n.spec.ts
@@ -0,0 +1,65 @@
+import { e2e } from '../utils';
+import { fromBaseUrl } from '../utils/support/url';
+
+describe('Verify i18n', () => {
+ const I18N_USER = 'i18n-test';
+ const I18N_PASSWORD = 'i18n-test';
+
+ // create a new user to isolate the language changes from other tests
+ before(() => {
+ e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
+ cy.request({
+ method: 'POST',
+ url: fromBaseUrl('/api/admin/users'),
+ body: {
+ email: I18N_USER,
+ login: I18N_USER,
+ name: I18N_USER,
+ password: I18N_PASSWORD,
+ },
+ }).then((response) => {
+ cy.wrap(response.body.uid).as('uid');
+ });
+ });
+
+ // remove the user created in the before hook
+ after(() => {
+ e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
+ cy.get('@uid').then((uid) => {
+ cy.request({
+ method: 'DELETE',
+ url: fromBaseUrl(`/api/admin/users/${uid}`),
+ });
+ });
+ });
+
+ beforeEach(() => {
+ e2e.flows.login(I18N_USER, I18N_PASSWORD);
+ });
+
+ // map between languages in the language picker and the corresponding translation of the 'Language' label
+ const languageMap: Record = {
+ Deutsch: 'Sprache',
+ English: 'Language',
+ Español: 'Idioma',
+ Français: 'Langue',
+ 'Português Brasileiro': 'Idioma',
+ '中文(简体)': '语言',
+ };
+
+ // basic test which loops through the defined languages in the picker
+ // and verifies that the corresponding label is translated correctly
+ it('loads all the languages correctly', () => {
+ cy.visit('/profile');
+ const LANGUAGE_SELECTOR = '[id="locale-select"]';
+
+ cy.wrap(Object.entries(languageMap)).each(([language, label]: [string, string]) => {
+ cy.get(LANGUAGE_SELECTOR).should('not.be.disabled');
+ cy.get(LANGUAGE_SELECTOR).click();
+ cy.get(LANGUAGE_SELECTOR).clear().type(language).type('{downArrow}{enter}');
+ e2e.components.UserProfile.preferencesSaveButton().click();
+ cy.contains('label', label).should('be.visible');
+ cy.get(LANGUAGE_SELECTOR).should('have.value', language);
+ });
+ });
+});
diff --git a/go.mod b/go.mod
index f69b277a9a8..de9c856fa8f 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
cloud.google.com/go/kms v1.18.5 // @grafana/grafana-backend-group
cloud.google.com/go/storage v1.43.0 // @grafana/grafana-backend-group
connectrpc.com/connect v1.17.0 // @grafana/observability-traces-and-profiling
- cuelang.org/go v0.6.0-0.dev // @grafana/grafana-as-code
+ cuelang.org/go v0.8.2 // @grafana/grafana-as-code
filippo.io/age v1.1.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/azidentity v1.7.0 // @grafana/grafana-backend-group
@@ -75,7 +75,7 @@ require (
github.com/grafana/alerting v0.0.0-20241021123319-be61d61f71e7 // @grafana/alerting-backend
github.com/grafana/authlib v0.0.0-20241024120339-84cd3a898e8a // @grafana/identity-access-team
github.com/grafana/authlib/claims v0.0.0-20241024115517-d30b00d7666d // @grafana/identity-access-team
- github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad
+ github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d // @grafana/dataviz-squad
github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics
@@ -109,7 +109,7 @@ require (
github.com/hashicorp/go-version v1.7.0 // @grafana/grafana-backend-group
github.com/hashicorp/golang-lru/v2 v2.0.7 // @grafana/alerting-backend
github.com/hashicorp/hcl/v2 v2.17.0 // @grafana/alerting-backend
- github.com/huandu/xstrings v1.3.3 // @grafana/partner-datasources
+ github.com/huandu/xstrings v1.5.0 // @grafana/partner-datasources
github.com/influxdata/influxdb-client-go/v2 v2.13.0 // @grafana/observability-metrics
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // @grafana/grafana-app-platform-squad
github.com/jmespath/go-jmespath v0.4.0 // indirect; @grafana/grafana-backend-group
@@ -266,7 +266,7 @@ require (
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
- github.com/emicklei/proto v1.10.0 // indirect
+ github.com/emicklei/proto v1.13.2 // 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
@@ -382,7 +382,7 @@ require (
github.com/prometheus/common/sigv4 v0.1.0 // indirect
github.com/prometheus/exporter-toolkit v0.11.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
- github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 // indirect
+ github.com/protocolbuffers/txtpbfmt v0.0.0-20240116145035-ef3ab179eed6 // 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
diff --git a/go.sum b/go.sum
index 08dc5b9fa9c..42f22da0808 100644
--- a/go.sum
+++ b/go.sum
@@ -1833,8 +1833,8 @@ github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8
github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
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=
-github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw=
-github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
+github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY=
+github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
@@ -2268,8 +2268,8 @@ github.com/grafana/authlib v0.0.0-20241024120339-84cd3a898e8a h1:X3mroOOXdryRY6S
github.com/grafana/authlib v0.0.0-20241024120339-84cd3a898e8a/go.mod h1:XFhcSCEDeOgV43x41we7mRBcizDpsTScb7XOuYipQZg=
github.com/grafana/authlib/claims v0.0.0-20241024115517-d30b00d7666d h1:7nZfaXdC4Xc2ocMz5/Bx/3EsaEO34KsmA2RRcuogLnc=
github.com/grafana/authlib/claims v0.0.0-20241024115517-d30b00d7666d/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
-github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
-github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
+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/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ=
github.com/grafana/cue v0.0.0-20230926092038-971951014e3f/go.mod h1:okjJBHFQFer+a41sAe2SaGm1glWS8oEb6CmJvn5Zdws=
github.com/grafana/cuetsy v0.1.11 h1:I3IwBhF+UaQxRM79HnImtrAn8REGdb5M3+C4QrYHoWk=
@@ -2472,8 +2472,9 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
-github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+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/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
@@ -3036,8 +3037,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/prometheus v0.52.0 h1:f7kHJgr7+zShpWdTCeKqbCWR7nKTScgLYQwRux9h1V0=
github.com/prometheus/prometheus v0.52.0/go.mod h1:3z74cVsmVH0iXOR5QBjB7Pa6A0KJeEAK5A6UsmAFb1g=
-github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 h1:sadMIsgmHpEOGbUs6VtHBXRR1OHevnj7hLx9ZcdNGW4=
-github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
+github.com/protocolbuffers/txtpbfmt v0.0.0-20240116145035-ef3ab179eed6 h1:MAzmm+JtFxQwTPb1cVMLkemw2OxLy5AB/d/rxtAwGQQ=
+github.com/protocolbuffers/txtpbfmt v0.0.0-20240116145035-ef3ab179eed6/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
diff --git a/go.work b/go.work
index 53eb16267fe..0b92b51884d 100644
--- a/go.work
+++ b/go.work
@@ -6,6 +6,7 @@ go 1.23.1
use (
. // skip:golangci-lint
./apps/playlist
+ ./kindsv2
./pkg/aggregator
./pkg/apimachinery
./pkg/apiserver
diff --git a/kindsv2/gen.go b/kindsv2/gen.go
new file mode 100644
index 00000000000..ee215e3d491
--- /dev/null
+++ b/kindsv2/gen.go
@@ -0,0 +1,52 @@
+//go:generate go run gen.go
+
+package main
+
+import (
+ "context"
+ "os"
+
+ "cuelang.org/go/cue/cuecontext"
+ "github.com/grafana/cog"
+)
+
+type codegenTargets struct {
+ schemaPath string
+ outputPath string
+}
+
+func main() {
+ cueCtx := cuecontext.New()
+
+ targets := []codegenTargets{
+ {
+ schemaPath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue",
+ outputPath: "../packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts",
+ },
+ }
+
+ for _, target := range targets {
+ rawSchema, err := os.ReadFile(target.schemaPath)
+ if err != nil {
+ panic(err)
+ }
+
+ value := cueCtx.CompileBytes(rawSchema)
+ if value.Err() != nil {
+ panic(value.Err())
+ }
+
+ codegenPipeline := cog.TypesFromSchema().
+ CUEValue("dashboard", value).
+ Typescript()
+
+ tsBytes, err := codegenPipeline.Run(context.Background())
+ if err != nil {
+ panic(err)
+ }
+
+ if err := os.WriteFile(target.outputPath, tsBytes, 0644); err != nil {
+ panic(err)
+ }
+ }
+}
diff --git a/kindsv2/go.mod b/kindsv2/go.mod
new file mode 100644
index 00000000000..525fd0ac78c
--- /dev/null
+++ b/kindsv2/go.mod
@@ -0,0 +1,46 @@
+module github.com/grafana/grafana/kindsv2
+
+go 1.23.1
+
+require (
+ cuelang.org/go v0.8.2
+ github.com/grafana/cog v0.0.0-20241029201114-f7f7db0c1070
+)
+
+require (
+ cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 // 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/perimeterx/marshmallow v1.1.5 // indirect
+ github.com/protocolbuffers/txtpbfmt v0.0.0-20240116145035-ef3ab179eed6 // indirect
+ github.com/rogpeppe/go-internal v1.13.1 // indirect
+ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
+ github.com/ugorji/go/codec v1.2.11 // indirect
+ github.com/yalue/merged_fs v1.3.0 // indirect
+ golang.org/x/mod v0.21.0 // indirect
+ golang.org/x/net v0.30.0 // indirect
+ golang.org/x/oauth2 v0.23.0 // indirect
+ golang.org/x/sync v0.8.0 // indirect
+ golang.org/x/text v0.19.0 // indirect
+ golang.org/x/tools v0.26.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/kindsv2/go.sum b/kindsv2/go.sum
new file mode 100644
index 00000000000..b7c257b2ad9
--- /dev/null
+++ b/kindsv2/go.sum
@@ -0,0 +1,94 @@
+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.8.2 h1:vWfHI1kQlBvwkna7ktAqXjV5LUEAgU6vyMlJjvZZaDw=
+cuelang.org/go v0.8.2/go.mod h1:CoDbYolfMms4BhWUlhD+t5ORnihR7wvjcfgyO9lL5FI=
+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/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=
+github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
+github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4=
+github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM=
+github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
+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.0-20241029201114-f7f7db0c1070 h1:ncAkFmIq3UbPiPhQ2CuBzdTuwnDqLsFFNqEIXKaiPGA=
+github.com/grafana/cog v0.0.0-20241029201114-f7f7db0c1070/go.mod h1:FqZi9WZ/Uzvs3tvo7l+OViThCpfvu3KDGDqHCB2LNbg=
+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=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+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/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=
+github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
+github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
+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-20240116145035-ef3ab179eed6 h1:MAzmm+JtFxQwTPb1cVMLkemw2OxLy5AB/d/rxtAwGQQ=
+github.com/protocolbuffers/txtpbfmt v0.0.0-20240116145035-ef3ab179eed6/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/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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.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/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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
+golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
+golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
+golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
+golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
+golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
+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=
diff --git a/package.json b/package.json
index e7cf16bafa2..042666902cf 100644
--- a/package.json
+++ b/package.json
@@ -279,7 +279,7 @@
"@grafana/ui": "workspace:*",
"@hello-pangea/dnd": "17.0.0",
"@kusto/monaco-kusto": "^10.0.0",
- "@leeoniya/ufuzzy": "1.0.14",
+ "@leeoniya/ufuzzy": "1.0.16",
"@lezer/common": "1.2.3",
"@lezer/highlight": "1.2.1",
"@lezer/lr": "1.4.2",
diff --git a/packages/grafana-data/src/themes/registry.ts b/packages/grafana-data/src/themes/registry.ts
index c6066d22075..f05ea638735 100644
--- a/packages/grafana-data/src/themes/registry.ts
+++ b/packages/grafana-data/src/themes/registry.ts
@@ -85,12 +85,30 @@ function createDebug(): GrafanaTheme2 {
border: '#ff000088',
text: '#cccc00',
contrastText: '#ffff00',
+ shade: '#9900dd',
},
secondary: {
...baseDarkColors.secondary,
border: '#ff000088',
text: '#cccc00',
contrastText: '#ffff00',
+ shade: '#9900dd',
+ },
+ info: {
+ ...baseDarkColors.info,
+ shade: '#9900dd',
+ },
+ warning: {
+ ...baseDarkColors.warning,
+ shade: '#9900dd',
+ },
+ success: {
+ ...baseDarkColors.success,
+ shade: '#9900dd',
+ },
+ error: {
+ ...baseDarkColors.error,
+ shade: '#9900dd',
},
action: {
hover: '#9900dd',
diff --git a/packages/grafana-data/src/types/explore.ts b/packages/grafana-data/src/types/explore.ts
index 90f21ae568b..f95d90ac409 100644
--- a/packages/grafana-data/src/types/explore.ts
+++ b/packages/grafana-data/src/types/explore.ts
@@ -54,6 +54,7 @@ export interface ExploreLogsPanelState {
labelFieldName?: string;
// Used for logs table visualisation, contains the refId of the dataFrame that is currently visualized
refId?: string;
+ displayedFields?: string[];
}
export interface SplitOpenOptions {
diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json
index 4de626ec320..ace41d3a1be 100644
--- a/packages/grafana-flamegraph/package.json
+++ b/packages/grafana-flamegraph/package.json
@@ -46,7 +46,7 @@
"@emotion/css": "11.13.4",
"@grafana/data": "11.4.0-pre",
"@grafana/ui": "11.4.0-pre",
- "@leeoniya/ufuzzy": "1.0.14",
+ "@leeoniya/ufuzzy": "1.0.16",
"d3": "^7.8.5",
"lodash": "4.17.21",
"react": "18.2.0",
diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json
index 6d241e46306..682bdbea469 100644
--- a/packages/grafana-prometheus/package.json
+++ b/packages/grafana-prometheus/package.json
@@ -40,12 +40,12 @@
"@floating-ui/react": "0.26.28",
"@grafana/data": "11.4.0-pre",
"@grafana/experimental": "2.1.2",
- "@grafana/faro-web-sdk": "1.12.0",
+ "@grafana/faro-web-sdk": "1.12.1",
"@grafana/runtime": "11.4.0-pre",
"@grafana/schema": "11.4.0-pre",
"@grafana/ui": "11.4.0-pre",
"@hello-pangea/dnd": "17.0.0",
- "@leeoniya/ufuzzy": "1.0.14",
+ "@leeoniya/ufuzzy": "1.0.16",
"@lezer/common": "1.2.3",
"@lezer/highlight": "1.2.1",
"@lezer/lr": "1.4.2",
diff --git a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts
index 04d7293bab8..2ce4b88e85f 100644
--- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts
+++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts
@@ -19,6 +19,7 @@ import {
AdHocVariableFilter,
} from '@grafana/data';
+import { reportInteraction } from '../analytics/utils';
import { config } from '../config';
import {
BackendSrvRequest,
@@ -355,8 +356,17 @@ class DataSourceWithBackend<
headers: this.getRequestHeaders(),
})
)
- .then((v: FetchResponse) => v.data)
- .catch((err) => err.data);
+ .then((v: FetchResponse) => v.data)
+ .catch((err) => {
+ let properties: Record = {
+ plugin_id: this.meta?.id || '',
+ plugin_version: this.meta?.info?.version || '',
+ datasource_healthcheck_status: err?.data?.status || 'error',
+ datasource_healthcheck_message: err?.data?.message || '',
+ };
+ reportInteraction('datasource_health_check_completed', properties);
+ return err.data;
+ });
}
/**
diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts
new file mode 100644
index 00000000000..23dfe83a255
--- /dev/null
+++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.gen.ts
@@ -0,0 +1,1249 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+
+export interface DashboardV2Spec {
+ // Unique numeric identifier for the dashboard.
+ // `id` is internal to a specific Grafana instance. `uid` should be used to identify a dashboard across Grafana instances.
+ id?: number;
+ // Title of dashboard.
+ title: string;
+ // Description of dashboard.
+ description?: string;
+ // Configuration of dashboard cursor sync behavior.
+ // "Off" for no shared crosshair or tooltip (default).
+ // "Crosshair" for shared crosshair.
+ // "Tooltip" for shared crosshair AND shared tooltip.
+ cursorSync: DashboardCursorSync;
+ // When set to true, the dashboard will redraw panels at an interval matching the pixel width.
+ // This will keep data "moving left" regardless of the query refresh rate. This setting helps
+ // avoid dashboards presenting stale live data.
+ liveNow?: boolean;
+ // When set to true, the dashboard will load all panels in the dashboard when it's loaded.
+ preload: boolean;
+ // Whether a dashboard is editable or not.
+ editable?: boolean;
+ // Links with references to other dashboards or external websites.
+ links: DashboardLink[];
+ // Tags associated with dashboard.
+ tags?: string[];
+ timeSettings: TimeSettingsSpec;
+ // Configured template variables.
+ variables: (QueryVariableKind | TextVariableKind | ConstantVariableKind | DatasourceVariableKind | IntervalVariableKind | CustomVariableKind | GroupByVariableKind | AdhocVariableKind)[];
+ // |* more element types in the future
+ elements: Record;
+ annotations: AnnotationQueryKind[];
+ layout: GridLayoutKind;
+ // Version of the JSON schema, incremented each time a Grafana update brings
+ // changes to said schema.
+ // version: will rely on k8s resource versioning, via metadata.resorceVersion
+ // revision?: int // for plugins only
+ // gnetId?: string // ??? Wat is this used for?
+ schemaVersion: number;
+}
+
+export const defaultDashboardV2Spec = (): DashboardV2Spec => ({
+ title: "",
+ cursorSync: DashboardCursorSync.Off,
+ preload: false,
+ editable: true,
+ links: [],
+ timeSettings: defaultTimeSettingsSpec(),
+ variables: [],
+ elements: {},
+ annotations: [],
+ layout: defaultGridLayoutKind(),
+ schemaVersion: 39,
+});
+
+export interface AnnotationPanelFilter {
+ // Should the specified panels be included or excluded
+ exclude?: boolean;
+ // Panel IDs that should be included or excluded
+ ids: number[];
+}
+
+export const defaultAnnotationPanelFilter = (): AnnotationPanelFilter => ({
+ exclude: false,
+ ids: [],
+});
+
+// "Off" for no shared crosshair or tooltip (default).
+// "Crosshair" for shared crosshair.
+// "Tooltip" for shared crosshair AND shared tooltip.
+export enum DashboardCursorSync {
+ Off = "Off",
+ Crosshair = "Crosshair",
+ Tooltip = "Tooltip",
+}
+
+export const defaultDashboardCursorSync = (): DashboardCursorSync => (DashboardCursorSync.Off);
+
+// Links with references to other dashboards or external resources
+export interface DashboardLink {
+ // Title to display with the link
+ title: string;
+ // Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
+ // FIXME: The type is generated as `type: DashboardLinkType | dashboardLinkType.Link;` but it should be `type: DashboardLinkType`
+ type: DashboardLinkType;
+ // Icon name to be displayed with the link
+ icon: string;
+ // Tooltip to display when the user hovers their mouse over it
+ tooltip: string;
+ // Link URL. Only required/valid if the type is link
+ url?: string;
+ // List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards
+ tags: string[];
+ // If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards
+ asDropdown: boolean;
+ // If true, the link will be opened in a new tab
+ targetBlank: boolean;
+ // If true, includes current template variables values in the link as query params
+ includeVars: boolean;
+ // If true, includes current time range in the link as query params
+ keepTime: boolean;
+}
+
+export const defaultDashboardLink = (): DashboardLink => ({
+ title: "",
+ type: DashboardLinkType.Link,
+ icon: "",
+ tooltip: "",
+ tags: [],
+ asDropdown: false,
+ targetBlank: false,
+ includeVars: false,
+ keepTime: false,
+});
+
+export interface DataSourceRef {
+ // The plugin type-id
+ type?: string;
+ // Specific datasource instance
+ uid?: string;
+}
+
+export const defaultDataSourceRef = (): DataSourceRef => ({
+});
+
+// Transformations allow to manipulate data returned by a query before the system applies a visualization.
+// Using transformations you can: rename fields, join time series data, perform mathematical operations across queries,
+// use the output of one transformation as the input to another transformation, etc.
+export interface DataTransformerConfig {
+ // Unique identifier of transformer
+ id: string;
+ // Disabled transformations are skipped
+ disabled?: boolean;
+ // Optional frame matcher. When missing it will be applied to all results
+ filter?: MatcherConfig;
+ // Where to pull DataFrames from as input to transformation
+ // replaced with common.DataTopic
+ topic?: "series" | "annotations" | "alertStates";
+ // Options to be passed to the transformer
+ // Valid options depend on the transformer id
+ options: any;
+}
+
+export const defaultDataTransformerConfig = (): DataTransformerConfig => ({
+ id: "",
+ options: {},
+});
+
+// The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.
+// Each column within this structure is called a field. A field can represent a single time series or table column.
+// Field options allow you to change how the data is displayed in your visualizations.
+export interface FieldConfigSource {
+ // Defaults are the options applied to all fields.
+ defaults: FieldConfig;
+ // Overrides are the options applied to specific fields overriding the defaults.
+ overrides: {
+ matcher: MatcherConfig;
+ properties: DynamicConfigValue[];
+ }[];
+}
+
+export const defaultFieldConfigSource = (): FieldConfigSource => ({
+ defaults: defaultFieldConfig(),
+ overrides: [],
+});
+
+// The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.
+// Each column within this structure is called a field. A field can represent a single time series or table column.
+// Field options allow you to change how the data is displayed in your visualizations.
+export interface FieldConfig {
+ // The display value for this field. This supports template variables blank is auto
+ displayName?: string;
+ // This can be used by data sources that return and explicit naming structure for values and labels
+ // When this property is configured, this value is used rather than the default naming strategy.
+ displayNameFromDS?: string;
+ // Human readable field metadata
+ description?: string;
+ // An explicit path to the field in the datasource. When the frame meta includes a path,
+ // This will default to `${frame.meta.path}/${field.name}
+ //
+ // When defined, this value can be used as an identifier within the datasource scope, and
+ // may be used to update the results
+ path?: string;
+ // True if data source can write a value to the path. Auth/authz are supported separately
+ writeable?: boolean;
+ // True if data source field supports ad-hoc filters
+ filterable?: boolean;
+ // Unit a field should use. The unit you select is applied to all fields except time.
+ // You can use the units ID availables in Grafana or a custom unit.
+ // Available units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts
+ // As custom unit, you can use the following formats:
+ // `suffix:` for custom unit that should go after value.
+ // `prefix:` for custom unit that should go before value.
+ // `time:` For custom date time formats type for example `time:YYYY-MM-DD`.
+ // `si:` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.
+ // `count:` for a custom count unit.
+ // `currency:` for custom a currency unit.
+ unit?: string;
+ // Specify the number of decimals Grafana includes in the rendered value.
+ // If you leave this field blank, Grafana automatically truncates the number of decimals based on the value.
+ // For example 1.1234 will display as 1.12 and 100.456 will display as 100.
+ // To display all decimals, set the unit to `String`.
+ decimals?: number;
+ // The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.
+ min?: number;
+ // The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.
+ max?: number;
+ // Convert input values into a display string
+ mappings?: ValueMapping[];
+ // Map numeric values to states
+ thresholds?: ThresholdsConfig;
+ // Panel color configuration
+ color?: FieldColor;
+ // The behavior when clicking on a result
+ links?: any[];
+ // Alternative to empty string
+ noValue?: string;
+ // custom is specified by the FieldConfig field
+ // in panel plugin schemas.
+ custom?: Record;
+}
+
+export const defaultFieldConfig = (): FieldConfig => ({
+});
+
+export interface DynamicConfigValue {
+ id: string;
+ value?: any;
+}
+
+export const defaultDynamicConfigValue = (): DynamicConfigValue => ({
+ id: "",
+});
+
+// Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.
+// It comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.
+export interface MatcherConfig {
+ // The matcher id. This is used to find the matcher implementation from registry.
+ id: string;
+ // The matcher options. This is specific to the matcher implementation.
+ options?: any;
+}
+
+export const defaultMatcherConfig = (): MatcherConfig => ({
+ id: "",
+});
+
+export interface Threshold {
+ value: number | null;
+ color: string;
+}
+
+export const defaultThreshold = (): Threshold => ({
+ value: 0,
+ color: "",
+});
+
+export enum ThresholdsMode {
+ Absolute = "absolute",
+ Percentage = "percentage",
+}
+
+export const defaultThresholdsMode = (): ThresholdsMode => (ThresholdsMode.Absolute);
+
+export interface ThresholdsConfig {
+ mode: ThresholdsMode;
+ steps: Threshold[];
+}
+
+export const defaultThresholdsConfig = (): ThresholdsConfig => ({
+ mode: ThresholdsMode.Absolute,
+ steps: [],
+});
+
+export type ValueMapping = ValueMap | RangeMap | RegexMap | SpecialValueMap;
+
+export const defaultValueMapping = (): ValueMapping => (defaultValueMap());
+
+// Supported value mapping types
+// `value`: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.
+// `range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.
+// `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.
+// `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.
+export enum MappingType {
+ Value = "value",
+ Range = "range",
+ Regex = "regex",
+ Special = "special",
+}
+
+export const defaultMappingType = (): MappingType => (MappingType.Value);
+
+// Maps text values to a color or different display text and color.
+// For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.
+export interface ValueMap {
+ type: "value";
+ // Map with : ValueMappingResult. For example: { "10": { text: "Perfection!", color: "green" } }
+ options: Record;
+}
+
+export const defaultValueMap = (): ValueMap => ({
+ type: "value",
+ options: {},
+});
+
+// Maps numerical ranges to a display text and color.
+// For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.
+export interface RangeMap {
+ type: "range";
+ // Range to match against and the result to apply when the value is within the range
+ options: {
+ // Min value of the range. It can be null which means -Infinity
+ from: number | null;
+ // Max value of the range. It can be null which means +Infinity
+ to: number | null;
+ // Config to apply when the value is within the range
+ result: ValueMappingResult;
+ };
+}
+
+export const defaultRangeMap = (): RangeMap => ({
+ type: "range",
+ options: {
+ from: 0,
+ to: 0,
+ result: defaultValueMappingResult(),
+},
+});
+
+// Maps regular expressions to replacement text and a color.
+// For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.
+export interface RegexMap {
+ type: "regex";
+ // Regular expression to match against and the result to apply when the value matches the regex
+ options: {
+ // Regular expression to match against
+ pattern: string;
+ // Config to apply when the value matches the regex
+ result: ValueMappingResult;
+ };
+}
+
+export const defaultRegexMap = (): RegexMap => ({
+ type: "regex",
+ options: {
+ pattern: "",
+ result: defaultValueMappingResult(),
+},
+});
+
+// Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.
+// See SpecialValueMatch to see the list of special values.
+// For example, you can configure a special value mapping so that null values appear as N/A.
+export interface SpecialValueMap {
+ type: "special";
+ options: {
+ // Special value to match against
+ match: SpecialValueMatch;
+ // Config to apply when the value matches the special value
+ result: ValueMappingResult;
+ };
+}
+
+export const defaultSpecialValueMap = (): SpecialValueMap => ({
+ type: "special",
+ options: {
+ match: SpecialValueMatch.True,
+ result: defaultValueMappingResult(),
+},
+});
+
+// Special value types supported by the `SpecialValueMap`
+export enum SpecialValueMatch {
+ True = "true",
+ False = "false",
+ Null = "null",
+ NotANumber = "nan",
+ NullNan = "null+nan",
+ Empty = "empty",
+}
+
+export const defaultSpecialValueMatch = (): SpecialValueMatch => (SpecialValueMatch.True);
+
+// Result used as replacement with text and color when the value matches
+export interface ValueMappingResult {
+ // Text to display when the value matches
+ text?: string;
+ // Text to use when the value matches
+ color?: string;
+ // Icon to display when the value matches. Only specific visualizations.
+ icon?: string;
+ // Position in the mapping array. Only used internally.
+ index?: number;
+}
+
+export const defaultValueMappingResult = (): ValueMappingResult => ({
+});
+
+// Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.
+// Continuous color interpolates a color using the percentage of a value relative to min and max.
+// Accepted values are:
+// `thresholds`: From thresholds. Informs Grafana to take the color from the matching threshold
+// `palette-classic`: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations
+// `palette-classic-by-name`: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations
+// `continuous-GrYlRd`: ontinuous Green-Yellow-Red palette mode
+// `continuous-RdYlGr`: Continuous Red-Yellow-Green palette mode
+// `continuous-BlYlRd`: Continuous Blue-Yellow-Red palette mode
+// `continuous-YlRd`: Continuous Yellow-Red palette mode
+// `continuous-BlPu`: Continuous Blue-Purple palette mode
+// `continuous-YlBl`: Continuous Yellow-Blue palette mode
+// `continuous-blues`: Continuous Blue palette mode
+// `continuous-reds`: Continuous Red palette mode
+// `continuous-greens`: Continuous Green palette mode
+// `continuous-purples`: Continuous Purple palette mode
+// `shades`: Shades of a single color. Specify a single color, useful in an override rule.
+// `fixed`: Fixed color mode. Specify a single color, useful in an override rule.
+export enum FieldColorModeId {
+ Thresholds = "thresholds",
+ PaletteClassic = "palette-classic",
+ PaletteClassicByName = "palette-classic-by-name",
+ ContinuousGrYlRd = "continuous-GrYlRd",
+ ContinuousRdYlGr = "continuous-RdYlGr",
+ ContinuousBlYlRd = "continuous-BlYlRd",
+ ContinuousYlRd = "continuous-YlRd",
+ ContinuousBlPu = "continuous-BlPu",
+ ContinuousYlBl = "continuous-YlBl",
+ ContinuousBlues = "continuous-blues",
+ ContinuousReds = "continuous-reds",
+ ContinuousGreens = "continuous-greens",
+ ContinuousPurples = "continuous-purples",
+ Fixed = "fixed",
+ Shades = "shades",
+}
+
+export const defaultFieldColorModeId = (): FieldColorModeId => (FieldColorModeId.Thresholds);
+
+// Defines how to assign a series color from "by value" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.
+export enum FieldColorSeriesByMode {
+ Min = "min",
+ Max = "max",
+ Last = "last",
+}
+
+export const defaultFieldColorSeriesByMode = (): FieldColorSeriesByMode => (FieldColorSeriesByMode.Min);
+
+// Map a field to a color.
+export interface FieldColor {
+ // The main color scheme mode.
+ mode: FieldColorModeId;
+ // The fixed color value for fixed or shades color modes.
+ fixedColor?: string;
+ // Some visualizations need to know how to assign a series color from by value color schemes.
+ seriesBy?: FieldColorSeriesByMode;
+}
+
+export const defaultFieldColor = (): FieldColor => ({
+ mode: FieldColorModeId.Thresholds,
+});
+
+// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
+export enum DashboardLinkType {
+ Link = "link",
+ Dashboards = "dashboards",
+}
+
+export const defaultDashboardLinkType = (): DashboardLinkType => (DashboardLinkType.Link);
+
+// --- Common types ---
+export interface Kind {
+ kind: string;
+ spec: any;
+ metadata?: any;
+}
+
+export const defaultKind = (): Kind => ({
+ kind: "",
+ spec: {},
+});
+
+// --- Kinds ---
+export interface VizConfigSpec {
+ pluginVersion: string;
+ options: Record;
+ fieldConfig: FieldConfigSource;
+}
+
+export const defaultVizConfigSpec = (): VizConfigSpec => ({
+ pluginVersion: "",
+ options: {},
+ fieldConfig: defaultFieldConfigSource(),
+});
+
+export interface VizConfigKind {
+ // The kind of a VizConfigKind is the plugin ID
+ kind: string;
+ spec: VizConfigSpec;
+}
+
+export const defaultVizConfigKind = (): VizConfigKind => ({
+ kind: "",
+ spec: defaultVizConfigSpec(),
+});
+
+export interface AnnotationQuerySpec {
+ datasource: DataSourceRef;
+ query: DataQueryKind;
+ // TODO: Should be figured out based on datasource (Grafana ds)
+ // builtIn?: int
+ // Below are currently existing options for annotation queries
+ enable: boolean;
+ filter: AnnotationPanelFilter;
+ hide: boolean;
+ iconColor: string;
+ name: string;
+}
+
+export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({
+ datasource: defaultDataSourceRef(),
+ query: defaultDataQueryKind(),
+ enable: false,
+ filter: defaultAnnotationPanelFilter(),
+ hide: false,
+ iconColor: "",
+ name: "",
+});
+
+export interface AnnotationQueryKind {
+ kind: "AnnotationQuery";
+ spec: AnnotationQuerySpec;
+}
+
+export const defaultAnnotationQueryKind = (): AnnotationQueryKind => ({
+ kind: "AnnotationQuery",
+ spec: defaultAnnotationQuerySpec(),
+});
+
+export interface QueryOptionsSpec {
+ timeFrom?: string;
+ maxDataPoints?: number;
+ timeShift?: string;
+ queryCachingTTL?: number;
+ interval?: string;
+ cacheTimeout?: string;
+}
+
+export const defaultQueryOptionsSpec = (): QueryOptionsSpec => ({
+});
+
+export interface DataQueryKind {
+ // The kind of a DataQueryKind is the datasource type
+ kind: string;
+ spec: Record;
+}
+
+export const defaultDataQueryKind = (): DataQueryKind => ({
+ kind: "",
+ spec: {},
+});
+
+export interface PanelQuerySpec {
+ query: DataQueryKind;
+ datasource: DataSourceRef;
+ refId: string;
+ hidden: boolean;
+}
+
+export const defaultPanelQuerySpec = (): PanelQuerySpec => ({
+ query: defaultDataQueryKind(),
+ datasource: defaultDataSourceRef(),
+ refId: "",
+ hidden: false,
+});
+
+export interface PanelQueryKind {
+ kind: "PanelQuery";
+ spec: PanelQuerySpec;
+}
+
+export const defaultPanelQueryKind = (): PanelQueryKind => ({
+ kind: "PanelQuery",
+ spec: defaultPanelQuerySpec(),
+});
+
+export interface TransformationKind {
+ // The kind of a TransformationKind is the transformation ID
+ kind: string;
+ spec: DataTransformerConfig;
+}
+
+export const defaultTransformationKind = (): TransformationKind => ({
+ kind: "",
+ spec: defaultDataTransformerConfig(),
+});
+
+export interface QueryGroupSpec {
+ queries: PanelQueryKind[];
+ transformations: TransformationKind[];
+ queryOptions: QueryOptionsSpec;
+}
+
+export const defaultQueryGroupSpec = (): QueryGroupSpec => ({
+ queries: [],
+ transformations: [],
+ queryOptions: defaultQueryOptionsSpec(),
+});
+
+export interface QueryGroupKind {
+ kind: "QueryGroup";
+ spec: QueryGroupSpec;
+}
+
+export const defaultQueryGroupKind = (): QueryGroupKind => ({
+ kind: "QueryGroup",
+ spec: defaultQueryGroupSpec(),
+});
+
+// Time configuration
+// It defines the default time config for the time picker, the refresh picker for the specific dashboard.
+export interface TimeSettingsSpec {
+ // Timezone of dashboard. Accepted values are IANA TZDB zone ID or "browser" or "utc".
+ timezone?: string;
+ // Start time range for dashboard.
+ // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z".
+ from: string;
+ // End time range for dashboard.
+ // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z".
+ to: string;
+ // Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d".
+ // v1: refresh
+ autoRefresh: string;
+ // Interval options available in the refresh picker dropdown.
+ // v1: timepicker.refresh_intervals
+ autoRefreshIntervals: string[];
+ // Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.
+ // v1: timepicker.time_options , not exposed in the UI
+ quickRanges: string[];
+ // Whether timepicker is visible or not.
+ // v1: timepicker.hidden
+ hideTimepicker: boolean;
+ // Day when the week starts. Expressed by the name of the day in lowercase, e.g. "monday".
+ weekStart: string;
+ // The month that the fiscal year starts on. 0 = January, 11 = December
+ fiscalYearStartMonth: number;
+ // Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.
+ // v1: timepicker.nowDelay
+ nowDelay?: string;
+}
+
+export const defaultTimeSettingsSpec = (): TimeSettingsSpec => ({
+ timezone: "browser",
+ from: "now-6h",
+ to: "now",
+ autoRefresh: "",
+ autoRefreshIntervals: [
+"5s",
+"10s",
+"30s",
+"1m",
+"5m",
+"15m",
+"30m",
+"1h",
+"2h",
+"1d",
+],
+ quickRanges: [
+"5m",
+"15m",
+"1h",
+"6h",
+"12h",
+"24h",
+"2d",
+"7d",
+"30d",
+],
+ hideTimepicker: false,
+ weekStart: "",
+ fiscalYearStartMonth: 0,
+});
+
+export interface GridLayoutItemSpec {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ // reference to a PanelKind from dashboard.spec.elements Expressed as JSON Schema reference
+ element: ElementReference;
+}
+
+export const defaultGridLayoutItemSpec = (): GridLayoutItemSpec => ({
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ element: defaultElementReference(),
+});
+
+export interface GridLayoutItemKind {
+ kind: "GridLayoutItem";
+ spec: GridLayoutItemSpec;
+}
+
+export const defaultGridLayoutItemKind = (): GridLayoutItemKind => ({
+ kind: "GridLayoutItem",
+ spec: defaultGridLayoutItemSpec(),
+});
+
+export interface GridLayoutSpec {
+ items: GridLayoutItemKind[];
+}
+
+export const defaultGridLayoutSpec = (): GridLayoutSpec => ({
+ items: [],
+});
+
+export interface GridLayoutKind {
+ kind: "GridLayout";
+ spec: GridLayoutSpec;
+}
+
+export const defaultGridLayoutKind = (): GridLayoutKind => ({
+ kind: "GridLayout",
+ spec: defaultGridLayoutSpec(),
+});
+
+export interface PanelSpec {
+ uid: string;
+ title: string;
+ description: string;
+ links: DashboardLink[];
+ data: QueryGroupKind;
+ vizConfig: VizConfigKind;
+}
+
+export const defaultPanelSpec = (): PanelSpec => ({
+ uid: "",
+ title: "",
+ description: "",
+ links: [],
+ data: defaultQueryGroupKind(),
+ vizConfig: defaultVizConfigKind(),
+});
+
+export interface PanelKind {
+ kind: "Panel";
+ spec: PanelSpec;
+}
+
+export const defaultPanelKind = (): PanelKind => ({
+ kind: "Panel",
+ spec: defaultPanelSpec(),
+});
+
+export interface ElementReference {
+ kind: "ElementReference";
+ name: string;
+}
+
+export const defaultElementReference = (): ElementReference => ({
+ kind: "ElementReference",
+ name: "",
+});
+
+// Variable types
+export type VariableValue = VariableValueSingle | VariableValueSingle[];
+
+export const defaultVariableValue = (): VariableValue => (defaultVariableValueSingle());
+
+export type VariableValueSingle = string | boolean | number | CustomVariableValue;
+
+export const defaultVariableValueSingle = (): VariableValueSingle => ("");
+
+// Custom formatter variable
+export interface CustomFormatterVariable {
+ name: string;
+ type: VariableType;
+ multi: boolean;
+ includeAll: boolean;
+}
+
+export const defaultCustomFormatterVariable = (): CustomFormatterVariable => ({
+ name: "",
+ type: VariableType.Query,
+ multi: false,
+ includeAll: false,
+});
+
+// Custom variable value
+export interface CustomVariableValue {
+ // The format name or function used in the expression
+ formatter: string | VariableCustomFormatterFn;
+}
+
+export const defaultCustomVariableValue = (): CustomVariableValue => ({
+ formatter: "",
+});
+
+// Custom formatter function
+export interface VariableCustomFormatterFn {
+ value: any;
+ legacyVariableModel: {
+ name: string;
+ type: VariableType;
+ multi: boolean;
+ includeAll: boolean;
+ };
+ legacyDefaultFormatter?: VariableCustomFormatterFn;
+}
+
+export const defaultVariableCustomFormatterFn = (): VariableCustomFormatterFn => ({
+ value: {},
+ legacyVariableModel: {
+ name: "",
+ type: VariableType.Query,
+ multi: false,
+ includeAll: false,
+},
+});
+
+// Dashboard variable type
+// `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on.
+// `adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only).
+// `constant`: Define a hidden constant.
+// `datasource`: Quickly change the data source for an entire dashboard.
+// `interval`: Interval variables represent time spans.
+// `textbox`: Display a free text input field with an optional default value.
+// `custom`: Define the variable options manually using a comma-separated list.
+// `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables
+export enum VariableType {
+ Query = "query",
+ Adhoc = "adhoc",
+ Groupby = "groupby",
+ Constant = "constant",
+ Datasource = "datasource",
+ Interval = "interval",
+ Textbox = "textbox",
+ Custom = "custom",
+ System = "system",
+ Snapshot = "snapshot",
+}
+
+export const defaultVariableType = (): VariableType => (VariableType.Query);
+
+// Sort variable options
+// Accepted values are:
+// `disabled`: No sorting
+// `alphabeticalAsc`: Alphabetical ASC
+// `alphabeticalDesc`: Alphabetical DESC
+// `numericalAsc`: Numerical ASC
+// `numericalDesc`: Numerical DESC
+// `alphabeticalCaseInsensitiveAsc`: Alphabetical Case Insensitive ASC
+// `alphabeticalCaseInsensitiveDesc`: Alphabetical Case Insensitive DESC
+// `naturalAsc`: Natural ASC
+// `naturalDesc`: Natural DESC
+// VariableSort enum with default value
+export enum VariableSort {
+ Disabled = "disabled",
+ AlphabeticalAsc = "alphabeticalAsc",
+ AlphabeticalDesc = "alphabeticalDesc",
+ NumericalAsc = "numericalAsc",
+ NumericalDesc = "numericalDesc",
+ AlphabeticalCaseInsensitiveAsc = "alphabeticalCaseInsensitiveAsc",
+ AlphabeticalCaseInsensitiveDesc = "alphabeticalCaseInsensitiveDesc",
+ NaturalAsc = "naturalAsc",
+ NaturalDesc = "naturalDesc",
+}
+
+export const defaultVariableSort = (): VariableSort => (VariableSort.Disabled);
+
+// Options to config when to refresh a variable
+// `never`: Never refresh the variable
+// `onDashboardLoad`: Queries the data source every time the dashboard loads.
+// `onTimeRangeChanged`: Queries the data source when the dashboard time range changes.
+export enum VariableRefresh {
+ Never = "never",
+ OnDashboardLoad = "onDashboardLoad",
+ OnTimeRangeChanged = "onTimeRangeChanged",
+}
+
+export const defaultVariableRefresh = (): VariableRefresh => (VariableRefresh.Never);
+
+// Determine if the variable shows on dashboard
+// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
+export enum VariableHide {
+ DontHide = "dontHide",
+ HideLabel = "hideLabel",
+ HideVariable = "hideVariable",
+}
+
+export const defaultVariableHide = (): VariableHide => (VariableHide.DontHide);
+
+// FIXME: should we introduce this? --- Variable value option
+export interface VariableValueOption {
+ label: string;
+ value: VariableValueSingle;
+ group?: string;
+}
+
+export const defaultVariableValueOption = (): VariableValueOption => ({
+ label: "",
+ value: defaultVariableValueSingle(),
+});
+
+// Variable option specification
+export interface VariableOption {
+ // Whether the option is selected or not
+ selected?: boolean;
+ // Text to be displayed for the option
+ text: string | string[];
+ // Value of the option
+ value: string | string[];
+}
+
+export const defaultVariableOption = (): VariableOption => ({
+ text: "",
+ value: "",
+});
+
+// Query variable specification
+export interface QueryVariableSpec {
+ name: string;
+ current: VariableOption;
+ label?: string;
+ hide: VariableHide;
+ refresh: VariableRefresh;
+ skipUrlSync: boolean;
+ description?: string;
+ datasource: DataSourceRef;
+ query: string | DataQueryKind;
+ regex: string;
+ sort: VariableSort;
+ definition?: string;
+ options: VariableOption[];
+ multi: boolean;
+ includeAll: boolean;
+ allValue?: string;
+ placeholder?: string;
+}
+
+export const defaultQueryVariableSpec = (): QueryVariableSpec => ({
+ name: "",
+ current: { text: "", value: "", },
+ hide: VariableHide.DontHide,
+ refresh: VariableRefresh.Never,
+ skipUrlSync: false,
+ datasource: defaultDataSourceRef(),
+ query: "",
+ regex: "",
+ sort: VariableSort.Disabled,
+ options: [],
+ multi: false,
+ includeAll: false,
+});
+
+// Query variable kind
+export interface QueryVariableKind {
+ kind: "QueryVariable";
+ spec: QueryVariableSpec;
+}
+
+export const defaultQueryVariableKind = (): QueryVariableKind => ({
+ kind: "QueryVariable",
+ spec: defaultQueryVariableSpec(),
+});
+
+// Text variable specification
+export interface TextVariableSpec {
+ name: string;
+ current: VariableOption;
+ query: string;
+ label?: string;
+ hide: VariableHide;
+ skipUrlSync: boolean;
+ description?: string;
+}
+
+export const defaultTextVariableSpec = (): TextVariableSpec => ({
+ name: "",
+ current: { text: "", value: "", },
+ query: "",
+ hide: VariableHide.DontHide,
+ skipUrlSync: false,
+});
+
+// Text variable kind
+export interface TextVariableKind {
+ kind: "TextVariable";
+ spec: TextVariableSpec;
+}
+
+export const defaultTextVariableKind = (): TextVariableKind => ({
+ kind: "TextVariable",
+ spec: defaultTextVariableSpec(),
+});
+
+// Constant variable specification
+export interface ConstantVariableSpec {
+ name: string;
+ query: string;
+ current: VariableOption;
+ label?: string;
+ hide: VariableHide;
+ skipUrlSync: boolean;
+ description?: string;
+}
+
+export const defaultConstantVariableSpec = (): ConstantVariableSpec => ({
+ name: "",
+ query: "",
+ current: { text: "", value: "", },
+ hide: VariableHide.DontHide,
+ skipUrlSync: false,
+});
+
+// Constant variable kind
+export interface ConstantVariableKind {
+ kind: "ConstantVariable";
+ spec: ConstantVariableSpec;
+}
+
+export const defaultConstantVariableKind = (): ConstantVariableKind => ({
+ kind: "ConstantVariable",
+ spec: defaultConstantVariableSpec(),
+});
+
+// Datasource variable specification
+export interface DatasourceVariableSpec {
+ name: string;
+ pluginId: string;
+ refresh: VariableRefresh;
+ regex: string;
+ current: VariableOption;
+ defaultOptionEnabled: boolean;
+ options: VariableOption[];
+ multi: boolean;
+ includeAll: boolean;
+ allValue?: string;
+ label?: string;
+ hide: VariableHide;
+ skipUrlSync: boolean;
+ description?: string;
+}
+
+export const defaultDatasourceVariableSpec = (): DatasourceVariableSpec => ({
+ name: "",
+ pluginId: "",
+ refresh: VariableRefresh.Never,
+ regex: "",
+ current: { text: "", value: "", },
+ defaultOptionEnabled: false,
+ options: [],
+ multi: false,
+ includeAll: false,
+ hide: VariableHide.DontHide,
+ skipUrlSync: false,
+});
+
+// Datasource variable kind
+export interface DatasourceVariableKind {
+ kind: "DatasourceVariable";
+ spec: DatasourceVariableSpec;
+}
+
+export const defaultDatasourceVariableKind = (): DatasourceVariableKind => ({
+ kind: "DatasourceVariable",
+ spec: defaultDatasourceVariableSpec(),
+});
+
+// Interval variable specification
+export interface IntervalVariableSpec {
+ name: string;
+ query: string;
+ current: VariableOption;
+ options: VariableOption[];
+ auto: boolean;
+ auto_min: string;
+ auto_count: number;
+ refresh: VariableRefresh;
+ label?: string;
+ hide: VariableHide;
+ skipUrlSync: boolean;
+ description?: string;
+}
+
+export const defaultIntervalVariableSpec = (): IntervalVariableSpec => ({
+ name: "",
+ query: "",
+ current: { text: "", value: "", },
+ options: [],
+ auto: false,
+ auto_min: "",
+ auto_count: 0,
+ refresh: VariableRefresh.Never,
+ hide: VariableHide.DontHide,
+ skipUrlSync: false,
+});
+
+// Interval variable kind
+export interface IntervalVariableKind {
+ kind: "IntervalVariable";
+ spec: IntervalVariableSpec;
+}
+
+export const defaultIntervalVariableKind = (): IntervalVariableKind => ({
+ kind: "IntervalVariable",
+ spec: defaultIntervalVariableSpec(),
+});
+
+// Custom variable specification
+export interface CustomVariableSpec {
+ name: string;
+ query: string;
+ current: VariableOption;
+ options: VariableOption[];
+ multi: boolean;
+ includeAll: boolean;
+ allValue?: string;
+ label?: string;
+ hide: VariableHide;
+ skipUrlSync: boolean;
+ description?: string;
+}
+
+export const defaultCustomVariableSpec = (): CustomVariableSpec => ({
+ name: "",
+ query: "",
+ current: defaultVariableOption(),
+ options: [],
+ multi: false,
+ includeAll: false,
+ hide: VariableHide.DontHide,
+ skipUrlSync: false,
+});
+
+// Custom variable kind
+export interface CustomVariableKind {
+ kind: "CustomVariable";
+ spec: CustomVariableSpec;
+}
+
+export const defaultCustomVariableKind = (): CustomVariableKind => ({
+ kind: "CustomVariable",
+ spec: defaultCustomVariableSpec(),
+});
+
+// GroupBy variable specification
+export interface GroupByVariableSpec {
+ name: string;
+ datasource: DataSourceRef;
+ current: VariableOption;
+ options: VariableOption[];
+ multi: boolean;
+ includeAll: boolean;
+ allValue?: string;
+ label?: string;
+ hide: VariableHide;
+ skipUrlSync: boolean;
+ description?: string;
+}
+
+export const defaultGroupByVariableSpec = (): GroupByVariableSpec => ({
+ name: "",
+ datasource: defaultDataSourceRef(),
+ current: { text: "", value: "", },
+ options: [],
+ multi: false,
+ includeAll: false,
+ hide: VariableHide.DontHide,
+ skipUrlSync: false,
+});
+
+// Group variable kind
+export interface GroupByVariableKind {
+ kind: "GroupByVariable";
+ spec: GroupByVariableSpec;
+}
+
+export const defaultGroupByVariableKind = (): GroupByVariableKind => ({
+ kind: "GroupByVariable",
+ spec: defaultGroupByVariableSpec(),
+});
+
+// Adhoc variable specification
+export interface AdhocVariableSpec {
+ name: string;
+ datasource: DataSourceRef;
+ baseFilters: AdHocFilterWithLabels[];
+ filters: AdHocFilterWithLabels[];
+ defaultKeys: MetricFindValue[];
+ label?: string;
+ hide: VariableHide;
+ skipUrlSync: boolean;
+ description?: string;
+}
+
+export const defaultAdhocVariableSpec = (): AdhocVariableSpec => ({
+ name: "",
+ datasource: defaultDataSourceRef(),
+ baseFilters: [],
+ filters: [],
+ defaultKeys: [],
+ hide: VariableHide.DontHide,
+ skipUrlSync: false,
+});
+
+// Define the MetricFindValue type
+export interface MetricFindValue {
+ text: string;
+ value?: string | number;
+ group?: string;
+ expandable?: boolean;
+}
+
+export const defaultMetricFindValue = (): MetricFindValue => ({
+ text: "",
+});
+
+// Define the AdHocFilterWithLabels type
+export interface AdHocFilterWithLabels {
+ key: string;
+ operator: string;
+ value: string;
+ values?: string[];
+ keyLabel?: string;
+ valueLabels?: string[];
+ forceEdit?: boolean;
+ // @deprecated
+ condition?: string;
+}
+
+export const defaultAdHocFilterWithLabels = (): AdHocFilterWithLabels => ({
+ key: "",
+ operator: "",
+ value: "",
+});
+
+// Adhoc variable kind
+export interface AdhocVariableKind {
+ kind: "AdhocVariable";
+ spec: AdhocVariableSpec;
+}
+
+export const defaultAdhocVariableKind = (): AdhocVariableKind => ({
+ kind: "AdhocVariable",
+ spec: defaultAdhocVariableSpec(),
+});
+
diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue
new file mode 100644
index 00000000000..e7922f46eae
--- /dev/null
+++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue
@@ -0,0 +1,789 @@
+package dashboard
+
+DashboardV2Spec: {
+ // Unique numeric identifier for the dashboard.
+ // `id` is internal to a specific Grafana instance. `uid` should be used to identify a dashboard across Grafana instances.
+ id?: int64
+
+ // Title of dashboard.
+ title: string
+
+ // Description of dashboard.
+ description?: string
+
+ // Configuration of dashboard cursor sync behavior.
+ // "Off" for no shared crosshair or tooltip (default).
+ // "Crosshair" for shared crosshair.
+ // "Tooltip" for shared crosshair AND shared tooltip.
+ cursorSync: DashboardCursorSync
+
+ // When set to true, the dashboard will redraw panels at an interval matching the pixel width.
+ // This will keep data "moving left" regardless of the query refresh rate. This setting helps
+ // avoid dashboards presenting stale live data.
+ liveNow?: bool
+
+ // When set to true, the dashboard will load all panels in the dashboard when it's loaded.
+ preload: bool
+
+ // Whether a dashboard is editable or not.
+ editable?: bool | *true
+
+ // Links with references to other dashboards or external websites.
+ links: [...DashboardLink]
+
+ // Tags associated with dashboard.
+ tags?: [...string]
+
+ timeSettings: TimeSettingsSpec
+
+ // Configured template variables.
+ variables: [...QueryVariableKind | TextVariableKind | ConstantVariableKind | DatasourceVariableKind | IntervalVariableKind | CustomVariableKind | GroupByVariableKind | AdhocVariableKind]
+
+ elements: [ElementReference.name]: PanelKind // |* more element types in the future
+
+ annotations: [...AnnotationQueryKind]
+
+ layout: GridLayoutKind
+
+ // Version of the JSON schema, incremented each time a Grafana update brings
+ // changes to said schema.
+ schemaVersion: uint16 | *39
+
+
+ // version: will rely on k8s resource versioning, via metadata.resorceVersion
+ // revision?: int // for plugins only
+ // gnetId?: string // ??? Wat is this used for?
+}
+
+
+AnnotationPanelFilter: {
+ // Should the specified panels be included or excluded
+ exclude?: bool | *false
+
+ // Panel IDs that should be included or excluded
+ ids: [...uint8]
+}
+
+// "Off" for no shared crosshair or tooltip (default).
+// "Crosshair" for shared crosshair.
+// "Tooltip" for shared crosshair AND shared tooltip.
+DashboardCursorSync: "Off" | "Crosshair" | "Tooltip"
+
+// Links with references to other dashboards or external resources
+DashboardLink: {
+ // Title to display with the link
+ title: string
+ // Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
+ // FIXME: The type is generated as `type: DashboardLinkType | dashboardLinkType.Link;` but it should be `type: DashboardLinkType`
+ type: DashboardLinkType
+ // Icon name to be displayed with the link
+ icon: string
+ // Tooltip to display when the user hovers their mouse over it
+ tooltip: string
+ // Link URL. Only required/valid if the type is link
+ url?: string
+ // List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards
+ tags: [...string]
+ // If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards
+ asDropdown: bool | *false
+ // If true, the link will be opened in a new tab
+ targetBlank: bool | *false
+ // If true, includes current template variables values in the link as query params
+ includeVars: bool | *false
+ // If true, includes current time range in the link as query params
+ keepTime: bool | *false
+}
+
+DataSourceRef: {
+ // The plugin type-id
+ type?: string
+
+ // Specific datasource instance
+ uid?: string
+}
+
+// Transformations allow to manipulate data returned by a query before the system applies a visualization.
+// Using transformations you can: rename fields, join time series data, perform mathematical operations across queries,
+// use the output of one transformation as the input to another transformation, etc.
+DataTransformerConfig: {
+ // Unique identifier of transformer
+ id: string
+ // Disabled transformations are skipped
+ disabled?: bool
+ // Optional frame matcher. When missing it will be applied to all results
+ filter?: MatcherConfig
+ // Where to pull DataFrames from as input to transformation
+ topic?: "series" | "annotations" | "alertStates" // replaced with common.DataTopic
+ // Options to be passed to the transformer
+ // Valid options depend on the transformer id
+ options: _
+}
+
+// The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.
+// Each column within this structure is called a field. A field can represent a single time series or table column.
+// Field options allow you to change how the data is displayed in your visualizations.
+FieldConfigSource: {
+ // Defaults are the options applied to all fields.
+ defaults: FieldConfig
+ // Overrides are the options applied to specific fields overriding the defaults.
+ overrides: [...{
+ matcher: MatcherConfig
+ properties: [...DynamicConfigValue]
+ }]
+}
+
+// The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.
+// Each column within this structure is called a field. A field can represent a single time series or table column.
+// Field options allow you to change how the data is displayed in your visualizations.
+FieldConfig: {
+ // The display value for this field. This supports template variables blank is auto
+ displayName?: string
+
+ // This can be used by data sources that return and explicit naming structure for values and labels
+ // When this property is configured, this value is used rather than the default naming strategy.
+ displayNameFromDS?: string
+
+ // Human readable field metadata
+ description?: string
+
+ // An explicit path to the field in the datasource. When the frame meta includes a path,
+ // This will default to `${frame.meta.path}/${field.name}
+ //
+ // When defined, this value can be used as an identifier within the datasource scope, and
+ // may be used to update the results
+ path?: string
+
+ // True if data source can write a value to the path. Auth/authz are supported separately
+ writeable?: bool
+
+ // True if data source field supports ad-hoc filters
+ filterable?: bool
+
+ // Unit a field should use. The unit you select is applied to all fields except time.
+ // You can use the units ID availables in Grafana or a custom unit.
+ // Available units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts
+ // As custom unit, you can use the following formats:
+ // `suffix:` for custom unit that should go after value.
+ // `prefix:` for custom unit that should go before value.
+ // `time:` For custom date time formats type for example `time:YYYY-MM-DD`.
+ // `si:` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.
+ // `count:` for a custom count unit.
+ // `currency:` for custom a currency unit.
+ unit?: string
+
+ // Specify the number of decimals Grafana includes in the rendered value.
+ // If you leave this field blank, Grafana automatically truncates the number of decimals based on the value.
+ // For example 1.1234 will display as 1.12 and 100.456 will display as 100.
+ // To display all decimals, set the unit to `String`.
+ decimals?: number
+
+ // The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.
+ min?: number
+ // The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.
+ max?: number
+
+ // Convert input values into a display string
+ mappings?: [...ValueMapping]
+
+ // Map numeric values to states
+ thresholds?: ThresholdsConfig
+
+ // Panel color configuration
+ color?: FieldColor
+
+ // The behavior when clicking on a result
+ links?: [...]
+
+ // Alternative to empty string
+ noValue?: string
+
+ // custom is specified by the FieldConfig field
+ // in panel plugin schemas.
+ custom?: {...}
+}
+
+DynamicConfigValue: {
+ id: string | *""
+ value?: _
+}
+
+// Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.
+// It comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.
+MatcherConfig: {
+ // The matcher id. This is used to find the matcher implementation from registry.
+ id: string | *""
+ // The matcher options. This is specific to the matcher implementation.
+ options?: _
+}
+
+Threshold: {
+ value: number | null
+ color: string
+}
+
+ThresholdsMode: "absolute" | "percentage"
+
+ThresholdsConfig: {
+ mode: ThresholdsMode
+ steps: [...Threshold]
+}
+
+ValueMapping: ValueMap | RangeMap | RegexMap | SpecialValueMap
+
+// Supported value mapping types
+// `value`: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.
+// `range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.
+// `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.
+// `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.
+MappingType: "value" | "range" | "regex" | "special"
+
+// Maps text values to a color or different display text and color.
+// For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.
+ValueMap: {
+ type: MappingType & "value"
+ // Map with : ValueMappingResult. For example: { "10": { text: "Perfection!", color: "green" } }
+ options: [string]: ValueMappingResult
+}
+
+// Maps numerical ranges to a display text and color.
+// For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.
+RangeMap: {
+ type: MappingType & "range"
+ // Range to match against and the result to apply when the value is within the range
+ options: {
+ // Min value of the range. It can be null which means -Infinity
+ from: float64 | null
+ // Max value of the range. It can be null which means +Infinity
+ to: float64 | null
+ // Config to apply when the value is within the range
+ result: ValueMappingResult
+ }
+}
+
+// Maps regular expressions to replacement text and a color.
+// For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.
+RegexMap: {
+ type: MappingType & "regex"
+ // Regular expression to match against and the result to apply when the value matches the regex
+ options: {
+ // Regular expression to match against
+ pattern: string
+ // Config to apply when the value matches the regex
+ result: ValueMappingResult
+ }
+}
+
+// Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.
+// See SpecialValueMatch to see the list of special values.
+// For example, you can configure a special value mapping so that null values appear as N/A.
+SpecialValueMap: {
+ type: MappingType & "special"
+ options: {
+ // Special value to match against
+ match: SpecialValueMatch
+ // Config to apply when the value matches the special value
+ result: ValueMappingResult
+ }
+}
+
+// Special value types supported by the `SpecialValueMap`
+SpecialValueMatch: "true" | "false" | "null" | "nan" | "null+nan" | "empty"
+
+// Result used as replacement with text and color when the value matches
+ValueMappingResult: {
+ // Text to display when the value matches
+ text?: string
+ // Text to use when the value matches
+ color?: string
+ // Icon to display when the value matches. Only specific visualizations.
+ icon?: string
+ // Position in the mapping array. Only used internally.
+ index?: int32
+}
+
+// Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.
+// Continuous color interpolates a color using the percentage of a value relative to min and max.
+// Accepted values are:
+// `thresholds`: From thresholds. Informs Grafana to take the color from the matching threshold
+// `palette-classic`: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations
+// `palette-classic-by-name`: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations
+// `continuous-GrYlRd`: ontinuous Green-Yellow-Red palette mode
+// `continuous-RdYlGr`: Continuous Red-Yellow-Green palette mode
+// `continuous-BlYlRd`: Continuous Blue-Yellow-Red palette mode
+// `continuous-YlRd`: Continuous Yellow-Red palette mode
+// `continuous-BlPu`: Continuous Blue-Purple palette mode
+// `continuous-YlBl`: Continuous Yellow-Blue palette mode
+// `continuous-blues`: Continuous Blue palette mode
+// `continuous-reds`: Continuous Red palette mode
+// `continuous-greens`: Continuous Green palette mode
+// `continuous-purples`: Continuous Purple palette mode
+// `shades`: Shades of a single color. Specify a single color, useful in an override rule.
+// `fixed`: Fixed color mode. Specify a single color, useful in an override rule.
+FieldColorModeId: "thresholds" | "palette-classic" | "palette-classic-by-name" | "continuous-GrYlRd" | "continuous-RdYlGr" | "continuous-BlYlRd" | "continuous-YlRd" | "continuous-BlPu" | "continuous-YlBl" | "continuous-blues" | "continuous-reds" | "continuous-greens" | "continuous-purples" | "fixed" | "shades"
+
+// Defines how to assign a series color from "by value" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.
+FieldColorSeriesByMode: "min" | "max" | "last"
+
+// Map a field to a color.
+FieldColor: {
+ // The main color scheme mode.
+ mode: FieldColorModeId
+ // The fixed color value for fixed or shades color modes.
+ fixedColor?: string
+ // Some visualizations need to know how to assign a series color from by value color schemes.
+ seriesBy?: FieldColorSeriesByMode
+}
+
+// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
+DashboardLinkType: "link" | "dashboards"
+
+// --- Common types ---
+Kind: {
+ kind: string,
+ spec: _
+ metadata?: _
+}
+
+// --- Kinds ---
+VizConfigSpec: {
+ pluginVersion: string
+ options: [string]: _
+ fieldConfig: FieldConfigSource
+}
+
+VizConfigKind: {
+ // The kind of a VizConfigKind is the plugin ID
+ kind: string
+ spec: VizConfigSpec
+}
+
+AnnotationQuerySpec: {
+ datasource: DataSourceRef
+ query: DataQueryKind
+
+ // TODO: Should be figured out based on datasource (Grafana ds)
+ // builtIn?: int
+ // Below are currently existing options for annotation queries
+ enable: bool
+ filter: AnnotationPanelFilter
+ hide: bool
+ iconColor: string
+ name: string
+}
+
+AnnotationQueryKind: {
+ kind: "AnnotationQuery"
+ spec: AnnotationQuerySpec
+}
+
+QueryOptionsSpec: {
+ timeFrom?: string
+ maxDataPoints?: int
+ timeShift?: string
+ queryCachingTTL?: int
+ interval?: string
+ cacheTimeout?: string
+}
+
+DataQueryKind: {
+ // The kind of a DataQueryKind is the datasource type
+ kind: string
+ spec: [string]: _
+}
+
+PanelQuerySpec: {
+ query: DataQueryKind
+ datasource: DataSourceRef
+
+ refId: string
+ hidden: bool
+}
+
+PanelQueryKind: {
+ kind: "PanelQuery"
+ spec: PanelQuerySpec
+}
+
+TransformationKind: {
+ // The kind of a TransformationKind is the transformation ID
+ kind: string
+ spec: DataTransformerConfig
+}
+
+QueryGroupSpec: {
+ queries: [...PanelQueryKind]
+ transformations: [...TransformationKind]
+ queryOptions: QueryOptionsSpec
+}
+
+QueryGroupKind: {
+ kind: "QueryGroup"
+ spec: QueryGroupSpec
+}
+
+// Time configuration
+// It defines the default time config for the time picker, the refresh picker for the specific dashboard.
+TimeSettingsSpec: {
+ // Timezone of dashboard. Accepted values are IANA TZDB zone ID or "browser" or "utc".
+ timezone?: string | *"browser"
+ // Start time range for dashboard.
+ // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z".
+ from: string | *"now-6h"
+ // End time range for dashboard.
+ // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z".
+ to: string | *"now"
+ // Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d".
+ autoRefresh: string // v1: refresh
+ // Interval options available in the refresh picker dropdown.
+ autoRefreshIntervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] // v1: timepicker.refresh_intervals
+ // Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.
+ quickRanges: [...string] | *["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] // v1: timepicker.time_options , not exposed in the UI
+ // Whether timepicker is visible or not.
+ hideTimepicker: bool // v1: timepicker.hidden
+ // Day when the week starts. Expressed by the name of the day in lowercase, e.g. "monday".
+ weekStart: string
+ // The month that the fiscal year starts on. 0 = January, 11 = December
+ fiscalYearStartMonth: int
+ // Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.
+ nowDelay?: string // v1: timepicker.nowDelay
+}
+
+GridLayoutItemSpec: {
+ x: int
+ y: int
+ width: int
+ height: int
+ element: ElementReference // reference to a PanelKind from dashboard.spec.elements Expressed as JSON Schema reference
+}
+
+GridLayoutItemKind: {
+ kind: "GridLayoutItem"
+ spec: GridLayoutItemSpec
+}
+
+GridLayoutSpec: {
+ items: [...GridLayoutItemKind]
+}
+
+GridLayoutKind: {
+ kind: "GridLayout"
+ spec: GridLayoutSpec
+}
+
+PanelSpec: {
+ uid: string
+ title: string
+ description: string
+ links: [...DashboardLink]
+ data: QueryGroupKind
+ vizConfig: VizConfigKind
+}
+
+PanelKind: {
+ kind: "Panel"
+ spec: PanelSpec
+}
+
+ElementReference: {
+ kind: "ElementReference"
+ name: string
+}
+
+
+
+
+// Start FIXME: variables - in CUE PR - this are things that should be added into the cue schema
+// TODO: properties such as `hide`, `skipUrlSync`, `multi` are type boolean, and in the old schema they are conditional,
+// should we make them conditional in the new schema as well? or should we make them required but default to false?
+
+// Variable types
+VariableValue: VariableValueSingle | [...VariableValueSingle]
+
+VariableValueSingle: string | bool | number | CustomVariableValue
+
+// Custom formatter variable
+CustomFormatterVariable: {
+ name: string
+ type: VariableType
+ multi: bool
+ includeAll: bool
+}
+
+// Custom variable value
+CustomVariableValue: {
+ // The format name or function used in the expression
+ formatter: *null | string | VariableCustomFormatterFn
+}
+
+// Custom formatter function
+VariableCustomFormatterFn: {
+ value: _
+ legacyVariableModel: {
+ name: string
+ type: VariableType
+ multi: bool
+ includeAll: bool
+ }
+ legacyDefaultFormatter?: VariableCustomFormatterFn
+}
+
+// Dashboard variable type
+ // `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on.
+ // `adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only).
+ // `constant`: Define a hidden constant.
+ // `datasource`: Quickly change the data source for an entire dashboard.
+ // `interval`: Interval variables represent time spans.
+ // `textbox`: Display a free text input field with an optional default value.
+ // `custom`: Define the variable options manually using a comma-separated list.
+ // `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables
+VariableType: "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" |
+ "system" | "snapshot"
+
+// Sort variable options
+// Accepted values are:
+// `disabled`: No sorting
+// `alphabeticalAsc`: Alphabetical ASC
+// `alphabeticalDesc`: Alphabetical DESC
+// `numericalAsc`: Numerical ASC
+// `numericalDesc`: Numerical DESC
+// `alphabeticalCaseInsensitiveAsc`: Alphabetical Case Insensitive ASC
+// `alphabeticalCaseInsensitiveDesc`: Alphabetical Case Insensitive DESC
+// `naturalAsc`: Natural ASC
+// `naturalDesc`: Natural DESC
+// VariableSort enum with default value
+VariableSort: "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAsc" | "numericalDesc" | "alphabeticalCaseInsensitiveAsc" | "alphabeticalCaseInsensitiveDesc" | "naturalAsc" | "naturalDesc"
+
+// Options to config when to refresh a variable
+// `never`: Never refresh the variable
+// `onDashboardLoad`: Queries the data source every time the dashboard loads.
+// `onTimeRangeChanged`: Queries the data source when the dashboard time range changes.
+VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged"
+
+// Determine if the variable shows on dashboard
+// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
+VariableHide: *"dontHide" | "hideLabel" | "hideVariable"
+
+
+// FIXME: should we introduce this? --- Variable value option
+VariableValueOption: {
+ label: string
+ value: VariableValueSingle
+ group?: string
+}
+
+// Variable option specification
+VariableOption: {
+ // Whether the option is selected or not
+ selected?: bool
+ // Text to be displayed for the option
+ text: string | [...string]
+ // Value of the option
+value: string | [...string]
+}
+
+// Query variable specification
+QueryVariableSpec: {
+ name: string | *""
+ current: VariableOption | *{
+ text: ""
+ value: ""
+ }
+ label?: string
+ hide: VariableHide
+ refresh: VariableRefresh
+ skipUrlSync: bool | *false
+ description?: string
+ datasource: DataSourceRef | *{}
+ query: string | DataQueryKind | *""
+ regex: string | *""
+ sort: VariableSort
+ definition?: string
+ options: [...VariableOption] | *[]
+ multi: bool | *false
+ includeAll: bool | *false
+ allValue?: string
+ placeholder?: string
+}
+
+// Query variable kind
+QueryVariableKind: {
+ kind: "QueryVariable"
+ spec: QueryVariableSpec
+}
+
+// Text variable specification
+TextVariableSpec: {
+ name: string | *""
+ current: VariableOption | *{
+ text: ""
+ value: ""
+ }
+ query: string | *""
+ label?: string
+ hide: VariableHide
+ skipUrlSync: bool | *false
+ description?: string
+}
+
+// Text variable kind
+TextVariableKind: {
+ kind: "TextVariable"
+ spec: TextVariableSpec
+}
+
+// Constant variable specification
+ConstantVariableSpec: {
+ name: string | *""
+ query: string | *""
+ current: VariableOption | *{
+ text: ""
+ value: ""
+ }
+ label?: string
+ hide: VariableHide
+ skipUrlSync: bool | *false
+ description?: string
+}
+
+// Constant variable kind
+ConstantVariableKind: {
+ kind: "ConstantVariable"
+ spec: ConstantVariableSpec
+}
+
+// Datasource variable specification
+DatasourceVariableSpec: {
+ name: string | *""
+ pluginId: string | *""
+ refresh: VariableRefresh
+ regex: string | *""
+ current: VariableOption | *{
+ text: ""
+ value: ""
+ }
+ defaultOptionEnabled: bool | *false
+ options: [...VariableOption] | *[]
+ multi: bool | *false
+ includeAll: bool | *false
+ allValue?: string
+ label?: string
+ hide: VariableHide
+ skipUrlSync: bool | *false
+ description?: string
+}
+
+// Datasource variable kind
+DatasourceVariableKind: {
+ kind: "DatasourceVariable"
+ spec: DatasourceVariableSpec
+}
+
+// Interval variable specification
+IntervalVariableSpec: {
+ name: string | *""
+ query: string | *""
+ current: VariableOption | *{
+ text: ""
+ value: ""
+ }
+ options: [...VariableOption] | *[]
+ auto: bool | *false
+ auto_min: string | *""
+ auto_count: int | *0
+ refresh: VariableRefresh
+ label?: string
+ hide: VariableHide
+ skipUrlSync: bool | *false
+ description?: string
+}
+
+// Interval variable kind
+IntervalVariableKind: {
+ kind: "IntervalVariable"
+ spec: IntervalVariableSpec
+}
+
+// Custom variable specification
+CustomVariableSpec: {
+ name: string | *""
+ query: string | *""
+ current: VariableOption
+ options: [...VariableOption] | *[]
+ multi: bool | *false
+ includeAll: bool | *false
+ allValue?: string
+ label?: string
+ hide: VariableHide
+ skipUrlSync: bool | *false
+ description?: string
+}
+
+// Custom variable kind
+CustomVariableKind: {
+ kind: "CustomVariable"
+ spec: CustomVariableSpec
+}
+
+// GroupBy variable specification
+GroupByVariableSpec: {
+ name: string | *""
+ datasource: DataSourceRef | *{}
+ current: VariableOption | *{
+ text: ""
+ value: ""
+ }
+ options: [...VariableOption] | *[]
+ multi: bool | *false
+ includeAll: bool | *false
+ allValue?: string
+ label?: string
+ hide: VariableHide
+ skipUrlSync: bool | *false
+ description?: string
+}
+
+// Group variable kind
+GroupByVariableKind: {
+ kind: "GroupByVariable"
+ spec: GroupByVariableSpec
+}
+
+// Adhoc variable specification
+AdhocVariableSpec: {
+ name: string | *""
+ datasource: DataSourceRef | *{}
+ baseFilters: [...AdHocFilterWithLabels] | *[]
+ filters: [...AdHocFilterWithLabels] | *[]
+ defaultKeys: [...MetricFindValue] | *[]
+ label?: string
+ hide: VariableHide
+ skipUrlSync: bool | *false
+ description?: string
+}
+
+// Define the MetricFindValue type
+MetricFindValue: {
+ text: string
+ value?: string | number
+ group?: string
+ expandable?: bool
+}
+
+// Define the AdHocFilterWithLabels type
+AdHocFilterWithLabels: {
+ key: string,
+ operator: string,
+ value: string,
+ values?: [...string],
+ keyLabel?: string,
+ valueLabels?: [...string],
+ forceEdit?: bool,
+ // @deprecated
+ condition?: string,
+}
+
+// Adhoc variable kind
+AdhocVariableKind: {
+ kind: "AdhocVariable"
+ spec: AdhocVariableSpec
+}
diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts
new file mode 100644
index 00000000000..5b98aaf0854
--- /dev/null
+++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts
@@ -0,0 +1,150 @@
+import { DashboardCursorSync, DashboardV2Spec } from './dashboard.gen';
+
+export const handyTestingSchema: DashboardV2Spec = {
+ id: 1,
+ title: 'Default Dashboard',
+ description: 'This is a default dashboard',
+ cursorSync: DashboardCursorSync.Off,
+ liveNow: false,
+ preload: false,
+ editable: true,
+ links: [],
+ tags: [],
+ schemaVersion: 39,
+ timeSettings: {
+ timezone: 'browser',
+ from: 'now-6h',
+ to: 'now',
+ autoRefresh: '10s',
+ autoRefreshIntervals: ['10s', '1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d'],
+ quickRanges: ['now/d', 'now/w', 'now/M', 'now/y'],
+ hideTimepicker: false,
+ weekStart: 'sunday',
+ fiscalYearStartMonth: 1,
+ },
+
+ elements: {
+ timeSeriesTest: {
+ kind: 'Panel',
+ spec: {
+ title: 'Time Series Test',
+ description: 'This is a test panel',
+ uid: 'timeSeriesTest',
+ links: [],
+ data: {
+ kind: 'QueryGroup',
+ spec: {
+ queries: [
+ {
+ kind: 'PanelQuery',
+ spec: {
+ query: {
+ kind: 'prometheus',
+ spec: {
+ query: 'up',
+ },
+ },
+ datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
+ hidden: false,
+ refId: 'A',
+ },
+ },
+ ],
+ transformations: [
+ {
+ kind: 'limit',
+ spec: {
+ id: 'limit', // id is competing w/ kind
+ options: {
+ limit: 10,
+ },
+ },
+ },
+ ],
+ queryOptions: {
+ maxDataPoints: 100,
+ cacheTimeout: '1m',
+ },
+ },
+ },
+ vizConfig: {
+ kind: 'timeseries',
+ spec: {
+ pluginVersion: '11.0.0',
+ options: {},
+ fieldConfig: {
+ defaults: {},
+ overrides: [],
+ },
+ },
+ },
+ },
+ },
+ },
+ layout: {
+ kind: 'GridLayout',
+ spec: {
+ items: [
+ {
+ kind: 'GridLayoutItem',
+ spec: {
+ element: { kind: 'ElementReference', name: 'timeSeriesTest' },
+ x: 0,
+ y: 0,
+ width: 12,
+ height: 6,
+ },
+ },
+ ],
+ },
+ },
+ variables: [],
+ annotations: [
+ {
+ kind: 'AnnotationQuery',
+ spec: {
+ datasource: { type: 'datasource', uid: 'grafana' },
+ query: {
+ kind: 'grafana',
+ spec: {
+ queryType: 'timeRegions',
+ matchAny: false,
+ timeRegion: {
+ from: '12:27',
+ fromDayOfWeek: 2,
+ timezone: 'browser',
+ to: '11:30',
+ toDayOfWeek: 2,
+ },
+ },
+ },
+ enable: true,
+ filter: {
+ ids: [],
+ },
+ hide: false,
+ iconColor: 'blue',
+ name: 'Grafana annotations',
+ },
+ },
+ {
+ kind: 'AnnotationQuery',
+ spec: {
+ datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
+ query: {
+ kind: 'prometheus',
+ spec: {
+ query: 'up',
+ },
+ },
+ enable: true,
+ filter: {
+ ids: [],
+ },
+ hide: false,
+ iconColor: 'red',
+ name: 'Prometheus annotations',
+ },
+ },
+ ],
+};
diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json
index 7994de17e82..bbf2eda4c06 100644
--- a/packages/grafana-ui/package.json
+++ b/packages/grafana-ui/package.json
@@ -56,7 +56,7 @@
"@grafana/faro-web-sdk": "^1.3.6",
"@grafana/schema": "11.4.0-pre",
"@hello-pangea/dnd": "17.0.0",
- "@leeoniya/ufuzzy": "1.0.14",
+ "@leeoniya/ufuzzy": "1.0.16",
"@monaco-editor/react": "4.6.0",
"@popperjs/core": "2.11.8",
"@react-aria/dialog": "3.5.19",
@@ -92,7 +92,7 @@
"react-calendar": "5.1.0",
"react-colorful": "5.6.1",
"react-custom-scrollbars-2": "4.5.0",
- "react-dropzone": "14.2.10",
+ "react-dropzone": "14.3.5",
"react-highlight-words": "0.20.0",
"react-hook-form": "^7.49.2",
"react-i18next": "^14.0.0",
diff --git a/packages/grafana-ui/src/components/Combobox/Combobox.story.tsx b/packages/grafana-ui/src/components/Combobox/Combobox.story.tsx
index 1b63c36bec1..7bb767e9dbd 100644
--- a/packages/grafana-ui/src/components/Combobox/Combobox.story.tsx
+++ b/packages/grafana-ui/src/components/Combobox/Combobox.story.tsx
@@ -4,16 +4,16 @@ import React, { ComponentProps, useCallback, useEffect, useState } from 'react';
import { SelectableValue } from '@grafana/data';
-import { useTheme2 } from '../../themes/ThemeContext';
import { Alert } from '../Alert/Alert';
-import { Divider } from '../Divider/Divider';
import { Field } from '../Forms/Field';
-import { AsyncSelect, Select } from '../Select/Select';
+import { AsyncSelect } from '../Select/Select';
import { Combobox, ComboboxOption } from './Combobox';
import mdx from './Combobox.mdx';
-type PropsAndCustomArgs = ComponentProps & { numberOfOptions: number };
+type PropsAndCustomArgs = ComponentProps> & {
+ numberOfOptions: number;
+};
const meta: Meta = {
title: 'Forms/Combobox',
@@ -27,6 +27,7 @@ const meta: Meta = {
loading: undefined,
invalid: undefined,
width: undefined,
+ isClearable: false,
placeholder: 'Select an option...',
options: [
{ label: 'Apple', value: 'apple' },
@@ -45,12 +46,6 @@ const meta: Meta = {
{ label: 'Honeydew', value: 'honeydew' },
{ label: 'Iceberg Lettuce', value: 'iceberg-lettuce' },
{ label: 'Jackfruit', value: 'jackfruit' },
- { label: '1', value: 1 },
- { label: '2', value: 2 },
- { label: '3', value: 3 },
- { label: '4', value: 4 },
- { label: '5', value: 5 },
- { label: '6', value: 6 },
],
value: 'banana',
},
@@ -59,16 +54,16 @@ const meta: Meta = {
decorators: [InDevDecorator],
};
-const BasicWithState: StoryFn = (args) => {
- const [value, setValue] = useState(args.value);
-
+const BasicWithState: StoryFn = (args) => {
+ const [value, setValue] = useState();
return (
{
+ onChange={(val: ComboboxOption | null) => {
+ // TODO: Figure out how to update value on args
setValue(val?.value || null);
action('onChange')(val);
}}
@@ -88,7 +83,7 @@ async function generateOptions(amount: number): Promise {
}));
}
-const ManyOptionsStory: StoryFn = ({ numberOfOptions, ...args }) => {
+const ManyOptionsStory: StoryFn> = ({ numberOfOptions, ...args }) => {
const [value, setValue] = useState(null);
const [options, setOptions] = useState([]);
const [isLoading, setIsLoading] = useState(true);
@@ -103,13 +98,14 @@ const ManyOptionsStory: StoryFn = ({ numberOfOptions, ...arg
}, 1000);
}, [numberOfOptions]);
+ const { onChange, ...rest } = args;
return (
{
+ onChange={(opt: ComboboxOption | null) => {
setValue(opt?.value || null);
action('onChange')(opt);
}}
@@ -117,132 +113,6 @@ const ManyOptionsStory: StoryFn = ({ numberOfOptions, ...arg
);
};
-const SelectComparisonStory: StoryFn = (args) => {
- const [comboboxValue, setComboboxValue] = useState(args.value);
- const theme = useTheme2();
-
- if (typeof args.options === 'function') {
- throw new Error('This story does not support async options');
- }
-
- return (
-
-
- {
- setComboboxValue(val?.value || null);
- action('onChange')(val);
- }}
- />
-
-
-
-
-
-
-
-
- {/*@ts-ignore minWidth and maxWidth has never, which is incompatible with args. It lacks the context that width=25 on the component*/}
- {
- setComboboxValue(val?.value || null);
- action('onChange')(val);
- }}
- />
-
-
-
-
-
-
-
-
- {
- setComboboxValue(val?.value || null);
- action('onChange')(val);
- }}
- />
-
-
-
-
-
-
- {
- setComboboxValue(val?.value || null);
- action('onChange')(val);
- }}
- />
-
-
-
-
-
- );
-};
-
export const AutoSize: StoryObj = {
args: {
width: 'auto',
@@ -294,6 +164,8 @@ const AsyncStory: StoryFn = (args) => {
}
}, []);
+ const { onChange, ...rest } = args;
+
return (
<>
= (args) => {
description="This tests when options have both a label and a value. Consumers are required to pass in a full ComboboxOption as a value with a label"
>
{
+ onChange={(val: ComboboxOption | null) => {
action('onChange')(val);
setSelectedOption(val);
}}
@@ -324,7 +196,7 @@ const AsyncStory: StoryFn = (args) => {
placeholder="Select an option"
options={loadOptionsOnlyValues}
value={selectedOption?.value ?? null}
- onChange={(val) => {
+ onChange={(val: ComboboxOption | null) => {
action('onChange')(val);
setSelectedOption(val);
}}
@@ -366,7 +238,7 @@ const AsyncStory: StoryFn = (args) => {
placeholder="Select an option"
options={loadOptionsWithErrors}
value={selectedOption}
- onChange={(val) => {
+ onChange={(val: ComboboxOption | null) => {
action('onChange')(val);
setSelectedOption(val);
}}
@@ -426,13 +298,6 @@ export const PositioningTest: StoryObj = {
render: PositioningTestStory,
};
-export const ComparisonToSelect: StoryObj = {
- args: {
- numberOfOptions: 100,
- },
- render: SelectComparisonStory,
-};
-
export default meta;
function InDevDecorator(Story: React.ElementType) {
diff --git a/packages/grafana-ui/src/components/Combobox/Combobox.test.tsx b/packages/grafana-ui/src/components/Combobox/Combobox.test.tsx
index d45c2cc2402..ab9e63fbb3b 100644
--- a/packages/grafana-ui/src/components/Combobox/Combobox.test.tsx
+++ b/packages/grafana-ui/src/components/Combobox/Combobox.test.tsx
@@ -1,5 +1,6 @@
import { act, render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+import React from 'react';
import { Combobox, ComboboxOption } from './Combobox';
@@ -49,7 +50,7 @@ describe('Combobox', () => {
expect(onChangeHandler).toHaveBeenCalledWith(options[0]);
});
- it("shows the placeholder with the menu open when there's no value", async () => {
+ it('shows the placeholder with the menu open when value is null', async () => {
render();
const input = screen.getByRole('combobox');
@@ -58,6 +59,15 @@ describe('Combobox', () => {
expect(input).toHaveAttribute('placeholder', 'Select an option');
});
+ it('shows the placeholder with the menu open when value is undefined', async () => {
+ render();
+
+ const input = screen.getByRole('combobox');
+ await userEvent.click(input);
+
+ expect(input).toHaveAttribute('placeholder', 'Select an option');
+ });
+
it('selects value by clicking that needs scrolling', async () => {
render();
@@ -106,6 +116,73 @@ describe('Combobox', () => {
expect(screen.queryByDisplayValue('Option 2')).not.toBeInTheDocument();
});
+ it.each(['very valid value', '', 0])('should handle an option with %p as a value', async (val) => {
+ const options = [
+ { label: 'Second option', value: '2' },
+ { label: 'Default', value: val },
+ ];
+
+ const ControlledCombobox = () => {
+ const [value, setValue] = React.useState(null);
+
+ return (
+ {
+ setValue(opt.value);
+ }}
+ />
+ );
+ };
+
+ render();
+
+ const input = screen.getByRole('combobox');
+ await userEvent.click(input);
+ await userEvent.click(screen.getByRole('option', { name: 'Default' }));
+ expect(screen.queryByDisplayValue('Default')).toBeInTheDocument();
+
+ await userEvent.click(input);
+
+ expect(screen.getByRole('option', { name: 'Default' })).toHaveAttribute('aria-selected', 'true');
+ });
+
+ describe('size support', () => {
+ it('should require minWidth to be set with auto width', () => {
+ // @ts-expect-error
+ render();
+ });
+
+ it('should change width when typing things with auto width', async () => {
+ render();
+
+ const input = screen.getByRole('combobox');
+ const inputWrapper = screen.getByTestId('input-wrapper');
+ const initialWidth = getComputedStyle(inputWrapper).width;
+
+ fireEvent.change(input, { target: { value: 'very very long value' } });
+
+ const newWidth = getComputedStyle(inputWrapper).width;
+
+ expect(initialWidth).not.toBe(newWidth);
+ });
+
+ it('should not change width when typing things with fixed width', async () => {
+ render();
+ const input = screen.getByRole('combobox');
+
+ const inputWrapper = screen.getByTestId('input-wrapper');
+ const initialWidth = getComputedStyle(inputWrapper).width;
+
+ fireEvent.change(input, { target: { value: 'very very long value' } });
+
+ const newWidth = getComputedStyle(inputWrapper).width;
+
+ expect(initialWidth).toBe(newWidth);
+ });
+ });
+
describe('with a value already selected', () => {
it('shows an empty text input when opening the menu', async () => {
const selectedValue = options[0].value;
diff --git a/packages/grafana-ui/src/components/Combobox/Combobox.tsx b/packages/grafana-ui/src/components/Combobox/Combobox.tsx
index 3b70f8e3f78..c3940a7fed1 100644
--- a/packages/grafana-ui/src/components/Combobox/Combobox.tsx
+++ b/packages/grafana-ui/src/components/Combobox/Combobox.tsx
@@ -28,7 +28,10 @@ export type ComboboxOption = {
// TODO: It would be great if ComboboxOption["label"] was more generic so that if consumers do pass it in (for async),
// then the onChange handler emits ComboboxOption with the label as non-undefined.
interface ComboboxBaseProps
- extends Omit {
+ extends Pick<
+ InputProps,
+ 'placeholder' | 'autoFocus' | 'id' | 'aria-labelledby' | 'disabled' | 'loading' | 'invalid'
+ > {
/**
* An `X` appears in the UI, which clears the input and sets the value to `null`. Do not use if you have no `null` case.
*/
@@ -38,20 +41,31 @@ interface ComboboxBaseProps
*/
createCustomValue?: boolean;
options: Array> | ((inputValue: string) => Promise>>);
- onChange: (option: ComboboxOption | null) => void;
+ onChange: (option: ComboboxOption) => void;
/**
* Most consumers should pass value in as a scalar string | number. However, sometimes with Async because we don't
* have the full options loaded to match the value to, consumers may also pass in an Option with a label to display.
*/
- value: T | ComboboxOption | null;
+ value?: T | ComboboxOption | null;
/**
* Defaults to 100%. Number is a multiple of 8px. 'auto' will size the input to the content.
* */
width?: number | 'auto';
+ onBlur?: () => void;
}
const RECOMMENDED_ITEMS_AMOUNT = 100_000;
+type ClearableConditionals =
+ | {
+ isClearable: true;
+ /**
+ * The onChange handler is called with `null` when clearing the Combobox.
+ */
+ onChange: (option: ComboboxOption | null) => void;
+ }
+ | { isClearable?: false; onChange: (option: ComboboxOption) => void };
+
type AutoSizeConditionals =
| {
width: 'auto';
@@ -70,13 +84,16 @@ type AutoSizeConditionals =
maxWidth?: never;
};
-type ComboboxProps = ComboboxBaseProps & AutoSizeConditionals;
+type ComboboxProps = ComboboxBaseProps & AutoSizeConditionals & ClearableConditionals;
-function itemToString(item: ComboboxOption | null) {
- if (item?.label?.includes('Custom value: ')) {
- return item?.value.toString();
+function itemToString(item?: ComboboxOption | null) {
+ if (!item) {
+ return '';
}
- return item?.label ?? item?.value.toString() ?? '';
+ if (item.label?.includes('Custom value: ')) {
+ return item.value.toString();
+ }
+ return item.label ?? item.value.toString();
}
function itemFilter(inputValue: string) {
@@ -85,8 +102,8 @@ function itemFilter(inputValue: string) {
return (item: ComboboxOption) => {
return (
!inputValue ||
- item?.label?.toLowerCase().includes(lowerCasedInputValue) ||
- item?.value?.toString().toLowerCase().includes(lowerCasedInputValue)
+ item.label?.toLowerCase().includes(lowerCasedInputValue) ||
+ item.value?.toString().toLowerCase().includes(lowerCasedInputValue)
);
};
}
@@ -109,8 +126,14 @@ export const Combobox = (props: ComboboxProps) =>
createCustomValue = false,
id,
width,
+ minWidth,
+ maxWidth,
'aria-labelledby': ariaLabelledBy,
- ...restProps
+ autoFocus,
+ onBlur,
+ disabled,
+ loading,
+ invalid,
} = props;
// Value can be an actual scalar Value (string or number), or an Option (value + label), so
@@ -158,7 +181,7 @@ export const Combobox = (props: ComboboxProps) =>
return null;
}
- if (value === null) {
+ if (valueProp === undefined || valueProp === null) {
return null;
}
@@ -168,9 +191,13 @@ export const Combobox = (props: ComboboxProps) =>
}
return index;
- }, [options, value, isAsync]);
+ }, [valueProp, options, value, isAsync]);
const selectedItem = useMemo(() => {
+ if (valueProp === undefined || valueProp === null) {
+ return null;
+ }
+
if (selectedItemIndex !== null && !isAsync) {
return options[selectedItemIndex];
}
@@ -329,7 +356,9 @@ export const Combobox = (props: ComboboxProps) =>
const { inputRef, floatingRef, floatStyles, scrollRef } = useComboboxFloat(items, rowVirtualizer.range, isOpen);
- const InputComponent = width === 'auto' ? AutoSizeInput : Input;
+ const isAutoSize = width === 'auto';
+
+ const InputComponent = isAutoSize ? AutoSizeInput : Input;
const suffixIcon = asyncLoading
? 'spinner'
@@ -343,7 +372,13 @@ export const Combobox = (props: ComboboxProps) =>
return (
@@ -368,7 +403,6 @@ export const Combobox = (props: ComboboxProps) =>
>
}
- {...restProps}
{...getInputProps({
ref: inputRef,
/* Empty onCall to avoid TS error
diff --git a/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx b/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx
index b6899a4caa3..9e7b275c120 100644
--- a/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx
+++ b/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx
@@ -3,6 +3,7 @@ import * as React from 'react';
import { measureText } from '../../utils/measureText';
+import { AutoSizeInputContext } from './AutoSizeInputContext';
import { Input, Props as InputProps } from './Input';
export interface Props extends InputProps {
@@ -44,34 +45,38 @@ export const AutoSizeInput = React.forwardRef((props, r
}, [placeholder, value, minWidth, maxWidth]);
return (
- {
- if (onChange) {
- onChange(event);
- }
- setValue(event.currentTarget.value);
- }}
- width={inputWidth}
- onBlur={(event) => {
- if (onBlur) {
- onBlur(event);
- } else if (onCommitChange) {
- onCommitChange(event);
- }
- }}
- onKeyDown={(event) => {
- if (onKeyDown) {
- onKeyDown(event);
- } else if (event.key === 'Enter' && onCommitChange) {
- onCommitChange(event);
- }
- }}
- data-testid={'autosize-input'}
- />
+ // Used to tell Input to increase the width properly of the input to fit the text.
+ // See comment in Input.tsx for more details
+
+ {
+ if (onChange) {
+ onChange(event);
+ }
+ setValue(event.currentTarget.value);
+ }}
+ onBlur={(event) => {
+ if (onBlur) {
+ onBlur(event);
+ } else if (onCommitChange) {
+ onCommitChange(event);
+ }
+ }}
+ onKeyDown={(event) => {
+ if (onKeyDown) {
+ onKeyDown(event);
+ } else if (event.key === 'Enter' && onCommitChange) {
+ onCommitChange(event);
+ }
+ }}
+ width={inputWidth}
+ data-testid="autosize-input"
+ />
+
);
});
diff --git a/packages/grafana-ui/src/components/Input/AutoSizeInputContext.ts b/packages/grafana-ui/src/components/Input/AutoSizeInputContext.ts
new file mode 100644
index 00000000000..328e382895b
--- /dev/null
+++ b/packages/grafana-ui/src/components/Input/AutoSizeInputContext.ts
@@ -0,0 +1,6 @@
+import React from 'react';
+
+// Used to tell Input to increase the width properly of the input to fit the text.
+// See comment in Input.tsx for more details
+export const AutoSizeInputContext = React.createContext(false);
+AutoSizeInputContext.displayName = 'AutoSizeInputContext';
diff --git a/packages/grafana-ui/src/components/Input/Input.tsx b/packages/grafana-ui/src/components/Input/Input.tsx
index 8bd92e180d8..1240569cb57 100644
--- a/packages/grafana-ui/src/components/Input/Input.tsx
+++ b/packages/grafana-ui/src/components/Input/Input.tsx
@@ -1,5 +1,5 @@
import { css, cx } from '@emotion/css';
-import { forwardRef, HTMLProps, ReactNode } from 'react';
+import { forwardRef, HTMLProps, ReactNode, useContext } from 'react';
import useMeasure from 'react-use/lib/useMeasure';
import { GrafanaTheme2 } from '@grafana/data';
@@ -8,6 +8,8 @@ import { stylesFactory, useTheme2 } from '../../themes';
import { getFocusStyle, sharedInputStyle } from '../Forms/commonStyles';
import { Spinner } from '../Spinner/Spinner';
+import { AutoSizeInputContext } from './AutoSizeInputContext';
+
export interface Props extends Omit, 'prefix' | 'size'> {
/** Sets the width to a multiple of 8px. Should only be used with inline forms. Setting width of the container is preferred in other cases.*/
width?: number;
@@ -32,7 +34,17 @@ interface StyleDeps {
}
export const Input = forwardRef((props, ref) => {
- const { className, addonAfter, addonBefore, prefix, suffix, invalid, loading, width = 0, ...restProps } = props;
+ const {
+ className,
+ addonAfter,
+ addonBefore,
+ prefix,
+ suffix: suffixProp,
+ invalid,
+ loading,
+ width = 0,
+ ...restProps
+ } = props;
/**
* Prefix & suffix are positioned absolutely within inputWrapper. We use client rects below to apply correct padding to the input
* when prefix/suffix is larger than default (28px = 16px(icon) + 12px(left/right paddings)).
@@ -41,13 +53,24 @@ export const Input = forwardRef((props, ref) => {
const [prefixRef, prefixRect] = useMeasure();
const [suffixRef, suffixRect] = useMeasure();
+ // Yes, this is gross - When Input is being wrapped by AutoSizeInput, add the suffix/prefix width to the overall width
+ // so the text content is not clipped. The intention is to make all the input's text appear without overflow/clipping,
+ // which isn't normally how width is used in this component.
+ // This behaviour is not controlled via a prop so we can limit API surface, and remove this as a 'breaking change' later
+ // if a better solution is found.
+ const isInAutoSizeInput = useContext(AutoSizeInputContext);
+ const accessoriesWidth = (prefixRect.width || 0) + (suffixRect.width || 0);
+ const finalWidth = isInAutoSizeInput && width ? width + accessoriesWidth / 8 : width;
+
const theme = useTheme2();
- const styles = getInputStyles({ theme, invalid: !!invalid, width });
+
+ const styles = getInputStyles({ theme, invalid: !!invalid, width: finalWidth });
+
+ const suffix = suffixProp || (loading && );
return (
-
+
+ Evaluation groups are containers for evaluating alert and recording rules.
+
+
+
+
+ An evaluation group defines an evaluation interval - how often a rule is evaluated. Alert rules within
+ the same evaluation group are evaluated over the same evaluation interval.
+
+
+
+
+ Pending period specifies how long the threshold condition must be met before the alert starts firing.
+ This option helps prevent alerts from being triggered by temporary issues.
+
+
+ {t(
+ 'alerting.rule-form.folders.help-info',
+ 'Folders are used for storing alert rules. You can extend the access provided by a role to alert rules and assign permissions to individual folders.'
+ )}
+
+
+ {t(
+ 'alerting.rule-form.labels.help-info',
+ 'Labels are used to differentiate an alert from all other alerts.You can use them for searching, silencing, and routing notifications.'
+ )}
+