Compare commits

...

16 Commits

Author SHA1 Message Date
grafana-delivery-bot[bot]
c6c701cf5b [v11.5.x] TransformationFilter: Include transformation outputs in transformation filtering options (#99878)
TransformationFilter: Include transformation outputs in transformation filtering options (#98323)

* wip: include transformation output as filtering option

* add refId to joinByField transformation

* clean up

* add refId to transformations that create new data frames

* adjust duplicate query removal for filtering options

* refactor transformation input/output subscription effect

* adjust input data frame filtering logic to include transformations as input for debug view

* transformation filter can only filter on output of previous transformation

(cherry picked from commit a32eed1d13)

Co-authored-by: Sergej-Vlasov <37613182+Sergej-Vlasov@users.noreply.github.com>
2025-01-31 15:37:32 +00:00
grafana-delivery-bot[bot]
c42e00bdac [v11.5.x] CodeEditor: Fix cursor alignment (#99863)
CodeEditor: Fix cursor alignment (#99090)

* remeasure fonts once they've loaded

* add test mock

* fix unit test

* remeasure fonts after the editor has mounted just to be safe

(cherry picked from commit 8e59f618c1)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2025-01-31 15:37:16 +00:00
Brian Gann
f7a938db9a update whats-new 2025-01-24 21:45:00 -05:00
github-actions[bot]
b9513983ce apply security patch: v11.5.x/305-202501232115.patch
commit 874ce8d12caad3742857ca86d2da7d5f81f3f825
Author: Matt Jacobson <matthew.jacobson@grafana.com>
Date:   Thu Jan 23 16:14:28 2025 -0500

    linting

commit c4b6d9194cc8b79e252e562a27a2d09a42d7a5e8
Author: Matt Jacobson <matthew.jacobson@grafana.com>
Date:   Thu Jan 23 14:56:35 2025 -0500

    CVE-2024-11741 - victorops url
2025-01-24 20:37:56 +00:00
grafana-delivery-bot[bot]
df7987a6b8 [v11.5.x] docs: Fixes broken link in data source LBAC (#99497)
Some checks failed
publish-technical-documentation-release / sync (push) Has been cancelled
Sync to mirror / trigger_downstream_patch_mirror (push) Has been cancelled
Co-authored-by: Irene Rodríguez <irene.rodriguez@grafana.com>
Co-authored-by: Alex Bikfalvi <alex@bikfalvi.com>
Fixes broken link in data source LBAC (#99496)
2025-01-24 13:31:53 +02:00
Jack Baldry
56e82b7a1c [v11.5.x] Remove codespell (#99428)
Some checks are pending
publish-technical-documentation-release / sync (push) Waiting to run
Sync to mirror / trigger_downstream_patch_mirror (push) Waiting to run
2025-01-23 12:41:36 +00:00
grafana-delivery-bot[bot]
41e7cd2c02 [v11.5.x] LibraryPanel: Fallback to panel title if library panel title is not set (#99411)
LibraryPanel: Fallback to panel title if library panel title is not set (#99377)

(cherry picked from commit c862aa4d68)

Co-authored-by: Ivan Ortega Alba <ivanortegaalba@gmail.com>
2025-01-23 11:09:54 +02:00
grafana-delivery-bot[bot]
6167aac263 [v11.5.x] Docs: add user de-duplication description, update usage billing instructions (#99407)
Co-authored-by: Irene Rodríguez <irene.rodriguez@grafana.com>
Co-authored-by: Mitch Seaman <mjseaman@users.noreply.github.com>
2025-01-23 09:56:45 +02:00
grafana-delivery-bot[bot]
6ee1bfb736 [v11.5.x] docs>alert-rules>templates:example-fix (#99361)
Some checks are pending
publish-technical-documentation-release / sync (push) Waiting to run
Sync to mirror / trigger_downstream_patch_mirror (push) Waiting to run
docs>alert-rules>templates:example-fix (#99355)

* docs>alert-rules>templates:example-fix

* second-fix

* fixed 2 other label templates

(cherry picked from commit 88d5ae8153)

Co-authored-by: antonio <45235678+tonypowa@users.noreply.github.com>
2025-01-22 13:20:12 +02:00
grafana-delivery-bot[bot]
e0d8318caa [v11.5.x] Alerting docs: add Admonitions to Link Doc pages with practical Tutorials (#99324)
Some checks are pending
publish-technical-documentation-release / sync (push) Waiting to run
Sync to mirror / trigger_downstream_patch_mirror (push) Waiting to run
Alerting docs: add Admonitions to Link Doc pages with practical Tutorials (#99317)

Alerting docs: add admonitions to link to practical tutorials

(cherry picked from commit c7edbffd82)

Co-authored-by: Pepe Cano <825430+ppcano@users.noreply.github.com>
2025-01-21 15:31:25 +01:00
grafana-delivery-bot[bot]
842179916d [v11.5.x] Alerting docs: add instructions to execute notification template examples (#99304)
Some checks are pending
publish-technical-documentation-release / sync (push) Waiting to run
Sync to mirror / trigger_downstream_patch_mirror (push) Waiting to run
Alerting docs: add instructions to execute notification template examples (#99275)

(cherry picked from commit 9ce9ad1777)

Co-authored-by: Pepe Cano <825430+ppcano@users.noreply.github.com>
2025-01-21 10:49:22 +01:00
grafana-delivery-bot[bot]
d7d80d2634 [v11.5.x] Docs: Reporting restructure (#99289)
Some checks are pending
publish-technical-documentation-release / sync (push) Waiting to run
Sync to mirror / trigger_downstream_patch_mirror (push) Waiting to run
Co-authored-by: Jack Baldry <jack.baldry@grafana.com>
Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>
Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>
2025-01-20 14:32:03 -05:00
grafana-delivery-bot[bot]
fb0204c8df [v11.5.x] Azure: Improve resource request error handling (#99259)
Azure: Improve resource request error handling (#99017)

* Improve resource request error handling

- Correctly parse JSON responses
- Log erroneous failures in JSON marshalling/unmarshalling
- Correctly set response status code
- Do not attempt to use the response writer as it will be nil

* Minor change

* Improve type assertion handling

(cherry picked from commit f6194931f5)

Co-authored-by: Andreas Christou <andreas.christou@grafana.com>
2025-01-20 15:17:04 +02:00
grafana-delivery-bot[bot]
5f7f04518b [v11.5.x] Add Apache instructions (#99211)
Some checks failed
publish-technical-documentation-release / sync (push) Has been cancelled
Sync to mirror / trigger_downstream_patch_mirror (push) Has been cancelled
Co-authored-by: Joseph Perez <45749060+josmperez@users.noreply.github.com>
Co-authored-by: Jack Baldry <jack.baldry@grafana.com>
Co-authored-by: Irene Rodriguez <irene.rodriguez@grafana.com>
Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com>
Co-authored-by: Ronald McCollam <mccollam@gmail.com>
2025-01-17 12:11:50 -06:00
grafana-delivery-bot[bot]
5e369c6709 [v11.5.x] Docs: move DynamoDB to Unsupported list (#99195)
Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>
2025-01-17 10:05:55 -05:00
Mariell Hoversholm
940d71250b Security: Update to Go 1.23.5 - Backport to v11.5.x (#99122)
Some checks are pending
Sync to mirror / trigger_downstream_patch_mirror (push) Waiting to run
* feat: update to Go 1.23.5

* chore: make drone

* chore: make update-workspace

* fix: 1.23.5, not 1.23.4

* fix: update in Makefile

* CI: Force re-build
2025-01-17 09:30:36 +01:00
69 changed files with 934 additions and 684 deletions

View File

@@ -25,7 +25,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- ./bin/build verify-drone
@@ -75,7 +75,7 @@ steps:
- go install github.com/bazelbuild/buildtools/buildifier@latest
- buildifier --lint=warn -mode=check -r .
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: lint-starlark
trigger:
event:
@@ -427,7 +427,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -436,21 +436,21 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-jsonnet
- commands:
- apk add --update make
- make gen-go
depends_on:
- verify-gen-cue
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- apk add --update build-base shared-mime-info shared-mime-info-lang
- go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m
depends_on:
- wire-install
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: test-backend
- commands:
- apk add --update build-base
@@ -459,7 +459,7 @@ steps:
| grep -o '\(.*\)/' | sort -u)
depends_on:
- wire-install
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: test-backend-integration
trigger:
event:
@@ -513,7 +513,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- echo $(/usr/bin/github-app-external-token) > /github-app/token
@@ -558,16 +558,16 @@ steps:
- apk add --update make
- make gen-go
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- go run scripts/modowners/modowners.go check go.mod
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: validate-modfile
- commands:
- apk add --update make
- make swagger-validate
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: validate-openapi-spec
trigger:
event:
@@ -643,7 +643,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -653,7 +653,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -662,7 +662,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-jsonnet
- commands:
- yarn install --immutable || yarn install --immutable
@@ -698,7 +698,7 @@ steps:
- /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64
-a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu
-a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7
-a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.4 --yarn-cache=$$YARN_CACHE_FOLDER
-a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.5 --yarn-cache=$$YARN_CACHE_FOLDER
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.20.3
--tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{
.version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD
@@ -1096,7 +1096,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- echo $DRONE_RUNNER_NAME
@@ -1110,7 +1110,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -1119,14 +1119,14 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-jsonnet
- commands:
- apk add --update make
- make gen-go
depends_on:
- verify-gen-cue
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- dockerize -wait tcp://postgres:5432 -timeout 120s
@@ -1147,7 +1147,7 @@ steps:
GRAFANA_TEST_DB: postgres
PGPASSWORD: grafanatest
POSTGRES_HOST: postgres
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: postgres-integration-tests
- commands:
- dockerize -wait tcp://mysql80:3306 -timeout 120s
@@ -1168,7 +1168,7 @@ steps:
environment:
GRAFANA_TEST_DB: mysql
MYSQL_HOST: mysql80
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: mysql-8.0-integration-tests
- commands:
- dockerize -wait tcp://redis:6379 -timeout 120s
@@ -1184,7 +1184,7 @@ steps:
- wait-for-redis
environment:
REDIS_URL: redis://redis:6379/0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: redis-integration-tests
- commands:
- dockerize -wait tcp://memcached:11211 -timeout 120s
@@ -1200,7 +1200,7 @@ steps:
- wait-for-memcached
environment:
MEMCACHED_HOSTS: memcached:11211
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: memcached-integration-tests
- commands:
- dockerize -wait tcp://mimir_backend:8080 -timeout 120s
@@ -1216,7 +1216,7 @@ steps:
environment:
AM_TENANT_ID: test
AM_URL: http://mimir_backend:8080
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: remote-alertmanager-integration-tests
trigger:
event:
@@ -1273,11 +1273,6 @@ steps:
depends_on: []
image: node:22.11.0-alpine
name: yarn-install
- commands:
- pip3 install codespell
- codespell -I docs/.codespellignore docs/
image: python:3.8
name: codespell
- commands:
- yarn run prettier:checkDocs
depends_on:
@@ -1303,7 +1298,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
trigger:
event:
@@ -1424,7 +1419,7 @@ steps:
&& return 1; fi
depends_on:
- clone-enterprise
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: swagger-gen
trigger:
event:
@@ -1529,7 +1524,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -1540,7 +1535,7 @@ steps:
- CODEGEN_VERIFY=1 make gen-cue
depends_on:
- clone-enterprise
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -1550,14 +1545,14 @@ steps:
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on:
- clone-enterprise
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-jsonnet
- commands:
- apk add --update make
- make gen-go
depends_on:
- verify-gen-cue
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- apk add --update build-base
@@ -1565,7 +1560,7 @@ steps:
- go test -v -run=^$ -benchmem -timeout=1h -count=8 -bench=. ${GO_PACKAGES}
depends_on:
- wire-install
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: sqlite-benchmark-integration-tests
- commands:
- apk add --update build-base
@@ -1577,7 +1572,7 @@ steps:
GRAFANA_TEST_DB: postgres
PGPASSWORD: grafanatest
POSTGRES_HOST: postgres
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: postgres-benchmark-integration-tests
- commands:
- apk add --update build-base
@@ -1588,7 +1583,7 @@ steps:
environment:
GRAFANA_TEST_DB: mysql
MYSQL_HOST: mysql80
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: mysql-8.0-benchmark-integration-tests
trigger:
event:
@@ -1635,11 +1630,6 @@ steps:
depends_on: []
image: node:22.11.0-alpine
name: yarn-install
- commands:
- pip3 install codespell
- codespell -I docs/.codespellignore docs/
image: python:3.8
name: codespell
- commands:
- yarn run prettier:checkDocs
depends_on:
@@ -1665,7 +1655,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
trigger:
branch: main
@@ -1838,7 +1828,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -1847,21 +1837,21 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-jsonnet
- commands:
- apk add --update make
- make gen-go
depends_on:
- verify-gen-cue
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- apk add --update build-base shared-mime-info shared-mime-info-lang
- go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m
depends_on:
- wire-install
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: test-backend
- commands:
- apk add --update build-base
@@ -1870,7 +1860,7 @@ steps:
| grep -o '\(.*\)/' | sort -u)
depends_on:
- wire-install
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: test-backend-integration
trigger:
branch: main
@@ -1915,22 +1905,22 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- apk add --update make
- make gen-go
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- go run scripts/modowners/modowners.go check go.mod
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: validate-modfile
- commands:
- apk add --update make
- make swagger-validate
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: validate-openapi-spec
- commands:
- ./bin/build verify-drone
@@ -2062,7 +2052,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -2072,7 +2062,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -2081,7 +2071,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-jsonnet
- commands:
- yarn install --immutable || yarn install --immutable
@@ -2116,7 +2106,7 @@ steps:
- /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64
-a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu
-a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7
-a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.4 --yarn-cache=$$YARN_CACHE_FOLDER
-a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.5 --yarn-cache=$$YARN_CACHE_FOLDER
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.20.3
--tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{
.version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD
@@ -2591,7 +2581,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- echo $DRONE_RUNNER_NAME
@@ -2605,7 +2595,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -2614,14 +2604,14 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-jsonnet
- commands:
- apk add --update make
- make gen-go
depends_on:
- verify-gen-cue
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- dockerize -wait tcp://postgres:5432 -timeout 120s
@@ -2642,7 +2632,7 @@ steps:
GRAFANA_TEST_DB: postgres
PGPASSWORD: grafanatest
POSTGRES_HOST: postgres
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: postgres-integration-tests
- commands:
- dockerize -wait tcp://mysql80:3306 -timeout 120s
@@ -2663,7 +2653,7 @@ steps:
environment:
GRAFANA_TEST_DB: mysql
MYSQL_HOST: mysql80
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: mysql-8.0-integration-tests
- commands:
- dockerize -wait tcp://redis:6379 -timeout 120s
@@ -2679,7 +2669,7 @@ steps:
- wait-for-redis
environment:
REDIS_URL: redis://redis:6379/0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: redis-integration-tests
- commands:
- dockerize -wait tcp://memcached:11211 -timeout 120s
@@ -2695,7 +2685,7 @@ steps:
- wait-for-memcached
environment:
MEMCACHED_HOSTS: memcached:11211
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: memcached-integration-tests
- commands:
- dockerize -wait tcp://mimir_backend:8080 -timeout 120s
@@ -2711,7 +2701,7 @@ steps:
environment:
AM_TENANT_ID: test
AM_URL: http://mimir_backend:8080
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: remote-alertmanager-integration-tests
trigger:
branch: main
@@ -2970,7 +2960,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -2979,21 +2969,21 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-jsonnet
- commands:
- apk add --update make
- make gen-go
depends_on:
- verify-gen-cue
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- apk add --update build-base shared-mime-info shared-mime-info-lang
- go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m
depends_on:
- wire-install
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: test-backend
- commands:
- apk add --update build-base
@@ -3002,7 +2992,7 @@ steps:
| grep -o '\(.*\)/' | sort -u)
depends_on:
- wire-install
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: test-backend-integration
trigger:
branch:
@@ -3045,22 +3035,22 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- apk add --update make
- make gen-go
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- go run scripts/modowners/modowners.go check go.mod
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: validate-modfile
- commands:
- apk add --update make
- make swagger-validate
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: validate-openapi-spec
trigger:
branch:
@@ -3139,7 +3129,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- echo $DRONE_RUNNER_NAME
@@ -3153,7 +3143,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -3162,14 +3152,14 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-jsonnet
- commands:
- apk add --update make
- make gen-go
depends_on:
- verify-gen-cue
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- dockerize -wait tcp://postgres:5432 -timeout 120s
@@ -3190,7 +3180,7 @@ steps:
GRAFANA_TEST_DB: postgres
PGPASSWORD: grafanatest
POSTGRES_HOST: postgres
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: postgres-integration-tests
- commands:
- dockerize -wait tcp://mysql80:3306 -timeout 120s
@@ -3211,7 +3201,7 @@ steps:
environment:
GRAFANA_TEST_DB: mysql
MYSQL_HOST: mysql80
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: mysql-8.0-integration-tests
- commands:
- dockerize -wait tcp://redis:6379 -timeout 120s
@@ -3227,7 +3217,7 @@ steps:
- wait-for-redis
environment:
REDIS_URL: redis://redis:6379/0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: redis-integration-tests
- commands:
- dockerize -wait tcp://memcached:11211 -timeout 120s
@@ -3243,7 +3233,7 @@ steps:
- wait-for-memcached
environment:
MEMCACHED_HOSTS: memcached:11211
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: memcached-integration-tests
- commands:
- dockerize -wait tcp://mimir_backend:8080 -timeout 120s
@@ -3259,7 +3249,7 @@ steps:
environment:
AM_TENANT_ID: test
AM_URL: http://mimir_backend:8080
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: remote-alertmanager-integration-tests
trigger:
branch:
@@ -3359,7 +3349,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- ./bin/build artifacts docker fetch --edition oss
@@ -3491,7 +3481,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- ./bin/build artifacts docker fetch --edition oss
@@ -3632,7 +3622,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- ./bin/build artifacts packages --artifacts-editions=oss --tag $${DRONE_TAG} --src-bucket
@@ -3724,7 +3714,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- yarn install --immutable || yarn install --immutable
@@ -3824,7 +3814,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- depends_on:
- compile-build-cmd
@@ -3921,7 +3911,7 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- ./bin/build publish grafana-com --edition oss ${DRONE_TAG}
@@ -3983,7 +3973,7 @@ steps:
from_secret: grafana_api_key
GCP_KEY_BASE64:
from_secret: gcp_key_base64
GO_VERSION: 1.23.4
GO_VERSION: 1.23.5
GPG_PASSPHRASE:
from_secret: packages_gpg_passphrase
GPG_PRIVATE_KEY:
@@ -4058,7 +4048,7 @@ steps:
from_secret: grafana_api_key
GCP_KEY_BASE64:
from_secret: gcp_key_base64
GO_VERSION: 1.23.4
GO_VERSION: 1.23.5
GPG_PASSPHRASE:
from_secret: packages_gpg_passphrase
GPG_PRIVATE_KEY:
@@ -4115,13 +4105,13 @@ steps:
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: compile-build-cmd
- commands:
- ./bin/build whatsnew-checker
depends_on:
- compile-build-cmd
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: whats-new-checker
trigger:
event:
@@ -4220,7 +4210,7 @@ steps:
from_secret: grafana_api_key
GCP_KEY_BASE64:
from_secret: gcp_key_base64
GO_VERSION: 1.23.4
GO_VERSION: 1.23.5
GPG_PASSPHRASE:
from_secret: packages_gpg_passphrase
GPG_PRIVATE_KEY:
@@ -4371,7 +4361,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -4380,21 +4370,21 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-jsonnet
- commands:
- apk add --update make
- make gen-go
depends_on:
- verify-gen-cue
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- apk add --update build-base shared-mime-info shared-mime-info-lang
- go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m
depends_on:
- wire-install
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: test-backend
- commands:
- apk add --update build-base
@@ -4403,7 +4393,7 @@ steps:
| grep -o '\(.*\)/' | sort -u)
depends_on:
- wire-install
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: test-backend-integration
trigger:
cron:
@@ -4457,7 +4447,7 @@ steps:
from_secret: grafana_api_key
GCP_KEY_BASE64:
from_secret: gcp_key_base64
GO_VERSION: 1.23.4
GO_VERSION: 1.23.5
GPG_PASSPHRASE:
from_secret: packages_gpg_passphrase
GPG_PRIVATE_KEY:
@@ -4601,7 +4591,7 @@ steps:
from_secret: grafana_api_key
GCP_KEY_BASE64:
from_secret: gcp_key_base64
GO_VERSION: 1.23.4
GO_VERSION: 1.23.5
GPG_PASSPHRASE:
from_secret: packages_gpg_passphrase
GPG_PRIVATE_KEY:
@@ -4708,7 +4698,7 @@ steps:
- export GITHUB_TOKEN=$(cat /github-app/token)
- 'dagger run --silent /src/grafana-build artifacts -a $${ARTIFACTS} --grafana-ref=$${GRAFANA_REF}
--enterprise-ref=$${ENTERPRISE_REF} --grafana-repo=$${GRAFANA_REPO} --version=$${VERSION} '
- --go-version=1.23.4
- --go-version=1.23.5
depends_on:
- github-app-generate-token
environment:
@@ -4729,7 +4719,7 @@ steps:
from_secret: grafana_api_key
GCP_KEY_BASE64:
from_secret: gcp_key_base64
GO_VERSION: 1.23.4
GO_VERSION: 1.23.5
GPG_PASSPHRASE:
from_secret: packages_gpg_passphrase
GPG_PRIVATE_KEY:
@@ -4863,7 +4853,7 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-cue
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-cue
- commands:
- '# It is required that generated jsonnet is committed and in sync with its inputs.'
@@ -4872,14 +4862,14 @@ steps:
- apk add --update make
- CODEGEN_VERIFY=1 make gen-jsonnet
depends_on: []
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: verify-gen-jsonnet
- commands:
- apk add --update make
- make gen-go
depends_on:
- verify-gen-cue
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: wire-install
- commands:
- dockerize -wait tcp://postgres:5432 -timeout 120s
@@ -4900,7 +4890,7 @@ steps:
GRAFANA_TEST_DB: postgres
PGPASSWORD: grafanatest
POSTGRES_HOST: postgres
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: postgres-integration-tests
- commands:
- dockerize -wait tcp://mysql80:3306 -timeout 120s
@@ -4921,7 +4911,7 @@ steps:
environment:
GRAFANA_TEST_DB: mysql
MYSQL_HOST: mysql80
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: mysql-8.0-integration-tests
- commands:
- dockerize -wait tcp://redis:6379 -timeout 120s
@@ -4937,7 +4927,7 @@ steps:
- wait-for-redis
environment:
REDIS_URL: redis://redis:6379/0
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: redis-integration-tests
- commands:
- dockerize -wait tcp://memcached:11211 -timeout 120s
@@ -4953,7 +4943,7 @@ steps:
- wait-for-memcached
environment:
MEMCACHED_HOSTS: memcached:11211
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: memcached-integration-tests
- commands:
- dockerize -wait tcp://mimir_backend:8080 -timeout 120s
@@ -4969,7 +4959,7 @@ steps:
environment:
AM_TENANT_ID: test
AM_URL: http://mimir_backend:8080
image: golang:1.23.4-alpine
image: golang:1.23.5-alpine
name: remote-alertmanager-integration-tests
trigger:
event:
@@ -5272,7 +5262,7 @@ steps:
- commands:
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM docker:27-cli
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine/git:2.40.1
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.23.4-alpine
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.23.5-alpine
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:22.11.0-alpine
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:22-bookworm
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM google/cloud-sdk:431.0.0
@@ -5310,7 +5300,7 @@ steps:
- commands:
- trivy --exit-code 1 --severity HIGH,CRITICAL docker:27-cli
- trivy --exit-code 1 --severity HIGH,CRITICAL alpine/git:2.40.1
- trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.23.4-alpine
- trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.23.5-alpine
- trivy --exit-code 1 --severity HIGH,CRITICAL node:22.11.0-alpine
- trivy --exit-code 1 --severity HIGH,CRITICAL node:22-bookworm
- trivy --exit-code 1 --severity HIGH,CRITICAL google/cloud-sdk:431.0.0
@@ -5579,6 +5569,6 @@ kind: secret
name: gcr_credentials
---
kind: signature
hmac: bdf4cd3767ad13e5a8d7221731676e8ee1991dae0da9ae2cee92087d5c660ece
hmac: 154c1f072294cef09ba4f579eed30b1e29bc5c263897c862f4574432d321502c
...

View File

@@ -2,7 +2,7 @@
ARG BASE_IMAGE=alpine:3.20
ARG JS_IMAGE=node:22-alpine
ARG JS_PLATFORM=linux/amd64
ARG GO_IMAGE=golang:1.23.1-alpine
ARG GO_IMAGE=golang:1.23.5-alpine
ARG GO_SRC=go-builder
ARG JS_SRC=js-builder

View File

@@ -8,7 +8,7 @@ WIRE_TAGS = "oss"
include .bingo/Variables.mk
GO = go
GO_VERSION = 1.23.4
GO_VERSION = 1.23.5
GO_LINT_FILES ?= $(shell ./scripts/go-workspace/golangci-lint-includes.sh)
GO_TEST_FILES ?= $(shell ./scripts/go-workspace/test-includes.sh)
SH_FILES ?= $(shell find ./scripts -name *.sh)

View File

@@ -1,6 +1,6 @@
module github.com/grafana/grafana/apps/alerting/notifications
go 1.23.4
go 1.23.5
replace github.com/grafana/grafana => ../../..

View File

@@ -1,13 +0,0 @@
aks
eror
geomap
Geomap
grafanalib
grafonnet
iam
Jsonnet
[Operato Windrose](https://grafana.com/grafana/plugins/operato-windrose-panel/)
runbook
sergent
sparkline
wan

View File

@@ -95,4 +95,4 @@ To set up LBAC for data sources for a data source, refer to [Configure LBAC for
## Provisioning of LBAC rules
We recommend using our Terraform provider to set up provisioning for [Resource data source config LBAC rules] (https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/data_source_config_lbac_rules). Refer to our provider documentation to learn how to configure rules for a data source.
We recommend using our Terraform provider to set up provisioning for [Resource data source config LBAC rules](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/data_source_config_lbac_rules). Refer to our provider documentation to learn how to configure rules for a data source.

View File

@@ -256,14 +256,24 @@ The system creates a session when a user signs in to Grafana from a new device,
When a user reaches the session limit, the fourth connection succeeds and the longest inactive session is signed out.
### Request usage billing
You can request Grafana Labs to activate usage billing which allows an unlimited number of active users. When usage billing is enabled, Grafana does not enforce active user limits or display warning banners. Instead, you are charged for active users that exceed the limit, according to your customer contract.
Usage billing involves a contractual agreement between you and Grafana Labs, and it is only available if Grafana Enterprise is configured to [automatically refresh its license token]({{< relref "../../setup-grafana/configure-grafana/enterprise-configuration/#auto_refresh_license" >}}).
### Request a change to your license
To increase the number of licensed users within Grafana, extend a license, or change your licensed URL, contact [Grafana support](/profile/org#support) or your Grafana Labs account team. They will update your license, which you can activate from within Grafana.
For instructions about how to activate your license after it is updated, refer to [Activate an Enterprise license]({{< relref "#activate-an-enterprise-license" >}}).
## Usage billing
Standard Grafana Enterprise licenses include a certain number of seats that can be used, and prevent more users logging into Grafana than have been licensed. This makes sense if you prefer a predictable bill. It can however be a problem if you anticipate uneven usage patterns over time or when it's critical that no user ever be prevented from logging into Grafana due to capacity constraints.
For those use-cases we support usage-based billing, where your license includes a certain number of included users and you are billed on a monthly basis for any excess active users during the month.
Usage billing involves a contractual agreement between you and Grafana Labs and an update to your license, and it is only available if Grafana Enterprise version 10.0.0 or higher is configured to [automatically refresh its license token]({{< relref "../../setup-grafana/configure-grafana/enterprise-configuration/#auto_refresh_license" >}}).
### User deduplication
If your organization has multiple Grafana Enterprise instances with usage billing enabled, then each active user counts only once toward your license, regardless of how many instances that user signs into. Each Grafana Enterprise instance submits a hashed list of users to Grafana Labs via API every day. Each user email address or anonymous device ID is hashed using a one-way sha256 algorithm, and submitted to Grafana where the hashed users are deduplicated across instances.
### Request usage billing
To request usage billing, contact your Grafana Labs account team or [submit a support ticket](https://grafana.com/profile/org#support).

View File

@@ -218,3 +218,7 @@ For further details on how to template alert rules, refer to:
- [Annotation and label template reference](ref:alert-rule-template-reference)
- [Annotation and label examples](ref:alert-rule-template-examples)
{{< admonition type="tip" >}}
For a practical example of templating, refer to our [Getting Started with Templating tutorial](https://grafana.com/tutorials/alerting-get-started-pt4/).
{{< /admonition >}}

View File

@@ -193,15 +193,11 @@ For additional functions to display or format data, refer to:
Heres an example of creating a `severity` label based on a query value:
```go
{{ if (gt $values.A.Value 90.0) -}}
critical
{{ else if (gt $values.A.Value 80.0) -}}
high
{{ else if (gt $values.A.Value 60.0) -}}
medium
{{ else -}}
low
{{- end }}
{{- if (gt $values.A.Value 90.0) -}}critical
{{- else if (gt $values.A.Value 80.0) -}}high
{{- else if (gt $values.A.Value 60.0) -}}medium
{{- else -}}low
{{- end -}}
```
In this example, the `severity` label is determined by the query value:
@@ -222,10 +218,13 @@ You should avoid displaying query values in labels, as this may create many aler
You can use labels to differentiate alerts coming from various environments (e.g., production, staging, dev). For example, you may want to add a label that sets the environment based on the instances label. Heres how you can template it:
```go
{{ if eq $labels.instance "prod-server-1" }}production
{{ else if eq $labels.instance "staging-server-1" }}staging
{{ else }}development
{{ end }}
{{- if eq $labels.instance "prod-server-1" -}}
production
{{- else if eq $labels.instance "staging-server-1" -}}
staging
{{- else -}}
development
{{- end -}}
```
This would print:
@@ -237,10 +236,13 @@ This would print:
To make this template more flexible, you can use a regular expression that matches the instance name with the instance name prefix using the [`match()`](ref:reference-match) function:
```go
{{ if match "^prod-server-.*" $labels.instance }}production
{{ else if match "^staging-server-.*" $labels.instance}}staging
{{ else }}development
{{ end }}
{{- if match "^prod-server-.*" $labels.instance -}}
production
{{- else if match "^staging-server-.*" $labels.instance -}}
staging
{{- else -}}
development
{{- end -}}
```
{{< collapse title="Legacy Alerting templates" >}}

View File

@@ -108,3 +108,7 @@ For further details on how to write notification templates, refer to:
- [Select, create, and preview a notification template](ref:manage-notification-templates)
- [Notification template reference](ref:reference)
- [Notification template examples](ref:examples)
{{< admonition type="tip" >}}
For a practical example of templating, refer to our [Getting Started with Templating tutorial](https://grafana.com/tutorials/alerting-get-started-pt4/).
{{< /admonition >}}

View File

@@ -111,6 +111,12 @@ The name of the alert is {{ .Labels.alertname }}
{{ end }}
```
You can then use the template by passing the [notification data (dot `.`)](ref:reference-notification-data):
```go
{{ template "custom_message" . }}
```
```template_output
The name of the alert is InstanceDown
@@ -140,7 +146,11 @@ In this example:
- A template (`alert.summary_and_description`) is defined to print the `summary`, `status`, and `description` of one [alert](ref:reference-alert).
- The main template `custom.alerts` iterates the list of alerts (`.Alerts`) in [notification data](ref:reference-notification-data), executing the `alert.summary_and_description` template to print the details of each alert.
The notification message would look like this:
Execute the template by passing the dot (`.`):
```go
{{ template "custom.alerts" . }}
```
```template_output
2 alert(s)
@@ -178,7 +188,11 @@ The following example is similar to the previous one, but it separates firing an
Instead of `.Alerts`, the template accesses `.Alerts.Firing` and `.Alerts.Resolved` separately to print details for each alert.
The output might now look like this:
Run the template by passing the dot (`.`):
```go
{{ template "custom.firing_and_resolved_alerts" . }}
```
```template_output
1 resolved alert(s)
@@ -215,6 +229,12 @@ Common annotations: {{ len .CommonAnnotations.SortedPairs }}
Note that `.CommonAnnotations` and `.CommonLabels` are part of [notification data](ref:reference-notification-data).
Execute the template by passing the dot (`.`) as argument:
```go
{{ template "custom.common_labels_and_annotations" . }}
```
```template_output
1 resolved alert(s)
1 firing alert(s)
@@ -257,6 +277,12 @@ In this example:
- The `custom.alert_labels_and_annotations` template iterates over the list of resolved and firing alerts, similar to previous examples. It then executes `alert.labels_and_annotations` for each alert.
- The `alert.labels_and_annotations` template prints all the alert labels and annotations by accessing `.Labels.SortedPairs` and `.Annotations.SortedPairs`.
Run the template by passing the dot (`.`):
```go
{{ template "custom.alert_labels_and_annotations" . }}
```
```template_output
1 resolved alert(s)
@@ -307,7 +333,11 @@ Note that the following example works only for Grafana-managed alerts. It displa
{{ end -}}
```
The output of this template looks like this:
Pass the dot (`.`) to execute the template:
```go
{{ template "custom.alert_additional_details" . }}
```
```template_output
1 resolved alert(s)
@@ -339,6 +369,12 @@ Heres a basic example that displays the number of firing and resolved alerts
{{ end -}}
```
Execute the template by passing the dot (`.`) as argument:
```go
{{ template "custom_title" . }}
```
```template_output
🚨 1 firing alerts. ✅ 1 resolved alerts.
```
@@ -357,6 +393,12 @@ This is a more advanced example:
- Outputs `.GroupLabels`, the labels used to [group multiple alerts in one notification](ref:group-alert-notifications).
- Prints `CommonLabels`, excluding labels in `.GroupLabels`.
Execute the template by passing the dot (`.`):
```go
{{ template "copy_of_default_title" . }}
```
```template_output
[FIRING:1, RESOLVED:1] api warning (sql_db)
```

View File

@@ -86,6 +86,12 @@ Here's an example that prints all available notification data from dot (`.`):
{{ end }}
```
You can execute this template by passing the dot (`.`):
```go
{{ template "custom_template" . }}
```
## Alert
`Alert` contains data for an individual alert:
@@ -133,6 +139,12 @@ This example iterates over the list of firing and resolved alerts (`.Alerts`) in
{{ end }}
```
You can run this template by passing the dot (`.`):
```go
{{ template "custom_template" . }}
```
## KV
`KV` is a set of key value pairs, where each key and value is a string.

View File

@@ -91,7 +91,7 @@ Note that `No Data` and `Error` states are supported only for Grafana-managed al
{{< docs/shared lookup="alerts/table-configure-no-data-and-error.md" source="grafana" version="<GRAFANA_VERSION>" >}}
Note that when you configure the **No Data** or **Error** behavior to `Alerting` or `Normal`, Grafana attempts to keep a stable set of fields under notification `Values`. If your query returns no data or an error, Grafana re-uses the latest known set of fields in `Values`, but will use `-1` in place of the measured value.
Note that when you configure the **No Data** or **Error** behavior to `Alerting` or `Normal`, Grafana attempts to keep a stable set of fields under notification `Values`. If your query returns no data or an error, Grafana reuses the latest known set of fields in `Values`, but will use `-1` in place of the measured value.
### Reduce `No Data` or `Error` alerts

View File

@@ -35,6 +35,10 @@ refs:
Grouping in Grafana Alerting allows you to batch relevant alerts together into a smaller number of notifications. This is particularly important if notifications are delivered to first-responders, such as engineers on-call, where receiving lots of notifications in a short period of time can be overwhelming. In some cases, it can negatively impact a first-responders ability to respond to an incident. For example, consider a large outage where many of your systems are down. In this case, grouping can be the difference between receiving 1 phone call and 100 phone calls.
{{< admonition type="tip" >}}
For a practical example of grouping, refer to our [Getting Started with Grouping tutorial](https://grafana.com/tutorials/alerting-get-started-pt3/).
{{< /admonition >}}
## Group notifications
Grouping combines similar alert instances within a specific period into a single notification, reducing alert noise.

View File

@@ -75,6 +75,10 @@ Each policy consists of a set of label matchers (0 or more) that specify which a
{{< figure src="/media/docs/alerting/notification-routing.png" max-width="750px" caption="Matching alert instances with notification policies" alt="Example of a notification policy tree" >}}
{{< admonition type="tip" >}}
For a practical example of routing with notification policies, refer to our [Getting Started with Alert Instances and Notification Routing tutorial](https://grafana.com/tutorials/alerting-get-started-pt4/).
{{< /admonition >}}
## Routing
To determine which notification policies handle an alert instance, the system looks for matching policies starting from the top of the tree—beginning with the default notification policy.

View File

@@ -69,6 +69,10 @@ In Grafana, you have various options to template your alert notification message
- Template notifications when you want to customize the appearance and information of your notifications.
- Avoid using notification templates to add extra information to alert instances—use annotations instead.
{{< admonition type="tip" >}}
For a practical example of templating, refer to our [Getting Started with Templating tutorial](https://grafana.com/tutorials/alerting-get-started-pt4/).
{{< /admonition >}}
This diagram illustrates the entire templating process, from querying labels and templating the alert summary and notification to the final alert notification message.
{{< figure src="/media/docs/alerting/how-notification-templates-works.png" max-width="1200px" caption="How templating works" >}}

View File

@@ -0,0 +1,351 @@
---
aliases:
- ../administration/reports/ # /docs/grafana/latest/administration/reports/
- ../enterprise/export-pdf/ # /docs/grafana/latest/enterprise/export-pdf/
- ../enterprise/reporting/ # /docs/grafana/latest/enterprise/reporting/
- ../panels/create-reports/ # /docs/grafana/latest/panels/create-reports/
- reporting/ # /docs/grafana/latest/dashboards/reporting/
keywords:
- grafana
- reporting
- export
- pdf
labels:
products:
- cloud
- enterprise
menuTitle: Reporting
title: Create and manage reports
description: Generate and share PDF reports from your Grafana dashboards
weight: 600
refs:
repeat-panels-or-rows:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-panel-options/#configure-repeating-panels
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-panel-options/#configure-repeating-panels
http-apis:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/developers/http_api/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/developers/http_api/
image-rendering:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/image-rendering/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/image-rendering/
rbac:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/
permission:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/
role-based-access-control:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/
configuration:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/
image-rendering:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/image-rendering/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/image-rendering/
templates-and-variables:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/variables/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/dashboards/variables/
grafana-enterprise:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/introduction/grafana-enterprise/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/introduction/grafana-enterprise/
configuration:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#filters
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#filters
time-range-controls:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/use-dashboards/#set-dashboard-time-range
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/dashboards/use-dashboards/#set-dashboard-time-range
send-report:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/developers/http_api/reporting/#send-a-report
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/developer-resources/api-reference/http-api/reporting/#send-a-report
smtp:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#smtp
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#smtp
temp-data-lifetime:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#temp_data_lifetime
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#temp_data_lifetime
rendering-configuration:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/create-reports/report-settings/#rendering-configuration
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/create-reports/report-settings/#rendering-configuration
reporting-configuration:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/enterprise-configuration/#reporting
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/enterprise-configuration/#reporting
---
# Create and manage reports
**Reporting** allows you to automatically generate PDFs from any of your dashboards and have Grafana email them to interested parties on a schedule. This is available in Grafana Cloud and in Grafana Enterprise.
{{< admonition type="note" >}}
If you have [Role-based access control](ref:role-based-access-control) enabled, for some actions you would need to have relevant permissions.
Refer to specific guides to understand what permissions are required.
{{< /admonition >}}
Any changes you make to a dashboard used in a report are reflected the next time the report is sent. For example, if you change the time range in the dashboard, then the time range in the report also changes, unless you've configured a custom time range.
## Requirements
- SMTP must be configured for reports to be sent. Refer to [SMTP](ref:smtp) in [Configuration](ref:configuration) for more information.
- The Image Renderer plugin must be installed or the remote rendering service must be set up. Refer to [Image rendering](ref:image-rendering) for more information.
### Rendering configuration
When generating reports, each panel renders separately before being collected in a PDF. You can configure the per-panel rendering timeout and number of concurrently rendered panels.
To make a panel more legible, you can set a scale factor for the rendered images. However, a higher scale factor increases the file size of the generated PDF.
You can also specify custom fonts that support different Unicode scripts. The DejaVu font is the default used for PDF rendering.
These options are available in the [reporting configuration](ref:reporting-configuration) of the `ini` file for Enterprise Grafana.
## Access control
When [RBAC](ref:rbac) is enabled, you need to have the relevant [Permissions](ref:permission) to create and manage reports.
## Create a report
Only organization administrators can create reports by default. You can customize who can create reports with [Role-based access control](ref:role-based-access-control).
The report creation process is multi-step, but you don't need to complete these steps in order and you can skip steps by clicking a step name at the top of the page:
![Reporting wizard](/media/docs/grafana/dashboards/screenshot-reporting-wizard-v11.5.png)
To create a report, follow these steps:
1. Click **Dashboards > Reporting** in the main menu.
1. Click **+ Create a new report**.
1. Complete the report steps, as needed; you don't need to complete these steps in order and you can skip steps by clicking a step name at the top of the page:
- [Select dashboard](#1-select-dashboard)
- [Format report](#2-format-report)
- [Schedule](#3-schedule)
- [Share](#4-share)
- [Confirm](#5-confirm)
1. Click one of the following buttons in the top-right corner of the screen:
- **Send now** or **Schedule send** - The report is sent according the schedule you've set.
- **Save as draft** - You can save a draft at any point during the report creation or update process, even if it's missing required fields. The report won't be sent according to its schedule while it's a draft.
- **Discard** - Delete the report draft. This action can't be reversed.
### 1. Select dashboard
At this step, select the dashboard or dashboards on which the report is based, as well as the variables and time range for those dashboards. The options are:
<!-- prettier-ignore-start -->
| Option | Description |
| ------ | ----------- |
| Source dashboard | Select the dashboard from which you want to generate the report. |
| [Template variables (optional)](#template-variables) | Select the variable values for the selected dashboard. This option is only displayed if the dashboard has variables. |
| [Time range (optional)](#time-range) | By default, reports use the saved time range of the dashboard. Optionally, you can change the time range of the report. |
| Add another dashboard | Add more dashboards to the report. Additional dashboards will be rendered as new pages in the same PDF file, or additional images if you chose to embed images in your report email. You can't add the same dashboard to a report multiple times. |
<!-- prettier-ignore-end -->
#### Template variables
This option is only displayed if the dashboard has variables.
You can configure report-specific template variables for the dashboard on the report page. The variables that you select will override the variables from the dashboard, and they are used when rendering a PDF file of the report. For detailed information about using template variables, refer to the [Templates and variables](ref:templates-and-variables) section.
The query variables saved with a report might become out of date if the results of that query change. For example, if your template variable queries for a list of hostnames and a new hostname is added, then it will not be included in the report. If that occurs, the selected variables must be manually updated in the report. If you select the **All** value for the template variable or if you keep the dashboard's original variable selection, then the report stays up-to-date as new values are added.
#### Time range
By default, reports use the saved time range of the dashboard. Optionally, you can change the time range of the report by:
- Saving a modified time range to the dashboard. Changing the dashboard time range without saving it doesn't change the time zone of the report.
- Setting a time range via the **Time range** field in the report form. If specified, the custom time range overrides the time range from the report's dashboard.
The page header of the report displays the time range for the dashboard's data queries.
##### Report time zones
Reports use the time zone of the dashboard from which theyre generated. You can control the time zone for your reports by setting the dashboard to a specific time zone. Note that this affects the display of the dashboard for all users.
If a dashboard has the **Browser Time** setting, the reports generated from that dashboard use the time zone of the Grafana server. As a result, this time zone might not match the time zone of users creating or receiving the report.
If the time zone is set differently between your Grafana server and its remote image renderer, then the time ranges in the report might be different between the page header and the time axes in the panels. To avoid this, set the time zone to UTC for dashboards when using a remote renderer. Each dashboard's time zone setting is visible in the [time range controls](ref:time-range-controls).
### 2. Format report
At this step, set the report formatting options. You can select multiple options:
- [Attach the report as a PDF](#attach-the-report-as-a-pdf)
- [Include table data as PDF appendix](#table-data-in-pdf) (Public preview only)
- [Embed a dashboard image in the email](#embed-a-dashboard-as-an-image-in-the-email)
- [Attach a CSV file of the table panel data](#attach-a-csv-file-of-the-table-panel-data)
- [Attach a separate PDF of table data](#table-data-in-pdf) (Public preview only)
#### Attach the report as a PDF
If you selected the PDF format option, under the **Style the PDF** section, set the following options:
- **Show template variables** - Click the checkbox to select this option.
- **Orientation** - Set the report orientation in **Portrait** or **Landscape**. Refer to the [Layout and orientation table](#layout-and-orientation) to see examples.
- **Layout** - Select one of the following:
- **Simple** - Renders each panel as full-width across the PDF. Refer to the [Layout and orientation table](#layout-and-orientation) to see examples.
- **Grid** - Renders the PDF with the same panel arrangement and width as the source dashboard. Refer to the [Layout and orientation table](#layout-and-orientation) to see examples.
- **Zoom** - Zoom in to enlarge text in your PDF, or zoom out to see more data (like table columns) per panel.
Click **Preview PDF** in the top-right corner of the screen to view a rendered PDF with the options you selected.
##### Layout and orientation
<!-- prettier-ignore-start -->
| Layout | Orientation | Description | Preview |
| ------ | ----------- | --------------------------------------------------------------------------------------------------------- | ------------ |
| Simple | Portrait | Generates an A4 page in portrait mode with three panels per page. | {{< figure src="/static/img/docs/enterprise/reports_portrait_preview.png" max-width="500px" alt="Simple layout in portrait" >}} |
| Simple | Landscape | Generates an A4 page in landscape mode with a single panel per page. | {{< figure src="/static/img/docs/enterprise/reports_landscape_preview.png" max-width="500px" alt="Simple layout in landscape" >}} |
| Grid | Portrait | Generates an A4 page in portrait mode with panels arranged in the same way as at the original dashboard. | {{< figure src="/static/img/docs/enterprise/reports_grid_portrait_preview.png" max-width="500px" alt="Grid layout in portrait" >}} |
| Grid | Landscape | Generates an A4 page in landscape mode with panels arranged in the same way as in the original dashboard. | {{< figure src="/static/img/docs/enterprise/reports_grid_landscape_preview.png" max-width="500px" alt="Grid layout in landscape" >}} |
<!-- prettier-ignore-end -->
#### Embed a dashboard as an image in the email
You can send a report email with an image of the dashboard embedded in the email instead of attached as a PDF. In this case, the email recipients can see the dashboard at a glance instead of having to open the PDF.
#### Attach a CSV file of the table panel data
{{< admonition type="note" >}}
To use this feature in Grafana Enterprise, you must have [Grafana image renderer plugin](/grafana/plugins/grafana-image-renderer) v3.0.
{{< /admonition >}}
You can attach a CSV file to the report email for each table panel on the selected dashboard, along with the PDF report. By default, CSV files larger than 10Mb are not sent, which keeps email servers from rejecting the email. You can increase or decrease this limit in the [reporting configuration](ref:rendering-configuration).
This feature relies on the same plugin that supports the [image rendering](ref:image-rendering) features.
When the CSV file is generated, it is temporarily written to the `csv` folder in the Grafana `data` folder.
A background job runs every 10 minutes and removes temporary CSV files. You can configure how long a CSV file should be stored before being removed by configuring the [temp-data-lifetime](ref:temp-data-lifetime) setting. This setting also affects how long a renderer PNG file should be stored.
Click **Download CSV** in the top-right corner of the screen to see the file.
#### Table data in PDF
{{% admonition type="note" %}}
Available in public preview (`pdfTables` feature toggle) in [Grafana Enterprise](ref:grafana-enterprise) v10.3+ with the [Grafana image renderer plugin](/grafana/plugins/grafana-image-renderer) v3.0+, as well as [Grafana Cloud](/docs/grafana-cloud/).
{{% /admonition %}}
When there's more data in your table visualizations than can be shown in the dashboard PDF, you can select one of these two options to access all table visualization data as PDF in your reports:
- **Include table data as PDF appendix** - Adds an appendix to the main dashboard PDF.
- **Attach a separate PDF of table data** - Generates a separate PDF file.
This feature relies on the same plugin that supports the [image rendering](ref:image-rendering) features.
### 3. Schedule
At this step, set scheduling information. Options vary depending on the frequency you select.
<!-- prettier-ignore-start -->
| Option | Description |
| ------ | ----------- |
| Frequency | Scheduled reports can be sent once, or repeated on an hourly, daily, weekly, or monthly basis, or sent at custom intervals. You can also disable scheduling by selecting **Never**, for example to send the report using the API. |
| Time | Choose one of the following:<ul><li>**Send now** sends the report immediately after you save it. To stop sending the report at some point in the future, add an end date.</li><li>**Send later** schedules a report for a later date. When you select this option, the required **Start date**, **Start time**, and **Time zone** options are displayed.</li></ul> |
| End date | If you leave the end date empty, the report is sent out indefinitely. |
| Send only from Monday to Friday | For reports that have an hourly or daily frequency, you can choose to send them only from Monday to Friday. |
| Send on the last day of the month | When you schedule a report with a monthly frequency, and set the start date between the 29th and the 31st of the month, the report is only sent during the months that have those dates. If you want the report to be sent every month, select this option instead. This way, the report is sent on the last day of every month regardless of how many days there are in any given month. |
<!-- prettier-ignore-end -->
### 4. Share
At this step, enter information related to sharing the report:
<!-- prettier-ignore-start -->
| Option | Description |
| ------ | ----------- |
| Report name | Name of the report as you want it to appear in the **Reports** list. The report name also populates the email subject line. |
| Recipients | Enter the email addresses of the people or teams that you want to receive the report, separated by commas or semicolons. |
| Reply-to email address | The address that appears in the **Reply to** field of the email. |
| Message | Message body in the email with the report. |
| Include a dashboard link | Include a link to the dashboard from within the report email. |
<!-- prettier-ignore-end -->
Click **Send test email** in the top-right corner of the screen to verify that the configuration works as expected and to verify that emails are working. You can choose to send this email to the recipients configured for the report, or to a different set of email addresses only used for testing. The last saved version of the report will be sent to selected emails.
### 5. Confirm
At this step, the confirmation page displays all your the settings. Review them and confirm that they're correct or click the provided **Edit** links for each section to make updates.
Then, click **Send now** or **Schedule send**.
You can also save the report as a draft or discard it. Discarding the report is irreversible.
## Send a report using the API
You can send reports programmatically with the [send report](ref:send-report) endpoint in the [HTTP APIs](ref:http-apis).
## Manage reports
On the **Reports** page, you can view and manage your existing reports or create new ones.
![Reports page](/media/docs/grafana/dashboards/screenshot-reports-page-v11.5.png)
### Edit
To edit a report, follow these steps:
1. In the main menu, click **Dashboards > Reporting**.
1. Click the row of the report you want to update.
1. Click the **Edit report** button in the top-right hand corner or click the **Edit** link for a specific section to go to that one directly.
1. When you've finished making changes, click **Confirm** at the top of the screen to go to the last step.
1. Click **Update report**.
### Pause or resume a report
You can pause and resume sending reports from the report list view. To do this, follow these steps:
1. In the main menu, click **Dashboards > Reporting**.
1. On the row of the report you want to update, do one of the following:
- Click the pause icon - The report won't be sent according to its schedule until it's resumed.
- Click the resume icon - The report resumes on its previous schedule.
## Troubleshoot Reporting
To troubleshoot and get more log information, enable debug logging in the configuration file. Refer to [Configuration](ref:configuration) for more information.
```bash
[log]
filters = report:debug
```

View File

@@ -1,363 +0,0 @@
---
aliases:
- ../administration/reports/ # /docs/grafana/latest/administration/reports/
- ../enterprise/export-pdf/ # /docs/grafana/latest/enterprise/export-pdf/
- ../enterprise/reporting/ # /docs/grafana/latest/enterprise/reporting/
- ../panels/create-reports/ # /docs/grafana/latest/panels/create-reports/
- reporting/ # /docs/grafana/latest/dashboards/reporting/
keywords:
- grafana
- reporting
- export
- pdf
labels:
products:
- cloud
- enterprise
menuTitle: Reporting
title: Create and manage reports
description: Generate and share PDF reports from your Grafana dashboards
weight: 600
refs:
repeat-panels-or-rows:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-panel-options/#configure-repeating-rows-or-panels
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-panel-options/#configure-repeating-rows-or-panels
http-apis:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/developers/http_api/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/developers/http_api/
image-rendering:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/image-rendering/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/image-rendering/
rbac:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/
permission:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/
role-based-access-control:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/
configuration:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/
image-rendering:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/image-rendering/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/image-rendering/
templates-and-variables:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/variables/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/dashboards/variables/
grafana-enterprise:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/introduction/grafana-enterprise/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/introduction/grafana-enterprise/
configuration:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#filters
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#filters
time-range-controls:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/use-dashboards/#set-dashboard-time-range
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/dashboards/use-dashboards/#set-dashboard-time-range
send-report:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/developers/http_api/reporting/#send-a-report
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/developer-resources/api-reference/http-api/reporting/#send-a-report
smtp:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#smtp
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#smtp
temp-data-lifetime:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#temp-data-lifetime
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#temp-data-lifetime
---
# Create and manage reports
Reporting enables you to automatically generate PDFs from any of your dashboards and have Grafana email them to interested parties on a schedule. This is available in Grafana Cloud and in Grafana Enterprise.
> If you have [Role-based access control](ref:role-based-access-control) enabled, for some actions you would need to have relevant permissions.
> Refer to specific guides to understand what permissions are required.
Any changes you make to a dashboard used in a report are reflected the next time the report is sent. For example, if you change the time range in the dashboard, then the time range in the report also changes, unless you've configured a custom time range.
For information about recent improvements to the reporting UI, refer to [Grafana reporting: How we improved the UX in Grafana](https://grafana.com/blog/2022/06/29/grafana-reporting-how-we-improved-the-ux-in-grafana/).
## Requirements
- SMTP must be configured for reports to be sent. Refer to [SMTP](ref:smtp) in [Configuration](ref:configuration) for more information.
- The Image Renderer plugin must be installed or the remote rendering service must be set up. Refer to [Image rendering](ref:image-rendering) for more information.
## Access control
When [RBAC](ref:rbac) is enabled, you need to have the relevant [Permissions](ref:permission) to create and manage reports.
## Create or update a report
Only organization administrators can create reports by default. You can customize who can create reports with [Role-based access control](ref:role-based-access-control).
1. Click **Dashboards > Reports** in the side navigation menu.
The Reports page allows you to view, create, and update your reports. The report form has a multi-step layout. The steps do not need to be completed in succession and can be skipped over by clicking a step name.
1. Click **+ Create a new report**.
1. Select report dashboard.
- **Source dashboard:** Select the dashboard from which you want to generate the report.
- **Time range:** (optional) Use custom time range for the report. For more information, refer to [Report time range](#report-time-range).
- **Add another dashboard:** Add more than one dashboard to the report.
1. Format the report.
- **Choose format options for the report:** Select at least one option. Attach report as PDF, embed dashboard as an image, or attach CSV file of table panel data.
- If you selected the PDF format option:
- Select an orientation for the report: **Portrait** or **Landscape**.
- Select a layout for the generated report: **Simple** or **Grid**. The simple layout renders each panel as full-width across the PDF. The grid layout renders the PDF with the same panel arrangement and width as the source dashboard.
- Select a zoom level for the report. Zoom in to enlarge text in your PDF, or zoom out to see more data (like table columns) per panel.
- Click **Preview PDF** to view a rendered PDF with the options you selected.
1. Schedule report.
- Enter scheduling information. Options vary depending on the frequency selected.
1. Enter report information. All fields are required unless otherwise indicated.
- **Report name:** Name of the report as you want it to appear in the **Reports** list. The report name populates the email subject line.
- **Recipients:** Enter the emails of the people or teams that you want to receive the report, separated by commas or semicolons.
- **Reply to:** (optional) The address that appears in the **Reply to** field of the email.
- **Message:** (optional) Message body in the email with the report.
- **Include a dashboard link:** Include a link to the dashboard from within the report email.
- **Send test email:** To verify that the configuration works as expected. You can choose to send this email to the recipients configured for the report, or to a different set of email addresses only used for testing.
1. Preview and save the report.
### Save as draft
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
You can save a report as a draft at any point during the report creation or update process. You can save a report as a draft even if it's missing required fields. Also, the report won't be sent according to its schedule while it's a draft.
### Choose template variables
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
You can configure report-specific template variables for the dashboard on the report page. The variables that you select will override the variables from the dashboard, and they are used when rendering a PDF file of the report. For detailed information about using template variables, refer to the [Templates and variables](ref:templates-and-variables) section.
{{% admonition type="note" %}}
The query variables saved with a report might become of date if the results of that query change. For example, if your template variable queries for a list of hostnames and a new hostname is added, then it will not be included in the report. If that occurs, the selected variables must be manually updated in the report. If you select the `All` value for the template variable or if you keep the dashboard's original variable selection, then the report stays up-to-date as new values are added.
{{% /admonition %}}
### Render a report with panels or rows set to repeat by a variable
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
You can include dynamic dashboards with panels or rows, set to repeat by a variable, into reports. For detailed information about setting up repeating panels or rows in dashboards, refer to [Repeat panels or rows](ref:repeat-panels-or-rows).
### Report time range
> **Note:** You can set custom report time ranges in [Grafana Enterprise](ref:grafana-enterprise) 7.2+ and [Grafana Cloud](/docs/grafana-cloud/).
By default, reports use the saved time range of the dashboard. You can change the time range of the report by:
- Saving a modified time range to the dashboard. Changing the dashboard time range without saving it doesn't change the time zone of the report.
- Setting a time range via the **Time range** field in the report form. If specified, the custom time range overrides the time range from the report's dashboard.
The page header of the report displays the time range for the dashboard's data queries.
#### Report time zones
Reports use the time zone of the dashboard from which theyre generated. You can control the time zone for your reports by setting the dashboard to a specific time zone. Note that this affects the display of the dashboard for all users.
If a dashboard has the **Browser Time** setting, the reports generated from that dashboard use the time zone of the Grafana server. As a result, this time zone might not match the time zone of users creating or receiving the report.
If the time zone is set differently between your Grafana server and its remote image renderer, then the time ranges in the report might be different between the page header and the time axes in the panels. To avoid this, set the time zone to UTC for dashboards when using a remote renderer. Each dashboard's time zone setting is visible in the [time range controls](ref:time-range-controls).
### Layout and orientation
| Layout | Orientation | Support | Description | Preview |
| ------ | ----------- | ------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Simple | Portrait | v6.4+ | Generates an A4 page in portrait mode with three panels per page. | {{< figure src="/static/img/docs/enterprise/reports_portrait_preview.png" max-width="500px" max-height="500px" class="docs-image--no-shadow" alt="Simple layout in portrait" >}} |
| Simple | Landscape | v6.7+ | Generates an A4 page in landscape mode with a single panel per page. | {{< figure src="/static/img/docs/enterprise/reports_landscape_preview.png" max-width="500px" class="docs-image--no-shadow" alt="Simple layout in landscape" >}} |
| Grid | Portrait | v7.2+ | Generates an A4 page in portrait mode with panels arranged in the same way as at the original dashboard. | {{< figure src="/static/img/docs/enterprise/reports_grid_portrait_preview.png" max-width="500px" max-height="500px" class="docs-image--no-shadow" alt="Grid layout in portrait" >}} |
| Grid | Landscape | v7.2+ | Generates an A4 page in landscape mode with panels arranged in the same way as in the original dashboard. | {{< figure src="/static/img/docs/enterprise/reports_grid_landscape_preview.png" max-width="500px" class="docs-image--no-shadow" alt="Grid layout in landscape" >}} |
### CSV export
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) 8+ with the [Grafana image renderer plugin](/grafana/plugins/grafana-image-renderer) v3.0+, and [Grafana Cloud](/docs/grafana-cloud/).
You can attach a CSV file to the report email for each table panel on the selected dashboard, along with the PDF report. By default, CSVs larger than 10Mb are not sent which keeps email servers from rejecting the email. You can increase or decrease this limit in the [reporting configuration](#rendering-configuration).
This feature relies on the same plugin that supports the [image rendering](ref:image-rendering) features.
When the CSV file is generated, it is temporarily written to the `csv` folder in the Grafana `data` folder.
A background job runs every 10 minutes and removes temporary CSV files. You can configure how long a CSV file should be stored before being removed by configuring the [temp-data-lifetime](ref:temp-data-lifetime) setting. This setting also affects how long a renderer PNG file should be stored.
### Table data in PDF
{{% admonition type="note" %}}
Available in public preview (`pdfTables` feature toggle) in [Grafana Enterprise](ref:grafana-enterprise) v10.3+ with the [Grafana image renderer plugin](/grafana/plugins/grafana-image-renderer) v3.0+, and [Grafana Cloud](/docs/grafana-cloud/).
{{% /admonition %}}
When there's more data in your table visualizations than can be shown in the dashboard PDF, you can select one of these two options to access all table visualization data as PDF in your reports:
- **Include table data as PDF appendix** - Adds an appendix to the main dashboard PDF.
- **Attach a separate PDF of table data** - Generates a separate PDF file.
This feature relies on the same plugin that supports the [image rendering](ref:image-rendering) features.
### Scheduling
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
Scheduled reports can be sent once, or repeated on an hourly, daily, weekly, or monthly basis, or sent at custom intervals. You can also disable scheduling by selecting **Never**, for example to send the report via the API.
**Send now or schedule for later**
- **Send now** sends the report immediately after you save it. To stop sending the report at some point in the future, add an end date. If you leave the end date empty, the report is sent out indefinitely.
- **Send later** schedules a report for a later date. Thus, the start date and time are required fields. If you leave the end date empty, the report is sent out indefinitely.
**Send only from Monday to Friday**
For reports that have an hourly or daily frequency, you can choose to send them only from Monday to Friday.
**Send on the last day of the month**
When you schedule a report with a monthly frequency, and set the start date between the 29th and the 31st of the month, the report is only sent during the months that have those dates. If you want the report to be sent every month, select the **Send on the last day of the month** option instead. This way, the report is sent on the last day of every month regardless of how many days there are in any given month.
#### Send a test email
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
1. In the report, click **Send test email**.
1. In the **Email** field, enter the email address or addresses that you want to test, separated by a semicolon.
If you want to use email addresses from the report, then select the **Use emails from report** check box.
1. Click **Send**.
The last saved version of the report will be sent to selected emails. You can use this to verify emails are working and to make sure the report is generated and displayed as you expect.
### Pause a report
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
You can pause sending reports from the report list view by clicking the pause icon. The report will not be sent according to its schedule until it is resumed by clicking the resume button on the report row.
### Add multiple dashboards to a report
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
You can add more than one dashboard to a report. Additional dashboards will be rendered as new pages in the same PDF file, or additional images if you chose to embed images in your report email. You cannot add the same dashboard to a report multiple times.
### Embed a dashboard as an image into a report
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
You can send a report email with an image of the dashboard embedded in the email instead of attached as a PDF. In this case, the email recipients can see the dashboard at a glance instead of having to open the PDF.
## Export dashboard as PDF
You can generate and save PDF files of any dashboard.
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
1. In the dashboard that you want to export as PDF, click the **Share** button.
1. On the PDF tab, select a layout option for the exported dashboard: **Portrait** or **Landscape**.
1. Click **Save as PDF** to render the dashboard as a PDF file.
Grafana opens the PDF file in a new window or browser tab.
## Send a report via the API
You can send reports programmatically with the [send report](ref:send-report) endpoint in the [HTTP APIs](ref:http-apis).
## Rendering configuration
When generating reports, each panel renders separately before being collected in a PDF. You can configure the per-panel rendering timeout and number of concurrently rendered panels.
To make a panel more legible, you can set a scale factor for the rendered images. However, a higher scale factor increases the file size of the generated PDF.
You can also specify custom fonts that support different Unicode scripts. The DejaVu font is the default used for PDF rendering.
These options are available in the [configuration](ref:configuration) file.
```ini
[reporting]
# Use this option to enable or disable the reporting feature. When disabled, no reports are generated, and the UI is hidden. By default, reporting is enabled.
enabled = true
# Set timeout for the following reporting rendering requests: generating PDFs, generating embedded dashboard images for report emails, and generating attached CSV files.
rendering_timeout = 10s
# Set maximum number of concurrent calls to the rendering service
concurrent_render_limit = 4
# Set the scale factor for rendering images. 2 is enough for monitor resolutions
# 4 would be better for printed material. Setting a higher value affects performance and memory
image_scale_factor = 2
# Set the maximum file size in megabytes for the report email attachments
max_attachment_size_mb = 10
# Path to the directory containing font files
fonts_path =
# Name of the TrueType font file with regular style
font_regular = DejaVuSansCondensed.ttf
# Name of the TrueType font file with bold style
font_bold = DejaVuSansCondensed-Bold.ttf
# Name of the TrueType font file with italic style
font_italic = DejaVuSansCondensed-Oblique.ttf
# Maximum number of times the following reporting rendering requests are retried before returning an error: generating PDFs, generating embedded dashboard images for report emails, and generating attached CSV files. To disable the retry feature, enter `0`. This is available in public preview and requires the `reportingRetries` feature toggle.
max_retries_per_panel = 3
# Allowed domains to receive reports. Use an asterisk (`*`) to allow all domains. Use a comma-separated list to allow multiple domains. Example: allowed_domains = grafana.com, example.org
allowed_domains = *
```
## Report settings
> **Note:** Available in [Grafana Enterprise](ref:grafana-enterprise) and [Grafana Cloud](/docs/grafana-cloud/).
You can configure organization-wide report settings in the **Settings** under **Dashboards > Reporting**. Settings are applied to all the reports for current organization.
You can customize the branding options.
### Attachment settings
#### PDF
- **Company logo:** Company logo displayed in the report PDF. It can be configured by specifying a URL, or by uploading a file. The maximum file size is 16 MB. Defaults to the Grafana logo.
- **Theme:** Theme of the PDF attached to the report. Defaults to **Light**. The selected theme is also applied to the PDFs generated when you click **Preview PDF** during report creation or select the **Export as PDF** option on a dashboard. If **Current** is selected, the PDF in the report will be in the Admin's instance theme, but the preview and exported PDFs will be in the user's instance theme.
#### Embedded Image
- **Theme:** Theme of the dashboard image embedded in the email. Defaults to **Dark**.
### Email branding
- **Company logo:** Company logo displayed in the report email. It can be configured by specifying a URL, or by uploading a file. The maximum file size is 16 MB. Defaults to the Grafana logo.
- **Email footer:** Toggle to enable the report email footer. Select **Sent by** or **None**.
- **Footer link text:** Text of the link in the report email footer. Defaults to `Grafana`.
- **Footer link URL:** Link of the report email footer.
Currently, the API does not allow for the simultaneous upload of files with identical names for both the email logo and report logo. You can still upload the same file for each logo separately in two distinct steps.
## Troubleshoot reporting
To troubleshoot and get more log information, enable debug logging in the configuration file. Refer to [Configuration](ref:configuration) for more information.
```bash
[log]
filters = report:debug
```

View File

@@ -0,0 +1,56 @@
---
keywords:
- grafana
- reporting
- settings
labels:
products:
- cloud
- enterprise
menuTitle: Settings
title: Reporting settings
description: Manage organizational Reporting settings
weight: 700
refs:
grafana-enterprise:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/introduction/grafana-enterprise/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/introduction/grafana-enterprise/
configuration:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#filters
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#filters
---
# Reporting settings
You can configure organization-wide report settings and branding options in **Dashboards > Reporting > Settings**. Settings are applied to all the reports for the current organization.
## Attachment settings
The options in this section control the branding and theming of the report attachments.
### PDF
- **Company logo** - Company logo displayed in the report PDF. It can be configured by specifying a URL, or by uploading a file. The maximum file size is 16 MB. Defaults to the Grafana logo.
- **Theme** - Theme of the PDF attached to the report. Defaults to **Light**. The selected theme is also applied to the PDFs generated when you click **Preview PDF** during report creation or select the **Export as PDF** option on a dashboard. If **Current** is selected, the PDF in the report will be in the instance theme of the Admin, but the preview and exported PDFs will be in the user's instance theme.
### Embedded Image
- **Theme** - Theme of the dashboard image embedded in the email. Defaults to **Dark**.
<!-- vale Grafana.WordList = NO -->
## Email branding
<!-- vale Grafana.WordList = YES -->
- **Company logo** - Company logo displayed in the report email. It can be configured by specifying a URL, or by uploading a file. The maximum file size is 16 MB. Defaults to the Grafana logo.
- **Email footer** - Toggle to enable the report email footer. Select **Sent by** or **None**.
- **Footer link text** - Text of the link in the report email footer. Defaults to `Grafana`.
- **Footer link URL** - Link of the report email footer.
Currently, the API does not allow for the simultaneous upload of files with identical names for both the email logo and report logo. You can still upload the same file for each logo separately in two distinct steps.

View File

@@ -244,9 +244,10 @@ guaranteed because plugin developers can override this functionality. The follow
### Unsupported
- DynamoDB
- Dynatrace
- Graphite
- Google Sheets
- Dynatrace
### Unconfirmed

View File

@@ -138,21 +138,25 @@ Age for recent active users.
## [reporting]
### enabled
Enable or disable the reporting feature. When disabled, no reports are generated, and the UI is hidden. By default, reporting is enabled (`true`).
### rendering_timeout
Timeout for the following reporting rendering requests: generating PDFs, generating embedded dashboard images for report emails, and generating attached CSV files.
Timeout for the following reporting rendering requests: generating PDFs, generating embedded dashboard images for report emails, and generating attached CSV files. Default is 10 seconds.
### concurrent_render_limit
Maximum number of concurrent calls to the rendering service.
Maximum number of concurrent calls to the rendering service. Default is `4`.
### image_scale_factor
Scale factor for rendering images. Value `2` is enough for monitor resolutions, `4` would be better for printed material. Setting a higher value affects performance and memory.
Scale factor for rendering images. Value `2` is enough for monitor resolutions, `4` would be better for printed material. Setting a higher value affects performance and memory. Default is `2`.
### max_attachment_size_mb
Set the maximum file size in megabytes for the report email attachments.
Set the maximum file size in megabytes for the report email attachments. Default is `10`.
### fonts_path
@@ -160,7 +164,7 @@ Path to the directory containing font files.
### font_regular
Name of the TrueType font file with regular style.
Name of the TrueType font file with regular style. Default is `DejaVuSansCondensed.ttf`.
### font_bold
@@ -168,15 +172,15 @@ Name of the TrueType font file with bold style.
### font_italic
Name of the TrueType font file with italic style.
Name of the TrueType font file with italic style. Default is `DejaVuSansCondensed-Oblique.ttf`.
### max_retries_per_panel
Maximum number of times the following reporting rendering requests are retried before returning an error: generating PDFs, generating embedded dashboard images for report emails, and generating attached CSV files. To disable the retry feature, enter `0`. This is available in public preview and requires the `reportingRetries` feature toggle.
Maximum number of times the following reporting rendering requests are retried before returning an error: generating PDFs, generating embedded dashboard images for report emails, and generating attached CSV files. To disable the retry feature, enter `0`. This is available in public preview and requires the `reportingRetries` feature toggle. Default is `3`.
### allowed_domains
Allowed domains to receive reports. Use an asterisk (`*`) to allow all domains. Use a comma-separated list to allow multiple domains. Example: allowed_domains = grafana.com, example.org
Allowed domains to receive reports. Use an asterisk (`*`) to allow all domains. Use a comma-separated list to allow multiple domains. Example: `allowed_domains = grafana.com`, example.org. Default is `*`.
## [auditing]

View File

@@ -187,7 +187,7 @@ This section describes the steps you must complete to access Grafana via web bro
## Customize Grafana default configuration
Helm is a popular package manager for Kubernetes. It bundles Kubernetes resource manifests to be re-used across different environments. These manifests are written in a templating language, allowing you to provide configuration values via `values.yaml` file, or in-line using Helm, to replace the placeholders in the manifest where these configurations should reside.
Helm is a popular package manager for Kubernetes. It bundles Kubernetes resource manifests to be reused across different environments. These manifests are written in a templating language, allowing you to provide configuration values via `values.yaml` file, or in-line using Helm, to replace the placeholders in the manifest where these configurations should reside.
The `values.yaml` file allows you to customize the chart's configuration by specifying values for various parameters such as image versions, resource limits, service configurations, etc.

View File

@@ -177,6 +177,41 @@ This is the rewrite rule that's generated in the `web.config`:
For more detailed instruction, refer to the [tutorial on IIS URL Rewrites](/tutorials/iis/).
### Configure Apache
To use Apache as a proxy, ensure its proper installation and configuration.
1. Ensure that the Apache proxy module [`mod_proxy`](https://httpd.apache.org/docs/current/mod/mod_proxy.html) is installed and enabled. To enable, run the following commands:
```bash
a2enmod proxy
a2enmod proxy_http
```
2. To configure the proxy, edit the site configuration file. To do so, inside the `<VirtualHost>` section, add the following code:
```bash
ProxyPreserveHost on
ProxyPass / http://your_grafana_server:3000
ProxyPassReverse / http://your_grafana_server:3000
```
3. Finally, restart Apache for the settings to take effect.
After you've restarted, navigate to your Apache server on port 80 and you will be redirected to Grafana.
To configure Grafana hosted in a sub path, replace the sub path with the following code (assuming your Grafana instance is on the sub path `your_path`):
```bash
ProxyPreserveHost on
ProxyPass /your_path http://your_grafana_server:3000
ProxyPassReverse /your_path http://your_grafana_server:3000
ProxyPass / http://your_grafana_server:3000/your_path
ProxyPassReverse / http://192.168.250.5:3000/your_path
```
Note that the lines containing `your_path` _must_ come before the lines referencing root path (`/`) in order for this to work correctly.
### Configure Traefik
[Traefik](https://traefik.io/traefik/) Cloud Native application proxy.

4
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/grafana/grafana
go 1.23.4
go 1.23.5
require (
buf.build/gen/go/parca-dev/parca/connectrpc/go v1.17.0-20240902100956-02fd72488966.1 // @grafana/observability-traces-and-profiling
@@ -64,7 +64,7 @@ require (
github.com/googleapis/gax-go/v2 v2.14.1 // @grafana/grafana-backend-group
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad
github.com/grafana/alerting v0.0.0-20250110220613-267368fd1968 // @grafana/alerting-backend
github.com/grafana/alerting v0.0.0-20250123190916-7b528a0bc1d5 // @grafana/alerting-backend
github.com/grafana/authlib v0.0.0-20250108202437-7a039176d884 // @grafana/identity-access-team
github.com/grafana/authlib/claims v0.0.0-20241202085737-df90af04f335 // @grafana/identity-access-team
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics

4
go.sum
View File

@@ -1489,8 +1489,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/alerting v0.0.0-20250110220613-267368fd1968 h1:dSA0aOMzNnpBmYcmwv2OT5Is4kE7rubdSxo9GZSePAY=
github.com/grafana/alerting v0.0.0-20250110220613-267368fd1968/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU=
github.com/grafana/alerting v0.0.0-20250123190916-7b528a0bc1d5 h1:mZezO6ccQl6AZv55f9JsPMph3eoHCofIJra2yhKzYMo=
github.com/grafana/alerting v0.0.0-20250123190916-7b528a0bc1d5/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU=
github.com/grafana/authlib v0.0.0-20250108202437-7a039176d884 h1:MSRBiQrSJZ+iowjU4Tgtq8+uC5/cs9XdtUdSWCNHrNE=
github.com/grafana/authlib v0.0.0-20250108202437-7a039176d884/go.mod h1:x7df73G3xuSD35Xv9cjaMLyPJCgM9Z/Wj5ISouoAfiI=
github.com/grafana/authlib/claims v0.0.0-20241202085737-df90af04f335 h1:3DHH81RJCi8Bcgn2MdBh7vgWUshmAFjZzBCVuxiQ0uk=

View File

@@ -1,4 +1,4 @@
go 1.23.4
go 1.23.5
// The `skip:golangci-lint` comment tag is used to exclude the package from the `golangci-lint` GitHub Action.
// The module at the root of the repo (`.`) is excluded because ./pkg/... is included manually in the `golangci-lint` configuration.

View File

@@ -1697,6 +1697,7 @@ github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGc
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.17.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-jsonnet v0.18.0 h1:/6pTy6g+Jh1a1I2UMoAODkqELFiVIdOxbNwv0DDzoOg=

View File

@@ -65,7 +65,7 @@
"generate-apis": "rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts"
},
"grafana": {
"whatsNewUrl": "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v11-4/",
"whatsNewUrl": "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v11-5/",
"releaseNotesUrl": "https://grafana.com/docs/grafana/next/release-notes/"
},
"devDependencies": {

View File

@@ -42,7 +42,10 @@ describe('ensureColumns transformer', () => {
options: {},
};
const data = [seriesA, seriesBC];
const data = [
{ refId: 'A', ...seriesA },
{ refId: 'B', ...seriesBC },
];
await expect(transformDataFrame([cfg], data)).toEmitValuesWith((received) => {
const filtered = received[0];
@@ -109,6 +112,7 @@ describe('ensureColumns transformer', () => {
},
],
"length": 2,
"refId": "joinByField-A-B",
}
`);
});

View File

@@ -592,5 +592,6 @@ export function histogramFieldsToFrame(info: HistogramFields, theme?: GrafanaThe
type: DataFrameType.Histogram,
},
fields: [info.xMin, info.xMax, ...info.counts],
refId: `${DataTransformerID.histogram}`,
};
}

View File

@@ -42,6 +42,7 @@ export const joinByFieldTransformer: SynchronousDataTransformerInfo<JoinByFieldO
}
const joined = joinDataFrames({ frames: data, joinBy, mode: options.mode });
if (joined) {
joined.refId = `${DataTransformerID.joinByField}-${data.map((frame) => frame.refId).join('-')}`;
return [joined];
}
}

View File

@@ -43,7 +43,10 @@ export const mergeTransformer: DataTransformerInfo<MergeTransformerOptions> = {
const fieldNames = new Set<string>();
const fieldIndexByName: Record<string, Record<number, number>> = {};
const fieldNamesForKey: string[] = [];
const dataFrame = new MutableDataFrame();
const dataFrame = new MutableDataFrame({
refId: `${DataTransformerID.merge}-${data.map((frame) => frame.refId).join('-')}`,
fields: [],
});
for (let frameIndex = 0; frameIndex < data.length; frameIndex++) {
const frame = data[frameIndex];

View File

@@ -56,7 +56,9 @@ export const reduceTransformer: DataTransformerInfo<ReduceTransformerOptions> =
// Add a row for each series
const res = reduceSeriesToRows(data, matcher, options.reducers, options.labelsToFields);
return res ? [res] : [];
return res
? [{ ...res, refId: `${DataTransformerID.reduce}-${data.map((frame) => frame.refId).join('-')}` }]
: [];
})
),
};

View File

@@ -37,7 +37,10 @@ export const seriesToRowsTransformer: DataTransformerInfo<SeriesToRowsTransforme
const timeFieldByIndex: Record<number, number> = {};
const targetFields = new Set<string>();
const dataFrame = new MutableDataFrame();
const dataFrame = new MutableDataFrame({
refId: `${DataTransformerID.seriesToRows}-${data.map((frame) => frame.refId).join('-')}`,
fields: [],
});
const metricField: Field = {
name: TIME_SERIES_METRIC_FIELD_NAME,
values: [],

View File

@@ -80,6 +80,7 @@ function transposeDataFrame(options: TransposeTransformerOptions, data: DataFram
...frame,
fields: newFields,
length: Math.max(...newFields.map((field) => field.values.length)),
refId: `${DataTransformerID.transpose}-${frame.refId}`,
};
});
}

View File

@@ -11,7 +11,7 @@ import type { ReactMonacoEditorProps } from './types';
monacoEditorLoader.config({ monaco });
export const ReactMonacoEditor = (props: ReactMonacoEditorProps) => {
const { beforeMount, options, ...restProps } = props;
const { beforeMount, onMount, options, ...restProps } = props;
const theme = useTheme2();
const onMonacoBeforeMount = useCallback(
@@ -31,6 +31,15 @@ export const ReactMonacoEditor = (props: ReactMonacoEditorProps) => {
}}
theme={theme.isDark ? 'grafana-dark' : 'grafana-light'}
beforeMount={onMonacoBeforeMount}
onMount={(editor, monaco) => {
// we use a custom font in our monaco editor
// we need monaco to remeasure the fonts after they are loaded to prevent alignment issues
// see https://github.com/microsoft/monaco-editor/issues/648#issuecomment-564978560
document.fonts.ready.then(() => {
monaco.editor.remeasureFonts();
});
onMount?.(editor, monaco);
}}
/>
);
};

View File

@@ -267,7 +267,7 @@ type ThreemaIntegration struct {
type VictoropsIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
URL string `json:"url" yaml:"url" hcl:"url"`
URL Secret `json:"url" yaml:"url" hcl:"url"`
MessageType *string `json:"messageType,omitempty" yaml:"messageType,omitempty" hcl:"message_type"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`

View File

@@ -388,6 +388,7 @@ func GetAvailableNotifiers() []*NotifierPlugin {
Placeholder: "VictorOps url",
PropertyName: "url",
Required: true,
Secure: true,
},
{ // New in 8.0.
Label: "Message Type",

View File

@@ -15,7 +15,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) {
{receiverType: "kafka", expectedSecretFields: []string{"password"}},
{receiverType: "email", expectedSecretFields: []string{}},
{receiverType: "pagerduty", expectedSecretFields: []string{"integrationKey"}},
{receiverType: "victorops", expectedSecretFields: []string{}},
{receiverType: "victorops", expectedSecretFields: []string{"url"}},
{receiverType: "oncall", expectedSecretFields: []string{"password", "authorization_credentials"}},
{receiverType: "pushover", expectedSecretFields: []string{"apiToken", "userKey"}},
{receiverType: "slack", expectedSecretFields: []string{"token", "url"}},

View File

@@ -485,6 +485,14 @@ func (rs *ReceiverService) UpdateReceiver(ctx context.Context, r *models.Receive
return nil, err
}
// We re-encrypt the existing receiver to ensure any unencrypted secure fields that are correctly encrypted, note this should NOT re-encrypt secure fields that are already encrypted.
// This is rare, but can happen if a receiver is created with unencrypted secure fields and then the secure option is added later.
// Preferably, this would be handled by receiver config versions and migrations but for now this is a good safety net.
err = existing.Encrypt(rs.encryptor(ctx))
if err != nil {
return nil, err
}
span.AddEvent("Loaded current receiver", trace.WithAttributes(
attribute.String("concurrency_token", revision.ConcurrencyToken),
attribute.String("receiver", existing.Name),

View File

@@ -583,6 +583,44 @@ func TestReceiverService_Update(t *testing.T) {
), rm.Encrypted(models.Base64Enrypt)),
expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone},
},
{
name: "encrypts previously unencrypted secure fields",
user: writer,
receiver: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
models.CopyIntegrationWith(slackIntegration, im.AddSetting("token", "unencryptedValue"))),
),
existing: util.Pointer(models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
models.CopyIntegrationWith(slackIntegration,
im.AddSetting("token", "unencryptedValue"), // This will get encrypted.
),
))),
expectedUpdate: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
models.CopyIntegrationWith(slackIntegration,
im.AddSecureSetting("token", "dW5lbmNyeXB0ZWRWYWx1ZQ==")),
), rm.Encrypted(models.Base64Enrypt)),
expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone},
},
{
// This test is important for covering the rare case when an existing field is marked as secure.
// The UI will receive the field as secure and, if unchanged, will pass it back on update as a secureField instead of a Setting.
name: "encrypts previously unencrypted secure fields when passed in as secureFields",
user: writer,
receiver: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
models.CopyIntegrationWith(slackIntegration, im.AddSetting("newField", "newValue"))),
),
secureFields: map[string][]string{slackIntegration.UID: {"token"}},
existing: util.Pointer(models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
models.CopyIntegrationWith(slackIntegration,
im.AddSetting("token", "unencryptedValue"), // This will get encrypted.
),
))),
expectedUpdate: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
models.CopyIntegrationWith(slackIntegration,
im.AddSetting("newField", "newValue"),
im.AddSecureSetting("token", "dW5lbmNyeXB0ZWRWYWx1ZQ==")),
), rm.Encrypted(models.Base64Enrypt)),
expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone},
},
{
name: "doesn't copy existing unsecure fields",
user: writer,
@@ -684,8 +722,22 @@ func TestReceiverService_Update(t *testing.T) {
sut := createReceiverServiceSut(t, &secretsService)
if tc.existing != nil {
created, err := sut.CreateReceiver(context.Background(), tc.existing, tc.user.GetOrgID(), tc.user)
// Create route after receivers as they will be referenced.
revision, err := sut.cfgStore.Get(context.Background(), tc.user.GetOrgID())
require.NoError(t, err)
result, err := revision.CreateReceiver(tc.existing)
require.NoError(t, err)
created, err := PostableApiReceiverToReceiver(result, tc.existing.Provenance)
require.NoError(t, err)
err = sut.cfgStore.Save(context.Background(), revision, tc.user.GetOrgID())
require.NoError(t, err)
for _, integration := range created.Integrations {
target := definitions.EmbeddedContactPoint{UID: integration.UID}
err = sut.provisioningStore.SetProvenance(context.Background(), &target, tc.user.GetOrgID(), created.Provenance)
require.NoError(t, err)
}
if tc.version == "" {
tc.version = created.Version

View File

@@ -1,6 +1,6 @@
module github.com/grafana/grafana/pkg/storage/unified/apistore
go 1.23.4
go 1.23.5
replace (
github.com/grafana/grafana => ../../../..
@@ -169,7 +169,7 @@ require (
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/grafana/alerting v0.0.0-20250110220613-267368fd1968 // indirect
github.com/grafana/alerting v0.0.0-20250123190916-7b528a0bc1d5 // indirect
github.com/grafana/authlib v0.0.0-20250108202437-7a039176d884 // indirect
github.com/grafana/dataplane/sdata v0.0.9 // indirect
github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect

View File

@@ -543,8 +543,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/alerting v0.0.0-20250110220613-267368fd1968 h1:dSA0aOMzNnpBmYcmwv2OT5Is4kE7rubdSxo9GZSePAY=
github.com/grafana/alerting v0.0.0-20250110220613-267368fd1968/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU=
github.com/grafana/alerting v0.0.0-20250123190916-7b528a0bc1d5 h1:mZezO6ccQl6AZv55f9JsPMph3eoHCofIJra2yhKzYMo=
github.com/grafana/alerting v0.0.0-20250123190916-7b528a0bc1d5/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU=
github.com/grafana/authlib v0.0.0-20250108202437-7a039176d884 h1:MSRBiQrSJZ+iowjU4Tgtq8+uC5/cs9XdtUdSWCNHrNE=
github.com/grafana/authlib v0.0.0-20250108202437-7a039176d884/go.mod h1:x7df73G3xuSD35Xv9cjaMLyPJCgM9Z/Wj5ISouoAfiI=
github.com/grafana/authlib/claims v0.0.0-20241202085737-df90af04f335 h1:3DHH81RJCi8Bcgn2MdBh7vgWUshmAFjZzBCVuxiQ0uk=

View File

@@ -1,6 +1,6 @@
module github.com/grafana/grafana/pkg/storage/unified/resource
go 1.23.4
go 1.23.5
replace (
github.com/grafana/grafana => ../../../..
@@ -116,7 +116,7 @@ require (
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/grafana/alerting v0.0.0-20250110220613-267368fd1968 // indirect
github.com/grafana/alerting v0.0.0-20250123190916-7b528a0bc1d5 // indirect
github.com/grafana/dataplane/sdata v0.0.9 // indirect
github.com/grafana/grafana-aws-sdk v0.31.5 // indirect
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.2 // indirect

View File

@@ -398,8 +398,8 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grafana/alerting v0.0.0-20250110220613-267368fd1968 h1:dSA0aOMzNnpBmYcmwv2OT5Is4kE7rubdSxo9GZSePAY=
github.com/grafana/alerting v0.0.0-20250110220613-267368fd1968/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU=
github.com/grafana/alerting v0.0.0-20250123190916-7b528a0bc1d5 h1:mZezO6ccQl6AZv55f9JsPMph3eoHCofIJra2yhKzYMo=
github.com/grafana/alerting v0.0.0-20250123190916-7b528a0bc1d5/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU=
github.com/grafana/authlib v0.0.0-20250108202437-7a039176d884 h1:MSRBiQrSJZ+iowjU4Tgtq8+uC5/cs9XdtUdSWCNHrNE=
github.com/grafana/authlib v0.0.0-20250108202437-7a039176d884/go.mod h1:x7df73G3xuSD35Xv9cjaMLyPJCgM9Z/Wj5ISouoAfiI=
github.com/grafana/authlib/claims v0.0.0-20241202085737-df90af04f335 h1:3DHH81RJCi8Bcgn2MdBh7vgWUshmAFjZzBCVuxiQ0uk=

View File

@@ -33,30 +33,36 @@ func (s *httpServiceProxy) writeErrorResponse(rw http.ResponseWriter, statusCode
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(statusCode)
// Set error response to initial error message
errorBody := map[string]string{"error": message}
// Attempt to locate JSON portion in error message
re := regexp.MustCompile(`\{.*?\}`)
jsonPart := re.FindString(message)
if jsonPart != "" {
var jsonData map[string]interface{}
if unmarshalErr := json.Unmarshal([]byte(jsonPart), &jsonData); unmarshalErr != nil {
errorBody["error"] = fmt.Sprintf("Invalid JSON format in error message. Raw error: %s", message)
s.logger.Error("failed to unmarshal JSON error message", "error", unmarshalErr)
} else {
// Extract relevant fields for a formatted error message
errorType, _ := jsonData["error"].(string)
errorDescription, ok := jsonData["error_description"].(string)
if !ok {
s.logger.Error("unable to convert error_description to string", "rawError", jsonData["error_description"])
// Attempt to just format the error as a string
errorDescription = fmt.Sprintf("%v", jsonData["error_description"])
}
if errorType == "" {
errorType = "UnknownError"
}
var jsonData map[string]interface{}
if unmarshalErr := json.Unmarshal([]byte(jsonPart), &jsonData); unmarshalErr != nil {
errorMsg, _ := json.Marshal(map[string]string{"error": "Invalid JSON format in error message"})
_, err := rw.Write(errorMsg)
if err != nil {
return fmt.Errorf("unable to write HTTP response: %v", err)
errorBody["error"] = fmt.Sprintf("%s: %s", errorType, errorDescription)
}
return unmarshalErr
}
// Extract relevant fields for a formatted error message
errorType, _ := jsonData["error"].(string)
errorDescription, _ := jsonData["error_description"].(string)
if errorType == "" {
errorType = "UnknownError"
}
formattedError := fmt.Sprintf("%s: %s", errorType, errorDescription)
errorMsg, _ := json.Marshal(map[string]string{"error": formattedError})
_, err := rw.Write(errorMsg)
jsonRes, _ := json.Marshal(errorBody)
_, err := rw.Write(jsonRes)
if err != nil {
return fmt.Errorf("unable to write HTTP response: %v", err)
}
@@ -117,7 +123,7 @@ func (s *Service) getDataSourceFromHTTPReq(req *http.Request) (types.DatasourceI
}
func writeErrorResponse(rw http.ResponseWriter, code int, msg string) {
rw.WriteHeader(http.StatusBadRequest)
rw.WriteHeader(code)
errorBody := map[string]string{
"error": msg,
}
@@ -154,9 +160,11 @@ func (s *Service) handleResourceReq(subDataSource string) func(rw http.ResponseW
req.URL.Host = serviceURL.Host
req.URL.Scheme = serviceURL.Scheme
rw, err = s.executors[subDataSource].ResourceRequest(rw, req, service.HTTPClient)
_, err = s.executors[subDataSource].ResourceRequest(rw, req, service.HTTPClient)
if err != nil {
writeErrorResponse(rw, http.StatusInternalServerError, fmt.Sprintf("unexpected error %v", err))
// The ResourceRequest function should handle writing the error response
// We log the error here to ensure it's captured
s.logger.Error("error in resource request", "error", err)
return
}
}

View File

@@ -223,7 +223,7 @@ async function buildTestSceneWithLibraryPanel() {
title: 'Panel A',
pluginId: 'table',
key: 'panel-12',
$behaviors: [new LibraryPanelBehavior({ title: 'LibraryPanel A title', name: 'LibraryPanel A', uid: '111' })],
$behaviors: [new LibraryPanelBehavior({ name: 'LibraryPanel A', uid: '111' })],
titleItems: [new VizPanelLinks({ menu: new VizPanelLinksMenu({}) })],
$data: new SceneDataTransformer({
transformations: [

View File

@@ -200,7 +200,6 @@ describe('PanelEditor', () => {
const libPanelBehavior = new LibraryPanelBehavior({
isLoaded: true,
title: libraryPanelModel.title,
uid: libraryPanelModel.uid,
name: libraryPanelModel.name,
_loadedPanel: libraryPanelModel,
@@ -239,7 +238,7 @@ describe('PanelEditor', () => {
// Wait for mock api to return and update the library panel
expect(libPanelBehavior.state._loadedPanel?.version).toBe(2);
expect(libPanelBehavior.state.name).toBe('changed name');
expect(libPanelBehavior.state.title).toBe('changed title');
expect(panel.state.title).toBe('changed title');
expect((gridItem.state.body as VizPanel).state.title).toBe('changed title');
});
@@ -258,7 +257,6 @@ describe('PanelEditor', () => {
const libPanelBehavior = new LibraryPanelBehavior({
isLoaded: true,
title: libraryPanelModel.title,
uid: libraryPanelModel.uid,
name: libraryPanelModel.name,
_loadedPanel: libraryPanelModel,

View File

@@ -177,7 +177,6 @@ describe('PanelOptions', () => {
const libraryPanel = new LibraryPanelBehavior({
isLoaded: true,
title: libraryPanelModel.title,
uid: libraryPanelModel.uid,
name: libraryPanelModel.name,
_loadedPanel: libraryPanelModel,

View File

@@ -96,7 +96,7 @@ describe('AddLibraryPanelWidget', () => {
title: 'Panel Title',
pluginId: 'table',
key: 'panel-1',
$behaviors: [new LibraryPanelBehavior({ title: 'LibraryPanel A title', name: 'LibraryPanel A', uid: 'uid' })],
$behaviors: [new LibraryPanelBehavior({ name: 'LibraryPanel A', uid: 'uid' })],
});
addLibPanelDrawer = new AddLibraryPanelDrawer({ panelToReplaceRef: libPanel.getRef() });

View File

@@ -408,7 +408,6 @@ describe('DashboardDatasourceBehaviour', () => {
it('should re-run queries when library panel re-runs query', async () => {
const libPanelBehavior = new LibraryPanelBehavior({
isLoaded: false,
title: 'Panel title',
uid: 'fdcvggvfy2qdca',
name: 'My Library Panel',
_loadedPanel: undefined,
@@ -469,7 +468,6 @@ describe('DashboardDatasourceBehaviour', () => {
jest.spyOn(console, 'error').mockImplementation();
const libPanelBehavior = new LibraryPanelBehavior({
isLoaded: false,
title: 'Panel title',
uid: 'fdcvggvfy2qdca',
name: 'My Library Panel',
_loadedPanel: undefined,
@@ -519,7 +517,6 @@ describe('DashboardDatasourceBehaviour', () => {
// Simulate library panel being loaded
libPanelBehavior.setState({
isLoaded: true,
title: 'Panel title',
uid: 'fdcvggvfy2qdca',
name: 'My Library Panel',
_loadedPanel: undefined,

View File

@@ -489,7 +489,7 @@ describe('DashboardScene', () => {
title: 'Library Panel',
pluginId: 'table',
key: 'panel-4',
$behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
$behaviors: [new LibraryPanelBehavior({ name: 'libraryPanel', uid: 'uid' })],
});
scene.copyPanel(libVizPanel);
@@ -544,7 +544,7 @@ describe('DashboardScene', () => {
title: 'Library Panel',
pluginId: 'table',
key: 'panel-4',
$behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
$behaviors: [new LibraryPanelBehavior({ name: 'libraryPanel', uid: 'uid' })],
}),
})
);
@@ -563,7 +563,7 @@ describe('DashboardScene', () => {
const libPanel = new VizPanel({
title: 'Panel B',
pluginId: 'table',
$behaviors: [new LibraryPanelBehavior({ title: 'title', name: 'lib panel', uid: 'abc', isLoaded: true })],
$behaviors: [new LibraryPanelBehavior({ name: 'lib panel', uid: 'abc', isLoaded: true })],
});
const scene = buildTestScene({
@@ -928,7 +928,7 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
title: 'Library Panel',
pluginId: 'table',
key: 'panel-5',
$behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
$behaviors: [new LibraryPanelBehavior({ name: 'libraryPanel', uid: 'uid' })],
}),
}),
],
@@ -946,7 +946,7 @@ function buildTestScene(overrides?: Partial<DashboardSceneState>) {
title: 'Library Panel',
pluginId: 'table',
key: 'panel-6',
$behaviors: [new LibraryPanelBehavior({ title: 'Library Panel', name: 'libraryPanel', uid: 'uid' })],
$behaviors: [new LibraryPanelBehavior({ name: 'libraryPanel', uid: 'uid' })],
}),
}),
],

View File

@@ -167,7 +167,7 @@ describe('LibraryPanelBehavior', () => {
});
async function buildTestSceneWithLibraryPanel() {
const behavior = new LibraryPanelBehavior({ title: 'LibraryPanel A title', name: 'LibraryPanel A', uid: '111' });
const behavior = new LibraryPanelBehavior({ name: 'LibraryPanel A', uid: '111' });
const vizPanel = new VizPanel({
title: 'Panel A',

View File

@@ -17,8 +17,6 @@ import { AngularDeprecation } from './angular/AngularDeprecation';
import { DashboardGridItem } from './layout-default/DashboardGridItem';
export interface LibraryPanelBehaviorState extends SceneObjectState {
// Library panels use title from dashboard JSON's panel model, not from library panel definition, hence we pass it.
title?: string;
uid: string;
name: string;
isLoaded?: boolean;
@@ -66,7 +64,7 @@ export class LibraryPanelBehavior extends SceneObjectBase<LibraryPanelBehaviorSt
titleItems.push(new PanelNotices());
const vizPanelState: VizPanelState = {
title: libPanelModel.title,
title: vizPanel.state.title ?? libPanelModel.title,
options: libPanelModel.options ?? {},
fieldConfig: libPanelModel.fieldConfig,
pluginId: libPanelModel.type,
@@ -88,7 +86,7 @@ export class LibraryPanelBehavior extends SceneObjectBase<LibraryPanelBehaviorSt
vizPanel.setState(vizPanelState);
vizPanel.changePluginType(libPanelModel.type, vizPanelState.options, vizPanelState.fieldConfig);
this.setState({ _loadedPanel: libPanel, isLoaded: true, name: libPanel.name, title: libPanelModel.title });
this.setState({ _loadedPanel: libPanel, isLoaded: true, name: libPanel.name });
const layoutElement = vizPanel.parent!;

View File

@@ -355,7 +355,6 @@ describe('transformSceneToSaveModel', () => {
$behaviors: [
new LibraryPanelBehavior({
name: 'Some lib panel panel',
title: 'A panel',
uid: 'lib-panel-uid',
}),
],
@@ -399,7 +398,7 @@ describe('transformSceneToSaveModel', () => {
x: 0,
y: 0,
});
expect(result.title).toBe('A panel');
expect(result.title).toBe('Panel blahh blah');
expect(result.transformations).toBeUndefined();
expect(result.fieldConfig).toBeUndefined();
expect(result.options).toBeUndefined();
@@ -851,7 +850,6 @@ describe('transformSceneToSaveModel', () => {
$behaviors: [
new LibraryPanelBehavior({
name: 'Some lib panel panel',
title: 'A panel',
uid: 'lib-panel-uid',
}),
],
@@ -865,7 +863,7 @@ describe('transformSceneToSaveModel', () => {
expect(result[0]).toMatchObject({
id: 4,
title: 'A panel',
title: 'Panel blahh blah',
libraryPanel: {
name: 'Some lib panel panel',
uid: 'lib-panel-uid',

View File

@@ -186,7 +186,7 @@ export function vizPanelToPanel(
panel = {
id: getPanelIdForVizPanel(vizPanel),
title: libPanel!.state.title,
title: vizPanel.state.title,
gridPos: gridPos,
libraryPanel: {
name: libPanel!.state.name,

View File

@@ -17,7 +17,6 @@ describe('PanelModelCompatibilityWrapper', () => {
const libPanel = new LibraryPanelBehavior({
uid: 'a',
name: 'aa',
title: 'a',
});
panel.setState({

View File

@@ -1,26 +1,17 @@
import { css } from '@emotion/css';
import { createElement, useEffect, useMemo, useState } from 'react';
import { mergeMap } from 'rxjs/operators';
import { createElement, useMemo } from 'react';
import {
DataFrame,
DataTransformerConfig,
GrafanaTheme2,
transformDataFrame,
TransformerRegistryItem,
getFrameMatchers,
DataTransformContext,
} from '@grafana/data';
import { DataFrame, DataTransformerConfig, GrafanaTheme2, TransformerRegistryItem } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { getTemplateSrv } from '@grafana/runtime';
import { Icon, JSONFormatter, useStyles2, Drawer } from '@grafana/ui';
import { TransformationsEditorTransformation } from './types';
interface TransformationEditorProps {
input: DataFrame[];
output: DataFrame[];
debugMode?: boolean;
index: number;
data: DataFrame[];
uiConfig: TransformerRegistryItem;
configs: TransformationsEditorTransformation[];
onChange: (index: number, config: DataTransformerConfig) => void;
@@ -28,45 +19,18 @@ interface TransformationEditorProps {
}
export const TransformationEditor = ({
input,
output,
debugMode,
index,
data,
uiConfig,
configs,
onChange,
toggleShowDebug,
}: TransformationEditorProps) => {
const styles = useStyles2(getStyles);
const [input, setInput] = useState<DataFrame[]>([]);
const [output, setOutput] = useState<DataFrame[]>([]);
const config = useMemo(() => configs[index], [configs, index]);
useEffect(() => {
const config = configs[index].transformation;
const matcher = config.filter?.options ? getFrameMatchers(config.filter) : undefined;
const inputTransforms = configs.slice(0, index).map((t) => t.transformation);
const outputTransforms = configs.slice(index, index + 1).map((t) => t.transformation);
const ctx: DataTransformContext = {
interpolate: (v: string) => getTemplateSrv().replace(v),
};
const inputSubscription = transformDataFrame(inputTransforms, data, ctx).subscribe((v) => {
if (matcher) {
v = data.filter((v) => matcher(v));
}
setInput(v);
});
const outputSubscription = transformDataFrame(inputTransforms, data, ctx)
.pipe(mergeMap((before) => transformDataFrame(outputTransforms, before, ctx)))
.subscribe(setOutput);
return function unsubscribe() {
inputSubscription.unsubscribe();
outputSubscription.unsubscribe();
};
}, [index, data, configs]);
const editor = useMemo(
() =>
createElement(uiConfig.editor, {

View File

@@ -1,40 +1,35 @@
import { css } from '@emotion/css';
import { useMemo } from 'react';
import {
DataTransformerConfig,
GrafanaTheme2,
StandardEditorContext,
StandardEditorsRegistryItem,
} from '@grafana/data';
import { DataFrame, DataTransformerConfig, GrafanaTheme2 } from '@grafana/data';
import { DataTopic } from '@grafana/schema';
import { Field, Select, useStyles2 } from '@grafana/ui';
import { FrameMultiSelectionEditor } from 'app/plugins/panel/geomap/editor/FrameSelectionEditor';
import { TransformationData } from './TransformationsEditor';
interface TransformationFilterProps {
/** data frames from the output of previous transformation */
data: DataFrame[];
index: number;
config: DataTransformerConfig;
data: TransformationData;
annotations?: DataFrame[];
onChange: (index: number, config: DataTransformerConfig) => void;
}
export const TransformationFilter = ({ index, data, config, onChange }: TransformationFilterProps) => {
export const TransformationFilter = ({ index, annotations, config, onChange, data }: TransformationFilterProps) => {
const styles = useStyles2(getStyles);
const opts = useMemo(() => {
return {
// eslint-disable-next-line
context: { data: data.series } as StandardEditorContext<unknown>,
showTopic: true || data.annotations?.length || config.topic?.length,
context: { data },
showTopic: true || annotations?.length || config.topic?.length,
showFilter: config.topic !== DataTopic.Annotations,
source: [
{ value: DataTopic.Series, label: `Query results` },
{ value: DataTopic.Series, label: `Query and Transformation results` },
{ value: DataTopic.Annotations, label: `Annotation data` },
],
};
}, [data, config.topic]);
}, [data, annotations?.length, config.topic]);
return (
<div className={styles.wrapper}>
@@ -59,8 +54,6 @@ export const TransformationFilter = ({ index, data, config, onChange }: Transfor
<FrameMultiSelectionEditor
value={config.filter!}
context={opts.context}
// eslint-disable-next-line
item={{} as StandardEditorsRegistryItem}
onChange={(filter) => onChange(index, { ...config, filter })}
/>
)}

View File

@@ -1,10 +1,18 @@
import { useCallback } from 'react';
import * as React from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useToggle } from 'react-use';
import { mergeMap } from 'rxjs';
import { DataTransformerConfig, TransformerRegistryItem, FrameMatcherID, DataTopic } from '@grafana/data';
import {
DataTransformerConfig,
TransformerRegistryItem,
FrameMatcherID,
DataTransformContext,
getFrameMatchers,
transformDataFrame,
DataFrame,
} from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { reportInteraction } from '@grafana/runtime';
import { getTemplateSrv, reportInteraction } from '@grafana/runtime';
import { ConfirmModal } from '@grafana/ui';
import {
QueryOperationAction,
@@ -46,6 +54,10 @@ export const TransformationOperationRow = ({
const topic = configs[index].transformation.topic;
const showFilterEditor = configs[index].transformation.filter != null || topic != null;
const showFilterToggle = showFilterEditor || data.series.length > 0 || (data.annotations?.length ?? 0) > 0;
const [input, setInput] = useState<DataFrame[]>([]);
const [output, setOutput] = useState<DataFrame[]>([]);
// output of previous transformation
const [prevOutput, setPrevOutput] = useState<DataFrame[]>([]);
const onDisableToggle = useCallback(
(index: number) => {
@@ -92,6 +104,48 @@ export const TransformationOperationRow = ({
[configs, index]
);
useEffect(() => {
const config = configs[index].transformation;
const matcher = config.filter?.options ? getFrameMatchers(config.filter) : undefined;
// we need previous transformation index to get its outputs
// to be used in this transforms inputs
const prevTransformIndex = index - 1;
let prevInputTransforms: Array<DataTransformerConfig<{}>> = [];
let prevOutputTransforms: Array<DataTransformerConfig<{}>> = [];
if (prevTransformIndex >= 0) {
prevInputTransforms = configs.slice(0, prevTransformIndex).map((t) => t.transformation);
prevOutputTransforms = configs.slice(prevTransformIndex, index).map((t) => t.transformation);
}
const inputTransforms = configs.slice(0, index).map((t) => t.transformation);
const outputTransforms = configs.slice(index, index + 1).map((t) => t.transformation);
const ctx: DataTransformContext = {
interpolate: (v: string) => getTemplateSrv().replace(v),
};
const inputSubscription = transformDataFrame(inputTransforms, data.series, ctx).subscribe((data) => {
if (matcher) {
data = data.filter((frame) => matcher(frame));
}
setInput(data);
});
const outputSubscription = transformDataFrame(inputTransforms, data.series, ctx)
.pipe(mergeMap((before) => transformDataFrame(outputTransforms, before, ctx)))
.subscribe(setOutput);
const prevOutputSubscription = transformDataFrame(prevInputTransforms, data.series, ctx)
.pipe(mergeMap((before) => transformDataFrame(prevOutputTransforms, before, ctx)))
.subscribe(setPrevOutput);
return function unsubscribe() {
inputSubscription.unsubscribe();
outputSubscription.unsubscribe();
prevOutputSubscription.unsubscribe();
};
}, [index, data, configs]);
const renderActions = () => {
return (
<>
@@ -162,13 +216,20 @@ export const TransformationOperationRow = ({
}}
>
{showFilterEditor && (
<TransformationFilter index={index} config={configs[index].transformation} data={data} onChange={onChange} />
<TransformationFilter
data={prevOutput}
index={index}
config={configs[index].transformation}
annotations={data.annotations}
onChange={onChange}
/>
)}
<TransformationEditor
input={input}
output={output}
debugMode={showDebug}
index={index}
data={topic === DataTopic.Annotations ? (data.annotations ?? []) : data.series}
configs={configs}
uiConfig={uiConfig}
onChange={onChange}

View File

@@ -35,7 +35,7 @@ interface JoinValues {
export function joinByLabels(options: JoinByLabelsTransformOptions, data: DataFrame[]): DataFrame {
if (!options.value?.length) {
return getErrorFrame('No value labele configured');
return getErrorFrame('No value label configured');
}
const distinctLabels = getDistinctLabels(data);
if (distinctLabels.size < 1) {
@@ -104,7 +104,11 @@ export function joinByLabels(options: JoinByLabelsTransformOptions, data: DataFr
}
}
const frame: DataFrame = { fields: [], length: nameValues[0].length };
const frame: DataFrame = {
fields: [],
length: nameValues[0].length,
refId: `${DataTransformerID.joinByLabels}-${data.map((frame) => frame.refId).join('-')}`,
};
for (let i = 0; i < join.length; i++) {
frame.fields.push({
name: join[i],

View File

@@ -12,6 +12,7 @@ describe('Rows to fields', () => {
{ name: 'Miiin', type: FieldType.number, values: [3, 100] },
{ name: 'max', type: FieldType.string, values: [15, 200] },
],
refId: 'A',
});
const result = rowsToFields(
@@ -57,6 +58,7 @@ describe('Rows to fields', () => {
},
],
"length": 1,
"refId": "rowsToFields-A",
}
`);
});

View File

@@ -64,6 +64,7 @@ export function rowsToFields(options: RowToFieldsTransformOptions, data: DataFra
return {
fields: outFields,
length: 1,
refId: `${DataTransformerID.rowsToFields}-${data.refId}`,
};
}

View File

@@ -59,15 +59,17 @@ describe('DataGrid', () => {
jest.clearAllMocks();
});
it('converts dataframe values to cell values properly', () => {
it('converts dataframe values to cell values properly', async () => {
jest.useFakeTimers();
render(<DataGridPanel {...props} />);
prep(false);
expect(screen.getByTestId('glide-cell-1-0')).toHaveTextContent('1');
expect(screen.getByTestId('glide-cell-2-1')).toHaveTextContent('b');
expect(screen.getByTestId('glide-cell-3-2')).toHaveTextContent('c');
expect(screen.getByTestId('glide-cell-3-3')).toHaveTextContent('d');
await waitFor(() => {
expect(screen.getByTestId('glide-cell-1-0')).toHaveTextContent('1');
expect(screen.getByTestId('glide-cell-2-1')).toHaveTextContent('b');
expect(screen.getByTestId('glide-cell-3-2')).toHaveTextContent('c');
expect(screen.getByTestId('glide-cell-3-3')).toHaveTextContent('d');
});
});
it('should open context menu on right click', async () => {

View File

@@ -29,7 +29,9 @@ export const FrameSelectionEditor = ({ value, context, onChange }: Props) => {
);
};
export const FrameMultiSelectionEditor = ({ value, context, onChange }: Props) => {
type FrameMultiSelectionEditorProps = Omit<StandardEditorProps<MatcherConfig>, 'item'>;
export const FrameMultiSelectionEditor = ({ value, context, onChange }: FrameMultiSelectionEditorProps) => {
const onFilterChange = useCallback(
(v: string[]) => {
onChange(

View File

@@ -61,6 +61,9 @@ const mockIntersectionObserver = jest
disconnect: jest.fn(),
}));
global.IntersectionObserver = mockIntersectionObserver;
Object.defineProperty(document, 'fonts', {
value: { ready: Promise.resolve({}) },
});
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;

View File

@@ -5,7 +5,6 @@ This module returns all the pipelines used in the event of documentation changes
load(
"scripts/drone/steps/lib.star",
"build_docs_website_step",
"codespell_step",
"identify_runner_step",
"verify_gen_cue_step",
"yarn_install_step",
@@ -33,7 +32,6 @@ def docs_pipelines(ver_mode, trigger):
steps = [
identify_runner_step(),
yarn_install_step(),
codespell_step(),
lint_docs(),
build_docs_website_step(),
verify_gen_cue_step(),

View File

@@ -753,16 +753,6 @@ def frontend_metrics_step(trigger = None):
step = dict(step, when = trigger)
return step
def codespell_step():
return {
"name": "codespell",
"image": images["python"],
"commands": [
"pip3 install codespell",
"codespell -I docs/.codespellignore docs/",
],
}
def grafana_server_step():
"""Runs the grafana-server binary as a service.

View File

@@ -3,7 +3,7 @@ global variables
"""
grabpl_version = "v3.1.1"
golang_version = "1.23.4"
golang_version = "1.23.5"
# nodejs_version should match what's in ".nvmrc", but without the v prefix.
nodejs_version = "22.11.0"