Compare commits

...

99 Commits

Author SHA1 Message Date
Grot (@grafanabot)
2386d78193 [v9.4.x] Alerting: Fix boolean default in migration from false to 0 (#63963)
Alerting: Fix boolean default in migration from false to 0 (#63952)

Fix boolean default in migration from false to 0

(cherry picked from commit a05bf41ff9)

Co-authored-by: Alex Moreno <alexander.moreno@grafana.com>
2023-03-01 16:08:07 -06:00
Grot (@grafanabot)
fac483c393 [v9.4.x] Alerting: Fix migration pauses all alert rules on PostgreSQL (#63968)
Alerting: Fix migration pauses all alert rules on PostgreSQL (#63951)

This commit fixes a serious bug in Grafana 9.4.1 where on upgrade
a migration would pause all existing alert rules and change the
default value of the column to true.

(cherry picked from commit 030f6c948f)

Co-authored-by: George Robinson <george.robinson@grafana.com>
2023-03-01 13:48:07 -06:00
Timur Olzhabayev
fd67ab151d fix(dashboard version service): add DashboardUID to query and respons… (#63821)
fix(dashboard version service): add DashboardUID to query and responses (#60800)

* fix(dashboard version service): add DashboardUID to query and responses

The DashboardUID was not populated in the response from Get and ListDashboardVersions. This adds the DashboardUID to the Get query (it was already in List) and populated the DashboardUID in the returned DashboardVersionDTOs.

(cherry picked from commit 42be0e106f)

Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2023-02-27 16:07:24 -06:00
Grot (@grafanabot)
27a6d700f8 [v9.4.x] InfluxDB: Fix getting empty response when querying fields with retention policy (#63671)
InfluxDB: Fix getting empty response when querying fields with retention policy (#63669)

Revert "InfluxDB: Send retention policy with InfluxQL queries if its been specified. (#62149)"

This reverts commit a5a85e0398.

(cherry picked from commit ffed779879)

Co-authored-by: ismail simsek <ismailsimsek09@gmail.com>
(cherry picked from commit 3e30c2024c)
2023-02-23 13:58:53 -06:00
Grot (@grafanabot)
fcd658359c [v9.4.x] InfluxDB datasource: Query variable breaks trying to interpolate __interval (#63685)
InfluxDB ds: Query variable breaks trying to interpolate `__interval` (#63682)

(cherry picked from commit ca4cd85504)

Co-authored-by: Ivan Ortega Alba <ivanortegaalba@gmail.com>
(cherry picked from commit 30f3f63bd6)
2023-02-23 13:57:45 -06:00
Grot (@grafanabot)
3d99babd0f Release: Bump version to 9.4.1 (#773)
"Release: Updated versions in package to 9.4.1"
2023-02-23 07:56:57 +01:00
Grot (@grafanabot)
1ffc7860e6 [v9.4.x] MSSQL Datasource: Revert functions within macros change (#63598)
MSSQL Datasource: Revert functions within macros change (#63592)

* Revert functions within macros change
* Add tests for function and macro for mssql
* Remove macro support tests

---------

Co-authored-by: Oscar Kilhed <oscar.kilhed@grafana.com>
(cherry picked from commit 356e2e1933)

Co-authored-by: Kyle Cunningham <codeincarnate@users.noreply.github.com>
2023-02-23 07:43:27 +01:00
Horst Gutmann
955b931756 [9.4.x] CI: Running Redis integration tests without grabpl (#63028) (#63074)
This restores some changes from
https://github.com/grafana/grafana/pull/61920 that were accidentally
deleted.

(cherry picked from commit 2804acd264)
2023-02-21 07:56:25 +01:00
Grot (@grafanabot)
afb449e8a1 [v9.4.x] Fix MSSQL queries failing because of bad interpolation (#63172)
Fix MSSQL queries failing because of bad interpolation (#63167)

fix failing mssql queries

(cherry picked from commit 62b078e4e4)

Co-authored-by: Victor Marin <36818606+mdvictor@users.noreply.github.com>
2023-02-21 07:23:25 +01:00
Grot (@grafanabot)
3bf22fcb21 [v9.4.x] SAML: Update library (fix single logout) (#62933)
SAML: Update library (fix single logout) (#62880)

* SAML: update library (fix single logout)

* Run go mod tidy

(cherry picked from commit 5205cfb11c)

Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com>
(cherry picked from commit 8e9fb8cf93)
2023-02-06 10:41:47 +02:00
Grot (@grafanabot)
8ad6df8266 [v9.4.x] TraceView: Add key and url escaping of json tag values (#761)
TraceView: Add key and url escaping of json tag values (#751)

(cherry picked from commit 8b53b448bc24fbfc4492ce1f4e2b1708218f70b7)

Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
2023-02-03 11:53:22 -06:00
Grot (@grafanabot)
a6370342b0 [v9.4.x] Geomap: Sanitize the attribution string (#755)
Geomap: Sanitize the attribution string (#745)

* SAML: Update grafana/saml library (#691)

Co-authored-by: jguer <joao.guerreiro@grafana.com>

* SVG: Add dompurify preprocessor step (#698)

* add sanitized SVG component

* add sanitize

* Fix frontend build

* Remove unnecessary yarn.lock changes

* Fix formatting

* Re-add yarn.lock message as I guess it is needed

---------

Co-authored-by: dsotirakis <dimitrios.sotirakis@grafana.com>
Co-authored-by: jguer <joao.guerreiro@grafana.com>
Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com>
(cherry picked from commit 37b4af7ffacfc24d5f24d190356a9ef32d99aa6f)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2023-02-03 11:53:22 -06:00
Grot (@grafanabot)
6895e75f70 [v9.4.x] Canvas: Support color themes for arrows (#62896)
Canvas: Support color themes for arrows (#62829)

(cherry picked from commit db953c9a9c)

Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com>
2023-02-03 12:47:27 -05:00
Grot (@grafanabot)
4f826bc76c [v9.4.x] Canvas: Improve anchor UX (#62898)
Canvas: Improve anchor UX (#62409)

Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
(cherry picked from commit bbb572e73f)

Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com>
2023-02-03 12:09:51 -05:00
Grot (@grafanabot)
efaf4f5e09 [v9.4.x] Canvas: Update server element design (#62897)
Canvas: Update server element design (#62832)

(cherry picked from commit 43ce077133)

Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com>
2023-02-03 12:07:29 -05:00
Grot (@grafanabot)
907381ed5c [v9.4.x] MSSQL: Add support for macro function calls (#62890)
MSSQL: Add support for macro function calls (#62742)

* MSSQL: Add support for macro function calls

* Add tests

(cherry picked from commit 9fc3b360a4)

Co-authored-by: Victor Marin <36818606+mdvictor@users.noreply.github.com>
2023-02-03 11:03:24 -05:00
Grot (@grafanabot)
8c9cb8e839 [v9.4.x] PanelChrome: Implement hover header (#62875)
PanelChrome: Implement hover header (#61774)

Closes #59078

Co-authored-by: polinaboneva <polina.boneva@grafana.com>
Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com>
Co-authored-by: Alexandra Vargas <alexa1866@gmail.com>
(cherry picked from commit 40ec4ef5b8)

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
2023-02-03 15:30:08 +00:00
Grot (@grafanabot)
028cc7e72b [v9.4.x] Navigation: move Connections plugin to be just after apps (#62871)
Navigation: move Connections plugin to be just after apps (#62801)

move connections plugin to be just after apps

(cherry picked from commit c819e95687)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2023-02-03 09:06:18 -05:00
Grot (@grafanabot)
00555606d5 [v9.4.x] Search: Fix not being able to clear sort value (#62869)
Search: Fix not being able to clear sort value (#62557)

* user essentials mob! 🔱

lastFile:public/app/features/search/state/SearchStateManager.ts

* user essentials mob! 🔱

lastFile:public/app/features/search/page/components/ActionRow.tsx

* user essentials mob! 🔱

* remove searchSort state from localStorage when cleared

---------

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
Co-authored-by: Joao Silva <joao.silva@grafana.com>
(cherry picked from commit f8809eef59)

Co-authored-by: Josh Hunt <joshhunt@users.noreply.github.com>
2023-02-03 08:51:47 -05:00
Grot (@grafanabot)
9cf5b4eb9e [v9.4.x] Tempo: Fix span name being dropped from the query (#62846)
Tempo: Fix span name being dropped from the query (#62257)

(cherry picked from commit c3b476e1dc)

Co-authored-by: Hamas Shafiq <hamas.shafiq@grafana.com>
2023-02-03 05:34:09 -05:00
Grot (@grafanabot)
8637518540 [v9.4.x] Added pageZoomLevel option to image renderer setup documentation (#62842)
Added pageZoomLevel option to image renderer setup documentation (#59472)

* Added pageZoomLevel option to image renderer setup

* Update _index.md

* Update docs/sources/setup-grafana/image-rendering/_index.md

* chore: prettier run in docs

---------

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
(cherry picked from commit 8b017f5aa4)

Co-authored-by: Gabriel Goller <gabrielgoller123@gmail.com>
2023-02-03 10:25:14 +01:00
Grot (@grafanabot)
0c27df8b8c [v9.4.x] Alerting docs: updates to alert rules docs for 9.4 (#62810)
Alerting docs: updates to alert rules docs for 9.4 (#62744)

* Alerting docs: updates to alert rules docs for 9.4

* Adds updates to oncall contact point and pause alerts

* Adds view read-only query update 9.4

(cherry picked from commit d19803a0f0)

Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com>
2023-02-03 07:17:00 +00:00
Grot (@grafanabot)
8fd1547edb [v9.4.x] Alerting: Add label query parameters to state history endpoint (#62835)
Alerting: Add label query parameters to state history endpoint (#62831)

* Allow equality-only matching of arbitrary labels via query params

* Pre-initialize map

(cherry picked from commit 9eeea8f5ea)

Co-authored-by: Alexander Weaver <weaver.alex.d@gmail.com>
2023-02-02 23:14:16 +00:00
Alexander Weaver
21f204d35a Alerting: implement loki query for alert state history (#62833)
Alerting: implement loki query for alert state history (#61992)

* Alerting: implement loki query for alert state history

* extract selector building

* add unit tests for selector creation

* backup

* give selectors their own type

* build dataframe

* add some tests

* small changes after manual testing

* use struct client

* golint

* more golint

* Make RuleUID optional for Loki implementation

* Drop initial assumption that we only have one series

* Pare down to three columns, fix timestamp overflows, improve failure cases in loki responses

* Embed structred log lines in the dataframe as objects rather than json strings

* Include state history label filter

* Remove dead code

---------

Co-authored-by: Jean-Philippe Quéméner <JohnnyQQQQ@users.noreply.github.com>
2023-02-02 16:56:26 -06:00
Grot (@grafanabot)
84da688400 [v9.4.x] Alerting: Pause dash alerts on migration (#62830)
Alerting: Pause dash alerts on migration (#62798)

* Alerting: Pause dash alerts on migration

(cherry picked from commit f49efa6e27)

Co-authored-by: George Robinson <george.robinson@grafana.com>
2023-02-02 22:07:06 +00:00
Grot (@grafanabot)
b742567ade [v9.4.x] Alerting: Fix template validation in provisioning api (#62825)
Alerting: Fix template validation in provisioning api (#62530)

* Alerting: Fix template validation in provisioning api

Fix issue where provisioning API accepts a malformed template having extra
text outside of definition block and template name matching definition name.

(cherry picked from commit f9ec16e74f)

Co-authored-by: Matthew Jacobson <matthew.jacobson@grafana.com>
2023-02-02 20:45:21 +00:00
Grot (@grafanabot)
385b15bf69 [v9.4.x] Alerting: Add static label to all state history entries (#62819)
Alerting: Add static label to all state history entries (#62817)

* Add static label to all state history entries

* Separate label and value visually

(cherry picked from commit 647f73ddc5)

Co-authored-by: Alexander Weaver <weaver.alex.d@gmail.com>
2023-02-02 14:00:09 -06:00
Grot (@grafanabot)
94ec932d29 [v9.4.x] Docs: corrects incorrect redirect and fixes link (#62820)
Docs: corrects incorrect redirect and fixes link (#62815)

corrects incorrect redirect and fixes link

(cherry picked from commit 060e0a4d18)

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
2023-02-02 13:52:26 -06:00
Grot (@grafanabot)
2525b30803 [v9.4.x] Alerting: Add endpoint for querying state history (#62813)
Alerting: Add endpoint for querying state history (#62166)

* Define endpoint and generate

* Wire up and register endpoint

* Cleanup, define authorization

* Forgot the leading slash

* Wire up query and SignedInUser

* Wire up timerange query params

* Add todo for label queries

* Drop comment

* Update path to rules subtree

(cherry picked from commit 6ad1cfef38)

Co-authored-by: Alexander Weaver <weaver.alex.d@gmail.com>
2023-02-02 12:32:30 -06:00
Grot (@grafanabot)
324964b86b [v9.4.x] Alerting: Usability adjustments to Loki representation of state history values (#62811)
Alerting: Usability adjustments to Loki representation of state history values (#62643)

* Extract label merge, add test file

* Extract error/NoData to first class fields, remove a layer from values

* Include dashUID and panelID as line-level fields

* Drop unnecessary object receiver

* Add tests for stream building

* Drop NoData field from log lines

(cherry picked from commit 9fa28c11c5)

Co-authored-by: Alexander Weaver <weaver.alex.d@gmail.com>
2023-02-02 11:15:51 -06:00
Grot (@grafanabot)
d31f932800 [v9.4.x] Auth: Rotate token patch (#62782)
Auth: Rotate token patch (#62676)

* Use singleflight.Group

* Align tests

* Cleanup

(cherry picked from commit 7c1d9769ca)

Co-authored-by: Misi <mgyongyosi@users.noreply.github.com>
2023-02-02 18:05:38 +01:00
Grot (@grafanabot)
363171b182 [v9.4.x] Alerting: Pass yaml as a query param in export request (#62806)
Alerting: Pass yaml as a query param in export request (#62751)

* Set YAML as default value for exporting alert rules

* use YAML format for rule list export

Co-authored-by: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com>

* lint

* Add new format query param to swagger+docs

* Fix broken test

---------

Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
Co-authored-by: Matt Jacobson <matthew.jacobson@grafana.com>
(cherry picked from commit 753c84f825)

Co-authored-by: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com>
2023-02-02 17:29:21 +01:00
Alexander Weaver
32e3cd7cbc Alerting: Refactor away a layer of indirection around the goroutine in Loki state history (#62802)
Alerting: Refactor away a layer of indirection around the goroutine in Loki state history (#62644)

Inline recordStreamsAsync in loki backend
2023-02-02 10:14:47 -06:00
Grot (@grafanabot)
2a223cbea2 [v9.4.x] Tempo: TraceQL syntax highlighting improvements (#62803)
Tempo: TraceQL syntax highlighting improvements (#62349)

* Better syntax highlighting for durations, floats and right-hand-side tags

* Remove hex rule in TraceQL syntax highlighting

(cherry picked from commit 60f2433a0f)

Co-authored-by: Andre Pereira <adrapereira@gmail.com>
2023-02-02 16:03:25 +00:00
Grot (@grafanabot)
c7a182e9d5 [v9.4.x] Tempo: Trace to logs update image (#62804)
Tempo: Trace to logs update image (#62796)

(cherry picked from commit 99a16d27c1)

Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
2023-02-02 17:02:43 +01:00
Grot (@grafanabot)
d8f757cb8c [v9.4.x] Tempo: Inject status and status.code for tags autocomplete (#62800)
Tempo: Inject status and status.code for tags autocomplete (#62794)

* Tempo: Inject status for v2 and status.code for v1 in the tags list for autocomplete

* Small comment fix

(cherry picked from commit b78af0b0f0)

Co-authored-by: Andre Pereira <adrapereira@gmail.com>
2023-02-02 15:53:17 +00:00
Grot (@grafanabot)
bfe6b520d7 [v9.4.x] Tempo: Update docs for trace to logs functionality (#62793)
Tempo: Update docs for trace to logs functionality (#62338)

(cherry picked from commit 1aae808723)

Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
2023-02-02 16:52:24 +01:00
Grot (@grafanabot)
190c3aad58 [v9.4.x] Plugins: Prefer to use the data source UID when querying (#62789)
Plugins: Prefer to use the data source UID when querying (#62776)

(cherry picked from commit 68862ce3e8)

Co-authored-by: Andres Martinez Gotor <andres.martinez@grafana.com>
2023-02-02 15:02:06 +00:00
Grot (@grafanabot)
022abcb47d [v9.4.x] SQLStore: Fix folder migration for MySQL < 5.7 (#62786)
SQLStore: Fix folder migration for MySQL < 5.7 (#62521)

* Nested folders: Do not skip integration tests

* SQLStore: Fix folder migration

It reduces the length of the title column to be equal with the respective
dashboard column.

(cherry picked from commit 4eaff63eda)

Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>
2023-02-02 14:40:51 +00:00
Horst Gutmann
5b305cb696 [9.4.x] CI: Allow other modules to register build sub-commands (PR #62741) (#62774)
CI: Allow other modules to register build sub-commands (#62741)

* Allow other modules to register build sub-commands

* CI: Fix retries on artifacts-page clone

* Fix linting errors

* Fix golint issues

* Update to grabpl 3.0.21

(cherry picked from commit 312ea59e6d)
2023-02-02 15:14:41 +01:00
Grot (@grafanabot)
a03069fb08 [v9.4.x] Alerting: Set YAML as default value for exporting alert rules (#62770)
Alerting: Set YAML as default value for exporting alert rules (#62760)

Set YAML as default value for exporting alert rules

(cherry picked from commit 517e614661)

Co-authored-by: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com>
2023-02-02 13:42:44 +01:00
Grot (@grafanabot)
0413fea8d2 [v9.4.x] Heatmap: Support heatmap rows with non-timeseries X axis (#62733)
Heatmap: Support heatmap rows with non-timeseries X axis (#60929)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
(cherry picked from commit 4a8763d7b6)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2023-02-02 12:27:09 +01:00
Grot (@grafanabot)
30081ca04b [v9.4.x] Nested Folder: Fix for SQLite not to overwrite the parent on restarts (#62756)
Nested Folder: Fix for SQLite not to overwrite the parent on restarts (#62709)

Nested Folder: Fix for SQLite not to overwrite the arent on restarts

(cherry picked from commit adaf82ffb7)

Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>
2023-02-02 10:26:55 +00:00
Grot (@grafanabot)
53a1e5b7e6 [v9.4.x] Fix/60084/save and test (#62750)
Fix: Save and test error message does not persist across datasource instance settings anymore

(cherry picked from commit ce50168b70)

Co-authored-by: Timur Olzhabayev <timur.olzhabayev@grafana.com>
2023-02-02 09:01:07 +00:00
Grot (@grafanabot)
29cc5f9c62 [v9.4.x] Elasticsearch: Fix consistent label order in alerting (#62743)
Elasticsearch: Fix consistent label order in alerting (#62497)

elasticsearch: backend: sort label-values
(cherry picked from commit d9fd807375)

Co-authored-by: Gábor Farkas <gabor.farkas@gmail.com>
2023-02-02 08:22:56 +00:00
Grot (@grafanabot)
b485d1cde9 [v9.4.x] Cloudwatch: Fix log group variable interpolation (#62713)
Cloudwatch: Fix log group variable interpolation (#62640)

(cherry picked from commit 1f09508d8c)

Co-authored-by: Isabella Siu <Isabella.siu@grafana.com>
2023-02-01 13:52:52 -05:00
Grot (@grafanabot)
045c2d4e59 [v9.4.x] docs: fix broken elasticsearch metrics play link (#62719)
docs: fix broken elasticsearch metrics play link (#62715)

fix broken elastic search play link

(cherry picked from commit 1225e8d6d8)

Co-authored-by: Isabel <76437239+imatwawana@users.noreply.github.com>
2023-02-01 12:05:00 -06:00
Grot (@grafanabot)
654af9a48d [v9.4.x] Explore: Fix graph not updating when changing config (#62706)
Explore: Fix graph not updating when changing config (#62473)

* Explore: Fix graph not updating when changing config

* move useStructureRev to a seprate hook and add specific tests

(cherry picked from commit 6b6b733229)

Co-authored-by: Giordano Ricci <me@giordanoricci.com>
2023-02-01 17:02:20 +00:00
Grot (@grafanabot)
5bb58c5172 [v9.4.x] Alerting docs: adds declare incident (#62710)
Alerting docs: adds declare incident (#62681)

* Alerting docs: adds declare incident

* adds alert detail view option

(cherry picked from commit 9d07b77532)

Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com>
2023-02-01 16:59:26 +00:00
Grot (@grafanabot)
42b9c898bf [v9.4.x] Search: Fix alignment of checkbox in folder view (#62708) 2023-02-01 16:44:11 +00:00
Grot (@grafanabot)
795c86b045 [v9.4.x] Tempo: Remove tempoApmTable feature flag (#62702)
Tempo: Remove tempoApmTable feature flag (#62499)

Remove tempoApmTable feature flag

(cherry picked from commit 5e1506dea0)

Co-authored-by: Andre Pereira <adrapereira@gmail.com>
2023-02-01 16:15:58 +00:00
Grot (@grafanabot)
5d5e7f97e9 [v9.4.x] Navigation: wrap dashboard settings actions in ToolbarButtonRow for responsiveness (#62692)
Navigation: wrap dashboard settings actions in `ToolbarButtonRow` for responsiveness (#62475)

wrap dashboard actions in toolbarbuttonrow for responsiveness

(cherry picked from commit 0a780d978e)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2023-02-01 15:14:49 +00:00
Grot (@grafanabot)
c8cf18d8f6 [v9.4.x] Alerting: hide "silence" button for external AM setups (#62691)
Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
2023-02-01 15:10:51 +00:00
Grot (@grafanabot)
70e4499f83 [v9.4.x] Loki: Fix label filter expression treating int as string (#62687)
Loki: Fix label filter expression treating int as string (#62496)

* fix: label filter expression treats int as string

* refactor: labelFilterRenderer

* test: add tests to labelFilterRenderer

* refactor: use backticks based on the operator not the value

* test: labelFilterRenderer uses the correct value type based on the operator

* test: use it.every rather than having multiple repetitive tests

(cherry picked from commit 42f8f5a9ea)

Co-authored-by: Gareth Dawson <gareth.dawson@grafana.com>
2023-02-01 14:52:46 +00:00
Grot (@grafanabot)
2e251a2b20 [v9.4.x] Alerting: Add support for "normal" as state filter in rule search (#62679)
Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
2023-02-01 15:05:51 +01:00
Grot (@grafanabot)
cdec4eb6ab [v9.4.x] Alerting: Add Copy action to templates table (#62683)
Alerting: Add Copy action to templates table (#62135)

* Add duplicate action to templates table

Co-authored-by: Virginia Cepeda <virginia.cepeda@grafana.com>

* Move duplicate icon, and remove provenance when rendering TemplateForm

* Create generic generateCopiedName method to avoid duplication and use it also in the CloneRuleEditor

* Use 'Copy' for duplicating templates and cloning alert rules

* Improve updating the template content with new unique define values when copying

Co-authored-by: Konrad Lalik <konradlalik@gmail.com>

* Fix typo

---------

Co-authored-by: Virginia Cepeda <virginia.cepeda@grafana.com>
Co-authored-by: Konrad Lalik <konradlalik@gmail.com>
(cherry picked from commit 151e57df70)

Co-authored-by: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com>
2023-02-01 14:57:12 +01:00
Grot (@grafanabot)
71a18da270 [v9.4.x] Command palette: section styling tweaks (#62682)
Command palette: section styling tweaks (#62671)

section styling tweaks

(cherry picked from commit 8d7e7693f2)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2023-02-01 13:12:55 +00:00
Grot (@grafanabot)
40354c6b40 [v9.4.x] Alerting: Allow alert rule pausing from API (#62675)
Alerting: Allow alert rule pausing from API (#62326)

* Add is_paused attr to the POST alert rule group endpoint

* Add is_paused to alerting API POST alert rule group

* Fixed tests

* Add is_paused to alerting gettable endpoints

* Fix integration tests

* Alerting: allow to pause existing rules (#62401)

* Display Pause Rule switch in Editing Rule form

* add isPaused property to form interface and dto

* map isPaused prop with is_paused value from DTO

Also update test snapshots

* Append '(Paused)' text on alert list state column when appropriate

* Change Switch styles according to discussion with UX

Also adding a tooltip with info what this means

* Adjust styles

* Fix alignment and isPaused type definition

Co-authored-by: gillesdemey <gilles.de.mey@gmail.com>

* Fix test

* Fix test

* Fix RuleList test

---------

Co-authored-by: gillesdemey <gilles.de.mey@gmail.com>

* wip

* Fix tests and add comments to clarify AlertRuleWithOptionals

* Fix one more test

* Fix tests

* Fix typo in comment

* Fix alert rule(s) cannot be paused via API

* Add integration tests for alerting api pausing flow

* Remove duplicated integration test

---------

Co-authored-by: Virginia Cepeda <virginia.cepeda@grafana.com>
Co-authored-by: gillesdemey <gilles.de.mey@gmail.com>
Co-authored-by: George Robinson <george.robinson@grafana.com>
(cherry picked from commit 53945afedf)

Co-authored-by: Alex Moreno <alexander.moreno@grafana.com>
2023-02-01 12:33:24 +00:00
Grot (@grafanabot)
68fb4da24a [v9.4.x] Alerting: Show 'start typing' message in evaluation group folder in case of empty options. (#62665)
Alerting: Show 'start typing' message in evaluation group folder in case of empty options. (#62611)

Show start typing message in evaluation group folder in case of empty options

(cherry picked from commit c0865c863d)

Co-authored-by: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com>
2023-02-01 11:30:55 +00:00
Grot (@grafanabot)
89f9081658 [v9.4.x] CI: Replace grafana/grafana-oss with grafana-oss when publishing to dockerhub repo (#62653)
CI: Replace `grafana/grafana-oss` with `grafana-oss` when publishing to dockerhub repo (#62651)

Replace grafana/grafana-oss with grafana-oss

(cherry picked from commit 1a5b54cff3)

Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com>
2023-02-01 12:26:17 +02:00
Grot (@grafanabot)
84d2814f7c [v9.4.x] PanelChrome: Adds display mode to support transparent option (#62654)
PanelChrome: Adds display mode to support transparent option (#62647)

* PanelChrome: Add transparent displayMode

* Remove comment

* Fixes to storybook and new example

* no background on TitleItem

---------

Co-authored-by: polinaboneva <polina.boneva@grafana.com>
(cherry picked from commit 533c8e4b7a)

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-02-01 10:05:53 +00:00
Andreas Christou
193b671246 [v9.4.x] TemplateVariables: Fix custom variable function support (#62509) (#62573)
TemplateVariables: Fix custom variable function support (#62509)

Validate query values

(cherry picked from commit 3ac97dd396)

# Conflicts:
#	public/app/features/query/state/runRequest.ts
2023-02-01 10:25:03 +01:00
Grot (@grafanabot)
2bb672a7de [v9.4.x] Geomap: Ensure options work while in table view (#62635)
Geomap: Ensure options work while in table view (#62632)

(cherry picked from commit cf6952a963)

Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com>
2023-01-31 15:31:54 -08:00
Grot (@grafanabot)
c156621981 [v9.4.x] Transforms: Fix schema definition (#62623)
Transforms: Fix schema definition (#62619)

(cherry picked from commit 4186871390)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2023-01-31 15:31:28 -08:00
Grot (@grafanabot)
9a8983e8e9 [v9.4.x] Alerting: Fix handling of special floating-point cases when writing observed values to annotations (#62637)
Alerting: Fix handling of special floating-point cases when writing observed values to annotations (#61074)

* Fix json serialization of state values

* Simplify two of the tests

* Fix linter complaint

* Don't return error if we fail to look up dashboard, just log it and move on

* Address linter complaint

(cherry picked from commit 03f3fbec0d)

Co-authored-by: Alexander Weaver <weaver.alex.d@gmail.com>
2023-01-31 21:51:52 +00:00
Grot (@grafanabot)
4bdfc2d926 [v9.4.x] Expressions: Fixes the issue showing expressions editor (#62622)
Expressions: Fixes the issue showing expressions editor (#62510)

* Use suggested value for uid

* update the snapshot

* use __expr__

* replace all -100 with __expr__

* update snapshot

* more changes

* revert redundant change

* Use expr.DatasourceUID where it's possible

* generate files

(cherry picked from commit 91221bc436)

Co-authored-by: ismail simsek <ismailsimsek09@gmail.com>
2023-01-31 18:10:13 +00:00
Grot (@grafanabot)
bd9707e8f3 [v9.4.x] Use requires_buildifier build tag to avoid needing buildifier locally (#62621)
Use requires_buildifier build tag to avoid needing buildifier locally (#62597)

Signed-off-by: Jack Baldry <jack.baldry@grafana.com>
(cherry picked from commit fdb1a47ca2)

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>
2023-01-31 18:03:03 +00:00
Grot (@grafanabot)
3023a43d4f [v9.4.x] TestData: Remove references to TestData "DB" (#62613)
TestData: Remove references to TestData "DB" (#62603)

* remove refs testdatadb

* fix trailing semi-colon

* remove pluginName completely

(cherry picked from commit e7bfc4e749)

Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
2023-01-31 18:02:42 +00:00
Grot (@grafanabot)
41b0393140 [v9.4.x] Transformations: Selectively apply transformation to queries (#62615)
Transformations: Selectively apply transformation to queries (#61735)

(cherry picked from commit bba80b6c7a)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2023-01-31 09:48:18 -08:00
Grot (@grafanabot)
90a4c84bc2 [v9.4.x] MySQL: Quote identifiers that include special characters (#62618)
MySQL: Quote identifiers that include special characters (#61135)

* SQL: toRawSQL required and escape table

* Fix autocomplete for MySQL

* Change the way we escape for builder

* Rework escape ident to be smart instead

* Fix A11y for alias

* Add first e2e test

* Add test for code editor

* Add doc

* Review comments

* Move functions to sqlUtil

(cherry picked from commit 62c30dea4d)

Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
2023-01-31 17:26:44 +00:00
Grot (@grafanabot)
6749e8667d [v9.4.x] TopNav: Fix right padding on signin link (#62606)
TopNav: Fix right padding on signin link (#62537)

* TopNav: Fix right padding on signin link

* revert yarn lock

* Update package.json

* Update

(cherry picked from commit c0f0c3d485)

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-01-31 16:31:45 +00:00
Grot (@grafanabot)
9727346e63 [v9.4.x] CI: Add artifacts publish build command (#62503)
CI: Add `artifacts publish` build command (#62445)

* CI: Add `artifacts publish` build command

* Lint release.star

(cherry picked from commit f23be415c5)

Co-authored-by: Horst Gutmann <horst.gutmann@grafana.com>
2023-01-31 16:29:34 +01:00
Grot (@grafanabot)
3683b7a5ff [v9.4.x] Alerting: Validate that tags are 100 characters or less (#62593)
Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
Co-authored-by: George Robinson <george.robinson@grafana.com>
2023-01-31 14:41:06 +00:00
Grot (@grafanabot)
f9ca726290 [v9.4.x] Navigation: add event tracking for dashboard save as events (#62588)
Navigation: add event tracking for dashboard save as events (#62568)

* add event tracking for dashboard save as events

* emit a grafana_dashboard_copied event instead

(cherry picked from commit 029253d5f6)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2023-01-31 14:01:08 +00:00
Grot (@grafanabot)
15ec06b593 [v9.4.x] Azure Monitor: Return query error for metrics (#62577)
Azure Monitor: Return query error for metrics (#62570)

(cherry picked from commit 13f8ea2656)

Co-authored-by: Andres Martinez Gotor <andres.martinez@grafana.com>
2023-01-31 14:41:21 +01:00
Grot (@grafanabot)
953d9db30d [v9.4.x] Azure Monitor: Fix selection when using a search term (#62572)
Azure Monitor: Fix selection when using a search term (#62562)

(cherry picked from commit a0c3dcb8c6)

Co-authored-by: Andres Martinez Gotor <andres.martinez@grafana.com>
2023-01-31 13:29:59 +00:00
Grot (@grafanabot)
a7b9dcdce8 [v9.4.x] Navigation: add event tracking for navigation elements (#62583)
Navigation: add event tracking for navigation elements (#62563)

add user tracking for navigation elements

(cherry picked from commit 77a186879d)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2023-01-31 13:13:34 +00:00
Grot (@grafanabot)
76f3ed3c3f [v9.4.x] AzureMonitor: Fix dimension migration (#62579)
AzureMonitor: Fix dimension migration (#62485)

Remove unneeded properties post migration

(cherry picked from commit f77853f91e)

Co-authored-by: Andreas Christou <andreas.christou@grafana.com>
2023-01-31 13:11:18 +00:00
Grot (@grafanabot)
2ad87ce213 [v9.4.x] Plugins: Fix circular reference in customOptions leading to MarshalJSON errors (#62565)
Plugins: Fix circular reference in customOptions leading to MarshalJSON errors (#62328)

* Plugins: test ds.JsonData.MarshalJSON()

* CustomOptions: copy to avoid cyclic marshal

(cherry picked from commit c41f97029e)

Co-authored-by: Yasir Ekinci <ekinci.yasir@gmail.com>
2023-01-31 13:29:23 +01:00
Grot (@grafanabot)
cc5b3c11c4 [v9.4.x] Login: Fix panic when UpsertUser is called without ReqContext (#62555)
Login: Fix panic when UpsertUser is called without ReqContext (#62539)

(cherry picked from commit b1151dd118)

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
2023-01-31 12:25:11 +01:00
Grot (@grafanabot)
20731672ed [v9.4.x] Chore: Update latest.json to 9.4.0-beta1 (#62560)
Chore: Update latest.json to 9.4.0-beta1 (#62556)

Chore: update latest.json to 9.4.0-beta1
(cherry picked from commit ef7716d6b3)

Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com>
2023-01-31 13:00:57 +02:00
Grot (@grafanabot)
f9ec04bbb7 [v9.4.x] PanelChrome: Styling issues (#62550)
PanelChrome: Styling issues  (#62466)

* all panel icons are 16x16 in size; allow ToolbarButton to have its icon size set from outside;

* use TitleItem for streaming too, so that the style of focus-visible is the same

* allow menu icon to be visible when panel is focused

* remove some styling of title icons in panel header

* panel alert notices are too big

* PanelHeaderNotice: Fix styling issue with background and hover when
feature toggle is not enable

---------

Co-authored-by: Alexandra Vargas <alexa1866@gmail.com>
(cherry picked from commit d48a8fd227)

Co-authored-by: Polina Boneva <13227501+polibb@users.noreply.github.com>
2023-01-31 12:39:56 +02:00
Grot (@grafanabot)
0e6d038934 Release: Bump version to 9.4.0 (#62554)
"Release: Updated versions in package to 9.4.0"
2023-01-31 12:07:54 +02:00
Grot (@grafanabot)
b8c6ff611d [v9.4.x] Plugins: Update migration guide for 9.4/forwarded headers (#62549)
Plugins: Update migration guide for 9.4/forwarded headers (#62505)

Update plugin migration guide in regards to v9.4 and forwarding
of headers in grafana-plugin-sdk-go.

(cherry picked from commit 6c02c7079f)

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
2023-01-31 10:55:56 +01:00
Grot (@grafanabot)
ecfd92ed30 [v9.4.x] Navigation: Sign in button now works correctly when served under a sub path (#62548)
Navigation: Sign in button now works correctly when served under a sub path (#62504)

make sure getUrlForPartial always includes the basePath + unit tests

(cherry picked from commit fd2641a542)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2023-01-31 09:36:37 +00:00
Grot (@grafanabot)
8eb9971797 [v9.4.x] Azure Monitor: Docs update for multiple resources (#62543)
Azure Monitor: Docs update for multiple resources (#62454)

(cherry picked from commit d3897b612f)

Co-authored-by: Andres Martinez Gotor <andres.martinez@grafana.com>
2023-01-31 09:56:45 +01:00
Grot (@grafanabot)
c567e690ad [v9.4.x] Alerting: Clarify PagerDuty integrationKey format (#62513)
Alerting: Clarify PagerDuty integrationKey format (#62463)

**What is this feature?**

Simply a documentation update.

**Why do we need this feature?**

Clarify the doc, so people don't waste time integrating the wrong key,
e.g. #8979, #9036, https://community.librenms.org/t/19013

**Who is this feature for?**

Developers doing a PagerDuty integration.

(cherry picked from commit a7c4872bcd)

Co-authored-by: Andre Miras <AndreMiras@users.noreply.github.com>
2023-01-31 08:56:14 +00:00
Grot (@grafanabot)
acf1b1285b [v9.4.x] Canvas: Update connection info on element rename (#62536) 2023-01-31 04:44:52 +00:00
Grot (@grafanabot)
3d65500a4f [v9.4.x] Alerting: Allow separate read and write path URLs for Loki state history (#62528)
Alerting: Allow separate read and write path URLs for Loki state history (#62268)

Extract config parsing and add tests

(cherry picked from commit e7ace4ed62)

Co-authored-by: Alexander Weaver <weaver.alex.d@gmail.com>
2023-01-30 17:01:53 -06:00
Grot (@grafanabot)
589284778a [v9.4.x] Canvas: Connections positioning ux improvements (#62525)
Canvas: Connections positioning ux improvements (#62516)

(cherry picked from commit a92c081a33)

Co-authored-by: Adela Almasan <88068998+adela-almasan@users.noreply.github.com>
2023-01-30 13:59:51 -08:00
Grot (@grafanabot)
b9e989cbf2 [v9.4.x] Geomap: Maintain consistent control styling (#62522)
Geomap: Maintain consistent control styling (#62518)

Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
(cherry picked from commit bf2cf76cad)

Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com>
2023-01-30 13:35:54 -08:00
Grot (@grafanabot)
f39c46a1b5 [v9.4.x] Alerting: Configurable externalLabels for Loki state history (#62517)
Alerting: Configurable externalLabels for Loki state history (#62404)

* Add config option for external labels

* Remove redundant nilcheck

(cherry picked from commit b4682fe3cb)

Co-authored-by: Alexander Weaver <weaver.alex.d@gmail.com>
2023-01-30 15:34:40 -06:00
Grot (@grafanabot)
48bd8ebe92 [v9.4.x] Consider y coord when determining bottom collision (#62506)
Consider y coord when determining bottom collision (#62403)

(cherry picked from commit b6f477ae03)

Co-authored-by: Kristina <kristina.durivage@grafana.com>
2023-01-30 17:01:53 +00:00
Grot (@grafanabot)
34524d6dfa [v9.4.x] Azure Monitor: Enable multiple resource queries (#62502)
Azure Monitor: Enable multiple resource queries (#62467)

(cherry picked from commit 6d230d95eb)

Co-authored-by: Andres Martinez Gotor <andres.martinez@grafana.com>
2023-01-30 17:55:06 +01:00
Grot (@grafanabot)
6e861b19fa [v9.4.x] Alerting: allow to pause existing rules (#62491)
Co-authored-by: gillesdemey <gilles.de.mey@gmail.com>
Co-authored-by: Virginia Cepeda <virginia.cepeda@grafana.com>
2023-01-30 17:21:27 +01:00
Grot (@grafanabot)
26f7b8ee65 [v9.4.x] Alerting: Add Rule UID and Clone button to the rule details page (#62487)
Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
2023-01-30 11:16:47 -05:00
Grot (@grafanabot)
04dd4e7f7c [v9.4.x] Alerting: Allow pausing alerts from provisioning (#62492)
Alerting: Allow pausing alerts from provisioning (#62263)

* Allow pausing alerts from provisioning

* Update swagger

* Add IsPaused to provision export endpoints

* Add pause field in sample.yml

* Add exception for reset state in first loop iteration of scheduler if rule is paused

* Update provision definition and swagger docs

* Fix provisioning export tests

* Suggestion: Simplify if condition

* Add more context to a comment

(cherry picked from commit 7a465f42a6)

Co-authored-by: Alex Moreno <alexander.moreno@grafana.com>
2023-01-30 17:01:37 +01:00
Grot (@grafanabot)
992e5d72ff [v9.4.x] Docs: Update wording / text and copy (#62490)
Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com>
Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
2023-01-30 15:46:25 +00:00
Grot (@grafanabot)
1b0f5f0a81 Release: Bump version to 9.4.0-beta1 (#62465)
"Release: Updated versions in package to 9.4.0-beta1"
2023-01-30 14:47:22 +02:00
371 changed files with 7100 additions and 2260 deletions

View File

@@ -575,8 +575,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
],
"packages/grafana-data/src/types/variables.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
@@ -719,17 +718,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
],
"packages/grafana-data/src/utils/location.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"]
],
"packages/grafana-data/src/utils/location.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
@@ -985,8 +973,9 @@ exports[`better eslint`] = {
"packages/grafana-schema/src/veneer/dashboard.types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"]
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"],
[0, 0, 0, "Do not use any type assertions.", "4"]
],
"packages/grafana-toolkit/src/cli/tasks/component.create.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],

View File

@@ -21,7 +21,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -77,7 +77,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -132,7 +132,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -278,7 +278,7 @@ steps:
image: grafana/build-container:v1.7.1
name: wire-install
- commands:
- go test -short -covermode=atomic -timeout=5m ./pkg/...
- go test -tags requires_buildifer -short -covermode=atomic -timeout=5m ./pkg/...
depends_on:
- wire-install
image: grafana/build-container:v1.7.1
@@ -396,7 +396,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -729,7 +729,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -844,7 +844,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -961,7 +961,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -1038,7 +1038,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -1178,7 +1178,7 @@ steps:
image: grafana/build-container:v1.7.1
name: wire-install
- commands:
- go test -short -covermode=atomic -timeout=5m ./pkg/...
- go test -tags requires_buildifer -short -covermode=atomic -timeout=5m ./pkg/...
depends_on:
- wire-install
image: grafana/build-container:v1.7.1
@@ -1288,7 +1288,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -1719,7 +1719,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -1835,7 +1835,7 @@ steps:
name: identify-runner
- commands:
- $$ProgressPreference = "SilentlyContinue"
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/windows/grabpl.exe
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/windows/grabpl.exe
-OutFile grabpl.exe
image: grafana/ci-wix:0.1.1
name: windows-init
@@ -2014,7 +2014,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2320,7 +2320,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2406,7 +2406,7 @@ steps:
image: grafana/build-container:v1.7.1
name: wire-install
- commands:
- go test -short -covermode=atomic -timeout=5m ./pkg/...
- go test -tags requires_buildifer -short -covermode=atomic -timeout=5m ./pkg/...
depends_on:
- wire-install
image: grafana/build-container:v1.7.1
@@ -2467,7 +2467,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2573,7 +2573,7 @@ steps:
name: identify-runner
- commands:
- $$ProgressPreference = "SilentlyContinue"
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/windows/grabpl.exe
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/windows/grabpl.exe
-OutFile grabpl.exe
image: grafana/ci-wix:0.1.1
name: windows-init
@@ -2630,7 +2630,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2956,7 +2956,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -3021,7 +3021,7 @@ steps:
name: clone-enterprise
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -3079,7 +3079,7 @@ steps:
image: grafana/build-container:v1.7.1
name: wire-install
- commands:
- go test -short -covermode=atomic -timeout=5m ./pkg/...
- go test -tags requires_buildifer -short -covermode=atomic -timeout=5m ./pkg/...
depends_on:
- wire-install
image: grafana/build-container:v1.7.1
@@ -3146,7 +3146,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -3238,7 +3238,9 @@ steps:
name: mysql-integration-tests
- commands:
- dockerize -wait tcp://redis:6379/0 -timeout 120s
- ./bin/grabpl integration-tests
- go clean -testcache
- go list './pkg/...' | xargs -I {} sh -c 'go test -run Integration -covermode=atomic
-timeout=5m {}'
depends_on:
- wire-install
environment:
@@ -3299,7 +3301,7 @@ steps:
name: identify-runner
- commands:
- $$ProgressPreference = "SilentlyContinue"
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/windows/grabpl.exe
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/windows/grabpl.exe
-OutFile grabpl.exe
- git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"
- cd grafana-enterprise
@@ -3376,7 +3378,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -3626,7 +3628,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -3877,7 +3879,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -3924,8 +3926,8 @@ steps:
- name: docker
path: /var/run/docker.sock
- commands:
- ./bin/grabpl artifacts docker publish --dockerhub-repo grafana/grafana/grafana-oss
--version-tag ${DRONE_TAG}
- ./bin/grabpl artifacts docker publish --dockerhub-repo grafana/grafana-oss --version-tag
${DRONE_TAG}
depends_on:
- fetch-images-oss
environment:
@@ -3936,7 +3938,7 @@ steps:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: publish-images-grafana/grafana-oss
name: publish-images-grafana-oss
volumes:
- name: docker
path: /var/run/docker.sock
@@ -3973,7 +3975,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -4052,7 +4054,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -4322,20 +4324,27 @@ platform:
services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
- commands:
- ./bin/grabpl artifacts publish --security --tag $${DRONE_TAG} --src-bucket $${PRERELEASE_BUCKET}
depends_on:
- grabpl
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.19.4
name: compile-build-cmd
- commands:
- ./bin/build artifacts publish --security --tag $${DRONE_TAG} --src-bucket $${PRERELEASE_BUCKET}
depends_on:
- compile-build-cmd
environment:
ENTERPRISE2_SECURITY_PREFIX:
from_secret: enterprise2_security_prefix
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
SECURITY_DEST_BUCKET:
from_secret: security_dest_bucket
STATIC_ASSET_EDITIONS:
from_secret: static_asset_editions
image: grafana/grafana-ci-deploy:1.3.3
name: publish-artifacts
trigger:
@@ -4366,20 +4375,27 @@ platform:
services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
- commands:
- ./bin/grabpl artifacts publish --tag $${DRONE_TAG} --src-bucket $${PRERELEASE_BUCKET}
depends_on:
- grabpl
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
depends_on: []
environment:
CGO_ENABLED: 0
image: golang:1.19.4
name: compile-build-cmd
- commands:
- ./bin/build artifacts publish --tag $${DRONE_TAG} --src-bucket $${PRERELEASE_BUCKET}
depends_on:
- compile-build-cmd
environment:
ENTERPRISE2_SECURITY_PREFIX:
from_secret: enterprise2_security_prefix
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
SECURITY_DEST_BUCKET:
from_secret: security_dest_bucket
STATIC_ASSET_EDITIONS:
from_secret: static_asset_editions
image: grafana/grafana-ci-deploy:1.3.3
name: publish-artifacts
trigger:
@@ -4477,7 +4493,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -4574,7 +4590,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -4654,7 +4670,7 @@ clone:
retries: 3
depends_on: []
environment:
EDITION: all
EDITION: enterprise
image_pull_secrets:
- dockerconfigjson
kind: pipeline
@@ -4668,14 +4684,47 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
- commands:
- ./bin/grabpl artifacts-page
- git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git"
- cd grafana-enterprise
- git checkout ${DRONE_TAG}
environment:
GITHUB_TOKEN:
from_secret: github_token
image: grafana/build-container:v1.7.1
name: clone-enterprise
- commands:
- mv bin/grabpl /tmp/
- rmdir bin
- mv grafana-enterprise /tmp/
- /tmp/grabpl init-enterprise --github-token $${GITHUB_TOKEN} /tmp/grafana-enterprise
${DRONE_TAG}
- mv /tmp/grafana-enterprise/deployment_tools_config.json deployment_tools_config.json
- mkdir bin
- mv /tmp/grabpl bin/
depends_on:
- grabpl
- clone-enterprise
environment:
GITHUB_TOKEN:
from_secret: github_token
image: grafana/build-container:v1.7.1
name: init-enterprise
- commands:
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
depends_on:
- init-enterprise
environment:
CGO_ENABLED: 0
image: golang:1.19.4
name: compile-build-cmd
- commands:
- ./bin/build artifacts-page
depends_on:
- compile-build-cmd
environment:
GCP_KEY:
from_secret: gcp_key
@@ -4713,7 +4762,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -4992,7 +5041,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -5075,7 +5124,7 @@ steps:
image: grafana/build-container:v1.7.1
name: wire-install
- commands:
- go test -short -covermode=atomic -timeout=5m ./pkg/...
- go test -tags requires_buildifer -short -covermode=atomic -timeout=5m ./pkg/...
depends_on:
- wire-install
image: grafana/build-container:v1.7.1
@@ -5133,7 +5182,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -5236,7 +5285,7 @@ steps:
name: identify-runner
- commands:
- $$ProgressPreference = "SilentlyContinue"
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/windows/grabpl.exe
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/windows/grabpl.exe
-OutFile grabpl.exe
image: grafana/ci-wix:0.1.1
name: windows-init
@@ -5286,7 +5335,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -5614,7 +5663,7 @@ steps:
name: identify-runner
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -5676,7 +5725,7 @@ steps:
name: clone-enterprise
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -5733,7 +5782,7 @@ steps:
image: grafana/build-container:v1.7.1
name: wire-install
- commands:
- go test -short -covermode=atomic -timeout=5m ./pkg/...
- go test -tags requires_buildifer -short -covermode=atomic -timeout=5m ./pkg/...
depends_on:
- wire-install
image: grafana/build-container:v1.7.1
@@ -5797,7 +5846,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -5888,7 +5937,9 @@ steps:
name: mysql-integration-tests
- commands:
- dockerize -wait tcp://redis:6379/0 -timeout 120s
- ./bin/grabpl integration-tests
- go clean -testcache
- go list './pkg/...' | xargs -I {} sh -c 'go test -run Integration -covermode=atomic
-timeout=5m {}'
depends_on:
- wire-install
environment:
@@ -5946,7 +5997,7 @@ steps:
name: identify-runner
- commands:
- $$ProgressPreference = "SilentlyContinue"
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/windows/grabpl.exe
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/windows/grabpl.exe
-OutFile grabpl.exe
- git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"
- cd grafana-enterprise
@@ -6016,7 +6067,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.20/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.21/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -6502,6 +6553,6 @@ kind: secret
name: aws_secret_access_key
---
kind: signature
hmac: 6e76bf175f2c58fd4ffdc42e2120c558345a71a45011279b14092acb67252b28
hmac: 934e2dbe22f80a17ebb301d68fdac9d91544dbb92486f798300bb39436405bff
...

View File

@@ -11,7 +11,7 @@ apiVersion: 1
# folder: my_first_folder
# # <duration, required> interval of the rule group evaluation
# interval: 60s
# # <list, required> list of rules that are part of the rule group
# # <list, required> list of rules that are part of the rule group
# rules:
# # <string, required> unique identifier for the rule
# - uid: my_id_1
@@ -23,7 +23,7 @@ apiVersion: 1
# # evaluation - should be obtained via the API
# data:
# - refId: A
# datasourceUid: "-100"
# datasourceUid: "__expr__"
# model:
# conditions:
# - evaluator:
@@ -40,7 +40,7 @@ apiVersion: 1
# type: query
# datasource:
# type: __expr__
# uid: "-100"
# uid: "__expr__"
# expression: 1==0
# intervalMs: 1000
# maxDataPoints: 43200
@@ -53,7 +53,7 @@ apiVersion: 1
# # <string> state of the alert rule when no data is returned
# # possible values: "NoData", "Alerting", "OK", default = NoData
# noDataState: Alerting
# # <string> state of the alert rule when the query execution
# # <string> state of the alert rule when the query execution
# # fails - possible values: "Error", "Alerting", "OK"
# # default = Alerting
# executionErrorState: Alerting
@@ -62,10 +62,11 @@ apiVersion: 1
# # <map<string, string>> map of strings to attach arbitrary custom data
# annotations:
# some_key: some_value
# # <map<string, string> map of strings to filter and
# # <map<string, string> map of strings to filter and
# # route alerts
# labels:
# team: sre_team_1
# isPaused: false
# # List of alert rule UIDs that should be deleted
# deleteRules:
@@ -103,7 +104,7 @@ apiVersion: 1
# # <list<string>> The labels by which incoming alerts are grouped together. For example,
# # multiple alerts coming in for cluster=A and alertname=LatencyHigh would
# # be batched into a single group.
# #
# #
# # To aggregate by all possible labels, use the special value '...' as
# # the sole label name, for example:
# # group_by: ['...']
@@ -127,7 +128,7 @@ apiVersion: 1
# mute_time_intervals:
# - abc
# # <duration> How long to initially wait to send a notification for a group
# # of alerts. Allows to collect more initial alerts for the same group.
# # of alerts. Allows to collect more initial alerts for the same group.
# # (Usually ~0s to few minutes), default = 30s
# group_wait: 30s
# # <duration> How long to wait before sending a notification about new alerts that
@@ -138,7 +139,7 @@ apiVersion: 1
# # been sent successfully for an alert. (Usually ~3h or more), default = 4h
# repeat_interval: 4h
# # <list> Zero or more child routes
# routes:
# routes:
# ...
# # List of orgIds that should be reset to the default policy

View File

@@ -5,8 +5,7 @@
"label": "gdev-testdata",
"description": "",
"type": "datasource",
"pluginId": "testdata",
"pluginName": "TestData DB"
"pluginId": "testdata"
}
],
"__requires": [
@@ -19,7 +18,7 @@
{
"type": "datasource",
"id": "testdata",
"name": "TestData DB",
"name": "TestData",
"version": "1.0.0"
},
{

View File

@@ -0,0 +1,326 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 116,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 10,
"x": 0,
"y": 0
},
"id": 2,
"options": {
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"pluginVersion": "9.4.0-pre",
"targets": [
{
"csvContent": "x,y1,y2\n1,8,12\n2,6,13\n3,7,9\n5,9,7\n6,5,9",
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_content"
}
],
"title": "Raw heatmap rows",
"type": "table"
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"fieldConfig": {
"defaults": {
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"scaleDistribution": {
"type": "linear"
}
}
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 14,
"x": 10,
"y": 0
},
"id": 4,
"options": {
"calculate": false,
"cellGap": 1,
"color": {
"exponent": 0.5,
"fill": "dark-orange",
"mode": "scheme",
"reverse": false,
"scale": "exponential",
"scheme": "Oranges",
"steps": 64
},
"exemplars": {
"color": "rgba(255,0,255,0.7)"
},
"filterValues": {
"le": 1e-9
},
"legend": {
"show": true
},
"rowsFrame": {
"layout": "auto"
},
"tooltip": {
"show": true,
"yHistogram": false
},
"yAxis": {
"axisPlacement": "left",
"reverse": false
}
},
"pluginVersion": "9.4.0-pre",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 2,
"refId": "A"
}
],
"title": "Row heatmap",
"type": "heatmap"
},
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 10,
"x": 0,
"y": 9
},
"id": 5,
"options": {
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"pluginVersion": "9.4.0-pre",
"targets": [
{
"csvContent": "x,y,count\n1,4,10\n1,6,11\n2,5,30\n2,4,22\n3,6,17",
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "csv_content"
}
],
"title": "Raw heatmap cells",
"type": "table"
},
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"fieldConfig": {
"defaults": {
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"scaleDistribution": {
"type": "linear"
}
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 14,
"x": 10,
"y": 9
},
"id": 6,
"options": {
"calculate": false,
"cellGap": 1,
"color": {
"exponent": 0.5,
"fill": "dark-orange",
"mode": "scheme",
"reverse": false,
"scale": "exponential",
"scheme": "Oranges",
"steps": 64
},
"exemplars": {
"color": "rgba(255,0,255,0.7)"
},
"filterValues": {
"le": 1e-9
},
"legend": {
"show": true
},
"rowsFrame": {
"layout": "auto"
},
"tooltip": {
"show": true,
"yHistogram": false
},
"yAxis": {
"axisPlacement": "left",
"reverse": false
}
},
"pluginVersion": "9.4.0-pre",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 5,
"refId": "A"
}
],
"title": "Cells heatmap",
"type": "heatmap"
}
],
"revision": 1,
"schemaVersion": 37,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Heatmap X axis",
"uid": "5Y0jv6pVz",
"version": 3,
"weekStart": ""
}

View File

@@ -299,7 +299,10 @@
"revision": 1,
"schemaVersion": 37,
"style": "dark",
"tags": [],
"tags": [
"gdev",
"transform"
],
"templating": {
"list": []
},
@@ -309,7 +312,7 @@
},
"timepicker": {},
"timezone": "",
"title": "Test extractFields JSON",
"title": "Transforms - Test extractFields JSON",
"uid": "pD4vPYhVz",
"version": 3,
"weekStart": ""

View File

@@ -0,0 +1,171 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 1394,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"uid": "PD8C576611E62080A",
"type": "testdata"
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
},
{
"color": "red",
"value": 80
}
]
},
"color": {
"mode": "thresholds"
}
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 0
},
"id": 2,
"targets": [
{
"scenarioId": "csv_content",
"refId": "A",
"datasource": {
"uid": "PD8C576611E62080A",
"type": "testdata"
},
"csvContent": "AAA\n1\n2\n3\n4"
},
{
"scenarioId": "csv_content",
"refId": "B",
"datasource": {
"uid": "PD8C576611E62080A",
"type": "testdata"
},
"csvContent": "BBB\n1\n2\n3\n4\n",
"hide": false
}
],
"title": "Transformer query filters",
"type": "table",
"transformations": [
{
"id": "reduce",
"options": {
"reducers": [
"min"
],
"mode": "reduceFields",
"includeTimeField": false
},
"filter": {
"id": "byRefId",
"options": "A"
}
},
{
"id": "reduce",
"options": {
"reducers": [
"max"
],
"mode": "reduceFields",
"includeTimeField": false
},
"filter": {
"id": "byRefId",
"options": "B"
}
},
{
"id": "concatenate",
"options": {}
},
{
"id": "organize",
"options": {
"excludeByName": {},
"indexByName": {},
"renameByName": {
"AAA": "Min from Query A",
"BBB": "Max from Query B"
}
}
}
],
"options": {
"showHeader": true,
"footer": {
"show": false,
"reducer": [
"sum"
],
"countRows": false,
"fields": ""
},
"frameIndex": 0
},
"pluginVersion": "9.4.0-pre"
}
],
"schemaVersion": 37,
"style": "dark",
"tags": [
"gdev",
"transform"
],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Transforms - Filters",
"uid": "fGWBVW4k"
}

View File

@@ -624,7 +624,7 @@
},
"timepicker": {},
"timezone": "",
"title": "Join by field",
"title": "Transforms - Join by field",
"uid": "gw0K4rmVz",
"version": 6,
"weekStart": ""

View File

@@ -347,7 +347,7 @@
},
"timepicker": {},
"timezone": "",
"title": "Join by labels",
"title": "Transforms - Join by labels",
"uid": "FVl-9CR4z",
"version": 10,
"weekStart": ""

View File

@@ -521,7 +521,10 @@
],
"schemaVersion": 37,
"style": "dark",
"tags": ["devenv"],
"tags": [
"gdev",
"transform"
],
"templating": {
"list": []
},
@@ -531,6 +534,6 @@
},
"timepicker": {},
"timezone": "",
"title": "Reuse dashboard queries",
"title": "Transforms - Reuse dashboard queries",
"uid": "fYGWTVW4k"
}

View File

@@ -184,6 +184,13 @@ local dashboard = grafana.dashboard;
id: 0,
}
},
dashboard.new('filter', import '../dev-dashboards/transforms/filter.json') +
resource.addMetadata('folder', 'dev-dashboards') +
{
spec+: {
id: 0,
}
},
dashboard.new('gauge-multi-series', import '../dev-dashboards/panel-gauge/gauge-multi-series.json') +
resource.addMetadata('folder', 'dev-dashboards') +
{
@@ -296,6 +303,13 @@ local dashboard = grafana.dashboard;
id: 0,
}
},
dashboard.new('heatmap-x', import '../dev-dashboards/panel-heatmap/heatmap-x.json') +
resource.addMetadata('folder', 'dev-dashboards') +
{
spec+: {
id: 0,
}
},
dashboard.new('histogram_tests', import '../dev-dashboards/panel-histogram/histogram_tests.json') +
resource.addMetadata('folder', 'dev-dashboards') +
{

View File

@@ -21,7 +21,7 @@ Watch this video to learn more about creating alerts: {{< vimeo 720001934 >}}
## Add Grafana managed rule
1. In the Grafana menu, click the **Alerting** (bell) icon to open the Alerting page listing existing alerts.
1. Click **New alert rule**. The new alerting rule page opens where the Grafana managed alerts option is selected by default.
1. Click **Create alert rule**. The new alerting rule page opens where the Grafana managed alerts option is selected by default.
1. In Step 1, add the rule name.
- In **Rule name**, add a descriptive name. This name is displayed in the alert rule list. It is also the `alertname` label for every alert instance that is created from this rule.
1. In Step 2, add queries and expressions to evaluate, and then select the alert condition.
@@ -31,12 +31,18 @@ Watch this video to learn more about creating alerts: {{< vimeo 720001934 >}}
- Click **Run queries** to verify that the query is successful.
- Next, select the query or expression for your alert condition.
1. In Step 3, specify the alert evaluation interval.
- From the **Condition** drop-down, select the query or expression to trigger the alert rule.
- For **Evaluate every**, specify the frequency of evaluation. Must be a multiple of 10 seconds. For examples, `1m`, `30s`.
- For **Evaluate for**, specify the duration for which the condition must be true before an alert fires.
> **Note:** Once a condition is breached, the alert goes into the Pending state. If the condition remains breached for the duration specified, the alert transitions to the `Firing` state, otherwise it reverts back to the `Normal` state.
- In **Configure no data and error handling**, configure alerting behavior in the absence of data. Use the guidelines in [No data and error handling](#no-data-and-error-handling).
- Click **Preview alerts** to check the result of running the query at this moment. Preview excludes no data and error handling.
**Note:**
You can pause alert rule evaluation to prevent noisy alerting while tuning your alerts. Pausing stops alert rule evaluation and does not create any alert instances. This is different to mute timings, which stop notifications from being delivered, but still allow for alert rule evaluation and the creation of alert instances.
1. In Step 4, add the storage location, rule group, as well as additional metadata associated with the rule.
- From the **Folder** drop-down, select the folder where you want to store the rule.
- For **Group**, specify a pre-defined group. Newly created rules are appended to the end of the group. Rules within a group are run sequentially at a regular interval, with the same evaluation time.

View File

@@ -35,7 +35,7 @@ You can create and manage recording rules for an external Grafana Mimir or Loki
To create a Grafana Mimir or Loki managed recording rule
1. In the Grafana menu, click the **Alerting** (bell) icon to open the Alerting page listing existing alerts.
1. Click **New alert rule**. The new alerting rule page opens where the **Grafana managed alert** option is selected by default.
1. Click **Create alert rule**. The new alerting rule page opens where the **Grafana managed alert** option is selected by default.
1. In Step 1, add the rule name. The recording name must be a Prometheus metric name and contain no whitespace.
- In **Rule name**, add a descriptive name.
1. In Step 2, select **Mimir or Loki recording rule** option.
@@ -49,7 +49,7 @@ To create a Grafana Mimir or Loki managed recording rule
1. Click **Save** to save the recording rule or **Save and exit** to save the recording rule and go back to the Alerting page.
1. In the Grafana menu, click the **Alerting** (bell) icon to open the Alerting page listing existing alerts.
1. Click **New alert rule**.
1. Click **Create alert rule**.
1. In Step 1, add the rule name.
- In **Rule name**, add a descriptive name. This name is displayed in the alert rule list. It is also the `alertname` label for every alert instance that is created from this rule.
1. In Step 2, add the type, and storage location.

View File

@@ -35,7 +35,7 @@ Watch this video to learn more about how to create a Mimir managed alert rule: {
## Add a Grafana Mimir or Loki managed alerting rule
1. In the Grafana menu, click the **Alerting** (bell) icon to open the Alerting page listing existing alerts.
1. Click **New alert rule**. The new alerting rule page opens where the Grafana managed alerts option is selected by default.
1. Click **Create alert rule**. The new alerting rule page opens where the Grafana managed alerts option is selected by default.
1. In Step 1, add the rule name.
- In **Rule name**, add a descriptive name. This name is displayed in the alert rule list. It is also the `alertname` label for every alert instance that is created from this rule.
1. In Step 2, select **Mimir or Loki alert** option.

View File

@@ -0,0 +1,49 @@
---
aliases:
description: Declare an incident from a firing alert
keywords:
- grafana
- alert rules
- incident
title: Declare an incident from a firing alert
weight: 430
---
# Declare incidents from firing alerts
Declare an incident from a firing alert to streamline your alert to incident workflow.
## Before you begin
- Ensure you have Grafana Incident installed
- You must have a firing alert
## Procedure
To declare an incident from a firing alert, complete the following steps.
1. Navigate to Alerts & Incidents -> Alerting -> Alert rules.
2. From the Alert rules list view, click the firing alert that you want to declare an incident for.
**Note:**
You can also access **Declare Incident** from the alert details page.
3. Click **Declare Incident**.
The **Declare Incident** pop-up opens in the Grafana Incident application.
4. In the **Declare Incident** pop-up, enter what's going on.
**Note**: This field is pre-filled with the name of the alert rule, but you can edit it as required.
The alert rule is also linked to the incident.
5. Select a severity.
6. Add labels, as required.
7. Click **More options** to include a channel prefix and status.
8. Click **Declare Incident**.
## Next steps
View and track the incident in the Grafana Incident application.
For more information, refer to [Grafana Incident documentation.](https://grafana.com/docs/grafana-cloud/incident/configure-settings/)

View File

@@ -20,8 +20,12 @@ The Alerting page lists all existing alert rules. By default, rules are grouped
The Mimir/Cortex/Loki rules section lists all rules for Mimir, Cortex, or Loki data sources. Cloud alert rules are also listed in this section.
When managing large volumes of alerts, you can use extended alert rule search capabilities to filter on folders, evaluation groups, and rules. Additionally, you can filter alert rules by their properties like labels, state, type, and health.
- [View and filter alert rules](#view-and-filter-alert-rules)
- [View alert rules](#view-alert-rules)
- [Export alert rules](#export-alert-rules)
- [View query definitions for provisioned alerts](#view-query-definitions-for-provisioned-alerts)
- [Grouped view](#grouped-view)
- [State view](#state-view)
- [Filter alert rules](#filter-alert-rules)
@@ -36,6 +40,18 @@ To view alerting details:
{{< figure src="/static/img/docs/alerting/unified/rule-details-8-0.png" max-width="650px" caption="Alerting rule details" >}}
From the Alert list page, you can also make copies of alert rules to help you reuse existing alert rules.
## Export alert rules
Click **Export** to create and tune an alert rule in the UI, then export to YAML or JSON, and use in the provisioning API or files. You can also export an entire rule group to review or use.
**Note:** This is supported in both the UI and provisioning API.
## View query definitions for provisioned alerts
View read-only query definitions for provisioned alerts. Check quickly if your alert rule queries are correct, without diving into your "as-code" repository for rule definitions.
### Grouped view
Grouped view shows Grafana alert rules grouped by folder and Loki or Prometheus alert rules grouped by `namespace` + `group`. This is the default rule list view, intended for managing rules. You can expand each group to view a list of rules in this group. Expand a rule further to view its details. You can also expand action buttons and alerts resulting from the rule to view their details.

View File

@@ -21,6 +21,10 @@ Use contact points to define how your contacts are notified when an alert rule f
You can also use notification templating to customize notification messages for contact point types.
**Note:**
If you've created an OnCall contact point in the Grafana OnCall application, you can view it in the Alerting application.
## Supported contact point types
The following table lists the contact point types supported by Grafana.

View File

@@ -24,13 +24,13 @@ Complete the following steps to add a contact point.
1. In the Grafana menu, click the **Alerting** (bell) icon to open the Alerting page listing existing alerts.
1. Click **Contact points** to open the page listing existing contact points.
1. Click **New contact point**.
1. Click **Add contact point**.
1. From the **Alertmanager** dropdown, select an Alertmanager. By default, Grafana Alertmanager is selected.
1. In **Name**, enter a descriptive name for the contact point.
1. From **Contact point integration**, select a type and fill out mandatory fields. For example, if you choose email, enter the email addresses. Or if you choose Slack, enter the Slack channel(s) and users who should be contacted.
1. Some contact point integrations, like email or webhook, have optional settings. In **Optional settings**, specify additional settings for the selected contact point integration.
1. In Notification settings, optionally select **Disable resolved message** if you do not want to be notified when an alert resolves.
1. To add another contact point integration, click **New contact point integration** and repeat steps 6 through 8.
1. To add another contact point integration, click **Add contact point integration** and repeat steps 6 through 8.
1. Click **Save contact point** to save your changes.
## Edit a contact point

View File

@@ -28,7 +28,7 @@ To add a silence, complete the following steps.
1. In the Grafana menu, click the **Alerting** (bell) icon to open the Alerting page listing existing alerts.
2. On the Alerting page, click **Silences** to open the page listing existing silences.
3. From Alertmanager drop-down, select an external Alertmanager to create and manage silences for the external data source. Otherwise, keep the default option of Grafana.
4. Click **New Silence** to open the Create silence page.
4. Click **Add Silence** to open the Create silence page.
5. In **Silence start and end**, select the start and end date to indicate when the silence should go into effect and expire.
6. Optionally, in **Duration**, specify how long the silence is enforced. This automatically updates the end time in the **Silence start and end** field.
7. In the **Name** and **Value** fields, enter one or more _Matching Labels_. Matchers determine which rules the silence will apply to.

View File

@@ -24,7 +24,7 @@ In the Contact points tab, you can see a list of your notification templates.
To create a template, complete the following steps.
1. Click New template.
1. Click **Add template**.
2. Choose a name for the notification template.
@@ -40,7 +40,7 @@ To create a template, complete the following steps.
To create a notification template that contains more than one template:
1. Click New Template.
1. Click **Add Template**.
2. Enter a name for the notification template.

View File

@@ -69,7 +69,7 @@ groups:
# evaluation - should be obtained trough the API
data:
- refId: A
datasourceUid: '-100'
datasourceUid: '__expr__'
model:
conditions:
- evaluator:
@@ -86,7 +86,7 @@ groups:
type: query
datasource:
type: __expr__
uid: '-100'
uid: '__expr__'
expression: 1==0
intervalMs: 1000
maxDataPoints: 43200
@@ -313,7 +313,7 @@ settings:
```yaml
type: pagerduty
settings:
# <string, required>
# <string, required> the 32-character Events API key https://support.pagerduty.com/docs/api-access-keys#events-api-keys
integrationKey: XXX
# <string> options: critical, error, warning, info
severity: critical

View File

@@ -282,7 +282,7 @@ resource "grafana_rule_group" "my_rule_group" {
// The query was configured to obtain data from the last 60 seconds. Let's alert on the average value of that series using a Reduce stage.
data {
datasource_uid = "-100"
datasource_uid = "__expr__"
// You can also create a rule in the UI, then GET that rule to obtain the JSON.
// This can be helpful when using more complex reduce expressions.
model = <<EOT
@@ -298,7 +298,7 @@ EOT
// Now, let's use a math expression as our threshold.
// We want to alert when the value of stage "B" above exceeds 70.
data {
datasource_uid = "-100"
datasource_uid = "__expr__"
ref_id = "C"
relative_time_range {
from = 0

View File

@@ -9,7 +9,6 @@ aliases:
- dashboard-folders/
- dashboard-manage/
- export-import/
- time-range-controls/
keywords:
- grafana
- dashboard

View File

@@ -7,6 +7,7 @@ aliases:
- dashboard-ui/dashboard-row/
- search/
- shortcuts/
- time-range-controls/
keywords:
- dashboard
- search
@@ -32,11 +33,11 @@ The following image and descriptions highlights all dashboards features.
- **Dashboard title** (2): When you click the dashboard title you can search for dashboard contained in the current folder.
- **Share dashboard** (3): Use this option to share the current dashboard by link or snapshot. You can also export the dashboard definition from the share modal.
- **Add a new panel** (4): Use this option to add a panel, dashboard row, or library panel to the current dashboard.
- **Dashboard settings** (5): Use this option to change dashboard name, folder, and tags and manage variables and annotation queries. For more information about dashboard settings, refer to [Modify dashboard settings]({{< relref "../build-dashboards/modify-dashboard-settings/" >}})
- **Dashboard settings** (5): Use this option to change dashboard name, folder, and tags and manage variables and annotation queries. For more information about dashboard settings, refer to [Modify dashboard settings]({{< relref "../build-dashboards/modify-dashboard-settings/" >}}).
- **Time picker dropdown** (6): Click to select relative time range options and set custom absolute time ranges.
- You can change the **Timezone** and **fiscal year** settings from the time range controls by clicking the **Change time settings** button.
- Time settings are saved on a per-dashboard basis.
- **Zoom out time range** (7): Click to zoom out the time range. For more information about how to use time range controls, refer to [Common time range controls](../time-range-controls/#common-time-range-controls).
- **Zoom out time range** (7): Click to zoom out the time range. For more information about how to use time range controls, refer to [Common time range controls]({{< relref "#common-time-range-controls" >}}).
- **Refresh dashboard** (8): Click to immediately trigger queries and refresh dashboard data.
- **Refresh dashboard time interval** (9): Click to select a dashboard auto refresh time interval.
- **View mode** (10): Click to display the dashboard on a large screen such as a TV or a kiosk. View mode hides irrelevant information such as navigation menus. For more information about view mode, refer to [How to Create Kiosks to Display Dashboards on a TV](https://grafana.com/blog/2019/05/02/grafana-tutorial-how-to-create-kiosks-to-display-dashboards-on-a-tv/).

View File

@@ -47,7 +47,7 @@ Variables can be used in titles, descriptions, text panels, and queries. Queries
The following dashboards in Grafana Play provide examples of template variables.
- [Elasticsearch Metrics](https://play.grafana.org/d/000000014/elasticsearch-metrics?orgId=1) - Uses ad hoc filters, global variables, and a custom variable.
- [Elasticsearch Metrics](https://play.grafana.org/d/z8OZC66nk/elasticsearch-8-2-0-sample-flight-data?orgId=1) - Uses ad hoc filters, global variables, and a custom variable.
- [Graphite Templated Nested](https://play.grafana.org/d/000000056/graphite-templated-nested?orgId=1) - Uses query variables, chained query variables, an interval variable, and a repeated panel.
- [Influx DB Group By Variable](https://play.grafana.org/d/000000137/influxdb-group-by-variable?orgId=1) - Query variable, panel uses the variable results to group the metric data.
- [InfluxDB Raw Query Template Var](https://play.grafana.org/d/000000083/influxdb-raw-query-template-var?orgId=1) - Uses query variables, chained query variables, and an interval variable.

View File

@@ -44,7 +44,7 @@ In contrast, Azure Monitor Logs can store a variety of data types, each with the
1. In a Grafana panel, select the **Azure Monitor** data source.
1. Select the **Metrics** service.
1. Select a resource from which to metrics by using the subscription, resource group, resource type, and resource fields.
1. Select a resource from which to query metrics by using the subscription, resource group, resource type, and resource fields. Multiple resources can also be selected as long as they belong to the same subscription, region and resource type. Note that only a limited amount of resource types support this feature.
1. To select a different namespace than the default—for instance, to select resources like storage accounts that are organized under multiple namespaces—use the **Namespace** option.
> **Note:** Not all metrics returned by the Azure Monitor Metrics API have values.
@@ -110,7 +110,7 @@ You can also perform complex analysis of Logs data by using KQL.
1. In a Grafana panel, select the **Azure Monitor** data source.
1. Select the **Logs** service.
1. Select a resource to query.
1. Select a resource to query. Multiple resources can be selected as long as they are of the same type.
Alternatively, you can dynamically query all resources under a single resource group or subscription.

View File

@@ -32,16 +32,17 @@ For an introduction to templating and template variables, refer to the [Templati
You can specify these Azure Monitor data source queries in the Variable edit view's **Query Type** field.
| Name | Description |
| ------------------- | -------------------------------------------------------------------------------------------- |
| **Subscriptions** | Returns subscriptions. |
| **Resource Groups** | Returns resource groups for a specified subscription. |
| **Namespaces** | Returns metric namespaces for the specified subscription and resource group. |
| **Resource Names** | Returns a list of resource names for a specified subscription, resource group and namespace. |
| **Metric Names** | Returns a list of metric names for a resource. |
| **Workspaces** | Returns a list of workspaces for the specified subscription. |
| **Logs** | Use a KQL query to return values. |
| **Resource Graph** | Use an ARG query to return values. |
| Name | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------ |
| **Subscriptions** | Returns subscriptions. |
| **Resource Groups** | Returns resource groups for a specified. Supports multi-value. subscription. |
| **Namespaces** | Returns metric namespaces for the specified subscription and resource group. |
| **Regions** | Returns regions for the specified subscription |
| **Resource Names** | Returns a list of resource names for a specified subscription, resource group and namespace. Supports multi-value. |
| **Metric Names** | Returns a list of metric names for a resource. |
| **Workspaces** | Returns a list of workspaces for the specified subscription. |
| **Logs** | Use a KQL query to return values. |
| **Resource Graph** | Use an ARG query to return values. |
Any Log Analytics Kusto Query Language (KQL) query that returns a single list of values can also be used in the Query field.
For example:
@@ -65,3 +66,13 @@ Perf
| summarize avg(CounterValue) by bin(TimeGenerated, $__interval), Computer
| order by TimeGenerated asc
```
### Multi-value variables
It is possible to select multiple values for **Resource Groups** and **Resource Names** and use a single metrics query pointing to those values as long as they:
- Belong to the same subscription.
- Are in the same region.
- Are of the same type (namespace).
Also, note that if a template variable pointing to multiple resource groups or names is used in another template variable as a parameter (e.g. to retrieve metric names), only the first value will be used. This means that the combination of the first resource group and name selected should be valid.

View File

@@ -118,6 +118,8 @@ The response from MySQL can be formatted as either a table or as a time series.
### Dataset and Table selection
> **Note:** If your table or database name contains a reserved word or a [not permitted character](https://dev.mysql.com/doc/refman/8.0/en/identifiers.html) the editor will put quotes around them. For example a table name like `table-name` will be quoted with backticks `` `table-name` ``.
In the dataset dropdown, choose the MySQL database to query. The dropdown is be populated with the databases that the user has access to.
When the dataset is selected, the table dropdown is populated with the tables that are available.

View File

@@ -48,37 +48,58 @@ You can also configure settings specific to the Tempo data source:
### Configure trace to logs
{{< figure src="/static/img/docs/explore/traces-to-logs-settings-8-2.png" class="docs-image--no-shadow" caption="Screenshot of the trace to logs settings" >}}
![Trace to logs settings](/media/docs/tempo/tempo-trace-to-logs-9-4.png)
> **Note:** Available in Grafana v7.4 and higher.
> If you use Grafana Cloud, open a [support ticket in the Cloud Portal](/profile/org#support) to access this feature.
The **Trace to logs** setting configures the [trace to logs feature]({{< relref "../../explore/trace-integration" >}}) that is available when you integrate Grafana with Tempo.
**To configure trace to logs:**
There are two ways to configure the trace to logs feature. You can use simplified configuration with default query, or you can configure custom query where you can use a [template language]({{< relref "../../dashboards/variables/variable-syntax">}}) to interpolate variables from the trace or span.
**To use simple configuration:**
1. Select the target data source.
1. Select which tags to use in the logs query. The tags you configure must be present in the spans attributes or resources for a trace to logs span link to appear.
1. Set start and end time shift. As the logs timestamps may not exactly match the timestamps of the spans in trace it may be necessary to search in larger or shifted time range to find the desired logs.
1. Select which tags to use in the logs query. The tags you configure must be present in the spans attributes or resources for a trace to logs span link to appear. You can optionally configure a new name for the tag. This is useful for example if the tag has dots in the name and the target data source does not allow using dots in labels. In that case you can for example remap `http.status` to `http_status`.
1. Optionally switch on Filter by Trace ID and/or Filter by Span ID to further filter the logs if your logs consistently contain trace or span IDs.
- **Single tag**
- Configuring `job` as a tag and clicking on a span link will take you to your configured logs datasource with the query `{job='value from clicked span'}`.
- **Multiple tags**
- If multiple tags are used they will be concatenated so the logs query would look like `{job='value from clicked span', service='value from clicked span'}`.
- **Mapped tags**
- For a mapped tag `service.name` with value `service`, clicking on a span link will take you to your configured logs datasource with the query `{service='value from clicked span'}` instead of `{service.name='value from clicked span'}`.
- This is useful for instances where your tracing datasource tags and your logs datasource tags don't match one-to-one.
**To use custom query configuration:**
1. Select the target data source.
1. Set start and end time shift. As the logs timestamps may not exactly match the timestamps of the spans in the trace it may be necessary to widen or shift the time range to find the desired logs.
1. Optionally select tags to map. These tags can be used in the custom query with `${__tags}` variable. This variable will interpolate the mapped tags as list in an appropriate syntax for the data source and will only include the tags that were present in the span omitting those that weren't present. You can optionally configure a new name for the tag. This is useful in cases where the tag has dots in the name and the target data source does not allow using dots in labels. For example, you can remap `http.status` to `http_status` in such a case. If you don't map any tags here, you can still use any tag in the query like this `method="${__span.tags.method}"`.
1. Skip Filter by Trace ID or Filter by Span ID as these cannot be used with custom query.
1. Switch on Use custom query.
1. Specify custom query to be used to query the logs. You can use various variables to make that query relevant for current span. The link will only be shown only if all the variables are interpolated with non-empty values to prevent creating invalid query.
**Variables that can be used in custom query**
To use a variable you need to wrap it in `${}`. For example `${__span.name}`.
| Variable name | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| \_\_tags | This variable is special because it uses the tag mapping in from the UI to create a label matcher string in the specific data source syntax. It uses only tags that are present in the span so the link will still be created even if only one of those tags is present in the span. You can use this if not all the tags are required for the query to be useful. |
| \_\_span.spanId | The ID of the span. |
| \_\_span.traceId | The ID of the trace. |
| \_\_span.duration | The duration of the span. |
| \_\_span.name | Name of the span. |
| \_\_span.tags | Namespace for the tags in the span. To access a specific tag named "version" you would use `${__span.tags.version}`. In case the tag contains dot you have to access it as `${__span.tags["http.status"]}`. |
| \_\_trace.traceId | The ID of the trace. |
| \_\_trace.duration | The duration of the trace. |
| \_\_trace.name | The name of the trace. |
The following table describes the ways in which you can configure your trace to logs settings:
| Setting name | Description |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data source** | Defines the target data source. You can select only Loki or Splunk \[logs\] data sources. |
| **Tags** | Defines the the tags to use in the logs query. Default is `'cluster', 'hostname', 'namespace', 'pod'`. |
| **Map tag names** | Enables you to configure how Tempo tag names map to logs label names. For example, you can map `service.name` to `service`. |
| **Span start time shift** | Shifts the start time for the logs query, based on the span's start time. You can use time units, such as `5s`, `1m`, `3h`. To extend the time to the past, use a negative value. Default is 0. |
| **Span end time shift** | Shifts the end time for the logs query, based on the span's end time. You can use time units. Default is 0. |
| **Filter by Trace ID** | Toggles whether to append the trace ID to the logs query. |
| **Filter by Span ID** | Toggles whether to append the span ID to the logs query. |
| Setting name | Description |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data source** | Defines the target data source. You can select only Loki or Splunk \[logs\] data sources. |
| **Span start time shift** | Shifts the start time for the logs query, based on the span's start time. You can use time units, such as `5s`, `1m`, `3h`. To extend the time to the past, use a negative value. Default is 0. |
| **Span end time shift** | Shifts the end time for the logs query, based on the span's end time. You can use time units. Default is 0. |
| **Tags** | Defines the the tags to use in the logs query. Default is `'cluster', 'hostname', 'namespace', 'pod'`. You can change the tag name for example to remove dots from the name if they are not allowed in the target data source. For example map `http.status` to `http_status` |
| **Filter by Trace ID** | Toggles whether to append the trace ID to the logs query. |
| **Filter by Span ID** | Toggles whether to append the span ID to the logs query. |
| **Use custom query** | Toggles use of custom query with interpolation. |
| **Query** | Input to write custom query. Use variable interpolation to customize it with variables from span. |
### Configure trace to metrics
@@ -293,7 +314,6 @@ For details, refer to the [APM dashboard documentation](/docs/tempo/latest/metri
**To display the APM table:**
1. Activate the `tempoApmTable` [feature toggle]({{< relref "../../setup-grafana/configure-grafana#feature_toggles" >}}) in your `grafana.ini` file.
1. Link a Prometheus data source in the Tempo data source settings.
1. Navigate to [Explore]({{< relref "../../explore/" >}}).
1. Select the Tempo data source.

View File

@@ -9,14 +9,14 @@ keywords:
- troubleshooting
- panels
- testdata
menuTitle: TestData DB
title: TestData DB data source
menuTitle: TestData
title: TestData data source
weight: 1500
---
# TestData DB data source
# TestData data source
Grafana ships with a TestData DB data source, which creates simulated time series data for any [panel]({{< relref "../../panels-visualizations/" >}}).
Grafana ships with a TestData data source, which creates simulated time series data for any [panel]({{< relref "../../panels-visualizations/" >}}).
You can use it to build your own fake and random time series data and render it in any panel, which helps you verify dashboard functionality since you can safely and easily share the data.
For instructions on how to add a data source to Grafana, refer to the [administration documentation]({{< relref "../../administration/data-source-management/" >}}).
@@ -28,7 +28,7 @@ Only users with the organization administrator role can add data sources.
1. Hover the cursor over the **Configuration** (gear) icon.
1. Select **Data Sources**.
1. Select the TestData DB data source.
1. Select the TestData data source.
The data source doesn't provide any settings beyond the most basic options common to all data sources:
@@ -41,11 +41,11 @@ The data source doesn't provide any settings beyond the most basic options commo
{{< figure src="/static/img/docs/v41/test_data_add.png" class="docs-image--no-shadow" caption="Adding test data" >}}
Once you've added the TestData DB data source, your Grafana instance's users can use it as a data source in any metric panel.
Once you've added the TestData data source, your Grafana instance's users can use it as a data source in any metric panel.
### Choose a scenario
Instead of providing a query editor, the TestData DB data source helps you select a **Scenario** that generates simulated data for panels.
Instead of providing a query editor, the TestData data source helps you select a **Scenario** that generates simulated data for panels.
You can assign an **Alias** to each scenario, and many have their own options that appear when selected.
@@ -81,7 +81,7 @@ You can assign an **Alias** to each scenario, and many have their own options th
## Import a pre-configured dashboard
TestData DB also provides an example dashboard.
TestData also provides an example dashboard.
**To import the example dashboard:**

View File

@@ -244,10 +244,11 @@ GET /api/v1/provisioning/alert-rules/{UID}/export
#### Parameters
| Name | Source | Type | Go type | Separator | Required | Default | Description |
| -------- | ------- | ------- | -------- | --------- | :------: | ------- | -------------------------------------------------- |
| UID | `path` | string | `string` | | ✓ | | Alert rule UID |
| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. |
| Name | Source | Type | Go type | Separator | Required | Default | Description |
| -------- | ------- | -------- | -------- | --------- | :------: | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| UID | `path` | string | `string` | | ✓ | | Alert rule UID |
| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. |
| format | `query` | `string` | string | | | `"yaml"` | Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence. |
#### All responses
@@ -322,11 +323,12 @@ GET /api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export
#### Parameters
| Name | Source | Type | Go type | Separator | Required | Default | Description |
| --------- | ------- | ------- | -------- | --------- | :------: | ------- | -------------------------------------------------- |
| FolderUID | `path` | string | `string` | | ✓ | | |
| Group | `path` | string | `string` | | ✓ | | |
| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. |
| Name | Source | Type | Go type | Separator | Required | Default | Description |
| --------- | ------- | -------- | -------- | --------- | :------: | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| FolderUID | `path` | string | `string` | | ✓ | | |
| Group | `path` | string | `string` | | ✓ | | |
| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. |
| format | `query` | `string` | string | | | `"yaml"` | Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence. |
#### All responses
@@ -381,9 +383,10 @@ GET /api/v1/provisioning/alert-rules/export
#### Parameters
| Name | Source | Type | Go type | Separator | Required | Default | Description |
| -------- | ------- | ------- | ------- | --------- | :------: | ------- | -------------------------------------------------- |
| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. |
| Name | Source | Type | Go type | Separator | Required | Default | Description |
| -------- | ------- | -------- | ------- | --------- | :------: | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. |
| format | `query` | `string` | string | | | `"yaml"` | Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence. |
#### All responses
@@ -992,14 +995,14 @@ Status: Accepted
**Properties**
| Name | Type | Go type | Required | Default | Description | Example |
| --------------------------------------------------------- | ----------------------------------------- | ------------------- | :------: | ------- | -------------------------------------------------------------------------------------------------- | ------- |
| datasourceUid | string | `string` | | | Grafana data source unique identifier; it should be '-100' for a Server Side Expression operation. | |
| model | [interface{}](#interface) | `interface{}` | | | JSON is the raw JSON query and includes the above properties as well as custom properties. | |
| queryType | string | `string` | | | QueryType is an optional identifier for the type of query. |
| Name | Type | Go type | Required | Default | Description | Example |
| --------------------------------------------------------- | ----------------------------------------- | ------------------- | :------: | ------- | ------------------------------------------------------------------------------------------------------ | ------- |
| datasourceUid | string | `string` | | | Grafana data source unique identifier; it should be '**expr**' for a Server Side Expression operation. | |
| model | [interface{}](#interface) | `interface{}` | | | JSON is the raw JSON query and includes the above properties as well as custom properties. | |
| queryType | string | `string` | | | QueryType is an optional identifier for the type of query. |
| It can be used to distinguish different types of queries. | |
| refId | string | `string` | | | RefID is the unique identifier of the query, set by the frontend call. | |
| relativeTimeRange | [RelativeTimeRange](#relative-time-range) | `RelativeTimeRange` | | | | |
| refId | string | `string` | | | RefID is the unique identifier of the query, set by the frontend call. | |
| relativeTimeRange | [RelativeTimeRange](#relative-time-range) | `RelativeTimeRange` | | | | |
### <span id="alert-query-export"></span> AlertQueryExport
@@ -1174,23 +1177,23 @@ Status: Accepted
**Properties**
| Name | Type | Go type | Required | Default | Description | Example |
| ------------ | ---------------------------- | ------------------- | :------: | ------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| annotations | map of string | `map[string]string` | | | | `{"runbook_url":"https://supercoolrunbook.com/page/13"}` |
| condition | string | `string` | ✓ | | | `A` |
| data | [][alertquery](#alert-query) | `[]*AlertQuery` | ✓ | | | `[{"datasourceUid":"-100","model":{"conditions":[{"evaluator":{"params":[0,0],"type":"gt"},"operator":{"type":"and"},"query":{"params":[]},"reducer":{"params":[],"type":"avg"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1 == 1","hide":false,"intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"},"queryType":"","refId":"A","relativeTimeRange":{"from":0,"to":0}}]` |
| execErrState | string | `string` | ✓ | | | |
| folderUID | string | `string` | ✓ | | | `project_x` |
| for | [Duration](#duration) | `Duration` | ✓ | | | |
| id | int64 (formatted integer) | `int64` | | | | |
| labels | map of string | `map[string]string` | | | | `{"team":"sre-team-1"}` |
| noDataState | string | `string` | ✓ | | | |
| orgID | int64 (formatted integer) | `int64` | ✓ | | | |
| provenance | [Provenance](#provenance) | `Provenance` | | | | |
| ruleGroup | string | `string` | ✓ | | | `eval_group_1` |
| title | string | `string` | ✓ | | | `Always firing` |
| uid | string | `string` | | | | |
| updated | date-time (formatted string) | `strfmt.DateTime` | | | | |
| Name | Type | Go type | Required | Default | Description | Example |
| ------------ | ---------------------------- | ------------------- | :------: | ------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| annotations | map of string | `map[string]string` | | | | `{"runbook_url":"https://supercoolrunbook.com/page/13"}` |
| condition | string | `string` | ✓ | | | `A` |
| data | [][alertquery](#alert-query) | `[]*AlertQuery` | ✓ | | | `[{"datasourceUid":"__expr__","model":{"conditions":[{"evaluator":{"params":[0,0],"type":"gt"},"operator":{"type":"and"},"query":{"params":[]},"reducer":{"params":[],"type":"avg"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1 == 1","hide":false,"intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"},"queryType":"","refId":"A","relativeTimeRange":{"from":0,"to":0}}]` |
| execErrState | string | `string` | ✓ | | | |
| folderUID | string | `string` | ✓ | | | `project_x` |
| for | [Duration](#duration) | `Duration` | ✓ | | | |
| id | int64 (formatted integer) | `int64` | | | | |
| labels | map of string | `map[string]string` | | | | `{"team":"sre-team-1"}` |
| noDataState | string | `string` | ✓ | | | |
| orgID | int64 (formatted integer) | `int64` | ✓ | | | |
| provenance | [Provenance](#provenance) | `Provenance` | | | | |
| ruleGroup | string | `string` | ✓ | | | `eval_group_1` |
| title | string | `string` | ✓ | | | `Always firing` |
| uid | string | `string` | | | | |
| updated | date-time (formatted string) | `strfmt.DateTime` | | | | |
### <span id="provisioned-alert-rules"></span> ProvisionedAlertRules

View File

@@ -15,6 +15,8 @@ This guide helps you identify the steps required to update a plugin from the Gra
- [Plugin migration guide](#plugin-migration-guide)
- [Introduction](#introduction)
- [Table of contents](#table-of-contents)
- [From version 9.3.x to 9.4.x](#from-version-93x-to-94x) - [Forwarded HTTP headers in grafana-plugin-sdk-go
](#forwarded-http-headers-in-grafana-plugin-sdk-go)
- [From version 9.1.x to 9.2.x](#from-version-91x-to-92x)
- [React and React-dom as peer dependencies](#react-and-react-dom-as-peer-dependencies)
- [NavModelItem requires a valid icon name](#navmodelitem-requires-a-valid-icon-name)
@@ -62,6 +64,19 @@ This guide helps you identify the steps required to update a plugin from the Gra
- [Migrate to data frames](#migrate-to-data-frames)
- [Troubleshoot plugin migration](#troubleshoot-plugin-migration)
## From version 9.3.x to 9.4.x
### Forwarded HTTP headers in grafana-plugin-sdk-go
It's recommended to use the `<request>.GetHTTPHeader` or `<request>.GetHTTPHeaders` methods when retrieving forwarded HTTP headers. See [Forward OAuth identity for the logged-in user]({{< relref "add-authentication-for-data-source-plugins.md#forward-oauth-identity-for-the-logged-in-user" >}}), [Forward cookies for the logged-in user
]({{< relref "add-authentication-for-data-source-plugins.md#forward-cookies-for-the-logged-in-user" >}}) or [Forward user header for the logged-in user]({{< relref "add-authentication-for-data-source-plugins.md#forward-user-header-for-the-logged-in-user" >}}) for example usages.
#### Technical details
The grafana-plugin-sdk-go [v0.147.0](https://github.com/grafana/grafana-plugin-sdk-go/releases/tag/v0.147.0) introduces a new interface [ForwardHTTPHeaders](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#ForwardHTTPHeaders) that `QueryDataRequest`, `CheckHealthRequest` and `CallResourceRequest` implements.
Newly introduced forwarded HTTP headers in Grafana v9.4.0 are `X-Grafana-User`, `X-Panel-Id`, `X-Dashboard-Uid`, `X-Datasource-Uid` and `X-Grafana-Org-Id`. Internally these are prefixed with `http_` and sent as `http_<HTTP header name>` in [CheckHealthRequest.Headers](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#CheckHealthRequest) and [QueryDataRequest.Headers](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#QueryDataRequest). By using the [ForwardHTTPHeaders](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#ForwardHTTPHeaders) methods you're guaranteed to be able to operate on HTTP headers without using the prefix, i.e. `X-Grafana-User`, `X-Panel-Id`, `X-Dashboard-Uid`, `X-Datasource-Uid` and `X-Grafana-Org-Id`.
## From version 9.1.x to 9.2.x
### React and React-dom as peer dependencies

View File

@@ -64,7 +64,6 @@ Alpha features might be changed or removed without prior notice.
| `live-pipeline` | Enable a generic live processing pipeline |
| `live-service-web-worker` | This will use a webworker thread to processes events rather than the main thread |
| `queryOverLive` | Use Grafana Live WebSocket to execute backend queries |
| `tempoApmTable` | Show APM table |
| `publicDashboards` | Enables public access to dashboards |
| `lokiLive` | Support WebSocket streaming for loki (early prototype) |
| `lokiDataframeApi` | Use experimental loki api for WebSocket streaming (early prototype) |
@@ -98,24 +97,22 @@ Alpha features might be changed or removed without prior notice.
| `sessionRemoteCache` | Enable using remote cache for user sessions |
| `alertingBacktesting` | Rule backtesting API for alerting |
| `editPanelCSVDragAndDrop` | Enables drag and drop for CSV and Excel files |
| `azureMultipleResourcePicker` | Azure multiple resource picker |
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view |
## Development feature toggles
The following toggles require explicitly setting Grafana's [app mode]({{< relref "../_index.md/#app_mode" >}}) to 'development' before you can enable this feature toggle. These features tend to be experimental.
| Feature toggle name | Description |
| -------------------------------------- | ----------------------------------------------------------------------- |
| `dashboardPreviewsAdmin` | Manage the dashboard previews crawler process from the UI |
| `showFeatureFlagsInUI` | Show feature flags in the settings UI |
| `publicDashboardsEmailSharing` | Allows public dashboard sharing to be restricted to only allowed emails |
| `k8s` | Explore native k8s integrations |
| `k8sDashboards` | Save dashboards via k8s |
| `dashboardsFromStorage` | Load dashboards from the generic storage interface |
| `export` | Export grafana instance (to git, etc) |
| `azureMonitorResourcePickerForMetrics` | New UI for Azure Monitor Metrics Query |
| `grpcServer` | Run GRPC server |
| `entityStore` | SQL-based entity store (requires storage flag also) |
| `queryLibrary` | Reusable query library |
| `nestedFolders` | Enable folder nesting |
| Feature toggle name | Description |
| ------------------------------ | ----------------------------------------------------------------------- |
| `dashboardPreviewsAdmin` | Manage the dashboard previews crawler process from the UI |
| `showFeatureFlagsInUI` | Show feature flags in the settings UI |
| `publicDashboardsEmailSharing` | Allows public dashboard sharing to be restricted to only allowed emails |
| `k8s` | Explore native k8s integrations |
| `k8sDashboards` | Save dashboards via k8s |
| `dashboardsFromStorage` | Load dashboards from the generic storage interface |
| `export` | Export grafana instance (to git, etc) |
| `grpcServer` | Run GRPC server |
| `entityStore` | SQL-based entity store (requires storage flag also) |
| `queryLibrary` | Reusable query library |
| `nestedFolders` | Enable folder nesting |

View File

@@ -459,3 +459,19 @@ Limit the maximum device scale factor that can be requested. Default is `4`.
}
}
```
#### Page zoom level
The following command sets a page zoom level. The default value is `1`. A value of `1.5` equals 150% zoom.
```bash
RENDERING_VIEWPORT_PAGE_ZOOM_LEVEL=1
```
```json
{
"rendering": {
"pageZoomLevel": 1
}
}
```

View File

@@ -187,7 +187,7 @@ Now, when you change the color in the panel editor, the fill color of the circle
Most panels visualize dynamic data from a Grafana data source. In this step, you'll create one circle per series, each with a radius equal to the last value in the series.
> To use data from queries in your panel, you need to set up a data source. If you don't have one available, you can use the [TestData DB](/docs/grafana/latest/features/datasources/testdata) data source while developing.
> To use data from queries in your panel, you need to set up a data source. If you don't have one available, you can use the [TestData](/docs/grafana/latest/features/datasources/testdata) data source while developing.
The results from a data source query within your panel are available in the `data` property inside your panel component.

View File

@@ -74,7 +74,7 @@ Each data source provisioning config file contains a _manifest_ that specifies t
At startup, Grafana loads the configuration files and provisions the data sources listed in the manifests.
Let's configure a [TestData DB](/docs/grafana/latest/features/datasources/testdata/) data source that you can use for your dashboards.
Let's configure a [TestData](/docs/grafana/latest/features/datasources/testdata/) data source that you can use for your dashboards.
#### Create a data source manifest
@@ -84,12 +84,12 @@ Let's configure a [TestData DB](/docs/grafana/latest/features/datasources/testda
apiVersion: 1
datasources:
- name: TestData DB
- name: TestData
type: testdata
```
1. Restart Grafana to load the new changes.
1. In the sidebar, hover the cursor over the **Configuration** (gear) icon and click **Data Sources**. The TestData DB appears in the list of data sources.
1. In the sidebar, hover the cursor over the **Configuration** (gear) icon and click **Data Sources**. TestData appears in the list of data sources.
> The configuration options can vary between different types of data sources. For more information on how to configure a specific data source, refer to [Data sources](/docs/grafana/latest/administration/provisioning/#datasources).
@@ -146,7 +146,7 @@ For more information on how to configure dashboard providers, refer to [Dashboar
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "TestData DB",
"datasource": "TestData",
"fill": 1,
"gridPos": {
"h": 8,

View File

@@ -248,6 +248,8 @@ e2e.scenario({
.parent()
.find('input')
.type('microsoft.storage/storageaccounts{downArrow}{enter}');
e2e.pages.Dashboard.SubMenu.submenuItemLabels('region').parent().find('button').click();
e2e.pages.Dashboard.SubMenu.submenuItemLabels('region').parent().find('input').type('uk south{downArrow}{enter}');
e2e.pages.Dashboard.SubMenu.submenuItemLabels('resource').parent().find('button').click();
e2e.pages.Dashboard.SubMenu.submenuItemLabels('resource')
.parent()
@@ -262,8 +264,7 @@ e2e.scenario({
e2eSelectors.queryEditor.resourcePicker.advanced.subscription.input().find('input').type('$subscription');
e2eSelectors.queryEditor.resourcePicker.advanced.resourceGroup.input().find('input').type('$resourceGroups');
e2eSelectors.queryEditor.resourcePicker.advanced.namespace.input().find('input').type('$namespaces');
// TODO: Enable this input once multiple resources feature flag is removed
// e2eSelectors.queryEditor.resourcePicker.advanced.region.input().find('input').type('$region');
e2eSelectors.queryEditor.resourcePicker.advanced.region.input().find('input').type('$region');
e2eSelectors.queryEditor.resourcePicker.advanced.resource.input().find('input').type('$resource');
e2eSelectors.queryEditor.resourcePicker.apply.button().click();
e2eSelectors.queryEditor.metricsQueryEditor.metricName.input().find('input').type('Transactions{enter}');

View File

@@ -0,0 +1,21 @@
{
"results": {
"datasets": {
"status": 200,
"frames": [
{
"schema": {
"refId": "datasets",
"meta": {
"executedQueryString": "SELECT DISTINCT TABLE_SCHEMA from information_schema.TABLES where TABLE_TYPE != 'SYSTEM VIEW' ORDER BY TABLE_SCHEMA"
},
"fields": [
{ "name": "TABLE_SCHEMA", "type": "string", "typeInfo": { "frame": "string", "nullable": true } }
]
},
"data": { "values": [["DataMaker", "mysql", "performance_schema", "sys"]] }
}
]
}
}
}

View File

@@ -0,0 +1,27 @@
{
"results": {
"fields": {
"status": 200,
"frames": [
{
"schema": {
"refId": "fields",
"meta": {
"executedQueryString": "SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'DataMaker' AND table_name = 'RandomIntsWithTimes' ORDER BY column_name"
},
"fields": [
{ "name": "COLUMN_NAME", "type": "string", "typeInfo": { "frame": "string", "nullable": true } },
{ "name": "DATA_TYPE", "type": "string", "typeInfo": { "frame": "string", "nullable": true } }
]
},
"data": {
"values": [
["createdAt", "id", "time", "updatedAt", "bigint"],
["datetime", "int", "datetime", "datetime", "int"]
]
}
}
]
}
}
}

View File

@@ -0,0 +1,59 @@
import { e2e } from '@grafana/e2e';
import datasetResponse from './datasets-response.json';
import fieldsResponse from './fields-response.json';
import tablesResponse from './tables-response.json';
const tableNameWithSpecialCharacter = tablesResponse.results.tables.frames[0].data.values[0][1];
const normalTableName = tablesResponse.results.tables.frames[0].data.values[0][0];
describe('MySQL datasource', () => {
it('code editor autocomplete should handle table name escaping/quoting', () => {
e2e.flows.login('admin', 'admin');
e2e().intercept('POST', '**/api/ds/query', (req) => {
if (req.body.queries[0].refId === 'datasets') {
req.alias = 'datasets';
req.reply({
body: datasetResponse,
});
} else if (req.body.queries[0].refId === 'tables') {
req.alias = 'tables';
req.reply({
body: tablesResponse,
});
} else if (req.body.queries[0].refId === 'fields') {
req.alias = 'fields';
req.reply({
body: fieldsResponse,
});
}
});
e2e.pages.Explore.visit();
e2e.components.DataSourcePicker.container().should('be.visible').type('gdev-mysql{enter}');
e2e().get("label[for^='option-code']").should('be.visible').click();
e2e().get('textarea').type('S{downArrow}{enter}');
e2e().wait('@tables');
e2e().get('.suggest-widget').contains(tableNameWithSpecialCharacter).should('be.visible');
e2e().get('textarea').type('{enter}');
e2e().get('textarea').should('have.value', `SELECT FROM grafana.\`${tableNameWithSpecialCharacter}\``);
const deleteTimes = new Array(tableNameWithSpecialCharacter.length + 2).fill(
'{backspace}',
0,
tableNameWithSpecialCharacter.length + 2
);
e2e().get('textarea').type(deleteTimes.join(''));
e2e().get('textarea').type('{command}i');
e2e().get('.suggest-widget').contains(tableNameWithSpecialCharacter).should('be.visible');
e2e().get('textarea').type('S{downArrow}{enter}');
e2e().get('textarea').should('have.value', `SELECT FROM grafana.${normalTableName}`);
e2e().get('textarea').type('.');
e2e().get('.suggest-widget').contains('No suggestions.').should('be.visible');
});
});

View File

@@ -0,0 +1,19 @@
{
"results": {
"tables": {
"status": 200,
"frames": [
{
"schema": {
"refId": "tables",
"meta": {
"executedQueryString": "SELECT table_name FROM information_schema.tables WHERE table_schema = 'DataMaker' ORDER BY table_name"
},
"fields": [{ "name": "TABLE_NAME", "type": "string", "typeInfo": { "frame": "string", "nullable": true } }]
},
"data": { "values": [["normalTable", "table-name"]] }
}
]
}
}
}

4
go.mod
View File

@@ -126,7 +126,7 @@ require (
gopkg.in/square/go-jose.v2 v2.5.1
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1
xorm.io/builder v0.3.6 // indirect
xorm.io/builder v0.3.6
xorm.io/core v0.7.3
xorm.io/xorm v0.8.2
)
@@ -408,7 +408,7 @@ require (
)
// Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream
replace github.com/crewjam/saml => github.com/grafana/saml v0.4.13-0.20230123091136-3b6b1ec6c3cb
replace github.com/crewjam/saml => github.com/grafana/saml v0.4.13-0.20230203140620-5f476db5c00a
// Thema's thema CLI requires cobra, which eventually works its way down to go-hclog@v1.0.0.
// Upgrading affects backend plugins: https://github.com/grafana/grafana/pull/47653#discussion_r850508593

4
go.sum
View File

@@ -1418,8 +1418,8 @@ github.com/grafana/phlare/api v0.1.2 h1:1jrwd3KnsXMzj/tJih9likx5EvbY3pbvLbDqAAYe
github.com/grafana/phlare/api v0.1.2/go.mod h1:29vcLwFDmZBDce2jwFIMtzvof7fzPadT8VMKw9ks7FU=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20230119183635-ec19b0a443b7 h1:ma1CfisUaAXQzL24tCao9yMleZYsFJ853m2l0rgahyE=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20230119183635-ec19b0a443b7/go.mod h1:MnBfDPXJqXmmfPwQlCLvVUdqfnvrAw+hSPtDeaaFwj4=
github.com/grafana/saml v0.4.13-0.20230123091136-3b6b1ec6c3cb h1:9PLj02xp4DeLTM2+ZyBMcN1sh0ir8GuF/1xXKyF+yws=
github.com/grafana/saml v0.4.13-0.20230123091136-3b6b1ec6c3cb/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA=
github.com/grafana/saml v0.4.13-0.20230203140620-5f476db5c00a h1:aWSTt/pTOI4uGY9DhBMG1l0GOnGjIYtaqxzYR3/q82o=
github.com/grafana/saml v0.4.13-0.20230203140620-5f476db5c00a/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA=
github.com/grafana/sqlds/v2 v2.3.10 h1:HWKhE0vR6LoEiE+Is8CSZOgaB//D1yqb2ntkass9Fd4=
github.com/grafana/sqlds/v2 v2.3.10/go.mod h1:c6ibxnxRVGxV/0YkEgvy7QpQH/lyifFyV7K/14xvdIs=
github.com/grafana/thema v0.0.0-20230122235053-b4b6714dd1c9 h1:nAdsZkvPYNH6wDPkAi9JaDSIf5i2iVz4+Rqk4AOt6sE=

View File

@@ -283,10 +283,18 @@ lineage: seqs: [
} @cuetsy(kind="interface")
// TODO docs
// FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it
#Transformation: {
#DataTransformerConfig: {
@grafana(TSVeneer="type")
// Unique identifier of transformer
id: string
options: {...}
// Disabled transformations are skipped
disabled?: bool
// Optional frame matcher. When missing it will be applied to all results
filter?: #MatcherConfig
// Options to be passed to the transformer
// Valid options depend on the transformer id
options: _
} @cuetsy(kind="interface") @grafanamaturity(NeedsExpertReview)
// 0 for no shared crosshair or tooltip (default).
@@ -383,7 +391,7 @@ lineage: seqs: [
// TODO docs
timeRegions?: [...] @grafanamaturity(NeedsExpertReview)
transformations: [...#Transformation] @grafanamaturity(NeedsExpertReview)
transformations: [...#DataTransformerConfig] @grafanamaturity(NeedsExpertReview)
// TODO docs
// TODO tighter constraint

View File

@@ -1,4 +1,4 @@
{
"stable": "9.3.6",
"testing": "9.3.0"
"testing": "9.4.0-beta1"
}

View File

@@ -1,6 +1,8 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"packages": ["packages/*"],
"version": "9.4.0-pre"
"packages": [
"packages/*"
],
"version": "9.4.1"
}

View File

@@ -3,7 +3,7 @@
"license": "AGPL-3.0-only",
"private": true,
"name": "grafana",
"version": "9.4.0-pre",
"version": "9.4.1",
"repository": "github:grafana/grafana",
"scripts": {
"build": "yarn i18n:compile && NODE_ENV=production webpack --progress --config scripts/webpack/webpack.prod.js",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/data",
"version": "9.4.0-pre",
"version": "9.4.1",
"description": "Grafana Data Library",
"keywords": [
"typescript"
@@ -36,7 +36,7 @@
},
"dependencies": {
"@braintree/sanitize-url": "6.0.1",
"@grafana/schema": "9.4.0-pre",
"@grafana/schema": "9.4.1",
"@types/d3-interpolate": "^3.0.0",
"d3-interpolate": "3.0.1",
"date-fns": "2.29.3",

View File

@@ -35,7 +35,6 @@ export enum FrameMatcherID {
byName = 'byName',
byRefId = 'byRefId',
byIndex = 'byIndex',
byLabel = 'byLabel',
}
/**

View File

@@ -3,10 +3,11 @@ import { FieldType } from '../types';
import { mockTransformationsRegistry } from '../utils/tests/mockTransformationsRegistry';
import { ReducerID } from './fieldReducer';
import { FrameMatcherID } from './matchers/ids';
import { transformDataFrame } from './transformDataFrame';
import { filterFieldsByNameTransformer } from './transformers/filterByName';
import { DataTransformerID } from './transformers/ids';
import { reduceTransformer } from './transformers/reduce';
import { reduceTransformer, ReduceTransformerMode } from './transformers/reduce';
const seriesAWithSingleField = toDataFrame({
name: 'A',
@@ -73,4 +74,44 @@ describe('transformDataFrame', () => {
expect(processed[0].fields[0].values.get(0)).toEqual('temperature');
});
});
it('Support filtering', async () => {
const frameA = toDataFrame({
refId: 'A',
fields: [{ name: 'value', type: FieldType.number, values: [5, 6] }],
});
const frameB = toDataFrame({
refId: 'B',
fields: [{ name: 'value', type: FieldType.number, values: [7, 8] }],
});
const cfg = [
{
id: DataTransformerID.reduce,
filter: {
id: FrameMatcherID.byRefId,
options: 'A', // Only apply to A
},
options: {
reducers: [ReducerID.first],
mode: ReduceTransformerMode.ReduceFields,
},
},
];
// Only apply A
await expect(transformDataFrame(cfg, [frameA, frameB])).toEmitValuesWith((received) => {
const processed = received[0].map((v) => v.fields[0].values.toArray());
expect(processed).toBeTruthy();
expect(processed).toMatchObject([[5], [7, 8]]);
});
// Only apply to B
cfg[0].filter.options = 'B';
await expect(transformDataFrame(cfg, [frameA, frameB])).toEmitValuesWith((received) => {
const processed = received[0].map((v) => v.fields[0].values.toArray());
expect(processed).toBeTruthy();
expect(processed).toMatchObject([[5, 6], [7]]);
});
});
});

View File

@@ -1,8 +1,9 @@
import { MonoTypeOperatorFunction, Observable, of } from 'rxjs';
import { map, mergeMap } from 'rxjs/operators';
import { DataFrame, DataTransformContext, DataTransformerConfig } from '../types';
import { DataFrame, DataTransformContext, DataTransformerConfig, FrameMatcher } from '../types';
import { getFrameMatchers } from './matchers';
import { standardTransformersRegistry, TransformerRegistryItem } from './standardTransformersRegistry';
const getOperator =
@@ -17,15 +18,30 @@ const getOperator =
const defaultOptions = info.transformation.defaultOptions ?? {};
const options = { ...defaultOptions, ...config.options };
const matcher = config.filter?.options ? getFrameMatchers(config.filter) : undefined;
return source.pipe(
mergeMap((before) =>
of(before).pipe(info.transformation.operator(options, ctx), postProcessTransform(before, info))
of(filterInput(before, matcher)).pipe(
info.transformation.operator(options, ctx),
postProcessTransform(before, info, matcher)
)
)
);
};
function filterInput(data: DataFrame[], matcher?: FrameMatcher) {
if (matcher) {
return data.filter((v) => matcher(v));
}
return data;
}
const postProcessTransform =
(before: DataFrame[], info: TransformerRegistryItem<any>): MonoTypeOperatorFunction<DataFrame[]> =>
(
before: DataFrame[],
info: TransformerRegistryItem<any>,
matcher?: FrameMatcher
): MonoTypeOperatorFunction<DataFrame[]> =>
(source) =>
source.pipe(
map((after) => {
@@ -46,6 +62,21 @@ const postProcessTransform =
}
}
// Add back the filtered out frames
if (matcher) {
// keep the frame order the same
let insert = 0;
const append = before.filter((v, idx) => {
const keep = !matcher(v);
if (keep && !insert) {
insert = idx;
}
return keep;
});
if (append.length) {
after.splice(insert, 0, ...append);
}
}
return after;
})
);

View File

@@ -28,7 +28,6 @@ export interface FeatureToggles {
['live-service-web-worker']?: boolean;
queryOverLive?: boolean;
panelTitleSearch?: boolean;
tempoApmTable?: boolean;
prometheusAzureOverrideAudience?: boolean;
showFeatureFlagsInUI?: boolean;
publicDashboards?: boolean;
@@ -47,7 +46,6 @@ export interface FeatureToggles {
supportBundles?: boolean;
dashboardsFromStorage?: boolean;
export?: boolean;
azureMonitorResourcePickerForMetrics?: boolean;
exploreMixedDatasource?: boolean;
tracing?: boolean;
commandPalette?: boolean;
@@ -90,7 +88,6 @@ export interface FeatureToggles {
alertingBacktesting?: boolean;
editPanelCSVDragAndDrop?: boolean;
alertingNoNormalState?: boolean;
azureMultipleResourcePicker?: boolean;
topNavCommandPalette?: boolean;
logsSampleInExplore?: boolean;
logsContextDatasourceUi?: boolean;

View File

@@ -1,11 +1,15 @@
export type { MatcherConfig } from '@grafana/schema';
import { MonoTypeOperatorFunction } from 'rxjs';
import { MatcherConfig, DataTransformerConfig } from '@grafana/schema';
import { RegistryItemWithOptions } from '../utils/Registry';
import { DataFrame, Field } from './dataFrame';
import { InterpolateFunction } from './panel';
/** deprecated, use it from schema */
export type { MatcherConfig };
/**
* Context passed to transformDataFrame and to each transform operator
*/
@@ -37,22 +41,9 @@ export interface SynchronousDataTransformerInfo<TOptions = any> extends DataTran
}
/**
* @public
* @deprecated use TransformationConfig from schema
*/
export interface DataTransformerConfig<TOptions = any> {
/**
* Unique identifier of transformer
*/
id: string;
/**
* Disabled transformations are skipped
*/
disabled?: boolean;
/**
* Options to be passed to the transformer
*/
options: TOptions;
}
export type { DataTransformerConfig };
export type FrameMatcher = (frame: DataFrame) => boolean;
export type FieldMatcher = (field: Field, frame: DataFrame, allFrames: DataFrame[]) => boolean;

View File

@@ -1,3 +1,7 @@
import { Location } from 'history';
import { GrafanaConfig } from '../types';
import { locationUtil } from './location';
describe('locationUtil', () => {
@@ -29,9 +33,9 @@ describe('locationUtil', () => {
describe('when appSubUrl configured', () => {
beforeEach(() => {
locationUtil.initialize({
config: { appSubUrl: '/subUrl' } as any,
getVariablesUrlParams: (() => {}) as any,
getTimeRangeForUrl: (() => {}) as any,
config: { appSubUrl: '/subUrl' } as GrafanaConfig,
getVariablesUrlParams: jest.fn(),
getTimeRangeForUrl: jest.fn(),
});
});
test('relative url', () => {
@@ -65,9 +69,9 @@ describe('locationUtil', () => {
describe('when appSubUrl not configured', () => {
beforeEach(() => {
locationUtil.initialize({
config: {} as any,
getVariablesUrlParams: (() => {}) as any,
getTimeRangeForUrl: (() => {}) as any,
config: {} as GrafanaConfig,
getVariablesUrlParams: jest.fn(),
getTimeRangeForUrl: jest.fn(),
});
});
@@ -115,12 +119,102 @@ describe('locationUtil', () => {
});
});
describe('getUrlForPartial', () => {
const mockLocation: Location = {
hash: '',
pathname: '/',
search: '',
state: {},
};
describe('when appSubUrl is not configured', () => {
beforeEach(() => {
locationUtil.initialize({
config: {
appSubUrl: '',
} as GrafanaConfig,
getVariablesUrlParams: jest.fn(),
getTimeRangeForUrl: jest.fn(),
});
});
it('can add params', () => {
expect(locationUtil.getUrlForPartial(mockLocation, { forceLogin: 'true' })).toEqual('/?forceLogin=true');
});
it('can remove params using undefined', () => {
expect(
locationUtil.getUrlForPartial(
{
...mockLocation,
search: '?a=1',
},
{ a: undefined }
)
).toEqual('/');
});
it('can remove params using null', () => {
expect(
locationUtil.getUrlForPartial(
{
...mockLocation,
search: '?a=1',
},
{ a: null }
)
).toEqual('/');
});
});
describe('when appSubUrl is configured', () => {
beforeEach(() => {
locationUtil.initialize({
config: {
appSubUrl: '/subpath',
} as GrafanaConfig,
getVariablesUrlParams: jest.fn(),
getTimeRangeForUrl: jest.fn(),
});
});
it('can add params', () => {
expect(locationUtil.getUrlForPartial(mockLocation, { forceLogin: 'true' })).toEqual(
'/subpath/?forceLogin=true'
);
});
it('can remove params using undefined', () => {
expect(
locationUtil.getUrlForPartial(
{
...mockLocation,
search: '?a=1',
},
{ a: undefined }
)
).toEqual('/subpath/');
});
it('can remove params using null', () => {
expect(
locationUtil.getUrlForPartial(
{
...mockLocation,
search: '?a=1',
},
{ a: null }
)
).toEqual('/subpath/');
});
});
});
describe('updateSearchParams', () => {
beforeEach(() => {
locationUtil.initialize({
config: {} as any,
getVariablesUrlParams: (() => {}) as any,
getTimeRangeForUrl: (() => {}) as any,
config: {} as GrafanaConfig,
getVariablesUrlParams: jest.fn(),
getTimeRangeForUrl: jest.fn(),
});
});

View File

@@ -78,7 +78,7 @@ const getUrlForPartial = (location: Location<any>, searchParamsToUpdate: Record<
searchParams[key] = searchParamsToUpdate[key];
}
}
return urlUtil.renderUrl(location.pathname, searchParams);
return assureBaseUrl(urlUtil.renderUrl(location.pathname, searchParams));
};
/**

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e-selectors",
"version": "9.4.0-pre",
"version": "9.4.1",
"description": "Grafana End-to-End Test Selectors Library",
"keywords": [
"cli",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e",
"version": "9.4.0-pre",
"version": "9.4.1",
"description": "Grafana End-to-End Test Library",
"keywords": [
"cli",
@@ -63,7 +63,7 @@
"@babel/core": "7.20.5",
"@babel/preset-env": "7.20.2",
"@cypress/webpack-preprocessor": "5.16.0",
"@grafana/e2e-selectors": "9.4.0-pre",
"@grafana/e2e-selectors": "9.4.1",
"@grafana/tsconfig": "^1.2.0-rc1",
"@mochajs/json-file-reporter": "^1.2.0",
"babel-loader": "9.1.0",

View File

@@ -1,7 +1,7 @@
{
"name": "@grafana/eslint-plugin",
"description": "ESLint rules for use within the Grafana repo. Not suitable (or supported) for external use.",
"version": "9.4.0-pre",
"version": "9.4.1",
"main": "./index.cjs",
"author": "Grafana Labs",
"license": "Apache-2.0",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/runtime",
"version": "9.4.0-pre",
"version": "9.4.1",
"description": "Grafana Runtime Library",
"keywords": [
"grafana",
@@ -37,10 +37,10 @@
"postpack": "mv package.json.bak package.json"
},
"dependencies": {
"@grafana/data": "9.4.0-pre",
"@grafana/e2e-selectors": "9.4.0-pre",
"@grafana/data": "9.4.1",
"@grafana/e2e-selectors": "9.4.1",
"@grafana/faro-web-sdk": "1.0.0-beta2",
"@grafana/ui": "9.4.0-pre",
"@grafana/ui": "9.4.1",
"@sentry/browser": "6.19.7",
"history": "4.10.1",
"lodash": "4.17.21",

View File

@@ -2,30 +2,30 @@ import { lastValueFrom, merge, Observable, of } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';
import {
DataSourceApi,
DataFrame,
dataFrameToJSON,
DataQuery,
DataQueryRequest,
DataQueryResponse,
DataSourceApi,
DataSourceInstanceSettings,
DataQuery,
DataSourceJsonData,
ScopedVars,
makeClassES5Compatible,
DataFrame,
parseLiveChannelAddress,
getDataSourceRef,
DataSourceRef,
dataFrameToJSON,
getDataSourceRef,
makeClassES5Compatible,
parseLiveChannelAddress,
ScopedVars,
} from '@grafana/data';
import { config } from '../config';
import {
BackendSrvRequest,
FetchResponse,
getBackendSrv,
getDataSourceSrv,
getGrafanaLiveSrv,
StreamingFrameOptions,
StreamingFrameAction,
BackendSrvRequest,
FetchResponse,
StreamingFrameOptions,
} from '../services';
import { BackendDataSourceResponse, toDataQueryResponse } from './queryResponse';

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/schema",
"version": "9.4.0-pre",
"version": "9.4.1",
"description": "Grafana Schema Library",
"keywords": [
"typescript"

View File

@@ -25,7 +25,6 @@ export type {
RegexMap,
SpecialValueMap,
ValueMappingResult,
Transformation,
LibraryPanelRef,
RowPanel,
GraphPanel,
@@ -62,6 +61,7 @@ export type {
Dashboard,
VariableModel,
DataSourceRef,
DataTransformerConfig,
Panel,
FieldConfigSource,
MatcherConfig,

View File

@@ -353,11 +353,25 @@ export interface ValueMappingResult {
/**
* TODO docs
* FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it
*/
export interface Transformation {
export interface DataTransformerConfig {
/**
* Disabled transformations are skipped
*/
disabled?: boolean;
/**
* Optional frame matcher. When missing it will be applied to all results
*/
filter?: MatcherConfig;
/**
* Unique identifier of transformer
*/
id: string;
options: Record<string, unknown>;
/**
* Options to be passed to the transformer
* Valid options depend on the transformer id
*/
options: unknown;
}
/**
@@ -470,7 +484,7 @@ export interface Panel {
* Panel title.
*/
title?: string;
transformations: Array<Transformation>;
transformations: Array<DataTransformerConfig>;
/**
* Whether to display the panel without a background.
*/

View File

@@ -47,6 +47,10 @@ export interface MatcherConfig<TConfig = any> extends raw.MatcherConfig {
options?: TConfig;
}
export interface DataTransformerConfig<TOptions = any> extends raw.DataTransformerConfig {
options: TOptions;
}
export const defaultDashboard = raw.defaultDashboard as Dashboard;
export const defaultVariableModel = {
...raw.defaultVariableModel,

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/toolkit",
"version": "9.4.0-pre",
"version": "9.4.1",
"description": "Grafana Toolkit",
"keywords": [
"grafana",
@@ -51,10 +51,10 @@
"@babel/preset-env": "7.18.9",
"@babel/preset-react": "7.18.6",
"@babel/preset-typescript": "7.18.6",
"@grafana/data": "9.4.0-pre",
"@grafana/data": "9.4.1",
"@grafana/eslint-config": "5.0.0",
"@grafana/tsconfig": "^1.2.0-rc1",
"@grafana/ui": "9.4.0-pre",
"@grafana/ui": "9.4.1",
"@jest/core": "27.5.1",
"@types/command-exists": "^1.2.0",
"@types/eslint": "8.4.1",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/ui",
"version": "9.4.0-pre",
"version": "9.4.1",
"description": "Grafana Components Library",
"keywords": [
"grafana",
@@ -49,9 +49,9 @@
"dependencies": {
"@emotion/css": "11.10.5",
"@emotion/react": "11.10.5",
"@grafana/data": "9.4.0-pre",
"@grafana/e2e-selectors": "9.4.0-pre",
"@grafana/schema": "9.4.0-pre",
"@grafana/data": "9.4.1",
"@grafana/e2e-selectors": "9.4.1",
"@grafana/schema": "9.4.1",
"@leeoniya/ufuzzy": "0.9.0",
"@monaco-editor/react": "4.4.6",
"@popperjs/core": "2.11.6",

View File

@@ -33,7 +33,7 @@ export const ContextMenu: React.FC<ContextMenuProps> = React.memo(
const OFFSET = 5;
const collisions = {
right: window.innerWidth < x + rect.width,
bottom: window.innerHeight < rect.bottom + rect.height + OFFSET,
bottom: window.innerHeight < y + rect.height + OFFSET,
};
setPositionStyles({

View File

@@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import { FocusScope } from '@react-aria/focus';
import React, { useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { usePopperTooltip } from 'react-popper-tooltip';
import { CSSTransition } from 'react-transition-group';
@@ -12,12 +12,19 @@ export interface Props {
overlay: React.ReactElement | (() => React.ReactElement);
placement?: TooltipPlacement;
children: React.ReactElement | ((isOpen: boolean) => React.ReactElement);
/** Amount in pixels to nudge the dropdown vertically and horizontally, respectively. */
offset?: [number, number];
onVisibleChange?: (state: boolean) => void;
}
export const Dropdown = React.memo(({ children, overlay, placement }: Props) => {
export const Dropdown = React.memo(({ children, overlay, placement, offset, onVisibleChange }: Props) => {
const [show, setShow] = useState(false);
const transitionRef = useRef(null);
useEffect(() => {
onVisibleChange?.(show);
}, [onVisibleChange, show]);
const { getArrowProps, getTooltipProps, setTooltipRef, setTriggerRef, visible } = usePopperTooltip({
visible: show,
placement: placement,
@@ -25,7 +32,7 @@ export const Dropdown = React.memo(({ children, overlay, placement }: Props) =>
interactive: true,
delayHide: 0,
delayShow: 0,
offset: [0, 8],
offset: offset ?? [0, 8],
trigger: ['click'],
});

View File

@@ -59,7 +59,7 @@ export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme2) => {
return {
wrapper: css`
display: flex;
display: inline-flex;
gap: ${theme.spacing(labelPadding)};
align-items: baseline;
position: relative;

View File

@@ -0,0 +1,108 @@
import { css } from '@emotion/css';
import classnames from 'classnames';
import React, { ReactElement, useCallback, useRef, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../themes';
import { Icon } from '../Icon/Icon';
import { PanelMenu } from './PanelMenu';
interface Props {
children?: React.ReactNode;
menu: ReactElement | (() => ReactElement);
title?: string;
offset?: number;
dragClass?: string;
}
export function HoverWidget({ menu, title, dragClass, children, offset = -32 }: Props) {
const styles = useStyles2(getStyles);
const draggableRef = useRef<HTMLDivElement>(null);
// Capture the pointer to keep the widget visible while dragging
const onPointerDown = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
draggableRef.current?.setPointerCapture(e.pointerId);
}, []);
const onPointerUp = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
draggableRef.current?.releasePointerCapture(e.pointerId);
}, []);
const [menuOpen, setMenuOpen] = useState(false);
if (children === undefined || React.Children.count(children) === 0) {
return null;
}
return (
<div
className={classnames(styles.container, { 'show-on-hover': !menuOpen })}
style={{ top: `${offset}px` }}
data-testid="hover-header-container"
>
<div
className={classnames(styles.square, styles.draggable, dragClass)}
onPointerDown={onPointerDown}
onPointerUp={onPointerUp}
ref={draggableRef}
>
<Icon name="draggabledots" />
</div>
{children}
<div className={styles.square}>
<PanelMenu
menu={menu}
title={title}
placement="bottom"
menuButtonClass={styles.menuButton}
onVisibleChange={setMenuOpen}
/>
</div>
</div>
);
}
function getStyles(theme: GrafanaTheme2) {
return {
hidden: css({
visibility: 'hidden',
opacity: '0',
}),
container: css({
label: 'hover-container-widget',
transition: `all .1s linear`,
display: 'flex',
position: 'absolute',
zIndex: 1,
boxSizing: 'border-box',
alignItems: 'center',
background: theme.colors.background.secondary,
color: theme.colors.text.primary,
border: `1px solid ${theme.colors.border.weak}`,
borderRadius: '1px',
height: theme.spacing(4),
boxShadow: theme.shadows.z1,
}),
square: css({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: theme.spacing(4),
height: '100%',
}),
draggable: css({
cursor: 'move',
}),
menuButton: css({
color: theme.colors.text.primary,
'&:hover': {
background: 'inherit',
},
}),
title: css({
padding: theme.spacing(0.75),
}),
};
}

View File

@@ -9,7 +9,7 @@ import { PanelChrome, PanelChromeProps } from '@grafana/ui';
import { DashboardStoryCanvas } from '../../utils/storybook/DashboardStoryCanvas';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { HorizontalGroup, VerticalGroup } from '../Layout/Layout';
import { HorizontalGroup } from '../Layout/Layout';
import { Menu } from '../Menu/Menu';
const meta: ComponentMeta<typeof PanelChrome> = {
@@ -91,8 +91,8 @@ export const Examples = () => {
return (
<DashboardStoryCanvas>
<HorizontalGroup spacing="md" align="flex-start">
<VerticalGroup spacing="md">
<div>
<HorizontalGroup spacing="md" align="flex-start" wrap>
{renderPanel('Has statusMessage', {
title: 'Default title',
statusMessage: 'Error text',
@@ -116,8 +116,7 @@ export const Examples = () => {
title: 'Default title',
loadingState: LoadingState.Loading,
})}
</VerticalGroup>
<VerticalGroup spacing="md">
{renderPanel('Default panel: no non-required props')}
{renderPanel('No padding', {
padding: 'none',
@@ -131,8 +130,7 @@ export const Examples = () => {
{renderPanel('No title, loading loadingState', {
loadingState: LoadingState.Loading,
})}
</VerticalGroup>
<VerticalGroup spacing="md">
{renderPanel('Error status, menu', {
title: 'Default title',
menu,
@@ -155,16 +153,11 @@ export const Examples = () => {
menu,
loadingState: LoadingState.Streaming,
})}
{renderPanel('loadingState is Loading, menu', {
title: 'Default title',
menu,
loadingState: LoadingState.Loading,
})}
</VerticalGroup>
</HorizontalGroup>
<HorizontalGroup spacing="md" align="flex-start">
<VerticalGroup spacing="md">
{renderPanel('Deprecated error indicator', {
title: 'Default title',
leftItems: [
@@ -186,8 +179,6 @@ export const Examples = () => {
/>,
],
})}
</VerticalGroup>
<VerticalGroup spacing="md">
{renderPanel('Deprecated error indicator, menu', {
title: 'Default title',
menu,
@@ -199,8 +190,14 @@ export const Examples = () => {
/>,
],
})}
</VerticalGroup>
</HorizontalGroup>
{renderPanel('Display mode = transparent', {
title: 'Default title',
displayMode: 'transparent',
menu,
leftItems: [],
})}
</HorizontalGroup>
</div>
</DashboardStoryCanvas>
);
};

View File

@@ -60,22 +60,12 @@ it('renders panel with a header if prop leftItems', () => {
expect(screen.getByTestId('header-container')).toBeInTheDocument();
});
// todo implement when hoverHeader is implemented
it.skip('renders panel without header if no title, no leftItems, and hoverHeader is undefined', () => {
setup();
expect(screen.getByTestId('header-container')).toBeInTheDocument();
it('renders panel with hover header if no title, no leftItems, hoverHeader is undefined but menu is present', () => {
setup({ title: '', leftItems: undefined, hoverHeader: undefined, menu: <div>Menu</div> });
expect(screen.getByTestId('hover-header-container')).toBeInTheDocument();
});
// todo implement when hoverHeader is implemented
it.skip('renders panel with a fixed header if prop hoverHeader is false', () => {
setup({ hoverHeader: false });
expect(screen.getByTestId('header-container')).toBeInTheDocument();
});
// todo implement when hoverHeader is implemented
it.skip('renders panel with a hovering header if prop hoverHeader is true', () => {
it('renders panel with a hovering header if prop hoverHeader is true', () => {
setup({ title: 'Test Panel Header', hoverHeader: true });
expect(screen.queryByTestId('header-container')).not.toBeInTheDocument();
@@ -97,10 +87,10 @@ it('renders panel with a header with icons in place if prop titleItems', () => {
expect(screen.getByTestId('title-items-container')).toBeInTheDocument();
});
it('renders panel with a header if prop menu', () => {
setup({ menu: <div> Menu </div> });
it('renders panel with a hover header if prop menu is present and hoverHeader is false', () => {
setup({ menu: <div> Menu </div>, hoverHeader: false });
expect(screen.getByTestId('header-container')).toBeInTheDocument();
expect(screen.getByTestId('hover-header-container')).toBeInTheDocument();
});
it('renders panel with a show-on-hover menu icon if prop menu', () => {

View File

@@ -5,14 +5,15 @@ import { GrafanaTheme2, LoadingState } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { useStyles2, useTheme2 } from '../../themes';
import { Dropdown } from '../Dropdown/Dropdown';
import { Icon } from '../Icon/Icon';
import { LoadingBar } from '../LoadingBar/LoadingBar';
import { ToolbarButton } from '../ToolbarButton';
import { Tooltip } from '../Tooltip';
import { HoverWidget } from './HoverWidget';
import { PanelDescription } from './PanelDescription';
import { PanelMenu } from './PanelMenu';
import { PanelStatus } from './PanelStatus';
import { TitleItem } from './TitleItem';
/**
* @internal
@@ -22,9 +23,10 @@ export interface PanelChromeProps {
height: number;
children: (innerWidth: number, innerHeight: number) => ReactNode;
padding?: PanelPadding;
hoverHeaderOffset?: number;
title?: string;
description?: string | (() => string);
titleItems?: ReactNode[];
titleItems?: ReactNode;
menu?: ReactElement | (() => ReactElement);
dragClass?: string;
dragClassCancel?: string;
@@ -50,6 +52,7 @@ export interface PanelChromeProps {
* of showing/interacting with the panel's state
*/
leftItems?: ReactNode[];
displayMode?: 'default' | 'transparent';
}
/**
@@ -67,11 +70,13 @@ export function PanelChrome({
padding = 'md',
title = '',
description = '',
titleItems = [],
displayMode = 'default',
titleItems,
menu,
dragClass,
dragClassCancel,
hoverHeader = false,
hoverHeaderOffset,
loadingState,
statusMessage,
statusMessageOnClick,
@@ -88,7 +93,7 @@ export function PanelChrome({
const hasHeader =
hoverHeader === false &&
(title.length > 0 ||
titleItems.length > 0 ||
titleItems !== undefined ||
description !== '' ||
loadingState === LoadingState.Streaming ||
(leftItems?.length ?? 0) > 0);
@@ -101,71 +106,84 @@ export function PanelChrome({
cursor: dragClass ? 'move' : 'auto',
};
const itemStyles: CSSProperties = {
minHeight: headerHeight,
minWidth: headerHeight,
};
const containerStyles: CSSProperties = { width, height };
if (displayMode === 'transparent') {
containerStyles.backgroundColor = 'transparent';
containerStyles.border = 'none';
}
const ariaLabel = title ? selectors.components.Panels.Panel.containerByTitle(title) : 'Panel';
const headerContent = (
<>
{title && (
<h6 title={title} className={styles.title}>
{title}
</h6>
)}
<PanelDescription description={description} className={dragClassCancel} />
{titleItems !== undefined && (
<div className={cx(styles.titleItems, dragClassCancel)} data-testid="title-items-container">
{titleItems}
</div>
)}
{loadingState === LoadingState.Streaming && (
<Tooltip content="Streaming">
<TitleItem className={dragClassCancel} data-testid="panel-streaming">
<Icon name="circle-mono" size="md" className={styles.streaming} />
</TitleItem>
</Tooltip>
)}
</>
);
return (
<div className={styles.container} style={containerStyles} aria-label={ariaLabel}>
<div
className={cx(styles.container, { [styles.regularHeader]: hasHeader })}
style={containerStyles}
aria-label={ariaLabel}
>
<div className={styles.loadingBarContainer}>
{loadingState === LoadingState.Loading ? (
<LoadingBar width={'28%'} height={'2px'} ariaLabel="Panel loading bar" />
) : null}
</div>
<div className={cx(styles.headerContainer, dragClass)} style={headerStyles} data-testid="header-container">
{title && (
<h6 title={title} className={styles.title}>
{title}
</h6>
)}
{(hoverHeader || !hasHeader) && menu && (
<HoverWidget menu={menu} title={title} offset={hoverHeaderOffset} dragClass={dragClass}>
{headerContent}
</HoverWidget>
)}
<PanelDescription description={description} className={dragClassCancel} />
{hasHeader && (
<div className={cx(styles.headerContainer, dragClass)} style={headerStyles} data-testid="header-container">
{headerContent}
{titleItems.length > 0 && (
<div className={cx(styles.titleItems, dragClassCancel)} data-testid="title-items-container">
{titleItems.map((item) => item)}
</div>
)}
{loadingState === LoadingState.Streaming && (
<div className={styles.item} style={itemStyles} data-testid="panel-streaming">
<Tooltip content="Streaming">
<Icon name="circle-mono" size="sm" className={styles.streaming} />
</Tooltip>
</div>
)}
<div className={styles.rightAligned}>
{menu && (
<Dropdown overlay={menu} placement="bottom">
<ToolbarButton
aria-label={`Menu for panel with ${title ? `title ${title}` : 'no title'}`}
title="Menu"
icon="ellipsis-v"
narrow
data-testid="panel-menu-button"
className={cx(styles.menuItem, dragClassCancel, 'menu-icon')}
<div className={styles.rightAligned}>
{menu && (
<PanelMenu
menu={menu}
title={title}
menuButtonClass={cx(styles.menuItem, dragClassCancel, 'show-on-hover')}
/>
</Dropdown>
)}
)}
{leftItems && <div className={styles.items}>{itemsRenderer(leftItems, (item) => item)}</div>}
{leftItems && <div className={styles.leftItems}>{itemsRenderer(leftItems, (item) => item)}</div>}
</div>
</div>
)}
{statusMessage && (
<PanelStatus
className={cx(styles.errorContainer, dragClassCancel)}
message={statusMessage}
onClick={statusMessageOnClick}
ariaLabel="Panel status"
/>
)}
</div>
{statusMessage && (
<PanelStatus
className={cx(styles.errorContainer, dragClassCancel)}
message={statusMessage}
onClick={statusMessageOnClick}
ariaLabel="Panel status"
/>
)}
<div className={styles.content} style={contentStyle}>
{children(innerWidth, innerHeight)}
@@ -210,7 +228,7 @@ const getContentStyle = (
};
const getStyles = (theme: GrafanaTheme2) => {
const { background, borderColor } = theme.components.panel;
const { background, borderColor, padding } = theme.components.panel;
return {
container: css({
@@ -224,10 +242,15 @@ const getStyles = (theme: GrafanaTheme2) => {
flexDirection: 'column',
flex: '1 1 0',
'.show-on-hover': {
visibility: 'hidden',
opacity: '0',
},
'&:focus-visible, &:hover': {
// only show menu icon on hover or focused panel
'.menu-icon': {
'.show-on-hover': {
visibility: 'visible',
opacity: '1',
},
},
@@ -235,6 +258,14 @@ const getStyles = (theme: GrafanaTheme2) => {
outline: `1px solid ${theme.colors.action.focus}`,
},
}),
regularHeader: css({
'&:focus-within': {
'.show-on-hover': {
visibility: 'visible',
opacity: '1',
},
},
}),
loadingBarContainer: css({
label: 'panel-loading-bar-container',
position: 'absolute',
@@ -251,7 +282,7 @@ const getStyles = (theme: GrafanaTheme2) => {
label: 'panel-header',
display: 'flex',
alignItems: 'center',
padding: theme.spacing(0, 0, 0, 1),
padding: theme.spacing(0, 0, 0, padding),
}),
streaming: css({
label: 'panel-streaming',
@@ -265,9 +296,11 @@ const getStyles = (theme: GrafanaTheme2) => {
title: css({
label: 'panel-title',
marginBottom: 0, // override default h6 margin-bottom
paddingRight: theme.spacing(1),
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
maxWidth: theme.spacing(50),
fontSize: theme.typography.h6.fontSize,
fontWeight: theme.typography.h6.fontWeight,
}),
@@ -289,6 +322,14 @@ const getStyles = (theme: GrafanaTheme2) => {
position: 'absolute',
left: '50%',
transform: 'translateX(-50%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: theme.zIndex.tooltip,
}),
leftItems: css({
display: 'flex',
paddingRight: theme.spacing(padding),
}),
rightAligned: css({
label: 'right-aligned-container',
@@ -298,9 +339,7 @@ const getStyles = (theme: GrafanaTheme2) => {
}),
titleItems: css({
display: 'flex',
alignItems: 'center',
overflow: 'hidden',
padding: theme.spacing(1),
height: '100%',
}),
};
};

View File

@@ -1,6 +1,9 @@
import { css, cx } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../themes';
import { Icon } from '../Icon/Icon';
import { Tooltip } from '../Tooltip';
@@ -12,7 +15,7 @@ interface Props {
}
export function PanelDescription({ description, className }: Props) {
const styles = getStyles();
const styles = useStyles2(getStyles);
const getDescriptionContent = (): JSX.Element => {
// description
@@ -28,13 +31,13 @@ export function PanelDescription({ description, className }: Props) {
return description !== '' ? (
<Tooltip interactive content={getDescriptionContent}>
<TitleItem className={cx(className, styles.description)}>
<Icon name="info-circle" size="lg" title="description" />
<Icon name="info-circle" size="md" title="description" />
</TitleItem>
</Tooltip>
) : null;
}
const getStyles = () => {
const getStyles = (theme: GrafanaTheme2) => {
return {
description: css({
code: {

View File

@@ -0,0 +1,40 @@
import { cx } from '@emotion/css';
import React, { ReactElement } from 'react';
import { Dropdown } from '../Dropdown/Dropdown';
import { ToolbarButton } from '../ToolbarButton';
import { TooltipPlacement } from '../Tooltip';
interface PanelMenuProps {
menu: ReactElement | (() => ReactElement);
menuButtonClass?: string;
dragClassCancel?: string;
title?: string;
placement?: TooltipPlacement;
offset?: [number, number];
onVisibleChange?: (state: boolean) => void;
}
export function PanelMenu({
menu,
title,
placement = 'bottom',
offset,
dragClassCancel,
menuButtonClass,
onVisibleChange,
}: PanelMenuProps) {
return (
<Dropdown overlay={menu} placement={placement} offset={offset} onVisibleChange={onVisibleChange}>
<ToolbarButton
aria-label={`Menu for panel with ${title ? `title ${title}` : 'no title'}`}
title="Menu"
icon="ellipsis-v"
iconSize="md"
narrow
data-testid="panel-menu-button"
className={cx(menuButtonClass, dragClassCancel)}
/>
</Dropdown>
);
}

View File

@@ -22,6 +22,7 @@ export function PanelStatus({ className, message, onClick, ariaLabel = 'status'
onClick={onClick}
variant={'destructive'}
icon="exclamation-triangle"
iconSize="md"
tooltip={message || ''}
aria-label={ariaLabel}
/>

View File

@@ -50,12 +50,11 @@ const getStyles = (theme: GrafanaTheme2) => {
item: css({
color: `${theme.colors.text.secondary}`,
label: 'panel-header-item',
backgroundColor: `${theme.colors.background.primary}`,
cursor: 'auto',
border: 'none',
borderRadius: `${theme.shape.borderRadius()}`,
padding: `${theme.spacing(0, 1)}`,
height: `${theme.spacing(theme.components.height.md)}`,
height: `${theme.spacing(theme.components.panel.headerHeight)}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',

View File

@@ -6,6 +6,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { styleMixins, useStyles2 } from '../../themes';
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
import { IconSize } from '../../types/icon';
import { getPropertiesForVariant } from '../Button';
import { Icon } from '../Icon/Icon';
import { Tooltip } from '../Tooltip';
@@ -13,6 +14,8 @@ import { Tooltip } from '../Tooltip';
type CommonProps = {
/** Icon name */
icon?: IconName | React.ReactNode;
/** Icon size */
iconSize?: IconSize;
/** Tooltip */
tooltip?: string;
/** For image icons */
@@ -42,6 +45,7 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, ToolbarButtonProps>(
{
tooltip,
icon,
iconSize,
className,
children,
imgSrc,
@@ -83,7 +87,7 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, ToolbarButtonProps>(
aria-expanded={isOpen}
{...rest}
>
{renderIcon(icon)}
{renderIcon(icon, iconSize)}
{imgSrc && <img className={styles.img} src={imgSrc} alt={imgAlt ?? ''} />}
{children && !iconOnly && <div className={contentStyles}>{children}</div>}
{isOpen === false && <Icon name="angle-down" />}
@@ -108,13 +112,13 @@ function getButtonAriaLabel(ariaLabel: string | undefined, tooltip: string | und
return ariaLabel ? ariaLabel : tooltip ? selectors.components.PageToolbar.item(tooltip) : undefined;
}
function renderIcon(icon: IconName | React.ReactNode) {
function renderIcon(icon: IconName | React.ReactNode, iconSize?: IconSize) {
if (!icon) {
return null;
}
if (isIconName(icon)) {
return <Icon name={icon} size="lg" />;
return <Icon name={icon} size={`${iconSize ? iconSize : 'lg'}`} />;
}
return icon;

View File

@@ -739,9 +739,10 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R
version, _ := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 32)
query := dashver.GetDashboardVersionQuery{
OrgID: c.OrgID,
DashboardID: dash.ID,
Version: int(version),
OrgID: c.OrgID,
DashboardID: dash.ID,
DashboardUID: dash.UID,
Version: int(version),
}
res, err := hs.dashboardVersionService.Get(c.Req.Context(), &query)
@@ -757,7 +758,7 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R
dashVersionMeta := &dashver.DashboardVersionMeta{
ID: res.ID,
DashboardID: res.DashboardID,
DashboardUID: dashUID,
DashboardUID: dash.UID,
Data: res.Data,
ParentVersion: res.ParentVersion,
RestoredFrom: res.RestoredFrom,
@@ -989,7 +990,7 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
return dashboardGuardianResponse(err)
}
versionQuery := dashver.GetDashboardVersionQuery{DashboardID: dashID, Version: apiCmd.Version, OrgID: c.OrgID}
versionQuery := dashver.GetDashboardVersionQuery{DashboardID: dashID, DashboardUID: dash.UID, Version: apiCmd.Version, OrgID: c.OrgID}
version, err := hs.dashboardVersionService.Get(c.Req.Context(), &versionQuery)
if err != nil {
return response.Error(404, "Dashboard version not found", nil)

View File

@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"net/http"
"strconv"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -268,7 +267,7 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, enabledPlugin
url := ds.Url
if ds.Access == datasources.DS_ACCESS_PROXY {
url = "/api/datasources/proxy/" + strconv.FormatInt(ds.Id, 10)
url = "/api/datasources/proxy/uid/" + ds.Uid
}
dsDTO := plugins.DataSourceDTO{

View File

@@ -52,4 +52,8 @@ var (
EnvVars: []string{"GITHUB_TOKEN"},
Usage: "GitHub token",
}
tagFlag = cli.StringFlag{
Name: "tag",
Usage: "Grafana version tag",
}
)

View File

@@ -9,6 +9,13 @@ import (
"github.com/urfave/cli/v2"
)
var additionalCommands []*cli.Command = make([]*cli.Command, 0, 5)
//nolint:unused
func registerAppCommand(c *cli.Command) {
additionalCommands = append(additionalCommands, c)
}
func main() {
app := cli.NewApp()
app.Commands = cli.Commands{
@@ -189,6 +196,59 @@ func main() {
Name: "artifacts",
Usage: "Handle Grafana artifacts",
Subcommands: cli.Commands{
{
Name: "publish",
Usage: "Publish Grafana artifacts",
Action: PublishArtifactsAction,
Flags: []cli.Flag{
&editionFlag,
&cli.BoolFlag{
Name: "security",
Usage: "Security release",
},
&cli.StringFlag{
Name: "security-dest-bucket",
Usage: "Google Cloud Storage bucket for security packages (or $SECURITY_DEST_BUCKET)",
},
&cli.StringFlag{
Name: "tag",
Usage: "Grafana version tag",
},
&cli.StringFlag{
Name: "src-bucket",
Value: "grafana-prerelease",
Usage: "Google Cloud Storage bucket",
},
&cli.StringFlag{
Name: "dest-bucket",
Value: "grafana-downloads",
Usage: "Google Cloud Storage bucket for published packages",
},
&cli.StringFlag{
Name: "enterprise2-dest-bucket",
Value: "grafana-downloads-enterprise2",
Usage: "Google Cloud Storage bucket for published packages",
},
&cli.StringFlag{
Name: "enterprise2-security-prefix",
Usage: "Bucket path prefix for enterprise2 security releases (or $ENTERPRISE2_SECURITY_PREFIX)",
},
&cli.StringFlag{
Name: "static-assets-bucket",
Value: "grafana-static-assets",
Usage: "Google Cloud Storage bucket for static assets",
},
&cli.StringSliceFlag{
Name: "static-asset-editions",
Usage: "All the editions of the static assets (or $STATIC_ASSET_EDITIONS)",
},
&cli.StringFlag{
Name: "storybook-bucket",
Value: "grafana-storybook",
Usage: "Google Cloud Storage bucket for storybooks",
},
},
},
{
Name: "docker",
Usage: "Handle Grafana Docker images",
@@ -226,10 +286,7 @@ func main() {
ArgsUsage: "[version]",
Action: NpmReleaseAction,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "tag",
Usage: "Grafana version tag",
},
&tagFlag,
},
},
{
@@ -237,10 +294,7 @@ func main() {
Usage: "Store npm packages tarball",
Action: NpmStoreAction,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "tag",
Usage: "Grafana version tag",
},
&tagFlag,
},
},
{
@@ -248,10 +302,7 @@ func main() {
Usage: "Retrieve npm packages tarball",
Action: NpmRetrieveAction,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "tag",
Usage: "Grafana version tag",
},
&tagFlag,
},
},
},
@@ -363,6 +414,8 @@ func main() {
},
}
app.Commands = append(app.Commands, additionalCommands...)
if err := app.Run(os.Args); err != nil {
log.Fatalln(err)
}

View File

@@ -0,0 +1,211 @@
package main
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/grafana/grafana/pkg/build/gcloud"
"github.com/grafana/grafana/pkg/build/versions"
"github.com/urfave/cli/v2"
)
type publishConfig struct {
tag string
srcBucket string
destBucket string
enterprise2DestBucket string
enterprise2SecurityPrefix string
staticAssetsBucket string
staticAssetEditions []string
storybookBucket string
security bool
}
// requireListWithEnvFallback first checks the CLI for a flag with the required
// name. If this is empty, it falls back to taking the environment variable.
// Sadly, we cannot use cli.Flag.EnvVars for this due to it potentially leaking
// environment variables as default values in usage-errors.
func requireListWithEnvFallback(cctx *cli.Context, name string, envName string) ([]string, error) {
result := cctx.StringSlice(name)
if len(result) == 0 {
for _, v := range strings.Split(os.Getenv(envName), ",") {
value := strings.TrimSpace(v)
if value != "" {
result = append(result, value)
}
}
}
if len(result) == 0 {
return nil, cli.Exit(fmt.Sprintf("Required flag (%s) or environment variable (%s) not set", name, envName), 1)
}
return result, nil
}
func requireStringWithEnvFallback(cctx *cli.Context, name string, envName string) (string, error) {
result := cctx.String(name)
if result == "" {
result = os.Getenv(envName)
}
if result == "" {
return "", cli.Exit(fmt.Sprintf("Required flag (%s) or environment variable (%s) not set", name, envName), 1)
}
return result, nil
}
// Action implements the sub-command "publish-artifacts".
func PublishArtifactsAction(c *cli.Context) error {
if c.NArg() > 0 {
if err := cli.ShowSubcommandHelp(c); err != nil {
return cli.Exit(err.Error(), 1)
}
return cli.Exit("", 1)
}
staticAssetEditions, err := requireListWithEnvFallback(c, "static-asset-editions", "STATIC_ASSET_EDITIONS")
if err != nil {
return err
}
securityDestBucket, err := requireStringWithEnvFallback(c, "security-dest-bucket", "SECURITY_DEST_BUCKET")
if err != nil {
return err
}
enterprise2SecurityPrefix, err := requireStringWithEnvFallback(c, "enterprise2-security-prefix", "ENTERPRISE2_SECURITY_PREFIX")
if err != nil {
return err
}
if err := gcloud.ActivateServiceAccount(); err != nil {
return fmt.Errorf("error connecting to gcp, %q", err)
}
cfg := publishConfig{
srcBucket: c.String("src-bucket"),
destBucket: c.String("dest-bucket"),
enterprise2DestBucket: c.String("enterprise2-dest-bucket"),
enterprise2SecurityPrefix: enterprise2SecurityPrefix,
staticAssetsBucket: c.String("static-assets-bucket"),
staticAssetEditions: staticAssetEditions,
storybookBucket: c.String("storybook-bucket"),
security: c.Bool("security"),
tag: strings.TrimPrefix(c.String("tag"), "v"),
}
if cfg.security {
cfg.destBucket = securityDestBucket
}
err = copyStaticAssets(cfg)
if err != nil {
return err
}
err = copyStorybook(cfg)
if err != nil {
return err
}
err = copyDownloads(cfg)
if err != nil {
return err
}
err = copyEnterprise2Downloads(cfg)
if err != nil {
return err
}
return nil
}
func copyStaticAssets(cfg publishConfig) error {
for _, edition := range cfg.staticAssetEditions {
log.Printf("Copying static assets for %s", edition)
srcURL := fmt.Sprintf("%s/artifacts/static-assets/%s/%s/*", cfg.srcBucket, edition, cfg.tag)
destURL := fmt.Sprintf("%s/%s/%s/", cfg.staticAssetsBucket, edition, cfg.tag)
err := gcsCopy("static assets", srcURL, destURL)
if err != nil {
return fmt.Errorf("error copying static assets, %q", err)
}
}
log.Printf("Successfully copied static assets!")
return nil
}
func copyStorybook(cfg publishConfig) error {
if cfg.security {
log.Printf("skipping storybook copy - not needed for a security release")
return nil
}
log.Printf("Copying storybooks...")
srcURL := fmt.Sprintf("%s/artifacts/storybook/v%s/*", cfg.srcBucket, cfg.tag)
destURL := fmt.Sprintf("%s/%s", cfg.storybookBucket, cfg.tag)
err := gcsCopy("storybook", srcURL, destURL)
if err != nil {
return fmt.Errorf("error copying storybook. %q", err)
}
stableVersion, err := versions.GetLatestVersion(versions.LatestStableVersionURL)
if err != nil {
return err
}
isLatest, err := versions.IsGreaterThanOrEqual(cfg.tag, stableVersion)
if err != nil {
return err
}
if isLatest {
log.Printf("Copying storybooks to latest...")
srcURL := fmt.Sprintf("%s/artifacts/storybook/v%s/*", cfg.srcBucket, cfg.tag)
destURL := fmt.Sprintf("%s/latest", cfg.storybookBucket)
err := gcsCopy("storybook (latest)", srcURL, destURL)
if err != nil {
return fmt.Errorf("error copying storybook to latest. %q", err)
}
}
log.Printf("Successfully copied storybook!")
return nil
}
func copyDownloads(cfg publishConfig) error {
for _, edition := range []string{
"oss", "enterprise",
} {
destURL := fmt.Sprintf("%s/%s/", cfg.destBucket, edition)
srcURL := fmt.Sprintf("%s/artifacts/downloads/v%s/%s/release/*", cfg.srcBucket, cfg.tag, edition)
if !cfg.security {
destURL = filepath.Join(destURL, "release")
}
log.Printf("Copying downloads for %s, from %s bucket to %s bucket", edition, srcURL, destURL)
err := gcsCopy("downloads", srcURL, destURL)
if err != nil {
return fmt.Errorf("error copying downloads, %q", err)
}
}
log.Printf("Successfully copied downloads.")
return nil
}
func copyEnterprise2Downloads(cfg publishConfig) error {
var prefix string
if cfg.security {
prefix = cfg.enterprise2SecurityPrefix
}
srcURL := fmt.Sprintf("%s/artifacts/downloads-enterprise2/v%s/enterprise2/release/*", cfg.srcBucket, cfg.tag)
destURL := fmt.Sprintf("%s/enterprise2/%srelease", cfg.enterprise2DestBucket, prefix)
log.Printf("Copying downloads for enterprise2, from %s bucket to %s bucket", srcURL, destURL)
err := gcsCopy("enterprise2 downloads", srcURL, destURL)
if err != nil {
return fmt.Errorf("error copying ")
}
return nil
}
func gcsCopy(desc, src, dest string) error {
args := strings.Split(fmt.Sprintf("-m cp -r gs://%s gs://%s", src, dest), " ")
// nolint:gosec
cmd := exec.Command("gsutil", args...)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to publish %s: %w\n%s", desc, err, out)
}
return nil
}

View File

@@ -1,3 +1,5 @@
//go:build requires_buildifier
package main
import (

View File

@@ -334,6 +334,20 @@ type DataSourceRef struct {
Uid *string `json:"uid,omitempty"`
}
// TODO docs
type DataTransformerConfig struct {
// Disabled transformations are skipped
Disabled *bool `json:"disabled,omitempty"`
Filter *MatcherConfig `json:"filter,omitempty"`
// Unique identifier of transformer
Id string `json:"id"`
// Options to be passed to the transformer
// Valid options depend on the transformer id
Options interface{} `json:"options"`
}
// DynamicConfigValue defines model for DynamicConfigValue.
type DynamicConfigValue struct {
Id string `json:"id"`
@@ -545,8 +559,8 @@ type Panel struct {
TimeShift *string `json:"timeShift,omitempty"`
// Panel title.
Title *string `json:"title,omitempty"`
Transformations []Transformation `json:"transformations"`
Title *string `json:"title,omitempty"`
Transformations []DataTransformerConfig `json:"transformations"`
// Whether to display the panel without a background.
Transparent bool `json:"transparent"`
@@ -707,13 +721,6 @@ type ThresholdsConfig struct {
// ThresholdsMode defines model for ThresholdsMode.
type ThresholdsMode string
// TODO docs
// FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it
type Transformation struct {
Id string `json:"id"`
Options map[string]interface{} `json:"options"`
}
// TODO docs
type ValueMap struct {
Options map[string]ValueMappingResult `json:"options"`

View File

@@ -299,7 +299,6 @@ var irregularPluginNames = map[string]string{
"azuremonitor": "grafana-azure-monitor-datasource",
"microsoftsqlserver": "mssql",
"postgresql": "postgres",
"testdatadb": "testdata",
}
func buildComposableLinks(pp plugindef.PluginDef, cp kindsys.ComposableProperties) KindLinks {

View File

@@ -13,10 +13,11 @@ import (
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/infra/db/dbtest"
"github.com/grafana/grafana/pkg/infra/fs"
@@ -344,9 +345,9 @@ func TestMiddlewareContext(t *testing.T) {
}
sc.userAuthTokenService.TryRotateTokenProvider = func(ctx context.Context, userToken *auth.UserToken,
clientIP net.IP, userAgent string) (bool, error) {
clientIP net.IP, userAgent string) (bool, *auth.UserToken, error) {
userToken.UnhashedToken = "rotated"
return true, nil
return true, userToken, nil
}
maxAge := int(sc.cfg.LoginMaxLifetime.Seconds())

View File

@@ -106,7 +106,7 @@ func UAEnabled(ctx context.Context) bool {
return enabled
}
func (e *DashAlertExtractorService) getAlertFromPanels(ctx context.Context, jsonWithPanels *simplejson.Json, validateAlertFunc func(*models.Alert) bool, logTranslationFailures bool, dashAlertInfo DashAlertInfo) ([]*models.Alert, error) {
func (e *DashAlertExtractorService) getAlertFromPanels(ctx context.Context, jsonWithPanels *simplejson.Json, validateAlertFunc func(*models.Alert) error, logTranslationFailures bool, dashAlertInfo DashAlertInfo) ([]*models.Alert, error) {
ret := make([]*models.Alert, 0)
for _, panelObj := range jsonWithPanels.Get("panels").MustArray() {
@@ -238,8 +238,8 @@ func (e *DashAlertExtractorService) getAlertFromPanels(ctx context.Context, json
return nil, err
}
if !validateAlertFunc(alert) {
return nil, ValidationError{Reason: fmt.Sprintf("Panel id is not correct, alertName=%v, panelId=%v", alert.Name, alert.PanelId)}
if err := validateAlertFunc(alert); err != nil {
return nil, err
}
ret = append(ret, alert)
@@ -248,8 +248,14 @@ func (e *DashAlertExtractorService) getAlertFromPanels(ctx context.Context, json
return ret, nil
}
func validateAlertRule(alert *models.Alert) bool {
return alert.ValidToSave()
func validateAlertRule(alert *models.Alert) error {
if !alert.ValidDashboardPanel() {
return ValidationError{Reason: fmt.Sprintf("Panel id is not correct, alertName=%v, panelId=%v", alert.Name, alert.PanelId)}
}
if !alert.ValidTags() {
return ValidationError{Reason: "Invalid tags, must be less than 100 characters"}
}
return nil
}
// GetAlerts extracts alerts from the dashboard json and does full validation on the alert json data.
@@ -257,7 +263,7 @@ func (e *DashAlertExtractorService) GetAlerts(ctx context.Context, dashAlertInfo
return e.extractAlerts(ctx, validateAlertRule, true, dashAlertInfo)
}
func (e *DashAlertExtractorService) extractAlerts(ctx context.Context, validateFunc func(alert *models.Alert) bool, logTranslationFailures bool, dashAlertInfo DashAlertInfo) ([]*models.Alert, error) {
func (e *DashAlertExtractorService) extractAlerts(ctx context.Context, validateFunc func(alert *models.Alert) error, logTranslationFailures bool, dashAlertInfo DashAlertInfo) ([]*models.Alert, error) {
dashboardJSON, err := copyJSON(dashAlertInfo.Dash.Data)
if err != nil {
return nil, err
@@ -294,8 +300,11 @@ func (e *DashAlertExtractorService) extractAlerts(ctx context.Context, validateF
// ValidateAlerts validates alerts in the dashboard json but does not require a valid dashboard id
// in the first validation pass.
func (e *DashAlertExtractorService) ValidateAlerts(ctx context.Context, dashAlertInfo DashAlertInfo) error {
_, err := e.extractAlerts(ctx, func(alert *models.Alert) bool {
return alert.OrgId != 0 && alert.PanelId != 0
_, err := e.extractAlerts(ctx, func(alert *models.Alert) error {
if alert.OrgId == 0 || alert.PanelId == 0 {
return errors.New("missing OrgId, PanelId or both")
}
return nil
}, false, dashAlertInfo)
return err
}

View File

@@ -92,8 +92,17 @@ type Alert struct {
Settings *simplejson.Json
}
func (a *Alert) ValidToSave() bool {
return a.DashboardId != 0 && a.OrgId != 0 && a.PanelId != 0
func (a *Alert) ValidDashboardPanel() bool {
return a.OrgId != 0 && a.DashboardId != 0 && a.PanelId != 0
}
func (a *Alert) ValidTags() bool {
for _, tag := range a.GetTagsFromSettings() {
if len(tag.Key) > 100 || len(tag.Value) > 100 {
return false
}
}
return true
}
func (a *Alert) ContainsUpdates(other *Alert) bool {

View File

@@ -62,7 +62,7 @@ type RevokeAuthTokenCmd struct {
type UserTokenService interface {
CreateToken(ctx context.Context, user *user.User, clientIP net.IP, userAgent string) (*UserToken, error)
LookupToken(ctx context.Context, unhashedToken string) (*UserToken, error)
TryRotateToken(ctx context.Context, token *UserToken, clientIP net.IP, userAgent string) (bool, error)
TryRotateToken(ctx context.Context, token *UserToken, clientIP net.IP, userAgent string) (bool, *UserToken, error)
RevokeToken(ctx context.Context, token *UserToken, soft bool) error
RevokeAllUserTokens(ctx context.Context, userId int64) error
GetUserToken(ctx context.Context, userId, userTokenId int64) (*UserToken, error)

View File

@@ -5,10 +5,13 @@ import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"net"
"strings"
"time"
"golang.org/x/sync/singleflight"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/remotecache"
@@ -41,6 +44,7 @@ func ProvideUserAuthTokenService(sqlStore db.DB,
log: log.New("auth"),
remoteCache: remoteCache,
features: features,
singleflight: new(singleflight.Group),
}
defaultLimits, err := readQuotaConfig(cfg)
@@ -68,6 +72,7 @@ type UserAuthTokenService struct {
log log.Logger
remoteCache *remotecache.RemoteCache
features *featuremgmt.FeatureManager
singleflight *singleflight.Group
}
func (s *UserAuthTokenService) CreateToken(ctx context.Context, user *user.User, clientIP net.IP, userAgent string) (*auth.UserToken, error) {
@@ -202,6 +207,7 @@ func (s *UserAuthTokenService) lookupToken(ctx context.Context, unhashedToken st
}
}
// Current incoming token is the previous auth token in the DB and the auth_token_seen is true
if model.AuthToken != hashedToken && model.PrevAuthToken == hashedToken && model.AuthTokenSeen {
modelCopy := model
modelCopy.AuthTokenSeen = false
@@ -229,6 +235,7 @@ func (s *UserAuthTokenService) lookupToken(ctx context.Context, unhashedToken st
}
}
// Current incoming token is not seen and it is the latest valid auth token in the db
if !model.AuthTokenSeen && model.AuthToken == hashedToken {
modelCopy := model
modelCopy.AuthTokenSeen = true
@@ -268,83 +275,102 @@ func (s *UserAuthTokenService) lookupToken(ctx context.Context, unhashedToken st
}
func (s *UserAuthTokenService) TryRotateToken(ctx context.Context, token *auth.UserToken,
clientIP net.IP, userAgent string) (bool, error) {
clientIP net.IP, userAgent string) (bool, *auth.UserToken, error) {
if token == nil {
return false, nil
return false, nil, nil
}
model, err := userAuthTokenFromUserToken(token)
if err != nil {
return false, err
return false, nil, err
}
now := getTime()
var needsRotation bool
rotatedAt := time.Unix(model.RotatedAt, 0)
if model.AuthTokenSeen {
needsRotation = rotatedAt.Before(now.Add(-time.Duration(s.cfg.TokenRotationIntervalMinutes) * time.Minute))
} else {
needsRotation = rotatedAt.Before(now.Add(-urgentRotateTime))
type rotationResult struct {
rotated bool
newToken *auth.UserToken
}
if !needsRotation {
return false, nil
}
ctxLogger := s.log.FromContext(ctx)
ctxLogger.Debug("token needs rotation", "tokenId", model.Id, "authTokenSeen", model.AuthTokenSeen, "rotatedAt", rotatedAt)
clientIPStr := clientIP.String()
if len(clientIP) == 0 {
clientIPStr = ""
}
newToken, err := util.RandomHex(16)
if err != nil {
return false, err
}
hashedToken := hashToken(newToken)
// very important that auth_token_seen is set after the prev_auth_token = case when ... for mysql to function correctly
sql := `
UPDATE user_auth_token
SET
seen_at = 0,
user_agent = ?,
client_ip = ?,
prev_auth_token = case when auth_token_seen = ? then auth_token else prev_auth_token end,
auth_token = ?,
auth_token_seen = ?,
rotated_at = ?
WHERE id = ? AND (auth_token_seen = ? OR rotated_at < ?)`
var affected int64
err = s.sqlStore.WithTransactionalDbSession(ctx, func(dbSession *db.Session) error {
res, err := dbSession.Exec(sql, userAgent, clientIPStr, s.sqlStore.GetDialect().BooleanStr(true), hashedToken,
s.sqlStore.GetDialect().BooleanStr(false), now.Unix(), model.Id, s.sqlStore.GetDialect().BooleanStr(true),
now.Add(-30*time.Second).Unix())
if err != nil {
return err
rotResult, err, _ := s.singleflight.Do(fmt.Sprint(model.Id), func() (interface{}, error) {
var needsRotation bool
rotatedAt := time.Unix(model.RotatedAt, 0)
if model.AuthTokenSeen {
needsRotation = rotatedAt.Before(now.Add(-time.Duration(s.cfg.TokenRotationIntervalMinutes) * time.Minute))
} else {
needsRotation = rotatedAt.Before(now.Add(-urgentRotateTime))
}
affected, err = res.RowsAffected()
return err
if !needsRotation {
return &rotationResult{rotated: false}, nil
}
ctxLogger := s.log.FromContext(ctx)
ctxLogger.Debug("token needs rotation", "tokenId", model.Id, "authTokenSeen", model.AuthTokenSeen, "rotatedAt", rotatedAt)
clientIPStr := clientIP.String()
if len(clientIP) == 0 {
clientIPStr = ""
}
newToken, err := util.RandomHex(16)
if err != nil {
return nil, err
}
hashedToken := hashToken(newToken)
// very important that auth_token_seen is set after the prev_auth_token = case when ... for mysql to function correctly
sql := `
UPDATE user_auth_token
SET
seen_at = 0,
user_agent = ?,
client_ip = ?,
prev_auth_token = case when auth_token_seen = ? then auth_token else prev_auth_token end,
auth_token = ?,
auth_token_seen = ?,
rotated_at = ?
WHERE id = ? AND (auth_token_seen = ? OR rotated_at < ?)`
var affected int64
err = s.sqlStore.WithTransactionalDbSession(ctx, func(dbSession *db.Session) error {
res, err := dbSession.Exec(sql, userAgent, clientIPStr, s.sqlStore.GetDialect().BooleanStr(true), hashedToken,
s.sqlStore.GetDialect().BooleanStr(false), now.Unix(), model.Id, s.sqlStore.GetDialect().BooleanStr(true),
now.Add(-30*time.Second).Unix())
if err != nil {
return err
}
affected, err = res.RowsAffected()
return err
})
if err != nil {
return nil, err
}
if affected > 0 {
ctxLogger.Debug("auth token rotated", "affected", affected, "auth_token_id", model.Id, "userId", model.UserId)
model.UnhashedToken = newToken
var result auth.UserToken
if err := model.toUserToken(&result); err != nil {
return nil, err
}
return &rotationResult{
rotated: true,
newToken: &result,
}, nil
}
return &rotationResult{rotated: false}, nil
})
if err != nil {
return false, err
return false, nil, err
}
ctxLogger.Debug("auth token rotated", "affected", affected, "auth_token_id", model.Id, "userId", model.UserId)
if affected > 0 {
model.UnhashedToken = newToken
if err := model.toUserToken(token); err != nil {
return false, err
}
return true, nil
}
result := rotResult.(*rotationResult)
return false, nil
return result.rotated, result.newToken, nil
}
func (s *UserAuthTokenService) RevokeToken(ctx context.Context, token *auth.UserToken, soft bool) error {

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/stretchr/testify/require"
"golang.org/x/sync/singleflight"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
@@ -178,7 +179,7 @@ func TestUserAuthToken(t *testing.T) {
getTime = func() time.Time { return now.Add(time.Hour) }
rotated, err := ctx.tokenService.TryRotateToken(context.Background(), userToken,
rotated, _, err := ctx.tokenService.TryRotateToken(context.Background(), userToken,
net.ParseIP("192.168.10.11"), "some user agent")
require.Nil(t, err)
require.True(t, rotated)
@@ -262,7 +263,7 @@ func TestUserAuthToken(t *testing.T) {
prevToken := userToken.AuthToken
unhashedPrev := userToken.UnhashedToken
rotated, err := ctx.tokenService.TryRotateToken(context.Background(), userToken,
rotated, _, err := ctx.tokenService.TryRotateToken(context.Background(), userToken,
net.ParseIP("192.168.10.12"), "a new user agent")
require.Nil(t, err)
require.False(t, rotated)
@@ -280,12 +281,12 @@ func TestUserAuthToken(t *testing.T) {
getTime = func() time.Time { return now.Add(time.Hour) }
rotated, err = ctx.tokenService.TryRotateToken(context.Background(), &tok,
rotated, newToken, err := ctx.tokenService.TryRotateToken(context.Background(), &tok,
net.ParseIP("192.168.10.12"), "a new user agent")
require.Nil(t, err)
require.True(t, rotated)
unhashedToken := tok.UnhashedToken
unhashedToken := newToken.UnhashedToken
model, err = ctx.getAuthTokenByID(tok.Id)
require.Nil(t, err)
@@ -326,7 +327,7 @@ func TestUserAuthToken(t *testing.T) {
require.NotNil(t, lookedUpModel)
require.False(t, lookedUpModel.AuthTokenSeen)
rotated, err = ctx.tokenService.TryRotateToken(context.Background(), userToken,
rotated, _, err = ctx.tokenService.TryRotateToken(context.Background(), userToken,
net.ParseIP("192.168.10.12"), "a new user agent")
require.Nil(t, err)
require.True(t, rotated)
@@ -351,7 +352,7 @@ func TestUserAuthToken(t *testing.T) {
getTime = func() time.Time { return now.Add(10 * time.Minute) }
prevToken := userToken.UnhashedToken
rotated, err := ctx.tokenService.TryRotateToken(context.Background(), userToken,
rotated, _, err := ctx.tokenService.TryRotateToken(context.Background(), userToken,
net.ParseIP("1.1.1.1"), "firefox")
require.Nil(t, err)
require.True(t, rotated)
@@ -407,7 +408,7 @@ func TestUserAuthToken(t *testing.T) {
return now.Add(10 * time.Minute)
}
rotated, err := ctx.tokenService.TryRotateToken(context.Background(), userToken,
rotated, _, err := ctx.tokenService.TryRotateToken(context.Background(), userToken,
net.ParseIP("1.1.1.1"), "firefox")
require.Nil(t, err)
require.True(t, rotated)
@@ -429,7 +430,7 @@ func TestUserAuthToken(t *testing.T) {
return now.Add(20 * time.Minute)
}
rotated, err = ctx.tokenService.TryRotateToken(context.Background(), userToken,
rotated, _, err = ctx.tokenService.TryRotateToken(context.Background(), userToken,
net.ParseIP("1.1.1.1"), "firefox")
require.Nil(t, err)
require.True(t, rotated)
@@ -456,7 +457,7 @@ func TestUserAuthToken(t *testing.T) {
return now.Add(2 * time.Minute)
}
rotated, err := ctx.tokenService.TryRotateToken(context.Background(), userToken,
rotated, _, err := ctx.tokenService.TryRotateToken(context.Background(), userToken,
net.ParseIP("1.1.1.1"), "firefox")
require.Nil(t, err)
require.True(t, rotated)
@@ -550,9 +551,10 @@ func createTestContext(t *testing.T) *testContext {
}
tokenService := &UserAuthTokenService{
sqlStore: sqlstore,
cfg: cfg,
log: log.New("test-logger"),
sqlStore: sqlstore,
cfg: cfg,
log: log.New("test-logger"),
singleflight: new(singleflight.Group),
}
return &testContext{

View File

@@ -15,7 +15,7 @@ import (
type FakeUserAuthTokenService struct {
CreateTokenProvider func(ctx context.Context, user *user.User, clientIP net.IP, userAgent string) (*auth.UserToken, error)
TryRotateTokenProvider func(ctx context.Context, token *auth.UserToken, clientIP net.IP, userAgent string) (bool, error)
TryRotateTokenProvider func(ctx context.Context, token *auth.UserToken, clientIP net.IP, userAgent string) (bool, *auth.UserToken, error)
LookupTokenProvider func(ctx context.Context, unhashedToken string) (*auth.UserToken, error)
RevokeTokenProvider func(ctx context.Context, token *auth.UserToken, soft bool) error
RevokeAllUserTokensProvider func(ctx context.Context, userId int64) error
@@ -34,8 +34,8 @@ func NewFakeUserAuthTokenService() *FakeUserAuthTokenService {
UnhashedToken: "",
}, nil
},
TryRotateTokenProvider: func(ctx context.Context, token *auth.UserToken, clientIP net.IP, userAgent string) (bool, error) {
return false, nil
TryRotateTokenProvider: func(ctx context.Context, token *auth.UserToken, clientIP net.IP, userAgent string) (bool, *auth.UserToken, error) {
return false, nil, nil
},
LookupTokenProvider: func(ctx context.Context, unhashedToken string) (*auth.UserToken, error) {
return &auth.UserToken{
@@ -79,7 +79,7 @@ func (s *FakeUserAuthTokenService) LookupToken(ctx context.Context, unhashedToke
}
func (s *FakeUserAuthTokenService) TryRotateToken(ctx context.Context, token *auth.UserToken, clientIP net.IP,
userAgent string) (bool, error) {
userAgent string) (bool, *auth.UserToken, error) {
return s.TryRotateTokenProvider(context.Background(), token, clientIP, userAgent)
}

View File

@@ -107,13 +107,14 @@ func (s *Session) RefreshTokenHook(ctx context.Context, identity *authn.Identity
s.log.Debug("failed to get client IP address", "addr", addr, "err", err)
ip = nil
}
rotated, err := s.sessionService.TryRotateToken(ctx, identity.SessionToken, ip, userAgent)
rotated, newToken, err := s.sessionService.TryRotateToken(ctx, identity.SessionToken, ip, userAgent)
if err != nil {
s.log.Error("failed to rotate token", "error", err)
return
}
if rotated {
identity.SessionToken = newToken
s.log.Debug("rotated session token", "user", identity.ID)
maxAge := int(s.loginMaxLifetime.Seconds())

View File

@@ -143,9 +143,9 @@ func (f *fakeResponseWriter) WriteHeader(statusCode int) {
func TestSession_RefreshHook(t *testing.T) {
s := ProvideSession(&authtest.FakeUserAuthTokenService{
TryRotateTokenProvider: func(ctx context.Context, token *auth.UserToken, clientIP net.IP, userAgent string) (bool, error) {
TryRotateTokenProvider: func(ctx context.Context, token *auth.UserToken, clientIP net.IP, userAgent string) (bool, *auth.UserToken, error) {
token.UnhashedToken = "new-token"
return true, nil
return true, token, nil
},
}, &usertest.FakeUserService{}, "grafana-session", 20*time.Second)

View File

@@ -11,6 +11,8 @@ import (
"strings"
"time"
"golang.org/x/sync/singleflight"
"github.com/grafana/grafana/pkg/components/apikeygen"
apikeygenprefix "github.com/grafana/grafana/pkg/components/apikeygenprefixed"
"github.com/grafana/grafana/pkg/infra/db"
@@ -70,6 +72,7 @@ func ProvideService(cfg *setting.Cfg, tokenService auth.UserTokenService, jwtSer
oauthTokenService: oauthTokenService,
features: features,
authnService: authnService,
singleflight: new(singleflight.Group),
}
}
@@ -91,6 +94,7 @@ type ContextHandler struct {
oauthTokenService oauthtoken.OAuthTokenService
features *featuremgmt.FeatureManager
authnService authn.Service
singleflight *singleflight.Group
// GetTime returns the current time.
// Stubbable by tests.
GetTime func() time.Time
@@ -568,15 +572,15 @@ func (h *ContextHandler) rotateEndOfRequestFunc(reqContext *contextmodel.ReqCont
ip = nil
}
// FIXME (jguer): rotation should return a new token instead of modifying the existing one.
rotated, err := h.AuthTokenService.TryRotateToken(ctx, reqContext.UserToken, ip, reqContext.Req.UserAgent())
rotated, newToken, err := h.AuthTokenService.TryRotateToken(ctx, reqContext.UserToken, ip, reqContext.Req.UserAgent())
if err != nil {
reqContext.Logger.Error("Failed to rotate token", "error", err)
return
}
if rotated {
cookies.WriteSessionCookie(reqContext, h.Cfg, reqContext.UserToken.UnhashedToken, h.Cfg.LoginMaxLifetime)
reqContext.UserToken = newToken
cookies.WriteSessionCookie(reqContext, h.Cfg, newToken.UnhashedToken, h.Cfg.LoginMaxLifetime)
}
}
}

View File

@@ -24,9 +24,9 @@ func TestDontRotateTokensOnCancelledRequests(t *testing.T) {
tryRotateCallCount := 0
ctxHdlr.AuthTokenService = &authtest.FakeUserAuthTokenService{
TryRotateTokenProvider: func(ctx context.Context, token *auth.UserToken, clientIP net.IP,
userAgent string) (bool, error) {
userAgent string) (bool, *auth.UserToken, error) {
tryRotateCallCount++
return false, nil
return false, nil, nil
},
}
@@ -46,11 +46,11 @@ func TestTokenRotationAtEndOfRequest(t *testing.T) {
ctxHdlr := getContextHandler(t)
ctxHdlr.AuthTokenService = &authtest.FakeUserAuthTokenService{
TryRotateTokenProvider: func(ctx context.Context, token *auth.UserToken, clientIP net.IP,
userAgent string) (bool, error) {
userAgent string) (bool, *auth.UserToken, error) {
newToken, err := util.RandomHex(16)
require.NoError(t, err)
token.AuthToken = newToken
return true, nil
return true, token, nil
},
}

View File

@@ -5,9 +5,8 @@ package dashboards
import (
context "context"
mock "github.com/stretchr/testify/mock"
folder "github.com/grafana/grafana/pkg/services/folder"
mock "github.com/stretchr/testify/mock"
)
// FakeDashboardService is an autogenerated mock type for the DashboardService type

View File

@@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/expr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -737,7 +738,7 @@ func insertTestRule(t *testing.T, sqlStore db.DB, foderOrgID int64, folderUID st
Data: []alertQuery{
{
RefID: "A",
DatasourceUID: "-100",
DatasourceUID: expr.DatasourceUID,
Model: json.RawMessage(`{
"type": "math",
"expression": "2 + 3 > 1"

View File

@@ -5,10 +5,11 @@ package dashboards
import (
context "context"
folder "github.com/grafana/grafana/pkg/services/folder"
mock "github.com/stretchr/testify/mock"
models "github.com/grafana/grafana/pkg/services/alerting/models"
folder "github.com/grafana/grafana/pkg/services/folder"
quota "github.com/grafana/grafana/pkg/services/quota"
)

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