Compare commits

...

92 Commits

Author SHA1 Message Date
Jacob Valdez 92f1fba9b4 docs: reorder whats new posts and include plugin translation (#111482) (#111485) 2025-09-23 16:40:24 +02:00
grafana-delivery-bot[bot] 563ca4aa39 Release: Bump version to 12.2.0 (#111346)
bump version 12.2.0

Co-authored-by: grafana-delivery-bot[bot] <grafana-delivery-bot[bot]@users.noreply.github.com>
2025-09-23 09:09:40 -05:00
Jacob Valdez f1d4c6433b backporting What's New in Grafana 12.2.0 (#111471) 2025-09-23 13:55:37 +02:00
grafana-delivery-bot[bot] ccf0c5b40e Release: Bump version to 12.2.0 (#111414)
bump version 12.2.0

Co-authored-by: grafana-delivery-bot[bot] <grafana-delivery-bot[bot]@users.noreply.github.com>
2025-09-19 13:59:49 -05:00
Josh Hunt 6569f64267 [release-12.2.0] CI: Backport release-npm.yml (#111395)
backport release-npm.yml from main
2025-09-19 18:31:45 +01:00
Josh Hunt 1cc2a4cbe7 [release-12.2.0] CI: Fix NPM workflow inputs (#111352)
Fix referring to inputs (#111345)

(cherry picked from commit 1d6c1da94f)
2025-09-18 23:26:53 +01:00
Kevin Minehart 93c9887bc4 [release-12.2.0] backport bump-version.yml and release-build.yml (#111340)
* [release-12.2.0] backport bump-version.yml and release-build.yml

* add release-npm.yml
2025-09-18 16:57:26 -05:00
Ashley Harrison 66d8379061 [release-12.2.0] Chore: bump axios to a version without CVE (#111113)
Chore: bump `axios` to a version without CVE (#111076)

bump axios to a version without CVE

(cherry picked from commit 7bba151416)
2025-09-15 16:25:59 +01:00
Adela Almasan 7e708e5976 [release-12.2.0] Actions: Add permission check to missing panels (#111115) 2025-09-15 09:55:11 -05:00
Luminessa Starlight a9f59cf340 [release-12.2.0]: Accessibility: enable responsive reflow of variables in dashboard edit (#111032)
Accessibility: enable responsive reflow of variables in dashboard edit (#110967)

enable responsive reflow of variables in dashboard edit
2025-09-15 10:46:52 -04:00
Ashley Harrison 02fadf48fc Geomap: Only prefix with grafana public path if relative url (#111081) (#111108)
only prefix with grafana public path if relative url
2025-09-15 15:42:53 +01:00
grafana-delivery-bot[bot] 496911e716 [release-12.2.0] Table: Restore previous footer behavior of reducers applying to filtered data (#111050)
Table: Restore previous footer behavior of reducers applying to filtered data (#111041)

* Table: Restore previous footer behavior of reducers applying to filtered data

* update e2e to match new behavior

(cherry picked from commit f258d8a417)

Co-authored-by: Paul Marbach <paul.marbach@grafana.com>
2025-09-12 19:10:29 -04:00
grafana-delivery-bot[bot] 6d8ad119bf [release-12.2.0] Table: Fix logic to calculate footer height (#111049)
Table: Fix logic to calculate footer height (#110954)

* Table: Fix logic to calculate footer height

* add non-numeric footer case to gdev

* Update packages/grafana-ui/src/components/Table/TableNG/utils.ts



* Update packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx



---------


(cherry picked from commit cb37539ed7)

Co-authored-by: Paul Marbach <paul.marbach@grafana.com>
Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
2025-09-12 17:51:37 -04:00
Kevin Minehart 49f78c15e8 [release-12.2.0] CI: fix bump version action to use grafana-delivery-bot (#110976) (#110981)
CI: fix bump version action to use grafana-delivery-bot (#110976)

* update bump-version

* Add id-token: write

* update generate-token step

* pull-requests -> pull_requests

* clone with token and set right name

(cherry picked from commit c28a917871)
2025-09-11 15:47:47 -05:00
grafana-delivery-bot[bot] 76340a9741 [release-12.2.0] Alerting: Fix bug where rules with identical mute/active intervals produced conflicting routes (#110971)
Alerting: Fix bug where rules with identical mute/active intervals produced conflicting routes (#110935)

Alerting: Fix hash collision in NotificationSettings fingerprint
(cherry picked from commit fc3636acf2)

Co-authored-by: Alexander Akhmetov <me@alx.cx>
2025-09-11 19:44:08 +02:00
Isabel Matwawana b15acdf1f2 [release-12.2.0] Docs: ad hoc filter improvements (#110961) 2025-09-11 12:15:52 -04:00
Isabel Matwawana ca8402fbda [release-12.2.0] docs: clarify that data links must use variable names, not labels (#110965)
Co-authored-by: Sidharth Chauhan <chauhansiddharth71@gmail.com>
2025-09-11 12:09:51 -04:00
Isabel Matwawana abb44794fe [release-12.2.0] Docs: Replace screenshots for pill and markdown cells (#110962) 2025-09-11 12:03:56 -04:00
Isabel Matwawana c228eaa99d [release-12.2.0] Docs: Add panel filtering (#110960)
Co-authored-by: Sam Jewell <2903904+samjewell@users.noreply.github.com>
2025-09-11 11:59:24 -04:00
Larissa Wandzura f41cc1c0d6 created new doc 2025-09-11 10:03:36 -05:00
Larissa Wandzura b557d71c9a Merge branch 'docs/sql-expressions-updates-082025' of github.com:grafana/grafana into docs/sql-expressions-updates-082025
(cherry picked from commit b0bfbbf547)
2025-09-11 10:01:19 -05:00
Will Browne e404352a38 Plugins: StaticFS should implement FSRemover (#110706) (#110943)
make staticfs implement fs removal interface
2025-09-11 14:44:47 +01:00
Josh Hunt 454380431d FS: Get CDN prefix from configuration (#110615)
* FS: Get CDN prefix from configuration

* undo logger change

* fix tests

* add unused property

* tests

* fix tests

* remove dead comment
2025-09-08 15:57:03 +00:00
Ihor Yeromin 2f2950eb29 Tests: Move Canvas tests to panel folder and add testing README (#110768)
* chore(e2e-tests): move canvas tests to panels
2025-09-08 17:52:10 +02:00
Ashley Harrison a95f556e24 Chore: Remove some anys (#110661)
* removing some (hopefully) dead code

* fix some more any

* more any fixes

* manually add suppressions

* remove type assertion

* couple more
2025-09-08 16:49:57 +01:00
Ryan McKinley 7c95d3c8a9 Folders: Split legacy out of folder.Service (and remove folder.FolderStore) (#110734) 2025-09-08 18:27:49 +03:00
Andrew Hackmann 854a8f7e70 Prometheus data source: Remove migration background service (#110764)
Prometheus data source: remove mig bg service
:
2025-09-08 14:55:05 +00:00
Oscar Kilhed ed69a1f16d Dashboards: Fix UTF-8 characters not working with excel downloads by replacing download for excel with excel compatibility mode. (#110099)
* convert to utf-16le

* Fix tests, remove download for excel

* update i18n

* Update copy

* update test
2025-09-08 16:23:06 +02:00
Zoltán Bedi 67c26c493e PostgreSQL: Fix error source in PGX (#110752) 2025-09-08 16:21:05 +02:00
Fayzal Ghantiwala 22ed5499a2 Alerting: Check if TimeInterval is used in ActiveTimings when deleting (#110691)
* check for active timing in route

* Update test

* Add integration test
2025-09-08 15:04:40 +01:00
Peter Štibraný 7fd9ab9481 Replace check for integration tests. (#110707)
* Replace check for integration tests.
* Revert changes in pkg/tsdb/mysql packages.
* Fix formatting of few tests.
2025-09-08 15:49:49 +02:00
Ashley Harrison 544872d117 Dashboard: revert variable css changes (#110684)
revert variable css changes
2025-09-08 14:33:34 +01:00
Dominik Broj 2e9432b9d5 Alerting: allow hiding alert rule column in CentralAlertHistoryScene (#110746)
allow hiding alert rule column
2025-09-08 15:00:21 +02:00
Misi badea8bc37 IAM: Create Service Account API and legacy store impl (#110411)
* wip

* IAM: Create Service Account

* Add dual writer

* Update openapi_test.go

* Add integration tests

* Add sql tests

* Add Role to SA spec, add validation, add DBTime, add tests

* Format, update test

* Fixes

* Add check for External

* Address feedback

* Update tests

* Address feedback

* make gen-go

* Simplify a bit

* Fixes

* make update-workspace

* Update pkg/registry/apis/iam/serviceaccount/store.go

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>

* Address feedback, add test for generateName

---------

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2025-09-08 14:31:32 +02:00
Hugo Häggmark 8a28381278 Chore: cleans up Angular tests (#110757) 2025-09-08 14:16:08 +02:00
Roberto Jiménez Sánchez ace05e999d Provisioning: Remove temporary logic to test clients in jobs operator (#110758)
Remove temporary logic to test clients in jobs operator
2025-09-08 12:15:13 +00:00
Torkel Ödegaard 958f5a7c52 Dashboard: Panel edit undo/redo and other edit actions should trigger repeat of grid/auto items (#110606)
* Panel edit undo-redo

* Update

* Update

* fix lint & test

* Try switching to source tab

* Added temp fix for old editing ux

* Update
2025-09-08 14:06:37 +02:00
renovate[bot] 3c5c6b8185 Update dependency @floating-ui/react to v0.27.16 (#110715)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 13:06:18 +02:00
Peter Štibraný d09708fe55 Move SkipIntegrationTestInShortMode to testutil. (#110750)
* Move SkipIntegrationTestInShortMode to testutil.

* make update-workspace
2025-09-08 12:50:31 +02:00
Ryan McKinley 08230cbc09 Chore: Remove unused bus.Bus events (#110738) 2025-09-08 10:47:16 +00:00
Hugo Häggmark bc843913e4 Chore: Removes HideAngularDeprecation configuration (#110665)
* Chore: cleans up HideAngularDeprecation

* Trigger build
2025-09-08 12:39:01 +02:00
Alex Khomenko 7020bdd7e1 Restore dashboards: Update cache invalidation (#110702)
* Restore dashboards: Update cache invalidation

* Delete unused component

* Update i18n

* Restore analytics toggle
2025-09-08 13:32:07 +03:00
Zoltán Bedi d9f0d642cc PostgreSQL: PGX fix multiple results handling (#110452)
* PostgreSQL: FIx multiple results handling

- Added tests for handling multiple result sets, including compatible and incompatible structures, ensuring no panics occur.
- Improved `convertResultsToFrame` function to validate column compatibility and handle null values correctly.
- Introduced a new helper function `convertPostgresValue` for converting raw PostgreSQL values to appropriate Go types.
- Added comprehensive unit tests for `convertResultsToFrame` covering various scenarios including row limits and mixed result types.

* Add more test case
2025-09-08 10:37:54 +02:00
Tobias Skarhed 7bccabfb1f Secrets: Add e2e selectors (#110689)
* Add e2e selectors for secrets and ConfirmModal

* Update secretform selector
2025-09-08 08:02:11 +00:00
Mariell Hoversholm 4497af20b2 docs: fix Prometheus provisioning example (#110747) 2025-09-08 09:48:04 +02:00
Levente Balogh 2f558e8cb7 Grafana Schema: Extend the VariableHide enum (#110579)
* feat: extend the `VariableHide` enum with a new option

* feat: extend the `VariableHide` for v1
2025-09-08 09:08:09 +02:00
Hugo Häggmark 1fd4611487 Plugin Extensions: refactors shared logic into validator function (#110434)
* Plugin Extensions: consolidate logic

* chore: add specific error to tests

* chore: refactors out common parts to getExtensionValidationResults

* chore: updates after PR feedback

* chore: update after PR feedback
2025-09-08 06:22:27 +02:00
Drew Slobodnjak 23fa9a1484 TimeSeries: Use exported time shift and fix time comparison tooltip (#109947)
* TimeSeries: Use exported time comparison function

* Add alignTimeRangeCompareData to grafana/data

* Simplify tooltip time text formatting

* Bump scenes version

* Add tests for alignTimeRangeCompareData

* Add backwards compatibility for older scenes

* Update shouldAlignTimeCompare for typical query

* Fix tooltip for older versions of scenes

* support for multiple shifts

---------

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
2025-09-06 18:24:42 -07:00
Daniele Stefano Ferru 76f7836419 Provisioning: correctly use resource clients in controllers (#110737)
* Provisioning: correctly use resource clients in controllers

* better names on fields

* fix struct initialisation

* updating roundtripper tests
2025-09-06 18:13:39 -06:00
Ryan McKinley 6f3d2106c0 Chore/Folders: Add authlib client to wire and cleanup (#110683) 2025-09-06 12:14:09 +03:00
owensmallwood c072ace9cd Unified Storage: Fix enterprise sort fields missing data (#110730) 2025-09-06 11:13:29 +03:00
grafana-pr-automation[bot] 0df8e42f4e I18n: Download translations from Crowdin (#110731)
New Crowdin translations by GitHub Action

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-06 00:36:42 +00:00
Matthew Jacobson d21178e348 Alerting: Fix field names on webhook HMAC/TLS config HCL export (#110722)
tlsConfig -> tls_config
hmacConfig -> hmac_config

tls_config export still does not match TF provider, as the provider currently
treats tls_config as a schemaless map. Once this is improved, they will now
match.
2025-09-05 19:58:11 -04:00
Daniele Stefano Ferru 04c3d9bff1 Provisioning: fixing some checks in config (#110729) 2025-09-05 23:27:35 +00:00
Mihai Turdean 62cc0f9c0e Udate IAM Folder Reconciler Operator config (#110728) 2025-09-05 22:56:23 +00:00
Kevin Yu e3e0a1b8ca CloudWatch: Use the correct metric name for errors per function panel in the AWS Lambda sample dashboard (#110718)
CloudWatch: Use the correct metric name for errors per function panel
2025-09-05 17:01:56 -04:00
Will Assis 005da25698 fix: ListManagedObjects and CountManagedObjects panic when search is not configured (#110726)
fix ListManagedObjects and CountManagedObjects panicking when search index not configured
2025-09-05 20:58:12 +00:00
Stephanie Hingtgen 2750a3516a Provisioning: Add provisioning api server to clients (#110721) 2025-09-05 20:36:52 +00:00
Isabel Matwawana d58b8aff7b Docs: Add saved queries content (#101845) 2025-09-05 16:36:16 -04:00
Andrew Hackmann 9445328a59 Prometheus data source: Migration service (#107364)
* copying from secrets migration

* service runs and mig promds type

* creating data source check

* adding aws

* split into azure/aws service. feature flag. auto install

* add tests

* clean up

* lint

* add code owner

* imporvments from andres

* remove prom mig from http_server

* remove interface for testing

* add prom mig to provisining data sources so prov happens before mig

* fit into prov

* comment

* log debug instead of returning in update type

* Trigger Build

* feature flag being weird

* not public method

* copying from secrets migration

* service runs and mig promds type

* creating data source check

* adding aws

* add tests

* clean up

* imporvments from andres

* remove prom mig from http_server

* remove interface for testing

* add prom mig to provisining data sources so prov happens before mig

* fit into prov

* Trigger Build

* not public method

* remove logger import
2025-09-05 15:07:06 -05:00
Paul Marbach 7cbc55d615 Table: Update UX for uniform-reducer case in new footer and overflow (#110493)
* Table: Update UX for single-reducer use case in new footer

* all cases are working; unit tests pass

* style and code cleanup in SummaryCell

* remove e2e test for sum reducer label

* reorganize code, todo tests

* slight style cleanup

* one more little reorganization

* updates based on CI failures

* update tests and docs

* unused prop

* update table footer image

* alt text, lint issue

* update gdev to create footer dashboard and re-point e2es there, add a few new cases

* remove console.log
2025-09-05 15:46:07 -04:00
Andres Torres f9e82aba9c chore(rbac): Remove settings resources mappings (#110708) 2025-09-05 18:56:09 +00:00
Roberto Jiménez Sánchez ed2273b2d2 Provisioning: processing of jobs in job controller (#110223)
* WIP: Controller

* WIP: more changes

* Use patcher from new location

* Separate import

* Move operators to grafana/grafana

* Tidy go mod

* Remove duplicate TODO

* Wrapper for unified storage

* WIP: build unified storage client

* More attempts

* Revert update workspace

* Improve comment

* Fix linting

* Change signature of repository getter

* Add ticket numbers

* Remove question

* Read config from file for decrypt service

* Config struct for unified torage

* Add local config

* Fix compilation

* Try to configure it

* Fix linting

* Add FIXME comment

* Move reusable logic into controller config

* Remove unused

* More logic to be reused

* Extract workers into separate function

* Clean up unified storage client

* Revert a couple of files

* Remove secrets decrypter from this PR

* Revert enterprise imports

* Clean up unified storage setup logic

* Add TODO

* Revert some changes

* Remove file

* Use the expected clients

---------

Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
2025-09-05 18:28:31 +00:00
Zoltán Bedi 8e8c36203f Add support for PostgreSQL enum types in PGX datasource (#109863) 2025-09-05 20:24:54 +02:00
Roberto Jiménez Sánchez e2913815d3 Provisioning: Build resource clients for operators (#110699)
---------

Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
2025-09-05 17:52:57 +00:00
Matias Chomicki 46258ac2c1 Log Controls: Add dropdown menu for timestamp and line wrapping (#110698)
* LogListControls: add dropdown menu for timestamps

* LogListControls: add dropdown menu for line wrapping

* Update styles

* Translations

* Update tests

* Update test

* LogListControls: all events
2025-09-05 17:43:34 +00:00
owensmallwood dc1c5a610c Unified Storage: Fix broken tests (#110710)
fix broken tests
2025-09-05 17:19:13 +00:00
colin-stuart 0f54622db7 SCIM: Add logo (#110621)
* SCIM: add logo

* remove accidentally committed logo
2025-09-05 11:53:30 -05:00
Yunwen Zheng 24107abea3 SaveProvisionedDashboardForm: Reset dashboard dirty state after provisioned dashboard save (#110571)
* SaveProvisionedDashboardForm: Bugfix when changes are saved, save button still enabled
2025-09-05 12:06:52 -04:00
owensmallwood d715bda8af Unified Storage: Adds pruner to kv backend (#110549)
* WIP adding pruner to kv store impl

* pruner only keeps 20 most recent versions

* ignore grafana-kv-data folder

* extracts some stuff to pruner.go file. Adds tests. Adds kvBackendOptions.

* update logging, comments, exports kvbackendoptions fields

* update nooppruner ref

* fixes field casing in test

* fix test

* linter fixes

* remove comment

* make KvStorageBackend private

* Adds pruner key validation and tests. Fixes broken tests.

* update error message when validating pruner key
2025-09-05 10:02:11 -06:00
Sonia Aguilar f0095d84e3 Alerting: Load labels in drop-downs without blocking the interaction with the form inputs (#110648)
* load labels in dropdown async without blockig the interaction with labels inputs

* fix tests

* update translations

* remove fetchOptions unused property

* revert removing wrong lines

* Preferch rules for autocomplete and use caching

* use selectedKey for getting gops values

* revert removed lines

---------

Co-authored-by: Konrad Lalik <konradlalik@gmail.com>
2025-09-05 15:51:19 +00:00
Cory Forseth 02227855e8 Authz: propagate folder changes to Zanzana (#110599)
* wire sync hooks for folder create/update

* cleanup

* add hook tests

* fix nil context

* better context
2025-09-05 10:46:30 -05:00
Ieva d692303e76 AuthZ: Deleting managed role permissions for a specified resource (#110617)
* basics for deleting managed role permissions for a specified resource

* fix the query

* fix query tests

* storage tests

* sql tests

* add missing import

* Update pkg/registry/apis/iam/resourcepermission/storage_backend.go

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>

* PR feedback

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>

---------

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
2025-09-05 16:22:09 +01:00
Sarah Zinger ba202ebab1 ds-querier: Handle top level datasourceuids (#110616) 2025-09-05 11:08:56 -04:00
Moustafa Baiou a459d43746 Alerting: Refactor prometheus api functions
Make state and health filters public

Co-authored-by: William Wernert <william.wernert@grafana.com>
Co-authored-by: Fayzal Ghantiwala <fayzal.ghantiwala@grafana.com>
2025-09-05 10:59:16 -04:00
Yuri Tseretyan ce55d70fa5 Alerting: Refactor notification legacy storage (#110619)
* make legacy store expose only model.Receiver
* use integration as provenance type provider
* use revision RenameReceiverInRoutes
* introduce function GetReceiversNames in config revision

---------

Co-authored-by: Matthew Jacobson <matthew.jacobson@grafana.com>
2025-09-05 14:46:46 +00:00
Alex Khomenko 439fefeda8 Provisioning: Delete repo config when navigating away (#110655) 2025-09-05 17:32:26 +03:00
Stephanie Hingtgen c4bc83019d Provisioning: Fix bug in config reading (#110703) 2025-09-05 14:30:43 +00:00
Bogdan Matei 3e14a48ebb Update swagger-ui-react to fix sha.js issues (#110696) 2025-09-05 14:04:17 +00:00
Bruno 9a641c651f secrets: update test to accept []byte(nil) and []byte{} (#110630)
Co-authored-by: Matheus Macabu <macabu.matheus@gmail.com>
2025-09-05 10:19:57 -03:00
Dominik Prokop b4e63c36c3 Migration v42: HideFrom tooltip consistency migration (#110517)
* Migration to be verified: v42 HideFrom tooltip migration

* snap update

* make gen cue

* Add comments of 42 being the final version
2025-09-05 15:07:30 +02:00
Gabriel MABILLE 801fde02a7 grafana-iam: Implement resourcepermission creation (#110246)
* Extract from #108753

Co-Authored-By: mohammad-hamid <mohammad.hamid@grafana.com>

* Tackle create

Co-Authored-By: mohammad-hamid <mohammad.hamid@grafana.com>

* WIP use identity store to resolve role names

* WIP

* create role

* Remove unecessary comments

* comments

* sql templates

* test role insert tplt

* Add tests 😅

* Test permission insert template

* Test permission delete template

* Test assignment_insert template

* Manually test insertion

* Remove delete permissions. This is a create case we don't have permissions for that resource

* generate name handled by the apiserver library

* Remove comment and conversion

* Small renaming nits

* changes from main

* Add storage backend tests

* Add test to sql

* Test role contains a unique permission

* linting

* Account for pr feedback

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

* Reuse mappers

* Move function to models

* Add check between name and spec resource

* Check if the resource does not already exist

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

* fix query

* Check basic roles

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

* Account for error

* Make struct names consistent

* Nit. I prefer createAndAssignManagedRole

* Remove notifyign

* log errors instead of returning them

* Fix exist query join

* Test errors

* Remove dup

---------

Co-authored-by: mohammad-hamid <mohammad.hamid@grafana.com>
Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2025-09-05 14:22:25 +02:00
maicon 726c7ba71b search: Force index IDX_dashboard_title when searching dashboards (#110595)
Signed-off-by: Maicon Costa <maiconscosta@gmail.com>
2025-09-05 08:34:29 -03:00
Peter Štibraný bd529226a3 unified-storage search: return results matching all search terms (#110672)
* Extract common indexing code.

* Extract common search code.

* Match all terms in the search query.
2025-09-05 11:00:54 +00:00
Roberto Jiménez Sánchez feb4368de5 Provisioning: Build unified storage client for operators (#110671)
* Build unified storage client in config
* Testing ticker to count managed objects
2025-09-05 10:51:51 +00:00
Alex Khomenko 647c1424d4 Restore dashboards: Clear cache when deleting or restoring dashboards (#110513)
* Restore dashboards: Clear cache when deleting or restoring dahboards

* More cache clearing
2025-09-05 13:03:13 +03:00
Ryan McKinley 9f7101e2ad Chore/Folders: reduce direct use of settings.Cfg (#110657) 2025-09-05 12:50:19 +03:00
Dominik Prokop 9920f4b437 DatasourceSrv: Fix getInstanceSettings for type-only datasource references (#110612)
• Add handling for type-only refs like {type: 'prometheus'} in getInstanceSettings()
• Ensure consistency with get() method behavior
• Add test case verifying both methods return same results for type-only refs
2025-09-05 11:30:13 +02:00
Hugo Häggmark 5eb42ece91 Chore: clean up angular related test (#110659) 2025-09-05 11:28:51 +02:00
Roberto Jiménez Sánchez 1b9e479b68 Provisioning: Abstract away how we build discovery and dynamic clients (#110662)
Abstract away how we get discovery and dynamic clients
2025-09-05 09:22:30 +00:00
grafana-pr-automation[bot] 6a54340501 I18n: Download translations from Crowdin (#110642)
New Crowdin translations by GitHub Action

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2025-09-05 09:02:17 +00:00
Alex Khomenko a8378af6ed Provisioning: Fix loading bitbucket branches (#110651)
* Provisioning: Fetch bitbucket branches

* reuse func

* Prettier
2025-09-05 07:42:43 +00:00
698 changed files with 14773 additions and 6120 deletions
+4
View File
@@ -416,6 +416,7 @@
/e2e-playwright/dashboards/cujs/ @grafana/dashboards-squad
/e2e-playwright/dashboards/DashboardForConditionalRendering.json @grafana/dashboards-squad
/e2e-playwright/dashboards/DashboardWithAllConditionalRendering.json @grafana/dashboards-squad
/e2e-playwright/dashboards/README.md @grafana/dashboards-squad
/e2e-playwright/dashboards/AdHocFilterTest.json @grafana/datapro
/e2e-playwright/dashboards/DashboardLiveTest.json @grafana/dashboards-squad
/e2e-playwright/dashboards/DataLinkWithoutSlugTest.json @grafana/dashboards-squad
@@ -460,6 +461,7 @@
/e2e-playwright/fixtures/long-trace-response.json @grafana/observability-traces-and-profiling
/e2e-playwright/fixtures/tempo-response.json @grafana/oss-big-tent
/e2e-playwright/fixtures/prometheus-response.json @grafana/datapro
/e2e-playwright/panels-suite/canvas-scene.spec.ts @grafana/dataviz-squad
/e2e-playwright/panels-suite/dashlist.spec.ts @grafana/grafana-search-navigate-organise
/e2e-playwright/panels-suite/datagrid-data-change.spec.ts @grafana/dataviz-squad
/e2e-playwright/panels-suite/datagrid-editing-features.spec.ts @grafana/dataviz-squad
@@ -1137,6 +1139,8 @@ eslint-suppressions.json @grafanabot
# Feature toggles
/pkg/services/featuremgmt/ @grafana/grafana-backend-services-squad
# Data source migrations
/pkg/services/promtypemigration/ @grafana/partner-datasources @grafana/aws-datasources
# Kind definitions
/kinds/dashboard @grafana/dashboards-squad
+20 -8
View File
@@ -13,17 +13,29 @@ on:
required: false
permissions:
contents: write
pull-requests: write
id-token: write
contents: read
jobs:
bump-version:
runs-on: ubuntu-latest
steps:
- name: Checkout Grafana
uses: actions/checkout@v4
- uses: grafana/shared-workflows/actions/get-vault-secrets@main
with:
persist-credentials: false
repo_secrets: |
GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a
with:
app_id: ${{ vars.DELIVERY_BOT_APP_ID }}
private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }}
repositories: '["grafana"]'
permissions: '{"contents": "write", "pull_requests": "write", "workflows": "write"}'
- name: Checkout Grafana
uses: actions/checkout@v5
with:
token: ${{ steps.generate_token.outputs.token }}
- name: Update package.json versions
uses: ./pkg/build/actions/bump-version
with:
@@ -35,10 +47,10 @@ jobs:
DRY_RUN: ${{ inputs.dry_run }}
REF_NAME: ${{ github.ref_name }}
RUN_ID: ${{ github.run_id }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
run: |
git config --local user.name "github-actions[bot]"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "grafana-delivery-bot[bot]"
git config --local user.email "grafana-delivery-bot[bot]@users.noreply.github.com"
git config --local --add --bool push.autoSetupRemote true
git checkout -b "bump-version/${RUN_ID}/${VERSION}"
git add .
+70 -3
View File
@@ -56,7 +56,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
persist-credentials: false
- name: Set up version (Release Branches)
@@ -140,7 +140,7 @@ jobs:
# The downside to this is that the frontend will be built for each one when it could be reused for all of them.
# This could be a future improvement.
include:
- name: linux-amd64
- name: linux-amd64 # publish-npm relies on this step building npm packages
artifacts: targz:grafana:linux/amd64,deb:grafana:linux/amd64,rpm:grafana:linux/amd64,docker:grafana:linux/amd64,docker:grafana:linux/amd64:ubuntu,npm:grafana,storybook
verify: true
- name: linux-arm64
@@ -169,7 +169,7 @@ jobs:
verify: true
steps:
- uses: grafana/shared-workflows/actions/dockerhub-login@dockerhub-login/v1.0.2
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
persist-credentials: false
- name: Set up QEMU
@@ -197,6 +197,7 @@ jobs:
name: artifacts-${{ matrix.name }}
path: ${{ steps.build.outputs.dist-dir }}
retention-days: 1
publish-artifacts:
name: Upload artifacts
uses: grafana/grafana/.github/workflows/publish-artifact.yml@main
@@ -211,6 +212,7 @@ jobs:
run-id: ${{ github.run_id }}
bucket-path: ${{ needs.setup.outputs.version }}_${{ github.run_id }}
environment: prod
publish-dockerhub:
if: github.ref_name == 'main'
permissions:
@@ -268,3 +270,68 @@ jobs:
docker manifest push grafana/grafana:main-ubuntu
docker manifest push "grafana/grafana-dev:${VERSION}"
docker manifest push "grafana/grafana-dev:${VERSION}-ubuntu"
publish-npm-canaries:
if: github.ref_name == 'main'
name: Publish NPM canaries
uses: ./.github/workflows/release-npm.yml
permissions:
contents: read
id-token: write
needs:
- setup
- build
with:
grafana_commit: ${{ needs.setup.outputs.grafana-commit }}
version: ${{ needs.setup.outputs.version }}
build_id: ${{ github.run_id }}
version_type: "canary"
# notify-pr creates (or updates) a comment in a pull request to link to this workflow where the release artifacts are
# being built.
notify-pr:
runs-on: ubuntu-x64-small
permissions:
contents: read
id-token: write
needs:
- setup
steps:
- id: vault-secrets
uses: grafana/shared-workflows/actions/get-vault-secrets@main
with:
repo_secrets: |
GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a
with:
app_id: ${{ vars.DELIVERY_BOT_APP_ID }}
private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }}
repositories: '["grafana"]'
permissions: '{"issues": "write", "pull_requests": "write", "contents": "read"}'
- name: Find PR
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
GRAFANA_COMMIT: ${{ needs.setup.outputs.grafana-commit }}
run: echo "ISSUE_NUMBER=$(gh api "/repos/grafana/grafana/commits/${GRAFANA_COMMIT}/pulls" | jq -r '.[0].number')" >> "$GITHUB_ENV"
- name: Find Comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3
id: fc
with:
issue-number: ${{ env.ISSUE_NUMBER }}
comment-author: 'grafana-delivery-bot[bot]'
body-includes: GitHub Actions Build
token: ${{ steps.generate_token.outputs.token }}
- name: Create or update comment
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
token: ${{ steps.generate_token.outputs.token }}
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ env.ISSUE_NUMBER }}
body: |
:rocket: Your submission is now being built and packaged.
- [GitHub Actions Build](https://github.com/grafana/grafana/actions/runs/${{ github.run_id }})
- Version: ${{ needs.setup.outputs.version }}
edit-mode: replace
+149
View File
@@ -0,0 +1,149 @@
name: Release NPM packages
run-name: Publish NPM ${{ inputs.version_type }} ${{ inputs.version }}
on:
workflow_call:
inputs:
grafana_commit:
description: 'Grafana commit SHA to build against'
required: true
type: string
version:
description: 'Version to publish as'
required: true
type: string
build_id:
description: 'Run ID from the original release-build workflow'
required: true
type: string
version_type:
description: 'Version type (canary, nightly, stable)'
required: true
type: string
workflow_dispatch:
inputs:
grafana_commit:
description: 'Grafana commit SHA to build against'
required: true
version:
description: 'Version to publish as'
required: true
build_id:
description: 'Run ID from the original release-build workflow'
required: true
version_type:
description: 'Version type (canary, nightly, stable)'
required: true
permissions: {}
jobs:
# If called with version_type 'canary' or 'stable', build + publish to NPM
# If called with version_type 'nightly', just tag the given version with nightly tag. It was already published by the canary build.
publish:
name: Publish NPM packages
runs-on: github-hosted-ubuntu-x64-small
if: inputs.version_type == 'canary' || inputs.version_type == 'stable'
permissions:
contents: read
id-token: write
steps:
- name: Info
env:
GITHUB_REF: ${{ github.ref }}
GRAFANA_COMMIT: ${{ inputs.grafana_commit }}
run: |
echo "GRAFANA_COMMIT: $GRAFANA_COMMIT"
echo "github.ref: $GITHUB_REF"
- name: Checkout workflow ref
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 100
fetch-tags: false
# this will fail with "{commit} is not a valid commit" if the commit is valid but
# not in the last 100 commits.
- name: Verify commit is in workflow HEAD
env:
GIT_COMMIT: ${{ inputs.grafana_commit }}
run: ./.github/workflows/scripts/validate-commit-in-head.sh
shell: bash
- name: Map version type to NPM tag
id: npm-tag
env:
VERSION: ${{ inputs.version }}
VERSION_TYPE: ${{ inputs.version_type }}
REFERENCE_PKG: "@grafana/runtime"
run: |
TAG=$(./.github/workflows/scripts/determine-npm-tag.sh)
echo "NPM_TAG=$TAG" >> "$GITHUB_OUTPUT"
shell: bash
- name: Checkout build commit
uses: actions/checkout@v4
with:
persist-credentials: false
ref: ${{ inputs.grafana_commit }}
- name: Setup Node
uses: ./.github/actions/setup-node
# Trusted Publishing is only available in npm v11.5.1 and later
- name: Update npm
run: npm install -g npm@^11.5.1
- name: Install dependencies
run: yarn install --immutable
- name: Typecheck packages
run: yarn run packages:typecheck
- name: Version, build, and pack packages
env:
VERSION: ${{ inputs.version }}
run: |
yarn run packages:build
yarn lerna version "$VERSION" \
--exact \
--no-git-tag-version \
--no-push \
--force-publish \
--yes
yarn run packages:pack
- name: Debug packed files
run: tree -a ./npm-artifacts
- name: Validate packages
run: ./scripts/validate-npm-packages.sh
- name: Debug OIDC Claims
uses: github/actions-oidc-debugger@2e9ba5d3f4bebaad1f91a2cede055115738b7ae8
with:
audience: '${{ github.server_url }}/${{ github.repository_owner }}'
- name: Publish packages
env:
NPM_TAG: ${{ steps.npm-tag.outputs.NPM_TAG }}
run: ./scripts/publish-npm-packages.sh --dist-tag "$NPM_TAG" --registry 'https://registry.npmjs.org/'
# TODO: finish this step
tag-nightly:
name: Tag nightly release
runs-on: github-hosted-ubuntu-x64-small
needs: publish
if: inputs.version_type == 'nightly'
steps:
- name: Checkout workflow ref
uses: actions/checkout@v4
with:
persist-credentials: false
# TODO: tag the given release with nightly
+4
View File
@@ -245,3 +245,7 @@ public/mockServiceWorker.js
/e2e-playwright/test-plugins/*/dist
/apps/provisioning/cmd/job-controller/bin/
# Ignore unified storage kv store files
/grafana-kv-data
@@ -684,8 +684,8 @@ VariableSort: "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAs
VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged"
// Determine if the variable shows on dashboard
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
VariableHide: *"dontHide" | "hideLabel" | "hideVariable"
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing), `inControlsMenu` (show in a drop-down menu).
VariableHide: *"dontHide" | "hideLabel" | "hideVariable" | "inControlsMenu"
// Determine the origin of the adhoc variable filter
FilterOrigin: "dashboard"
@@ -78,7 +78,7 @@ lineage: schemas: [{
// Version of the JSON schema, incremented each time a Grafana update brings
// changes to said schema.
schemaVersion: uint16 | *41
schemaVersion: uint16 | *42
// Version of the dashboard, incremented each time the dashboard is updated.
version?: uint32
@@ -243,8 +243,8 @@ lineage: schemas: [{
#VariableRefresh: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="never|onDashboardLoad|onTimeRangeChanged")
// Determine if the variable shows on dashboard
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing).
#VariableHide: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable") @grafana(TSVeneer="type")
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing), 3 (show under the controls dropdown menu).
#VariableHide: 0 | 1 | 2 | 3 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable|inControlsMenu") @grafana(TSVeneer="type")
// Sort variable options
// Accepted values are:
@@ -78,7 +78,7 @@ lineage: schemas: [{
// Version of the JSON schema, incremented each time a Grafana update brings
// changes to said schema.
schemaVersion: uint16 | *41
schemaVersion: uint16 | *42
// Version of the dashboard, incremented each time the dashboard is updated.
version?: uint32
@@ -243,8 +243,8 @@ lineage: schemas: [{
#VariableRefresh: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="never|onDashboardLoad|onTimeRangeChanged")
// Determine if the variable shows on dashboard
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing).
#VariableHide: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable") @grafana(TSVeneer="type")
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing), 3 (show under the controls dropdown menu).
#VariableHide: 0 | 1 | 2 | 3 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable|inControlsMenu") @grafana(TSVeneer="type")
// Sort variable options
// Accepted values are:
@@ -688,8 +688,8 @@ VariableSort: "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAs
VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged"
// Determine if the variable shows on dashboard
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
VariableHide: *"dontHide" | "hideLabel" | "hideVariable"
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing), `inControlsMenu` (show in a drop-down menu).
VariableHide: *"dontHide" | "hideLabel" | "hideVariable" | "inControlsMenu"
// Determine the origin of the adhoc variable filter
FilterOrigin: "dashboard"
@@ -1282,14 +1282,15 @@ func NewDashboardVariableOption() *DashboardVariableOption {
}
// Determine if the variable shows on dashboard
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing), `inControlsMenu` (show in a drop-down menu).
// +k8s:openapi-gen=true
type DashboardVariableHide string
const (
DashboardVariableHideDontHide DashboardVariableHide = "dontHide"
DashboardVariableHideHideLabel DashboardVariableHide = "hideLabel"
DashboardVariableHideHideVariable DashboardVariableHide = "hideVariable"
DashboardVariableHideDontHide DashboardVariableHide = "dontHide"
DashboardVariableHideHideLabel DashboardVariableHide = "hideLabel"
DashboardVariableHideHideVariable DashboardVariableHide = "hideVariable"
DashboardVariableHideInControlsMenu DashboardVariableHide = "inControlsMenu"
)
// Options to config when to refresh a variable
@@ -278,15 +278,15 @@ func TestConversionMetrics(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{UID: "test-uid-2"},
Spec: common.Unstructured{Object: map[string]any{
"title": "test dashboard",
"schemaVersion": 41,
"schemaVersion": 42,
}},
},
target: &dashv0.Dashboard{},
expectSuccess: true,
expectedSourceAPI: dashv1.APIVERSION,
expectedTargetAPI: dashv0.APIVERSION,
expectedSourceSchema: "41",
expectedTargetSchema: "41", // V1→V0 keeps same schema version
expectedSourceSchema: "42",
expectedTargetSchema: "42", // V1→V0 keeps same schema version
},
{
name: "successful v2alpha1 to v2beta1 conversion",
@@ -600,7 +600,7 @@ func TestConversionLogging(t *testing.T) {
"targetVersionAPI": dashv1.APIVERSION,
"dashboardUID": "test-uid-log-1",
"sourceSchemaVersion": "20",
"targetSchemaVersion": fmt.Sprintf("%d", 41), // LATEST_VERSION
"targetSchemaVersion": fmt.Sprintf("%d", 42), // LATEST_VERSION
},
},
{
@@ -620,7 +620,7 @@ func TestConversionLogging(t *testing.T) {
"targetVersionAPI": dashv1.APIVERSION,
"dashboardUID": "test-uid-log-2",
"sourceSchemaVersion": "5",
"targetSchemaVersion": fmt.Sprintf("%d", 41), // LATEST_VERSION
"targetSchemaVersion": fmt.Sprintf("%d", 42), // LATEST_VERSION
},
},
}
@@ -8,7 +8,7 @@ import (
const (
MIN_VERSION = 13
LATEST_VERSION = 41
LATEST_VERSION = 42
// The pluginVersion to set after simulating auto-migrate for angular panels
pluginVersionForAutoMigrate = "12.1.0"
@@ -66,6 +66,7 @@ func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionM
39: V39,
40: V40,
41: V41,
42: V42,
}
}
@@ -0,0 +1,102 @@
package schemaversion
import "context"
// V42 ensures that when a field is hidden from visualization, it is also hidden from tooltips.
//
// This migration addresses the inconsistency where fields could be hidden from visualizations
// (hideFrom.viz = true) but would still appear in tooltips. To prevent user confusion and ensure
// consistent behavior, this migration automatically sets hideFrom.tooltip = true for any field
// configuration override that has hideFrom.viz = true.
//
// The migration specifically targets field configuration overrides, including the special
// __systemRef override, and updates the hideFrom object to include tooltip: true whenever
// viz: true is found.
//
// Example transformation:
//
// Before migration:
//
// fieldConfig: {
// overrides: [{
// properties: [{
// id: "custom.hideFrom",
// value: { viz: true }
// }]
// }]
// }
//
// After migration:
//
// fieldConfig: {
// overrides: [{
// properties: [{
// id: "custom.hideFrom",
// value: { viz: true, tooltip: true }
// }]
// }]
// }
func V42(_ context.Context, dash map[string]interface{}) error {
dash["schemaVersion"] = int(42)
// Get panels from dashboard
panels, ok := dash["panels"].([]interface{})
if !ok {
return nil
}
// Process each panel
for _, panelInterface := range panels {
panel, ok := panelInterface.(map[string]interface{})
if !ok {
continue
}
migrateHideFromForPanel(panel)
}
return nil
}
// migrateHideFromForPanel processes a single panel and its nested panels
func migrateHideFromForPanel(panel map[string]interface{}) {
// Process the panel's field config
if fieldConfig, ok := panel["fieldConfig"].(map[string]interface{}); ok {
if overrides, ok := fieldConfig["overrides"].([]interface{}); ok {
for _, overrideInterface := range overrides {
override, ok := overrideInterface.(map[string]interface{})
if !ok {
continue
}
if properties, ok := override["properties"].([]interface{}); ok {
for _, propertyInterface := range properties {
property, ok := propertyInterface.(map[string]interface{})
if !ok {
continue
}
// Check if this is a custom.hideFrom property
if id, ok := property["id"].(string); ok && id == "custom.hideFrom" {
if value, ok := property["value"].(map[string]interface{}); ok {
// If viz is true, also set tooltip to true
if GetBoolValue(value, "viz") {
value["tooltip"] = true
}
}
}
}
}
}
}
}
// Process nested panels (for rows)
if nestedPanels, ok := panel["panels"].([]interface{}); ok {
for _, nestedPanelInterface := range nestedPanels {
if nestedPanel, ok := nestedPanelInterface.(map[string]interface{}); ok {
migrateHideFromForPanel(nestedPanel)
}
}
}
}
@@ -0,0 +1,422 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestV42(t *testing.T) {
tests := []migrationTestCase{
{
name: "hideFrom.viz = true should also set hideFrom.tooltip = true",
input: map[string]interface{}{
"title": "Test Dashboard",
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"title": "Panel 1",
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"matcher": map[string]interface{}{
"id": "byName",
"options": "Field 1",
},
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"viz": true,
},
},
},
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 42,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"title": "Panel 1",
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"matcher": map[string]interface{}{
"id": "byName",
"options": "Field 1",
},
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"viz": true,
"tooltip": true,
},
},
},
},
},
},
},
},
},
},
{
name: "hideFrom.viz = false should not change",
input: map[string]interface{}{
"title": "Test Dashboard",
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"title": "Panel 1",
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"viz": false,
},
},
},
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 42,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"title": "Panel 1",
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"viz": false,
},
},
},
},
},
},
},
},
},
},
{
name: "multiple panels with hideFrom.viz = true",
input: map[string]interface{}{
"title": "Test Dashboard",
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"viz": true,
},
},
},
},
},
},
},
map[string]interface{}{
"id": 2,
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"viz": true,
"legend": false,
},
},
},
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 42,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"viz": true,
"tooltip": true,
},
},
},
},
},
},
},
map[string]interface{}{
"id": 2,
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"viz": true,
"legend": false,
"tooltip": true,
},
},
},
},
},
},
},
},
},
},
{
name: "panel without hideFrom property",
input: map[string]interface{}{
"title": "Test Dashboard",
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"title": "Panel 1",
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"properties": []interface{}{
map[string]interface{}{
"id": "unit",
"value": "short",
},
},
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 42,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"title": "Panel 1",
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"properties": []interface{}{
map[string]interface{}{
"id": "unit",
"value": "short",
},
},
},
},
},
},
},
},
},
{
name: "nested panels in rows should also be migrated",
input: map[string]interface{}{
"title": "Test Dashboard",
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "row",
"title": "Row 1",
"panels": []interface{}{
map[string]interface{}{
"id": 2,
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"viz": true,
},
},
},
},
},
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 42,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "row",
"title": "Row 1",
"panels": []interface{}{
map[string]interface{}{
"id": 2,
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"viz": true,
"tooltip": true,
},
},
},
},
},
},
},
},
},
},
},
},
{
name: "__systemRef override should also be migrated",
input: map[string]interface{}{
"title": "Test Dashboard",
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"__systemRef": "hideSeriesFrom",
"matcher": map[string]interface{}{
"id": "byNames",
"options": map[string]interface{}{
"mode": "exclude",
"names": []interface{}{"foo"},
"prefix": "All except:",
"readOnly": true,
},
},
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"legend": false,
"tooltip": false,
"viz": true,
},
},
},
},
},
},
},
},
},
expected: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 42,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"fieldConfig": map[string]interface{}{
"overrides": []interface{}{
map[string]interface{}{
"__systemRef": "hideSeriesFrom",
"matcher": map[string]interface{}{
"id": "byNames",
"options": map[string]interface{}{
"mode": "exclude",
"names": []interface{}{"foo"},
"prefix": "All except:",
"readOnly": true,
},
},
"properties": []interface{}{
map[string]interface{}{
"id": "custom.hideFrom",
"value": map[string]interface{}{
"legend": false,
"tooltip": true,
"viz": true,
},
},
},
},
},
},
},
},
},
},
{
name: "dashboard without panels",
input: map[string]interface{}{
"title": "Test Dashboard",
},
expected: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 42,
},
},
{
name: "panel without fieldConfig",
input: map[string]interface{}{
"title": "Test Dashboard",
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"title": "Panel 1",
},
},
},
expected: map[string]interface{}{
"title": "Test Dashboard",
"schemaVersion": 42,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"title": "Panel 1",
},
},
},
},
}
runMigrationTests(t, tests, schemaversion.V42)
}
@@ -0,0 +1,161 @@
{
"title": "v42 Migration Test - HideFrom Tooltip",
"schemaVersion": 41,
"panels": [
{
"id": 1,
"title": "Panel with hideFrom.viz = true",
"type": "timeseries",
"fieldConfig": {
"defaults": {},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Field1"
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"viz": true
}
}
]
}
]
}
},
{
"id": 2,
"title": "Panel with multiple overrides",
"type": "graph",
"fieldConfig": {
"defaults": {},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Field2"
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"viz": true,
"legend": false
}
}
]
},
{
"matcher": {
"id": "__systemRef",
"options": "hiddenSeries"
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"viz": true
}
}
]
}
]
}
},
{
"id": 3,
"type": "row",
"title": "Row with nested panels",
"collapsed": true,
"panels": [
{
"id": 4,
"title": "Nested panel with hideFrom",
"type": "stat",
"fieldConfig": {
"defaults": {},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/.*/"
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"viz": true
}
}
]
}
]
}
},
{
"id": 5,
"title": "Panel without hideFrom",
"type": "table",
"fieldConfig": {
"defaults": {},
"overrides": [
{
"properties": [
{
"id": "unit",
"value": "short"
}
]
}
]
}
}
]
},
{
"id": 6,
"title": "Panel with viz false (should not be modified)",
"type": "gauge",
"fieldConfig": {
"defaults": {},
"overrides": [
{
"properties": [
{
"id": "custom.hideFrom",
"value": {
"viz": false,
"tooltip": false
}
}
]
}
]
}
},
{
"id": 7,
"title": "Panel with already set tooltip (should not be modified)",
"type": "barchart",
"fieldConfig": {
"defaults": {},
"overrides": [
{
"properties": [
{
"id": "custom.hideFrom",
"value": {
"viz": true,
"tooltip": false
}
}
]
}
]
}
}
]
}
@@ -125,7 +125,7 @@
}
],
"refresh": "1m",
"schemaVersion": 41,
"schemaVersion": 42,
"style": "dark",
"tags": [
"example-service",
@@ -3886,7 +3886,7 @@
}
],
"refresh": "1m",
"schemaVersion": 41,
"schemaVersion": 42,
"style": "dark",
"tags": [
"example-service",
@@ -3888,7 +3888,7 @@
}
],
"refresh": "1m",
"schemaVersion": 41,
"schemaVersion": 42,
"style": "dark",
"tags": [
"example-service",
@@ -2262,7 +2262,7 @@
}
],
"refresh": "5m",
"schemaVersion": 41,
"schemaVersion": 42,
"style": "dark",
"tags": [
"metrics",
@@ -62,7 +62,7 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"templating": {
"list": [
{
@@ -108,7 +108,7 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"templating": {
"list": [
{
@@ -938,7 +938,7 @@
}
],
"refresh": "30s",
"schemaVersion": 41,
"schemaVersion": 42,
"style": "dark",
"tags": [
"sample-monitoring"
@@ -482,6 +482,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V16 Grid Layout Migration Test Dashboard"
}
@@ -312,6 +312,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V17 MinSpan to MaxPerRow Migration Test Dashboard"
}
@@ -157,6 +157,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V18 Gauge Options Migration Test Dashboard"
}
@@ -201,6 +201,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V19 Panel Links Migration Test Dashboard"
}
@@ -215,7 +215,7 @@
}
],
"refresh": "5s",
"schemaVersion": 41,
"schemaVersion": 42,
"style": "dark",
"tags": [
"migration-test"
@@ -161,6 +161,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V21 Data Links Series to Field Migration Test Dashboard"
}
@@ -88,6 +88,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V22 Table Panel Styles Test"
}
@@ -30,7 +30,7 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"templating": {
"list": [
{
@@ -1369,5 +1369,5 @@
}
],
"refresh": "",
"schemaVersion": 41
}
"schemaVersion": 42
}
@@ -61,7 +61,7 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"templating": {
"list": [
{
@@ -68,5 +68,5 @@
}
],
"refresh": "",
"schemaVersion": 41
"schemaVersion": 42
}
@@ -64,7 +64,7 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"templating": {
"list": [
{
@@ -1,7 +1,7 @@
{
"panels": [],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"templating": {
"list": [
{
@@ -262,7 +262,7 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"templating": {
"list": [
{
@@ -535,7 +535,7 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"templating": {
"list": []
},
@@ -19,7 +19,7 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"templating": {
"list": [
{
@@ -368,6 +368,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V30 Value Mappings and Tooltip Options Migration Test Dashboard"
}
@@ -285,6 +285,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V31 LabelsToFields Merge Migration Test Dashboard"
}
@@ -124,7 +124,7 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"templating": {
"list": [
{
@@ -265,6 +265,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V33 Panel Datasource Name to Ref Test"
}
@@ -634,6 +634,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "CloudWatch Multiple Statistics Test Dashboard"
}
@@ -255,6 +255,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "X-Axis Visibility Test Dashboard"
}
@@ -322,7 +322,7 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"templating": {
"list": [
{
@@ -125,6 +125,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V37 Legend Normalization Test Dashboard"
}
@@ -218,6 +218,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V38 Table Migration Comprehensive Test Dashboard"
}
@@ -218,6 +218,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V38 Table Migration Test Dashboard"
}
@@ -154,6 +154,6 @@
}
],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"title": "V39 TimeSeriesTable Transformation Migration Test Dashboard"
}
@@ -1,7 +1,7 @@
{
"panels": [],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"time": {
"from": "now-6h",
"to": "now"
@@ -1,7 +1,7 @@
{
"panels": [],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"time": {
"from": "now-6h",
"to": "now"
@@ -1,7 +1,7 @@
{
"panels": [],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"time": {
"from": "now-6h",
"to": "now"
@@ -1,7 +1,7 @@
{
"panels": [],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"time": {
"from": "now-6h",
"to": "now"
@@ -1,7 +1,7 @@
{
"panels": [],
"refresh": "1m",
"schemaVersion": 41,
"schemaVersion": 42,
"time": {
"from": "now-6h",
"to": "now"
@@ -1,7 +1,7 @@
{
"panels": [],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"time": {
"from": "now-6h",
"to": "now"
@@ -1,7 +1,7 @@
{
"panels": [],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"time": {
"from": "now-6h",
"to": "now"
@@ -1,5 +1,5 @@
{
"schemaVersion": 41,
"schemaVersion": 42,
"timepicker": {
"refresh_intervals": [
"5s",
@@ -1,7 +1,7 @@
{
"panels": [],
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"time": {
"from": "now-6h",
"to": "now"
@@ -0,0 +1,165 @@
{
"panels": [
{
"fieldConfig": {
"defaults": {},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Field1"
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"tooltip": true,
"viz": true
}
}
]
}
]
},
"id": 1,
"title": "Panel with hideFrom.viz = true",
"type": "timeseries"
},
{
"fieldConfig": {
"defaults": {},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Field2"
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"legend": false,
"tooltip": true,
"viz": true
}
}
]
},
{
"matcher": {
"id": "__systemRef",
"options": "hiddenSeries"
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"tooltip": true,
"viz": true
}
}
]
}
]
},
"id": 2,
"title": "Panel with multiple overrides",
"type": "graph"
},
{
"collapsed": true,
"id": 3,
"panels": [
{
"fieldConfig": {
"defaults": {},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/.*/"
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"tooltip": true,
"viz": true
}
}
]
}
]
},
"id": 4,
"title": "Nested panel with hideFrom",
"type": "stat"
},
{
"fieldConfig": {
"defaults": {},
"overrides": [
{
"properties": [
{
"id": "unit",
"value": "short"
}
]
}
]
},
"id": 5,
"title": "Panel without hideFrom",
"type": "table"
}
],
"title": "Row with nested panels",
"type": "row"
},
{
"fieldConfig": {
"defaults": {},
"overrides": [
{
"properties": [
{
"id": "custom.hideFrom",
"value": {
"tooltip": false,
"viz": false
}
}
]
}
]
},
"id": 6,
"title": "Panel with viz false (should not be modified)",
"type": "gauge"
},
{
"fieldConfig": {
"defaults": {},
"overrides": [
{
"properties": [
{
"id": "custom.hideFrom",
"value": {
"tooltip": true,
"viz": true
}
}
]
}
]
},
"id": 7,
"title": "Panel with already set tooltip (should not be modified)",
"type": "barchart"
}
],
"schemaVersion": 42,
"title": "v42 Migration Test - HideFrom Tooltip"
}
+1 -1
View File
@@ -109,7 +109,7 @@ func LoadConfigFromEnv() (*Config, error) {
cfg.KubeConfig = kubeConfig
}
cfg.ZanzanaClient.Address = os.Getenv("ZANZANA_ADDR")
cfg.ZanzanaClient.URL = os.Getenv("ZANZANA_ADDR")
cfg.ZanzanaClient.Token = os.Getenv("ZANZANA_TOKEN")
cfg.ZanzanaClient.TokenExchangeURL = os.Getenv("TOKEN_EXCHANGE_URL")
cfg.ZanzanaClient.ServerCertFile = os.Getenv("ZANZANA_SERVER_CERT_FILE")
-2
View File
@@ -744,8 +744,6 @@ github.com/grafana/grafana-aws-sdk v1.1.0 h1:G0fvwbQmHw14c5RXPd7Gnw9ZQcgzl139LtM
github.com/grafana/grafana-aws-sdk v1.1.0/go.mod h1:7e+47EdHynteYWGoT5Ere9KeOXQObsk8F0vkOLQ1tz8=
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA=
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0/go.mod h1:H9sVh9A4yg5egMGZeh0mifxT1Q/uqwKe1LBjBJU6pN8=
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs=
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ=
github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho=
github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4=
github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0=
@@ -1,6 +1,10 @@
package v0alpha1
ServiceAccountSpec: {
disabled: bool |* false
plugin: string
role: OrgRole
title: string
disabled: bool
}
OrgRole: "None" | "Viewer" | "Editor" | "Admin" @cuetsy(kind="enum")
@@ -2,13 +2,27 @@
package v0alpha1
// +k8s:openapi-gen=true
type ServiceAccountOrgRole string
const (
ServiceAccountOrgRoleNone ServiceAccountOrgRole = "None"
ServiceAccountOrgRoleViewer ServiceAccountOrgRole = "Viewer"
ServiceAccountOrgRoleEditor ServiceAccountOrgRole = "Editor"
ServiceAccountOrgRoleAdmin ServiceAccountOrgRole = "Admin"
)
// +k8s:openapi-gen=true
type ServiceAccountSpec struct {
Title string `json:"title"`
Disabled bool `json:"disabled"`
Disabled bool `json:"disabled"`
Plugin string `json:"plugin"`
Role ServiceAccountOrgRole `json:"role"`
Title string `json:"title"`
}
// NewServiceAccountSpec creates a new ServiceAccountSpec object.
func NewServiceAccountSpec() *ServiceAccountSpec {
return &ServiceAccountSpec{}
return &ServiceAccountSpec{
Disabled: false,
}
}
@@ -1877,13 +1877,6 @@ func schema_pkg_apis_iam_v0alpha1_ServiceAccountSpec(ref common.ReferenceCallbac
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"title": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"disabled": {
SchemaProps: spec.SchemaProps{
Default: false,
@@ -1891,8 +1884,29 @@ func schema_pkg_apis_iam_v0alpha1_ServiceAccountSpec(ref common.ReferenceCallbac
Format: "",
},
},
"plugin": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"role": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"title": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"title", "disabled"},
Required: []string{"disabled", "plugin", "role", "title"},
},
},
}
@@ -25,6 +25,11 @@ func (c *ZanzanaPermissionStore) SetFolderParent(ctx context.Context, namespace,
return err
}
if parentUID == "" {
// Setting the parent to empty means the folder is at root which Zanzana doesn't care about.
return nil
}
user, err := toFolderTuple(parentUID)
if err != nil {
return err
+4 -3
View File
@@ -6,7 +6,6 @@ import (
"net/http"
"github.com/grafana/authlib/authn"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
utilnet "k8s.io/apimachinery/pkg/util/net"
)
@@ -19,20 +18,22 @@ type tokenExchanger interface {
type RoundTripper struct {
client tokenExchanger
transport http.RoundTripper
audience string
}
// NewRoundTripper constructs a RoundTripper that exchanges the provided token per request
// and forwards the request to the provided base transport.
func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper) *RoundTripper {
func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper, audience string) *RoundTripper {
return &RoundTripper{
client: tokenExchangeClient,
transport: base,
audience: audience,
}
}
func (t *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
tokenResponse, err := t.client.Exchange(req.Context(), authn.TokenExchangeRequest{
Audiences: []string{provisioning.GROUP},
Audiences: []string{t.audience},
Namespace: "*",
})
if err != nil {
@@ -34,7 +34,7 @@ func TestRoundTripper_SetsAccessTokenHeader(t *testing.T) {
rr := httptest.NewRecorder()
rr.WriteHeader(http.StatusOK)
return rr.Result(), nil
}))
}), "example-audience")
req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example", nil)
resp, err := tr.RoundTrip(req)
@@ -50,7 +50,7 @@ func TestRoundTripper_PropagatesExchangeError(t *testing.T) {
tr := NewRoundTripper(&fakeExchanger{err: io.EOF}, roundTripperFunc(func(_ *http.Request) (*http.Response, error) {
t.Fatal("transport should not be called on exchange error")
return nil, nil
}))
}), "example-audience")
req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example", nil)
resp, err := tr.RoundTrip(req)
-2
View File
@@ -1903,8 +1903,6 @@ public_key_retrieval_disabled = false
public_key_retrieval_on_startup = false
# Enter a comma-separated list of plugin identifiers to avoid loading (including core plugins). These plugins will be hidden in the catalog.
disable_plugins =
# Comma separated list of plugin ids for which angular deprecation UI should be disabled
hide_angular_deprecation =
# Comma separated list of plugin ids for which environment variables should be forwarded. Used only when feature flag pluginsSkipHostEnvVars is enabled.
forward_host_env_vars =
# Comma separated list of plugin ids to install as part of the startup process.
File diff suppressed because it is too large Load Diff
+1
View File
@@ -94,6 +94,7 @@
"rows-to-fields": (import '../dev-dashboards/transforms/rows-to-fields.json'),
"shared_queries": (import '../dev-dashboards/panel-common/shared_queries.json'),
"slow_queries_and_annotations": (import '../dev-dashboards/scenarios/slow_queries_and_annotations.json'),
"table_footer": (import '../dev-dashboards/panel-table/table_footer.json'),
"table_kitchen_sink": (import '../dev-dashboards/panel-table/table_kitchen_sink.json'),
"table_markdown": (import '../dev-dashboards/panel-table/table_markdown.json'),
"table_pagination": (import '../dev-dashboards/panel-table/table_pagination.json'),
@@ -75,6 +75,16 @@ refs:
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-overrides/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-overrides/
saved-queries:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#saved-queries
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#saved-queries
save-query:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#save-a-query
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#save-a-query
---
## Create a dashboard
@@ -108,11 +118,21 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee
{{< figure class="float-right" src="/media/docs/grafana/dashboards/screenshot-data-source-selector-10.0.png" max-width="800px" alt="Select data source modal" >}}
The **Edit panel** view opens with your data source selected.
You can change the panel data source later using the drop-down in the **Query** tab of the panel editor if needed.
You can change the panel data source later using the drop-down in the **Queries** tab of the panel editor if needed.
For more information about data sources, refer to [Data sources](ref:data-sources) for specific guidelines.
1. Write or construct a query in the query language of your data source.
1. To add a query, do one of the following:
- Write or construct a query in the query language of your data source.
- Click **+ Add from saved queries** to add a previously saved query.
- If you've already written a query, you can click the **Replace with saved query** icon to use a previously saved query instead.
1. (Optional) To [save the query](ref:save-query) for reuse, click the **Save query** icon.
{{< admonition type="note" >}}
[Saved queries](ref:saved-queries) is in [public preview](https://grafana.com/docs/release-life-cycle/) in Grafana Enterprise and Cloud only.
{{< /admonition >}}
1. Click **Refresh** to query the data source.
1. In the visualization list, select a visualization type.
@@ -298,16 +298,21 @@ groupByNode(summarize(movingAverage(apps.$app.$server.counters.requests.count, 5
## Add ad hoc filters
_Ad hoc filters_ are one of the most complex and flexible variable options available.
Instead of a regular list of variable options, this variable allows you to build a dashboard-wide ad hoc query.
Instead of creating a variable for each dimension by which you want to filter, ad hoc filters automatically create variables (key/value pairs) for all the dimensions returned by your data source query.
This allows you to apply filters dashboard-wide.
Ad hoc filters let you add label/value filters that are automatically added to all metric queries that use the specified data source.
Unlike other variables, you don't use ad hoc filters in queries.
Instead, you use ad hoc filters to write filters for existing queries.
{{< admonition type="note" >}}
Not all data sources support ad hoc filters.
Examples of those that do include Prometheus, Loki, InfluxDB, and Elasticsearch.
{{< /admonition >}}
The following data sources support ad hoc filters:
- Dashboard - Use this special data source to [apply ad hoc filters to data from unsupported data sources](#filter-any-data-using-the-dashboard-data-source).
- Prometheus
- Loki
- InfluxDB
- Elasticsearch
- OpenSearch
To create an ad hoc filter, follow these steps:
@@ -324,6 +329,60 @@ To create an ad hoc filter, follow these steps:
Now you can [filter data on the dashboard](ref:filter-dashboard).
### Filter any data using the Dashboard data source
In cases where a data source doesn't support the use of ad hoc filters, you can use the Dashboard data source to reference that data, and then filter it in a new panel.
This allows you to bypass the limitations of the data source in the source panel.
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-dashboard-ds-v12.2.png" max-width="750px" alt="The query section of a panel with the Dashboard data source configured" >}}
To use ad hoc filters on data from an unsupported data source, follow these steps:
1. Navigate to the dashboard with the panel with the data you want to filter.
1. Click **Edit** in top-right corner of the dashboard.
1. At the top of the dashboard, click **Add** and select **Visualization** in the drop-down list.
1. In the **Queries** tab of the edit panel view, enter `Dashboard` in the **Data source** field and select **-- Dashboard --**.
1. In the query configuration section, make the following selections:
- **Source panel** - Choose the panel with the source data.
- **Data** - Select **All Data** to use the data of the panel, and not just the annotations. This is the default selection.
- **AdHoc Filters** - Toggle on the switch to make the data from the referenced panel filterable.
{{< admonition type="note">}}
If you're referencing multiple panels in a dashboard with the Dashboard data source, you can only use one of those source panels at a time for ad hoc filtering.
{{< /admonition >}}
1. Configure any other needed options for the panel.
1. Click **Save dashboard**.
Now you can filter the data from the source panel by way of the Dashboard data source.
Add as many panels as you need.
### Dashboard drilldown with ad hoc filters
In table and bar chart visualizations, you can apply ad hoc filters directly from the visualization.
To quickly apply ad hoc filter variables, follow these steps:
1. To display the filter icons, hover your cursor over the table cell with the value for which you want to filter. In this example, the cell value is `ConfigMap Updated`, which is in the `alertname` column:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-icon-v12.2.png" max-width="550px" alt="Table and bar chart with ad hoc filter icon displayed on a table cell" >}}
In bar chart visualizations, hover and click the bar to display the filter button:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-icon-bar-v12.2.png" max-width="300px" alt="The ad hoc filter button in a bar chart tooltip">}}
1. Click the add filter icon.
The variable pair `alertname = ConfigMap Updated` is added to the ad hoc filter and all panels using the same data source that include that variable value are filtered by that value:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-applied-v12.2.png" max-width="550px" alt="Table and bar chart, filtered" >}}
If one of the panels in the dashboard using that data source doesn't include that variable value, the panel won't return any data. In this example, the variable pair `_name_ = ALERTS` has been added to the ad hoc filter so the bar chart doesn't return any results:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-no-data-v12.2.png" max-width="650px" alt="Table, filtered and bar chart returning no results" >}}
In cases where the data source you're using doesn't support ad hoc filtering, consider using the special Dashboard data source.
For more information, refer to [Filter any data using the Dashboard data source](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/variables/add-template-variables/#filter-any-data-using-the-dashboard-data-source).
<!-- vale Grafana.Spelling = YES -->
<!-- vale Grafana.WordList = YES -->
@@ -255,34 +255,34 @@ After you have provisioned a data source you cannot edit it.
**Example of a Prometheus data source configuration:**
```yaml
apiVersion: 1
```yaml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://localhost:9090
jsonData:
httpMethod: POST
manageAlerts: true
allowAsRecordingRulesTarget: true
prometheusType: Prometheus
prometheusVersion: 3.3.0
cacheLevel: 'High'
disableRecordingRules: false
timeInterval: 10s # Prometheus scrape interval
incrementalQueryOverlapWindow: 10m
exemplarTraceIdDestinations:
# Field with internal link pointing to data source in Grafana.
# datasourceUid value can be anything, but it should be unique across all defined data source uids.
- datasourceUid: my_jaeger_uid
name: traceID
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://localhost:9090
jsonData:
httpMethod: POST
manageAlerts: true
allowAsRecordingRulesTarget: true
prometheusType: Prometheus
prometheusVersion: 3.3.0
cacheLevel: 'High'
disableRecordingRules: false
timeInterval: 10s # Prometheus scrape interval
incrementalQueryOverlapWindow: 10m
exemplarTraceIdDestinations:
# Field with internal link pointing to data source in Grafana.
# datasourceUid value can be anything, but it should be unique across all defined data source uids.
- datasourceUid: my_jaeger_uid
name: traceID
# Field with external link.
- name: traceID
url: 'http://localhost:3000/explore?orgId=1&left=%5B%22now-1h%22,%22now%22,%22Jaeger%22,%7B%22query%22:%22$${__value.raw}%22%7D%5D'
```
# Field with external link.
- name: traceID
url: 'http://localhost:3000/explore?orgId=1&left=%5B%22now-1h%22,%22now%22,%22Jaeger%22,%7B%22query%22:%22$${__value.raw}%22%7D%5D'
```
## Azure authentication settings
@@ -11,6 +11,17 @@ labels:
- enterprise
- oss
title: Get started with Explore
refs:
saved-queries:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#saved-queries
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#saved-queries
save-query:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#save-a-query
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#save-a-query
weight: 5
---
@@ -61,8 +72,14 @@ Explore consists of a toolbar, outline, query editor, the ability to add multipl
- **Query editor** - Interface where you construct the query for a specific data source. Query editor elements differ based on data source. In order to run queries across multiple data sources you need to select **Mixed** from the data source picker.
- **+Add query** - Add additional queries.
- **Query history** - Query history contains the list of queries that you created in Explore. Refer to [Query history](/docs/grafana/<GRAFANA_VERSION>/explore/query-management/#query-history) for detailed information on working with your query history.
- **+ Add query** - Add additional queries.
- **+ Add from saved queries** - Add a saved query. If you've already written a query, you can click the **Replace with saved query** icon to use a previously saved query instead. To [save the query](ref:save-query) for reuse, click the **Save query** icon.
{{< admonition type="note" >}}
[Saved queries](ref:saved-queries) is in [public preview](https://grafana.com/docs/release-life-cycle/) in Grafana Enterprise and Cloud only.
{{< /admonition >}}
- **Query history** - Query history contains the list of queries that you created in Explore. You can also add queries from the history to your saved queries. Refer to [Query history](/docs/grafana/<GRAFANA_VERSION>/explore/query-management/#query-history) for detailed information on working with your query history.
- **Query inspector** - Provides detailed statistics regarding your query. Inspector functions as a kind of debugging tool that "inspects" your query. It provides query statistics under **Stats**, request response time under **Query**, data frame details under **{} JSON**, and the shape of your data under **Data**. Refer to [Query inspector in Explore](/docs/grafana/latest/explore/explore-inspector/) for additional information.
## Access Explore
+7
View File
@@ -10,6 +10,12 @@ labels:
- oss
title: Query management in Explore
weight: 10
refs:
saved-queries:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#saved-queries
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#saved-queries
---
# Query management in Explore
@@ -49,6 +55,7 @@ The Query history depicts a history of your queries for the past two weeks, unle
- Copy a shortened link with the query to the clipboard.
- Delete a query.
- Star a query.
- Add a query from your history to your [saved queries](ref:saved-queries).
By default, query history shows you newest queries first. Click the sort box in the upper right to change to **Oldest first** to older queries first. You can search your queries using keywords.
@@ -278,6 +278,17 @@ When linking to another dashboard that uses template variables, select variable
If you want to add all of the current dashboard's variables to the URL, then use `${__all_variables}`.
When you link to another dashboard, ensure that:
- The target dashboard has the same variable name. If it doesn't (for example, `server` in the source dashboard and `host` in the target), you must align them or explicitly map values (for example, `&var-host=${server}`).
- You use the variable _name_, and not the label. Labels are only used as display text and aren't recognized in URLs.
For example, if you have a variable with the name `var-server` and the label `ChooseYourServer`, you must use `var-server` in the URL, as shown in the following table:
| Correct link | Incorrect link |
| ---------------------------------------------- | -------------------------------------------------------- |
| `/d/xxxx/dashboard-b?orgId=1&var-server=web02` | `/d/xxxx/dashboard-b?orgId=1&var-ChooseYourServer=web02` |
## Add data links or actions {#add-a-data-link}
The following tasks describe how to configure data links and actions.
@@ -296,9 +307,7 @@ To add a data link, follow these steps:
This is a human-readable label for the link displayed in the UI. This is a required field.
1. Enter the **URL** to which you want to link.
To add a data link variable, click in the **URL** field and enter `$` or press Ctrl+Space or Cmd+Space to see a list of available variables. This is a required field.
1. (Optional) To add a data link variable, click in the **URL** field and enter `$` or press Ctrl+Space or Cmd+Space to see a list of available variables.
1. If you want the link to open in a new tab, toggle the **Open in a new tab** switch.
1. If you want the data link to open with a single click on the visualization, toggle the **One click** switch.
@@ -29,29 +29,39 @@ refs:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/transform-data/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/transform-data/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/transform-data/
the-overview-of-grafana-alerting:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/
destination: /docs/grafana-cloud/alerting-and-irm/alerting/
table:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/visualizations/table/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/visualizations/table/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/visualizations/table/
add-a-query:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#add-a-query
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#add-a-query
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#add-a-query
saved-queries:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#saved-queries
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#saved-queries
save-query:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#save-a-query
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#save-a-query
---
# Panel editor
In the panel editor, you can update all the elements of a visualization including the data source, queries, time range, and visualization display options.
![Panel editor](/media/docs/grafana/panels-visualizations/screenshot-grafana-11.2-panel-editor.png)
![Panel editor](/media/docs/grafana/panels-visualizations/screenshot-panel-editor-v11.6.png)
This following sections describe the areas of the Grafana panel editor.
@@ -75,7 +85,14 @@ The visualization preview section contains the following options:
The data section contains tabs where you enter queries, transform your data, and create alert rules (if applicable).
- **Queries** - Select your data source and enter queries here. For more information, refer to [Add a query](ref:add-a-query). When you create a new dashboard, you'll be prompted to select a data source before you get to the panel editor. You set or update the data source in existing dashboards using the drop-down in the **Queries** tab.
- **Queries**
- Select your data source. You can also set or update the data source in existing dashboards using the drop-down menu in the **Queries** tab.
- [Add queries](ref:add-a-query). Write or construct a query in the query language of your data source or click **+ Add from saved queries** to add a previously saved query. If you've already written a query, you can click the **Replace with saved query** icon to use a previously saved query instead. To [save the query](ref:save-query) for reuse, click the **Save query** icon.
{{< admonition type="note" >}}
[Saved queries](ref:saved-queries) is in [public preview](https://grafana.com/docs/release-life-cycle/) in Grafana Enterprise and Cloud only.
{{< /admonition >}}
- **Transformations** - Apply data transformations. For more information, refer to [Transform data](ref:transform-data).
- **Alert** - Write alert rules. For more information, refer to [the overview of Grafana Alerting](ref:the-overview-of-grafana-alerting).
@@ -26,55 +26,63 @@ refs:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/
built-in-core-data-sources:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#data-source-plugins
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#built-in-core-data-sources
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#data-source-plugins
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/#built-in-core-data-sources
use-expressions-to-manipulate-data:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/expression-queries/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/expression-queries/
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/expression-queries/
global-variables:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/variables/add-template-variables/#global-variables
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/variables/add-template-variables/#global-variables
destination: /docs/grafana-cloud/visualizations/dashboards/variables/add-template-variables/#global-variables
plugin-management:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/plugin-management/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/plugin-management/
recorded-queries:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/recorded-queries/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/recorded-queries/
special-data-sources:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#special-data-sources
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/#special-data-sources
---
# Query and transform data
Grafana supports many types of [data sources](ref:data-sources).
Data source **queries** return data that Grafana can **transform** and visualize.
Data source _queries_ return data that Grafana can _transform_ and visualize.
Each data source uses its own query language, and data source plugins each implement a query-building user interface called a query editor.
## About queries
Grafana panels communicate with data sources via queries, which retrieve data for the visualization.
Grafana panels communicate with data sources using queries, which retrieve data for the visualization.
A query is a question written in the query language used by the data source.
You can configure query frequency and data collection limits in the panel's data source options.
Grafana supports up to 26 queries per panel.
> **Important:** You **must** be familiar with a data source's query language.
> For more information, refer to [Data sources](ref:data-sources).
{{< admonition type="note" >}}
You **must** be familiar with a data source's query language.
For more information, refer to [Data sources](ref:data-sources).
{{< /admonition >}}
### Query editors
{{< figure src="/static/img/docs/queries/influxdb-query-editor-7-2.png" class="docs-image--no-shadow" max-width="1000px" alt="The InfluxDB query editor" >}}
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-queries-tab-v11.6.png" max-width="750px" alt="The InfluxDB query editor" >}}
Each data source's **query editor** provides a customized user interface that helps you write queries that take advantage of its unique capabilities.
Each data source's query editor provides a customized user interface that helps you write queries that take advantage of its unique capabilities.
Because of the differences between query languages, each data source query editor looks and functions differently.
Depending on your data source, the query editor might provide auto-completion features, metric names, variable suggestions, or a visual query-building interface.
@@ -86,7 +94,7 @@ For example, this video demonstrates the visual Prometheus query builder:
For details on a specific data source's unique query editor features, refer to its documentation:
- For data sources included with Grafana, refer to [Built-in core data sources](ref:built-in-core-data-sources), which links to each core data source's documentation.
- For data sources installed as plugins, refer to its own documentation.
- For data sources installed as plugins, refer to the documentation for the plugin.
- Data source plugins in Grafana's [plugin catalog](/grafana/plugins/) link to or include their documentation in their catalog listings.
For details about the plugin catalog, refer to [Plugin management](ref:plugin-management).
- For links to Grafana Enterprise data source plugin documentation, refer to the [Enterprise plugins index](/docs/plugins/).
@@ -108,42 +116,121 @@ SELECT hostname FROM host WHERE region IN($region)
query_result(max_over_time(<metric>[${__range_s}s]) != <state>)
```
### Saved queries
{{< admonition type="note" >}}
Saved queries is currently in [public preview](https://grafana.com/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available.
This feature is only available on Grafana Enterprise and Grafana Cloud.
{{< /admonition >}}
You can save queries that you've created so they can be reused by you and others in your organization.
This helps users across your organization create dashboards or find insights in Explore without having to create their own queries or know a query language.
It also helps you avoid having several users build the same queries for the same data sources multiple times.
You can see a list of these queries in the **Saved queries** drawer:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-saved-queries-v12.2png.png" max-width="550px" alt="List of saved queries and the edit query form" caption="The Saved queries drawer accessed from Dashboards" >}}
When you first open the drawer, the list of queries in the **All** tab is filtered by the data source of the panel.
However, you can clear that filter to display all saved queries.
The list in the **Favorites** tab is also filtered by data source, by default.
The **Recent** tab displays the last 20 queries across all data sources from your **Query history** in Explore.
From this tab, you can save queries for reuse as well.
In the **Saved queries** drawer, you can:
- Search for queries by data source name, query content, title, or description.
- Sort queries alphabetically or by creation date.
- Filter by data source name, author name, and tags (the tag filter uses the `OR` operator, while the others use the `AND` operator).
- Set queries as favorites.
- Duplicate, lock and unlock a query for editing, or delete a saved query.
- Edit a query title, description, tags, or the availability of the query to other users in your organization. By default, saved queries are locked for editing.
- When you access the **Saved queries** drawer from Explore, you can use the **Edit in Explore** option to edit the body of a query.
Access the duplicate, lock, unlock, and delete query options through the menu in the top-right corner of the query form next to the **Edit** button.
To access your saved queries, click **+ Add from saved queries** in the query editor:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-add-from-saved-2-v12.2.png" max-width="750px" alt="Add a saved query" >}}
If you've already entered a query, you also have the option to replace it with a saved one:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-replace-w-saved-v12.2.png" max-width="750px" alt="Replace a query with a saved one" >}}
#### Save a query
To save a query you've created:
1. From the query editor, click the **Save query** icon:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-save-query-v12.2.png" max-width="750px" alt="Save a query" >}}
1. In the **Saved queries** drawer, enter a title for the query that will make it easy to find later.
1. (Optional) Enter a description and relevant tags.
1. Clear the **Share query with all users** checkbox if you only want the saved query to be available to you.
1. Click **Save**.
#### Known limitations
- No validation is performed when you save a query, so it's possible to save an invalid query. You should confirm the query is working properly before you save it.
- Saved queries are currently accessible from the query editors in Dashboards and Explore.
- You can save a maximum of 1000 queries.
- Users with the Viewer role who have access to Explore can use saved queries, but can't write them.
- If you have multiple queries open in Explore and you edit one of them by way of the **Edit in Explore** function in the **Saved queries** drawer, the edited query replaces your open queries in Explore.
### Special data sources
Grafana also includes three special data sources: **Grafana**, **Mixed**, and **Dashboard**.
For details, refer to [Data sources](ref:data-sources)
## Navigate the Query tab
## Navigate the Queries tab {#navigate-the-query-tab}
A panel's Query tab consists of the following elements:
A panel's **Queries** tab consists of the following elements:
- **Data source selector:** Selects the data source to query.
- **Data source selector** - Selects the data source to query.
For more information about data sources, refer to [Data sources](ref:data-sources).
- **Query options:** Sets maximum data retrieval parameters and query execution time intervals.
- **Query inspector button:** Opens the query inspector panel, where you can view and optimize your query.
- **Query editor list:** Lists the queries you've written.
- **Expressions:** Uses the expression builder to create alert expressions.
- **Query options** - Sets maximum data retrieval parameters and query execution time intervals.
- **Query inspector button** - Opens the query inspector panel, where you can view and optimize your query.
- **Query editor list** - The list of queries you've written. Each query can be expanded or collapsed.
- **Expressions** - Uses the expression builder to create alert expressions.
For more information about expressions, refer to [Use expressions to manipulate data](ref:use-expressions-to-manipulate-data).
{{< figure src="/static/img/docs/queries/query-editor-7-2.png" class="docs-image--no-shadow" max-width="1000px" alt="The Query tab of the panel editor" >}}
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-queries-tab2-v11.6.png" max-width="750px" alt="The Query tab of the panel editor" >}}
## Add a query
A query returns data that Grafana visualizes in dashboard panels.
When you create a panel, Grafana automatically selects the default data source.
**To add a query:**
To add a query, follow these steps:
1. Edit the panel to which you're adding a query.
1. Click the **Query** tab.
1. Hover the cursor over any part of the panel to which you're adding a query to display the menu icon in the top-right corner.
1. Click the menu and select **Edit**.
1. In the panel editor, click the **Queries** tab.
1. Click the **Data source** drop-down menu and select a data source.
If you're creating a new dashboard, you'll be prompted to select a data source when you add the first panel.
1. Click **Query options** to configure the maximum number of data points you need.
For more information about query options, refer to [Query options](#query-options).
1. Write the query using the query editor.
1. Click **Apply**.
1. To add a query, do one of the following:
- Write or construct a query in the query language of your data source.
- Click **+ Add from saved queries** to add a previously saved query.
- If you've already written a query, you can click the **Replace with saved query** icon to use a previously saved query instead.
1. (Optional) To save the query for reuse, click the **Save query** icon.
{{< admonition type="note" >}}
[Saved queries](#saved-queries) is currently in [public preview](https://grafana.com/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available.
This feature is only available on Grafana Enterprise and Grafana Cloud.
{{< /admonition >}}
1. Click **Run queries**.
Grafana queries the data source and visualizes the data.
@@ -154,20 +241,24 @@ Each query row contains a query editor and is identified with a letter (A, B, C,
You can:
| Icon | Description |
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| {{< figure src="/static/img/docs/queries/query-editor-help-7-4.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" alt="Help icon" >}} | Toggles query editor help. If supported by the data source, click this icon to display information on how to use the query editor or provide quick access to common queries. |
| {{< figure src="/static/img/docs/queries/duplicate-query-icon-7-0.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" alt="Duplicate icon" >}} | Copies a query. Duplicating queries is useful when working with multiple complex queries that are similar and you want to either experiment with different variants or do minor alterations. |
| {{< figure src="/static/img/docs/queries/hide-query-icon-7-0.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" alt="Hide icon" >}} | Hides a query. Grafana does not send hidden queries to the data source. |
| {{< figure src="/static/img/docs/queries/remove-query-icon-7-0.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" alt="Remove icon">}} | Removes a query. Removing a query permanently deletes it, but sometimes you can recover deleted queries by reverting to previously saved versions of the panel. |
| {{< figure src="/static/img/docs/queries/query-drag-icon-7-2.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" alt="Drag icon" >}} | Reorders queries. Change the order of queries by clicking and holding the drag icon, then drag queries where desired. The order of results reflects the order of the queries, so you can often adjust your visual results based on query order. |
<!-- prettier-ignore-start -->
| Icon | Description |
| ------- | -------------------------------------------- |
| {{< figure src="/static/img/docs/queries/query-editor-help-7-4.png" max-width="30px" max-height="30px" alt="Help icon" >}} | Toggles query editor help. If supported by the data source, click this icon to display information on how to use the query editor or provide quick access to common queries. |
| {{< figure src="/media/docs/grafana/panels-visualizations/create-recorded-query-icon.png" max-width="30px" max-height="30px" alt="Create recorded query icon" >}} | Create [recorded queries](ref:recorded-queries) so you can see trends over time by taking a snapshot of a data point on a set interval (Enterprise and Cloud only). |
| {{< figure src="/media/docs/grafana/panels-visualizations/save-to-query-icon.png" max-width="30px" max-height="30px" alt="Save query icon" >}} | Save query. Saves the query so it can be reused. Access saved queries by clicking **+ Add saved query**. For more information, refer to [Saved queries](#saved-queries) (Enterprise and Cloud only). |
| {{< figure src="/static/img/docs/queries/duplicate-query-icon-7-0.png" max-width="30px" max-height="30px" alt="Duplicate icon" >}} | Copies a query. Duplicating queries is useful when working with multiple complex queries that are similar and you want to either experiment with different variants or do minor alterations. |
| {{< figure src="/static/img/docs/queries/hide-query-icon-7-0.png" max-width="30px" max-height="30px" alt="Hide icon" >}} | Hides a query. Grafana does not send hidden queries to the data source. |
| {{< figure src="/static/img/docs/queries/remove-query-icon-7-0.png" max-width="30px" max-height="30px" alt="Remove icon">}} | Removes a query. Removing a query permanently deletes it, but sometimes you can recover deleted queries by reverting to previously saved versions of the panel. |
| {{< figure src="/static/img/docs/queries/query-drag-icon-7-2.png" max-width="30px" max-height="30px" alt="Drag icon" >}} | Reorders queries. Change the order of queries by clicking and holding the drag icon, then drag queries where desired. The order of results reflects the order of the queries, so you can often adjust your visual results based on query order. |
<!-- prettier-ignore-end -->
## Query options
Click **Query options** next to the data source selector to see settings for the selected data source.
Changes you make here affect only queries made in this panel.
{{< figure src="/static/img/docs/queries/data-source-options-7-0.png" class="docs-image--no-shadow" max-width="1000px" alt="Data source query options" >}}
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-query-options-v11.6.png" max-width="750px" alt="Data source query options" >}}
Grafana sets defaults that are shown in dark gray text.
Changes are displayed in white text.
@@ -175,7 +266,7 @@ To return a field to the default setting, delete the white text from the field.
Panel data source query options include:
- **Max data points:** If the data source supports it, this sets the maximum number of data points for each series returned.
- **Max data points** - If the data source supports it, this sets the maximum number of data points for each series returned.
If the query returns more data points than the max data points setting, then the data source reduces the number of points returned by aggregating them together by average, max, or another function.
You can limit the number of points to improve query performance or smooth the visualized line.
@@ -185,7 +276,7 @@ Panel data source query options include:
Streaming is a continuous flow of data, and buffering divides the stream into chunks.
For example, Loki streams data in its live tailing mode.
- **Min interval:** Sets a minimum limit for the automatically calculated interval, which is typically the minimum scrape interval.
- **Min interval** - Sets a minimum limit for the automatically calculated interval, which is typically the minimum scrape interval.
If a data point is saved every 15 seconds, you don't benefit from having an interval lower than that.
You can also set this to a higher minimum than the scrape interval to retrieve queries that are more coarse-grained and well-functioning.
@@ -193,7 +284,7 @@ Panel data source query options include:
The **Min interval** corresponds to the min step in Prometheus. Changing the Prometheus interval can change the start and end of the query range because Prometheus aligns the range to the interval. Refer to [Min step](https://grafana.com/docs/grafana/latest/datasources/prometheus/query-editor/#min-step) for more details.
{{< /admonition >}}
- **Interval:** Sets a time span that you can use when aggregating or grouping data points by time.
- **Interval** - Sets a time span that you can use when aggregating or grouping data points by time.
Grafana automatically calculates an appropriate interval that you can use as a variable in templated queries.
The variable is measured in either seconds (`$__interval`) or milliseconds (`$__interval_ms`).
@@ -207,10 +298,12 @@ Panel data source query options include:
For more information, refer to [Global variables](ref:global-variables).
- **Relative time:** Overrides the relative time range for individual panels, which causes them to be different than what is selected in the dashboard time picker in the top-right corner of the dashboard.
- **Relative time** - Overrides the relative time range for individual panels, which causes them to be different than what is selected in the dashboard time picker in the top-right corner of the dashboard.
You can use this to show metrics from different time periods or days on the same dashboard.
> **Note:** Panel time overrides have no effect when the dashboard's time range is absolute.
{{< admonition type="note">}}
Panel time overrides have no effect when the dashboard's time range is absolute.
{{< /admonition >}}
| Example | Relative time field |
| ---------------- | ------------------- |
@@ -222,10 +315,12 @@ Panel data source query options include:
{{< docs/play title="Time range override" url="https://play.grafana.org/d/000000041/" >}}
- **Time shift:** Overrides the time range for individual panels by shifting its start and end relative to the time picker.
- **Time shift** - Overrides the time range for individual panels by shifting its start and end relative to the time picker.
For example, you can shift the time range for the panel to be two hours earlier than the dashboard time picker.
> **Note:** Panel time overrides have no effect when the dashboard's time range is absolute.
{{< admonition type="note">}}
Panel time overrides have no effect when the dashboard's time range is absolute.
{{< /admonition >}}
| Example | Time shift field |
| -------------------- | ---------------- |
@@ -235,5 +330,5 @@ Panel data source query options include:
| This entire year | `1d/y` |
| Last entire year | `1y/y` |
- **Cache timeout:** _(Visible only if available in the data source)_ Overrides the default cache timeout if your time series store has a query cache.
- **Cache timeout** - _(Visible only if available in the data source)_ Overrides the default cache timeout if your time series store has a query cache.
Specify this value as a numeric value in seconds.
@@ -19,7 +19,7 @@ refs:
# SQL expressions
{{< docs/private-preview product="SQL expressions" >}}
{{< docs/public-preview product="SQL expressions" >}}
SQL Expressions are server-side expressions that manipulate and transform the results of data source queries using MySQL-like syntax. They allow you to easily query and transform your data after it has been queried, using SQL, which provides a familiar and powerful syntax that can handle everything from simple filters to highly complex, multi-step transformations.
@@ -60,11 +60,17 @@ A key capability of SQL expressions is the ability to JOIN data from multiple ta
To work with SQL expressions, you must use data from a backend data source. In Grafana, a backend data source refers to a data source plugin or integration that communicates with a database, service, or API through the Grafana server, rather than directly from the browser (frontend).
## Known limitations
- Currently, only one SQL expression is supported per panel or alert.
- Grafana supports certain data sources. Refer to [compatible data sources](#compatible-data-sources) for a current list.
- Autocomplete is available, but column/field autocomplete is only available after enabling the `sqlExpressionsColumnAutoComplete` feature toggle, which is provided on an experimental basis.
## Compatible data sources
The following are compatible data sources:
**Full support:** All query types for each data source are supported.
**Full support:** Grafana supports all query types for each of these data sources.
- Elasticsearch
- MySQL
@@ -73,7 +79,7 @@ The following are compatible data sources:
- Google Sheets
- Amazon Athena
**Partial support:** The following data sources offer limited or conditional support. Some allow different types of queries, depending on the service being accessed. For example, Azure Monitor can query multiple services, each with its own query format. In some cases, you can also change the query type within a panel.
**Partial support:** The following data sources have limited or conditional support. Some support multiple query types depending on the service. For example, Azure Monitor can query multiple services, each with its own query format. In some cases, you can also switch the query type within a panel.
- InfluxDB
- Infinity
@@ -97,6 +103,10 @@ To create a SQL expression, complete the following steps:
After you have added a SQL expression, you can select from other data source queries by referencing the RefIDs of the queries in your SQL expression as if they were tables in a SQL database.
{{< admonition type="note" >}}
The **RefID** is a unique identifier assigned to each query within a Grafana panel that serves as a reference name for that query's data.
{{< /admonition >}}
![Using the RefID](/media/docs/sql-expressions/using-the-RefID.png)
## Workflow to build SQL expressions
@@ -134,22 +144,65 @@ The SQL expression workflow in Grafana is designed with the following behaviors:
- **Non-tabular or incorrectly shaped data will not render in certain panels.** Visualizations such as graphs or gauges require properly structured data. Mismatched formats will result in rendering issues or missing data.
For data to be used in SQL expressions, it must be in a **tabular format**, specifically the **FullLong format**. This means all relevant data is contained within a single table, with values such as metric labels stored as columns and individual cells. Because not all data sources return results in this format by default, Grafana will automatically convert compatible query results to FullLong format when they are referenced in a SQL expression.
## SQL conversion rules
When a RefID is referenced within a SQL statement (e.g., `SELECT * FROM A`), the system invokes a distinct SQL conversion process.
When you reference a RefID within a SQL statement (e.g., `SELECT * FROM A`), the system invokes a distinct SQL conversion process.
The SQL conversion path:
- The query result is treated as a single data frame, without labels, and is mapped directly to a tabular format.
- If the frame type is present and is either numeric, wide time series, or multi-frame time series (for example, labeled formats), Grafana automatically converts the data into a table structure.
- The query result appears as a single data frame, without labels, and is mapped directly to a tabular format.
- If the frame type is present and is either numeric, wide time series, or multi-frame time series (for example: labeled formats), Grafana automatically converts the data into a table structure.
## Known limitations
## Supported functions
- Currently, only one SQL expression is supported per panel or alert.
- Grafana supports certain data sources. Refer to [compatible data sources](#compatible-data-sources) for a current list.
- Autocomplete is available, but column/field autocomplete is only available after enabling the `sqlExpressionsColumnAutoComplete` feature toggle, which is provided on an experimental basis.
Grafana maintains a complete list of supported SQL keywords, operators, and functions in the SQL expressions query validator implementation.
For the most up-to-date reference of all supported SQL functionality, refer to the `allowedNode` and `allowedFunction` definitions in the Grafana [codebase](https://github.com/grafana/grafana/blob/main/pkg/expr/sql/parser_allow.go).
## Alerting and recording rules
SQL expressions integrates alerting and recording rules, allowing you to define complex conditions and metrics using standard SQL queries. The system processes your query results and automatically creates alert instances or recorded metrics based on the returned data structure.
For SQL Expressions to work properly with alerting and recording rules, your query must return:
- One numeric column - **_required_**. This contains the value that triggers alerts or gets recorded.
- Unique string column combinations - **_required_**. Each row must have a unique combination of string column values.
- One or more string columns - _optional_. These become **labels** for the alert instances or metrics. Examples: `service`, `region`.
Consider the following query results:
```sql
error_count,service,region
25,auth-service,us-east
0,payment-service,us-west
15,user-service,eu-west
```
This query returns:
- the numeric column `error_count` (values: 25, 0, 15)
- the string columns `service` and `region`
For alert rules, this creates three alert instances:
- First instance with labels {service=auth-service, region=us-east} and value 25 (triggers alert - high error count)
- Second instance with labels {service=payment-service, region=us-west} and value 0 (no alert - zero errors)
- Third instance with labels {service=user-service, region=eu-west} and value 15 (triggers alert - elevated error count)
For recording rules, creates one metric with three series:
- First series: error_count_total{service=auth-service, region=us-east} 25
- Second series: error_count_total{service=payment-service, region=us-west} 0
- Third series: error_count_total{service=user-service, region=eu-west} 15
Following are some best practices for alerting and recording rules:
- Keep numeric values meaningful (for example: error counts, request duration).
- Use clear, descriptive column names - these become your labels.
- Keep string values short and consistent.
- Avoid too many unique label combinations, as this can result in high cardinality.
- Always use `GROUP BY` to avoid duplicate label errors.
- Aggregate numeric values logically (for example: `SUM(error_count)`).
## Supported data source formats
@@ -202,3 +255,19 @@ During conversion:
2. Add the SQL expression `SELECT * from A`. After you add a SQL expression that selects from RefID A, Grafana converts it to a table response:
![Add the SQL expression](/media/docs/sql-expressions/add-the-sql-expression.png)
## LLM integration
The Grafana LLM plugin seamlessly integrates AI-powered assistance into your SQL expressions workflow.
{{< admonition type="note" >}}
The Grafana LLM plugin is currently in public preview, meaning Grafana offers limited support, and breaking changes might occur prior to the feature being made generally available.
{{< /admonition >}}
To use this integration, first [install and configure the LLM plugin](https://grafana.com/grafana/plugins/grafana-llm-app/). After installation, open your dashboard and select **Edit** to open the panel editor. Navigate to the **Queries** tab and scroll to the bottom where you'll find two new buttons positioned to the right of the **Run query** button in your SQL Expressions query.
{{< figure src="/media/docs/sql-expressions/sqlexpressions-LLM-integration-v12.2.png" caption="LLM integration" >}}
Click **Explain query** to open a drawer that displays a detailed explanation of your query, including its interpreted business meaning and performance statistics. Once the explanation is generated, the button changes to **View explanation**.
Click **Improve query** to open a suggestions drawer that contains performance and reliability enhancements, column naming best practices, and guidance on panel optimization. Click **Apply** to implement a suggestion. After youve interacted with the interface, you'll see a **Suggestions** button for quick access. Newer suggestions appear at the top, with older ones listed below, creating a history of improvements. If your SQL query has a parsing error, such as a syntax issue, the LLM will attempt to provide a corrected version. The LLM automatically identifies errors and helps you rewrite the query correctly.
@@ -88,6 +88,22 @@ While the first field can be time-based and you can use a bar chart to plot time
We recommend that you only use one dataset in a bar chart because using multiple datasets can result in unexpected behavior.
<!-- vale Grafana.WordList = NO -->
<!-- vale Grafana.Spelling = NO -->
## Apply ad hoc filters from the bar chart
In bar charts, you can apply ad hoc filters directly from the visualization.
To display the filter button, hover your cursor over the bar that has the value for which you want to filter and click the bar:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-icon-bar-v12.2.png" max-width="300px" alt="The ad hoc filter button in a bar chart tooltip">}}
For more information about applying ad hoc filters this way, refer to [Dashboard drilldown with ad hoc filters](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/variables/add-template-variables/#dashboard-drilldown-with-ad-hoc-filters).
<!-- vale Grafana.Spelling = YES -->
<!-- vale Grafana.WordList = YES -->
## Configuration options
{{< docs/shared lookup="visualizations/config-options-intro.md" source="grafana" version="<GRAFANA_VERSION>" >}}
@@ -174,6 +174,22 @@ Columns with filters applied have a blue filter displayed next to the title.
To remove the filter, click the blue filter icon and then click **Clear filter**.
<!-- vale Grafana.WordList = NO -->
<!-- vale Grafana.Spelling = NO -->
### Apply ad hoc filters from the table
In tables, you can apply ad hoc filters directly from the visualization with one click.
To display the filter icons, hover your cursor over the cell that has the value for which you want to filter:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-adhoc-filter-v12.2.png" max-width="500px" alt="Table with ad hoc filter icon displayed on a cell" >}}
For more information about applying ad hoc filters this way, refer to [Dashboard drilldown with ad hoc filters](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/variables/add-template-variables/#dashboard-drilldown-with-ad-hoc-filters).
<!-- vale Grafana.Spelling = YES -->
<!-- vale Grafana.WordList = YES -->
## Sort columns
Click a column title to change the sort order from default to descending to ascending.
@@ -219,7 +235,7 @@ This option is only available when you're editing the panel.
The table footer displays the results of calculations (and reducer functions) on fields.
The footer is only displayed after you select an option in the **Calculation** drop-down list:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-footer-selector-v12.2.png" max-width="300px" alt="" >}}
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-footer-selector-v12.2.png" max-width="300px" alt="The footer calculation selector, open" >}}
There are several calculations you can choose from including minimum, maximum, first, last, and total.
For the full list of options, refer to [Calculations](ref:calculations).
@@ -227,21 +243,16 @@ For the full list of options, refer to [Calculations](ref:calculations).
In the table footer:
- You can apply multiple calculations at once.
- All calculations and reducer functions are labeled except **Total** when it's the only function applied.
- The calculations and reducer functions apply to all fields in the table, by default. To control which fields have a calculation or function applied, add the table footer in an override instead.
- If you enable a mathematical function for a non-numeric field, nothing for that function is displayed for that field.
In the following image, multiple calculations&mdash;**Mean**, **Max**, and **Last**&mdash;have been applied:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-footer-1-v12.2.png" max-width="750px" alt="" >}}
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-tablefooter-v12.2.png" max-width="750px" alt="Table with footer displaying mean, max, and last" >}}
You can also see in the previous image that the mathematical functions, **Mean** and **Max**, haven't been applied to the text field in the table.
Only the **Last** function has been applied to that field.
In the following image, the **Total** calculation has been applied, and no label is displayed because it's the only function:
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-footer-2-v12.2.png" max-width="750px" alt="" >}}
{{< admonition type="note">}}
Calculations applied to cell types like **Markdown + HTML** might have unexpected results.
{{< /admonition>}}
@@ -413,7 +424,7 @@ However, you can switch back and forth between tabs.
The **Pill** cell type displays each item in a comma-separated string in a colored block.
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-pills-v12.1.png" max-width="750px" alt="Table using the pill cell type" >}}
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-pill-cells-v12.2.png" max-width="750px" alt="Table using the pill cell type" >}}
The colors applied to each piece of text are maintained throughout the table.
For example, if the word "test" is first displayed in a red pill, it will always be displayed in a red pill.
@@ -444,6 +455,8 @@ in these cells if the [`disable_sanitize_html`](https://grafana.com/docs/grafana
Toggle on the **Tooltip from field** switch to use the values from another field (or column) in a tooltip.
For more information, refer to [Tooltip from field](#tooltip-from-field).
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-markdown-v12.2.png" max-width="600px" alt="Table using the pill cell type" >}}
#### Image
If you have a field value that is an image URL or a base64 encoded image, this cell type displays it as an image.
@@ -0,0 +1,20 @@
---
description: Guide for upgrading to Grafana v12.2
keywords:
- grafana
- configuration
- documentation
- upgrade
- '12.2'
title: Upgrade to Grafana v12.2
menuTitle: Upgrade to v12.2
weight: 498
---
# Upgrade to Grafana v12.2
{{< docs/shared lookup="upgrade/intro_2.md" source="grafana" version="<GRAFANA_VERSION>" >}}
{{< docs/shared lookup="back-up/back-up-grafana.md" source="grafana" version="<GRAFANA_VERSION>" leveloffset="+1" >}}
{{< docs/shared lookup="upgrade/upgrade-common-tasks.md" source="grafana" version="<GRAFANA_VERSION>" >}}
+1
View File
@@ -192,6 +192,7 @@ For a complete list of every change, with links to pull requests and related iss
## Grafana 12
- [What's new in 12.2](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/whatsnew/whats-new-in-v12-2)
- [What's new in 12.1](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/whatsnew/whats-new-in-v12-1)
- [What's new in 12.0](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/whatsnew/whats-new-in-v12-0)
@@ -0,0 +1,72 @@
---
description: Feature and improvement highlights for Grafana v12.2
keywords:
- grafana
- new
- documentation
- '12.2'
- release notes
labels:
products:
- cloud
- enterprise
- oss
title: What's new in Grafana v12.2
posts:
- title: SQL expressions
items:
- whats-new/2025-09-05-sql-expressions.md
- title: Dashboards and visualizations
items:
- whats-new/2025-08-22-new-table-visualization-is-generally-available.md
- whats-new/2025-08-27-generate-tooltips-from-table-fields.md
- whats-new/2025-08-27-improved-footer-for-table-visualization.md
- whats-new/2025-07-17-disable-tooltips-in-canvas-visualizations.md
- whats-new/2025-07-14-static-options-for-query-variable.md
- whats-new/2025-07-24-dynamic-connection-direction-in-canvas.md
- whats-new/2025-08-04-canvas-pan-zoom-improvements.md
- whats-new/2025-09-01-actions-authentication-via-infinity-datasource.md
- whats-new/2025-09-02-enhanced-ad-hoc-filter-support.md
- whats-new/2025-09-02-new-dashboard-apis-now-enabled-by-default.md
- title: Reporting
items:
- whats-new/2025-05-27-new-and-improved-reporting.md
- title: Data sources
items:
- whats-new/2025-08-12-jenkins-enterprise-data-source-for-grafana.md
- whats-new/2025-07-16-google-sheets-data-source-now-supports-template-variables.md
- whats-new/2025-09-04-azure-monitor-resource-picker-filtering-and-recent-resources.md
- title: Explore
items:
- whats-new/2025-07-08-saved-queries-in-dashboards-and-explore.md
- title: Logs Drilldown
items:
- whats-new/2025-08-29-json-log-line-viewer-in-logs-drilldown-is-now-generally-available.md
- title: Metrics Drilldown
items:
- whats-new/2025-08-07-grafana-metrics-drilldown-entry-point-from-alerting-rule.md
- title: Plugins
items:
- whats-new/2025-09-11-translate-your-plugin.md
- title: Authentication and authorization
items:
- whats-new/2025-09-10-scim-configuration-ui.md
whats_new_grafana_version: 12.2
weight: -51
---
# Whats new in Grafana v12.2
Welcome to Grafana 12.2! This release focuses on making it easier to gain insights from your data.
We're excited to announce several features are now GA. Enhanced ad hoc filtering transforms your dashboards into true command centers, allowing you to slice and dice datasets on the fly. The redesigned table visualization offers improved performance and visual aids for quick pattern and anomaly identification, helping you make faster decisions. The Logs Drilldown JSON viewer makes intimidating log structures organized and explorable. Metrics Drilldown now integrates with alert creation in Grafana, so you can explore Prometheus data with intuitive point-and-click interactions, find the right visualization, and easily use its query in your alert rule.
We're also collecting feedback on some new public preview features. AI-powered SQL expressions eliminate the barrier between questions and answers by generating SQL queries from natural language and providing instant explanations for existing queries. Our enhanced Canvas Pan and Zoom experience lets you design complex dashboards exactly as you envision them.
Keep reading to learn more about everything 12.2 has in store.
{{< youtube id=-7A_tePidEM >}}
For even more detail about all the changes in this release, refer to the [changelog](https://github.com/grafana/grafana/blob/main/CHANGELOG.md). For the specific steps we recommend when you upgrade to v12.2, check out our [Upgrade Guide](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/upgrade-guide/upgrade-v12.2/).
{{< docs/whats-new >}}
+9
View File
@@ -0,0 +1,9 @@
# Dashboards
## Adding Dashboard JSONs
When adding dashboard JSON files to this folder that were exported from Grafana:
⚠️ **Important:** Don't forget to remove the `"id"` field from the exported JSON before adding it to this folder.
Exported dashboard JSONs contain an `id` field that should be removed to avoid conflicts when importing the dashboard in tests.
@@ -43,10 +43,10 @@ test.describe('Canvas Panel - Scene Tests', () => {
await expect(viewerBounds).toBeDefined();
// Test pan functionality
const startX = viewerBounds.x + 50;
const startY = viewerBounds.y + 50;
const endX = viewerBounds.x + 250;
const endY = viewerBounds.y + 250;
const startX = viewerBounds!.x + 50;
const startY = viewerBounds!.y + 50;
const endX = viewerBounds!.x + 250;
const endY = viewerBounds!.y + 250;
await page.getByTestId('canvas-scene-pan-zoom');
await page.mouse.move(startX, startY);
await page.mouse.down({ button: 'middle' });
@@ -79,9 +79,9 @@ async function isOutsideViewport(element: Locator, viewPort: Locator): Promise<b
const elementBounds = await element.boundingBox();
const viewportBounds = await viewPort.boundingBox();
return (
elementBounds.x + elementBounds.width < viewportBounds.x ||
elementBounds.x > viewportBounds.x + viewportBounds.width ||
elementBounds.y + elementBounds.height < viewportBounds.y ||
elementBounds.y > viewportBounds.y + viewportBounds.height
elementBounds!.x + elementBounds!.width < viewportBounds!.x ||
elementBounds!.x > viewportBounds!.x + viewportBounds!.width ||
elementBounds!.y + elementBounds!.height < viewportBounds!.y ||
elementBounds!.y > viewportBounds!.y + viewportBounds!.height
);
}
@@ -4,14 +4,14 @@ import { test, expect } from '@grafana/plugin-e2e';
import { getColumnIdx } from './table-utils';
const DASHBOARD_UID = '1ea31838-e4e8-4aa0-9333-1d4c3fa95641';
const DASHBOARD_UID = '8100236d-603c-421e-a21b-2a0b0ea4eaa3';
const waitForTableLoad = async (loc: Page | Locator) => {
await expect(loc.locator('.rdg')).toBeVisible();
};
test.describe('Panels test: Table - Footer', { tag: ['@panels', '@table'] }, () => {
test('Footer unaffected by filtering', async ({ gotoDashboardPage, selectors, page }) => {
test('Footer affected by filtering', async ({ gotoDashboardPage, selectors, page }) => {
const dashboardPage = await gotoDashboardPage({
uid: DASHBOARD_UID,
queryParams: new URLSearchParams({ editPanel: '4' }),
@@ -26,12 +26,6 @@ test.describe('Panels test: Table - Footer', { tag: ['@panels', '@table'] }, ()
const minColumnIdx = await getColumnIdx(page, 'Min');
// this is the footer cell for the "Min" column.
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.ReducerLabel)
.nth(minColumnIdx)
).toHaveText('Last *');
const minReducerValue = await dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.Value)
.nth(minColumnIdx)
@@ -57,7 +51,7 @@ test.describe('Panels test: Table - Footer', { tag: ['@panels', '@table'] }, ()
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.Value)
.nth(minColumnIdx)
).toHaveText(minReducerValue);
).not.toHaveText(minReducerValue);
});
test('Footer unaffected by sorting', async ({ gotoDashboardPage, selectors, page }) => {
@@ -75,12 +69,6 @@ test.describe('Panels test: Table - Footer', { tag: ['@panels', '@table'] }, ()
const minColumnIdx = await getColumnIdx(page, 'Min');
// this is the footer cell for the "Min" column.
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.ReducerLabel)
.nth(minColumnIdx)
).toHaveText('Last *');
const minReducerValue = await dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.Value)
.nth(minColumnIdx)
@@ -114,26 +102,6 @@ test.describe('Panels test: Table - Footer', { tag: ['@panels', '@table'] }, ()
).toHaveText(minReducerValue);
});
test('Single-sum reducer label is hidden', async ({ gotoDashboardPage, selectors, page }) => {
const dashboardPage = await gotoDashboardPage({
uid: DASHBOARD_UID,
queryParams: new URLSearchParams({ editPanel: '6' }),
});
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Single sum reducer'))
).toBeVisible();
await waitForTableLoad(page);
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.ReducerLabel)
).not.toBeVisible();
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.Value)
).toBeVisible();
});
test('Count rows for normal case', async ({ gotoDashboardPage, selectors, page }) => {
const dashboardPage = await gotoDashboardPage({
uid: DASHBOARD_UID,
@@ -1,6 +1,6 @@
{
"name": "@test-plugins/extensions-test-app",
"version": "12.2.0-pre",
"version": "12.2.0",
"private": true,
"scripts": {
"build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production",
@@ -1,6 +1,6 @@
{
"name": "@test-plugins/grafana-e2etest-datasource",
"version": "12.2.0-pre",
"version": "12.2.0",
"private": true,
"scripts": {
"build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production",
+12 -67
View File
@@ -2040,21 +2040,6 @@
"count": 2
}
},
"public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupCondition.tsx": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupVisibility.tsx": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingTimeRangeSize.tsx": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/dashboard-scene/edit-pane/DashboardEditPaneRenderer.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
@@ -2389,9 +2374,6 @@
"public/app/features/dashboard-scene/v2schema/ImportDashboardOverviewV2.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 10
},
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"public/app/features/dashboard-scene/v2schema/test-helpers.ts": {
@@ -3487,11 +3469,6 @@
"count": 9
}
},
"public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"public/app/features/transformers/FilterByValueTransformer/ValueMatchers/types.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 3
@@ -3590,7 +3567,7 @@
},
"public/app/features/transformers/spatial/optionsHelper.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 3
"count": 2
},
"@typescript-eslint/no-explicit-any": {
"count": 3
@@ -4008,7 +3985,7 @@
},
"public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 3
"count": 1
}
},
"public/app/plugins/datasource/cloudwatch/types.ts": {
@@ -4041,7 +4018,7 @@
},
"public/app/plugins/datasource/elasticsearch/LanguageProvider.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 4
"count": 3
}
},
"public/app/plugins/datasource/elasticsearch/QueryBuilder.ts": {
@@ -4154,9 +4131,6 @@
"public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationQueryEditor.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
},
"@typescript-eslint/no-explicit-any": {
"count": 2
}
},
"public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationSchemaForm.tsx": {
@@ -4167,9 +4141,6 @@
"public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
},
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx": {
@@ -4183,9 +4154,6 @@
"public/app/plugins/datasource/grafana/components/QueryEditor.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 2
},
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx": {
@@ -4216,7 +4184,7 @@
"count": 4
},
"@typescript-eslint/no-explicit-any": {
"count": 15
"count": 10
}
},
"public/app/plugins/datasource/graphite/gfunc.ts": {
@@ -4232,7 +4200,7 @@
"count": 1
},
"@typescript-eslint/no-explicit-any": {
"count": 10
"count": 9
}
},
"public/app/plugins/datasource/graphite/lexer.ts": {
@@ -4333,12 +4301,12 @@
},
"public/app/plugins/datasource/influxdb/influx_series.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 9
"count": 8
}
},
"public/app/plugins/datasource/influxdb/query_part.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 14
"count": 12
}
},
"public/app/plugins/datasource/influxdb/response_parser.ts": {
@@ -4351,11 +4319,6 @@
"count": 1
}
},
"public/app/plugins/datasource/loki/LanguageProvider.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"public/app/plugins/datasource/loki/LogContextProvider.ts": {
"no-restricted-syntax": {
"count": 2
@@ -4376,11 +4339,6 @@
"count": 2
}
},
"public/app/plugins/datasource/loki/configuration/ConfigEditor.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"public/app/plugins/datasource/loki/configuration/DerivedField.tsx": {
"no-restricted-syntax": {
"count": 13
@@ -4463,7 +4421,7 @@
},
"public/app/plugins/datasource/opentsdb/datasource.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 18
"count": 16
}
},
"public/app/plugins/datasource/parca/QueryEditor/QueryOptions.tsx": {
@@ -4502,9 +4460,6 @@
}
},
"public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilterRenderer.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 1
},
"no-restricted-syntax": {
"count": 1
}
@@ -4542,11 +4497,6 @@
"count": 2
}
},
"public/app/plugins/datasource/tempo/language_provider.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"public/app/plugins/datasource/tempo/resultTransformer.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 2
@@ -4574,11 +4524,6 @@
"count": 1
}
},
"public/app/plugins/datasource/zipkin/utils/transforms.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"public/app/plugins/panel/annolist/AnnoListPanel.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
@@ -4928,12 +4873,12 @@
},
"public/app/types/events.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 9
"count": 5
}
},
"public/app/types/jquery/jquery.d.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 8
"count": 3
}
},
"public/app/types/store.ts": {
@@ -4971,7 +4916,7 @@
},
"public/test/core/thunk/thunkTester.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 6
"count": 4
}
},
"public/test/global-jquery-shim.ts": {
@@ -4996,7 +4941,7 @@
},
"public/test/specs/helpers.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 5
"count": 1
}
}
}
-2
View File
@@ -98,7 +98,6 @@ require (
github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend
github.com/grafana/grafana-app-sdk v0.40.3 // @grafana/grafana-app-platform-squad
github.com/grafana/grafana-app-sdk/logging v0.40.3 // @grafana/grafana-app-platform-squad
github.com/grafana/grafana-app-sdk/plugin v0.40.3 // @grafana/grafana-app-platform-squad
github.com/grafana/grafana-aws-sdk v1.1.0 // @grafana/aws-datasources
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 // @grafana/partner-datasources
github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 // @grafana/grafana-operator-experience-squad
@@ -233,7 +232,6 @@ require (
require (
github.com/grafana/grafana/apps/advisor v0.0.0 // @grafana/plugins-platform-backend
github.com/grafana/grafana/apps/alerting/alertenrichment v0.0.0 // @grafana/alerting-backend
github.com/grafana/grafana/apps/alerting/notifications v0.0.0 // @grafana/alerting-backend
github.com/grafana/grafana/apps/dashboard v0.0.0 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad
github.com/grafana/grafana/apps/folder v0.0.0 // @grafana/grafana-search-and-storage
-2
View File
@@ -1605,8 +1605,6 @@ github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhck
github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk=
github.com/grafana/grafana-app-sdk/logging v0.40.3 h1:2VXsXXEQiqAavRP8wusRDB6rDqf5lufP7A6NfjELqPE=
github.com/grafana/grafana-app-sdk/logging v0.40.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU=
github.com/grafana/grafana-app-sdk/plugin v0.40.3 h1:uH0oFZnYOUL+OXcyhd5NVYwoM+Wa0WUXvZ2Om1M91r0=
github.com/grafana/grafana-app-sdk/plugin v0.40.3/go.mod h1:+ylwE0P8WgPu5zURK5aDnVJpwRpuK3573rwrVV28qzQ=
github.com/grafana/grafana-aws-sdk v1.1.0 h1:G0fvwbQmHw14c5RXPd7Gnw9ZQcgzl139LtMDoe0KhmE=
github.com/grafana/grafana-aws-sdk v1.1.0/go.mod h1:7e+47EdHynteYWGoT5Ere9KeOXQObsk8F0vkOLQ1tz8=
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA=
+3 -3
View File
@@ -74,7 +74,7 @@ lineage: schemas: [{
// Version of the JSON schema, incremented each time a Grafana update brings
// changes to said schema.
schemaVersion: uint16 | *41
schemaVersion: uint16 | *42
// Version of the dashboard, incremented each time the dashboard is updated.
version?: uint32
@@ -239,8 +239,8 @@ lineage: schemas: [{
#VariableRefresh: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="never|onDashboardLoad|onTimeRangeChanged")
// Determine if the variable shows on dashboard
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing).
#VariableHide: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable") @grafana(TSVeneer="type")
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing), 3 (show under the controls dropdown menu).
#VariableHide: 0 | 1 | 2 | 3 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable|inControlsMenu") @grafana(TSVeneer="type")
// Sort variable options
// Accepted values are:
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"npmClient": "yarn",
"version": "12.2.0-pre"
"version": "12.2.0"
}
+5 -5
View File
@@ -3,7 +3,7 @@
"license": "AGPL-3.0-only",
"private": true,
"name": "grafana",
"version": "12.2.0-pre",
"version": "12.2.0",
"repository": "github:grafana/grafana",
"scripts": {
"predev": "./scripts/check-frontend-dev.sh",
@@ -264,7 +264,7 @@
"@emotion/css": "11.13.5",
"@emotion/react": "11.14.0",
"@fingerprintjs/fingerprintjs": "^3.4.2",
"@floating-ui/react": "0.27.15",
"@floating-ui/react": "0.27.16",
"@formatjs/intl-durationformat": "^0.7.0",
"@glideapps/glide-data-grid": "^6.0.0",
"@grafana/alerting": "workspace:*",
@@ -286,8 +286,8 @@
"@grafana/plugin-ui": "^0.10.10",
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
"@grafana/scenes": "6.33.0",
"@grafana/scenes-react": "6.33.0",
"@grafana/scenes": "6.34.0",
"@grafana/scenes-react": "6.34.0",
"@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*",
@@ -414,7 +414,7 @@
"slate": "0.47.9",
"slate-plain-serializer": "0.7.13",
"slate-react": "0.22.10",
"swagger-ui-react": "5.27.0",
"swagger-ui-react": "5.28.1",
"symbol-observable": "4.0.0",
"systemjs": "6.15.1",
"tinycolor2": "1.6.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/alerting",
"version": "12.2.0-pre",
"version": "12.2.0",
"description": "Grafana Alerting Library Build vertical integrations on top of the industry-leading alerting solution",
"keywords": [
"typescript",
+3 -3
View File
@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/data",
"version": "12.2.0-pre",
"version": "12.2.0",
"description": "Grafana Data Library",
"keywords": [
"typescript"
@@ -56,8 +56,8 @@
},
"dependencies": {
"@braintree/sanitize-url": "7.0.1",
"@grafana/i18n": "12.2.0-pre",
"@grafana/schema": "12.2.0-pre",
"@grafana/i18n": "12.2.0",
"@grafana/schema": "12.2.0",
"@leeoniya/ufuzzy": "1.0.18",
"@types/d3-interpolate": "^3.0.0",
"@types/string-hash": "1.1.3",
@@ -1,7 +1,8 @@
import { FieldType } from '../types/dataFrame';
import { TimeRange } from '../types/time';
import { createDataFrame, toDataFrame } from './processDataFrame';
import { anySeriesWithTimeField, addRow } from './utils';
import { anySeriesWithTimeField, addRow, alignTimeRangeCompareData, shouldAlignTimeCompare } from './utils';
describe('anySeriesWithTimeField', () => {
describe('single frame', () => {
@@ -104,3 +105,287 @@ describe('addRow', () => {
expect(frame.length).toBe(2);
});
});
describe('alignTimeRangeCompareData', () => {
const ONE_DAY_MS = 24 * 60 * 60 * 1000; // 86400000ms
const ONE_WEEK_MS = 7 * ONE_DAY_MS; // 604800000ms
it('should align time field values with positive diff (1 day)', () => {
const frame = toDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000] },
{ name: 'value', type: FieldType.number, values: [10, 20, 30] },
],
});
alignTimeRangeCompareData(frame, ONE_DAY_MS);
expect(frame.fields[0].values).toEqual([ONE_DAY_MS + 1000, ONE_DAY_MS + 2000, ONE_DAY_MS + 3000]);
expect(frame.fields[1].values).toEqual([10, 20, 30]); // non-time fields unchanged
});
it('should align time field values with negative diff (1 week)', () => {
const frame = toDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000] },
{ name: 'value', type: FieldType.number, values: [10, 20, 30] },
],
});
alignTimeRangeCompareData(frame, -ONE_WEEK_MS);
// When diff is negative, function does v - diff, so v - (-ONE_WEEK_MS) = v + ONE_WEEK_MS
expect(frame.fields[0].values).toEqual([ONE_WEEK_MS + 1000, ONE_WEEK_MS + 2000, ONE_WEEK_MS + 3000]);
});
it('should apply default gray color and timeCompare config', () => {
const frame = toDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [1000, 2000] },
{ name: 'value', type: FieldType.number, values: [10, 20] },
],
});
alignTimeRangeCompareData(frame, ONE_DAY_MS);
frame.fields.forEach((field) => {
expect(field.config.color?.fixedColor).toBe('gray');
expect(field.config.custom?.timeCompare).toEqual({
diffMs: ONE_DAY_MS,
isTimeShiftQuery: true,
});
});
});
it('should apply custom color when provided', () => {
const frame = toDataFrame({
fields: [{ name: 'value', type: FieldType.number, values: [10, 20] }],
});
alignTimeRangeCompareData(frame, ONE_DAY_MS, 'red');
expect(frame.fields[0].config.color?.fixedColor).toBe('red');
});
it('should preserve existing config when merging', () => {
const frame = toDataFrame({
fields: [
{
name: 'value',
type: FieldType.number,
values: [10, 20],
config: {
displayName: 'My Display Name',
custom: { existingProperty: 'existingValue' },
},
},
],
});
alignTimeRangeCompareData(frame, ONE_WEEK_MS);
expect(frame.fields[0].config.displayName).toBe('My Display Name');
expect(frame.fields[0].config.custom?.existingProperty).toBe('existingValue');
expect(frame.fields[0].config.custom?.timeCompare?.diffMs).toBe(ONE_WEEK_MS);
});
});
describe('shouldAlignTimeCompare', () => {
const TIME_VALUES_A = [1000, 2000, 3000];
const TIME_VALUES_B = [5000, 6000, 7000];
const ORIGINAL_VALUES = [10, 20, 30];
const COMPARE_VALUES = [15, 25, 35];
const mockTimeRange: TimeRange = {
from: { valueOf: () => 4000 },
to: { valueOf: () => 8000 },
raw: { from: 'now-1h', to: 'now' },
} as TimeRange;
it('should return true when compare first time is before time range', () => {
const originalFrame = toDataFrame({
refId: 'A',
fields: [
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
],
});
const compareFrame = toDataFrame({
refId: 'A-compare',
fields: [
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
{ name: 'value', type: FieldType.number, values: COMPARE_VALUES },
],
meta: {
timeCompare: {
isTimeShiftQuery: true,
diffMs: 86400000,
},
},
});
const allFrames = [originalFrame, compareFrame];
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(true);
});
it('should return false when compare first time is after time range', () => {
const originalFrame = toDataFrame({
refId: 'A',
fields: [
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
],
});
const compareFrame = toDataFrame({
refId: 'A-compare',
fields: [
{ name: 'time', type: FieldType.time, values: TIME_VALUES_B },
{ name: 'value', type: FieldType.number, values: COMPARE_VALUES },
],
meta: {
timeCompare: {
isTimeShiftQuery: true,
diffMs: 86400000,
},
},
});
const allFrames = [originalFrame, compareFrame];
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
});
it('should return false when compare frame refId does not end with -compare', () => {
const compareFrame = toDataFrame({
refId: 'A',
fields: [
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
],
});
const allFrames = [compareFrame];
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
});
it('should return false when original frame is not found', () => {
const compareFrame = toDataFrame({
refId: 'A-compare',
fields: [
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
],
});
const allFrames = [compareFrame]; // No original frame with refId 'A'
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
});
it('should return false when compare frame has no time field', () => {
const originalFrame = toDataFrame({
refId: 'A',
fields: [
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
],
});
const compareFrame = toDataFrame({
refId: 'A-compare',
fields: [{ name: 'value', type: FieldType.number, values: COMPARE_VALUES }],
});
const allFrames = [originalFrame, compareFrame];
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
});
it('should return false when original frame has no time field', () => {
const originalFrame = toDataFrame({
refId: 'A',
fields: [{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES }],
});
const compareFrame = toDataFrame({
refId: 'A-compare',
fields: [
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
{ name: 'value', type: FieldType.number, values: COMPARE_VALUES },
],
});
const allFrames = [originalFrame, compareFrame];
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
});
it('should return false when time fields have empty values', () => {
const EMPTY_VALUES: number[] = [];
const originalFrame = toDataFrame({
refId: 'A',
fields: [
{ name: 'time', type: FieldType.time, values: EMPTY_VALUES },
{ name: 'value', type: FieldType.number, values: EMPTY_VALUES },
],
});
const compareFrame = toDataFrame({
refId: 'A-compare',
fields: [
{ name: 'time', type: FieldType.time, values: EMPTY_VALUES },
{ name: 'value', type: FieldType.number, values: EMPTY_VALUES },
],
});
const allFrames = [originalFrame, compareFrame];
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
});
it('should handle null values and return true when first non-null time is before range', () => {
const TIME_WITH_NULLS = [null, ...TIME_VALUES_A];
const ORIGINAL_WITH_NULLS = [null, ...ORIGINAL_VALUES];
const COMPARE_WITH_NULLS = [null, ...COMPARE_VALUES];
const originalFrame = toDataFrame({
refId: 'A',
fields: [
{ name: 'time', type: FieldType.time, values: TIME_WITH_NULLS },
{ name: 'value', type: FieldType.number, values: ORIGINAL_WITH_NULLS },
],
});
const compareFrame = toDataFrame({
refId: 'A-compare',
fields: [
{ name: 'time', type: FieldType.time, values: TIME_WITH_NULLS },
{ name: 'value', type: FieldType.number, values: COMPARE_WITH_NULLS },
],
});
const allFrames = [originalFrame, compareFrame];
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(true);
});
it('should return false when all time values are null', () => {
const ALL_NULL_TIMES = [null, null, null];
const originalFrame = toDataFrame({
refId: 'A',
fields: [
{ name: 'time', type: FieldType.time, values: ALL_NULL_TIMES },
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
],
});
const compareFrame = toDataFrame({
refId: 'A-compare',
fields: [
{ name: 'time', type: FieldType.time, values: ALL_NULL_TIMES },
{ name: 'value', type: FieldType.number, values: COMPARE_VALUES },
],
});
const allFrames = [originalFrame, compareFrame];
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
});
});
@@ -1,4 +1,5 @@
import { DataFrame, Field, FieldType } from '../types/dataFrame';
import { TimeRange } from '../types/time';
import { getTimeField } from './processDataFrame';
@@ -123,3 +124,79 @@ export function addRow(dataFrame: DataFrame, row: Record<string, unknown> | unkn
// does not need any external updating.
}
}
/**
* Aligns time range comparison data by adjusting timestamps and applying compare-specific styling
* @param series - The DataFrame containing the comparison data
* @param diff - The time difference in milliseconds to align the timestamps
* @param compareColor - Optional color to use for the comparison series (defaults to 'gray')
*/
export function alignTimeRangeCompareData(series: DataFrame, diff: number, compareColor = 'gray') {
series.fields.forEach((field: Field) => {
// Align compare series time stamps with reference series
if (field.type === FieldType.time) {
field.values = field.values.map((v: number) => {
return diff < 0 ? v - diff : v + diff;
});
}
field.config = {
...(field.config ?? {}),
color: {
mode: 'fixed',
fixedColor: compareColor,
},
custom: {
...(field.config?.custom ?? {}),
timeCompare: {
diffMs: diff,
isTimeShiftQuery: true,
},
},
};
});
}
/**
* Checks if a time comparison frame needs alignment based on whether its first time is before the current time range.
* Returns true if the first time in compare is before timeRange.from, indicating it needs shifting.
* @param compareFrame - The frame with time comparison data
* @param allFrames - Array of all frames to find the matching original frame
* @param timeRange - The current panel time range
* @returns true if alignment is needed
*/
export function shouldAlignTimeCompare(compareFrame: DataFrame, allFrames: DataFrame[], timeRange: TimeRange): boolean {
// Find the matching original frame by removing '-compare' from refId
const compareRefId = compareFrame.refId;
if (!compareRefId || !compareRefId.endsWith('-compare')) {
return false;
}
const originalRefId = compareRefId.replace('-compare', '');
const originalFrame = allFrames.find(
(frame) => frame.refId === originalRefId && !frame.meta?.timeCompare?.isTimeShiftQuery
);
if (!originalFrame) {
return false;
}
// Find time fields
const compareTimeField = compareFrame.fields.find((field) => field.type === FieldType.time);
const originalTimeField = originalFrame.fields.find((field) => field.type === FieldType.time);
if (!compareTimeField?.values.length || !originalTimeField?.values.length) {
return false;
}
// Find first non-null time value from each frame
const compareFirstTime = compareTimeField.values.find((value) => value != null);
const originalFirstTime = originalTimeField.values.find((value) => value != null);
if (compareFirstTime == null || originalFirstTime == null) {
return false;
}
// Check if first non-null time value is before timeRange.from
return compareFirstTime < timeRange.from.valueOf();
}
+2
View File
@@ -50,6 +50,8 @@ export {
isTimeSeriesField,
getRowUniqueId,
addRow,
alignTimeRangeCompareData,
shouldAlignTimeCompare,
} from './dataframe/utils';
export {
StreamingDataFrame,

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