Compare commits

...

139 Commits

Author SHA1 Message Date
Will Browne
6134e3cf35 Merge pull request #320 from grafana/bump-version-8.5.0
Release: Bump version to 8.5.0
2022-04-21 12:36:58 +02:00
grafanabot
c7bc0bd29d "Release: Updated versions in package to 8.5.0" 2022-04-21 10:34:49 +00:00
Grot (@grafanabot)
ad35db8636 Service accounts: rename feature toggle (#48037) (#48040)
* refactor: renaming service-accounts to serviceAccounts

* refactor: renaming service-accounts to serviceAccounts in docs

* tests

(cherry picked from commit 8677552dda)

Co-authored-by: Eric Leijonmarck <eric.leijonmarck@gmail.com>
2022-04-21 11:53:32 +02:00
Grot (@grafanabot)
c75be3bb3e Navigation: Fixes issue with menu closing when hovering back to the trigger (#47992) (#48038) 2022-04-21 10:35:41 +01:00
Grot (@grafanabot)
f368cac796 Prometheus: Highlight operations added in the query builder (#47961) (#48033)
* Highlight newly added operations

* Better diff for the operations change

* Changed the highlight style

(cherry picked from commit ff5aef194c)

Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
2022-04-21 10:39:41 +02:00
Grot (@grafanabot)
a228985d97 Alerting: Add integration test for AddDashAlertMigration (#47730) (#48019)
Adds tests for: 
what circumstances should trigger alert migration from legacy alerting to unified alerting. 
the execution of the migration itself.

Co-authored-by: gotjosh <josue.abreu@gmail.com>
Co-authored-by: Matthew Jacobson <matthew.jacobson@grafana.com>
2022-04-21 13:38:31 +08:00
Grot (@grafanabot)
feaf32f04c TimeSeries: clamp max bar width to 200px (#48021) (#48024)
(cherry picked from commit 5c3be630f2)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
2022-04-21 07:22:16 +02:00
Grot (@grafanabot)
447752c00d Docs: Adding What's New for the 8.5 release (#47995) (#48005)
* initial commit

* more changes

* more changes

* run prettier check

(cherry picked from commit b034bd18b5)

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
2022-04-20 16:20:40 -04:00
Grot (@grafanabot)
d300a27ee7 Comments: fix wrong SQL query generated if Settings are not empty (#47552) (#47999)
(cherry picked from commit ce89d7e874)

Co-authored-by: Alexander Emelin <frvzmb@gmail.com>
2022-04-20 13:00:37 -07:00
Grot (@grafanabot)
301be18d86 Use trialExpiry for determining if a user is on trial (#47975) (#47998)
(cherry picked from commit 6c1994cb86)

Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
2022-04-20 20:54:14 +02:00
Grot (@grafanabot)
644b23aba2 Alerting: Fix navlinks for legacy alerting (#47972) (#47984)
* split building legacy and ng alert nav links.

(cherry picked from commit c1f766a374)

Co-authored-by: Yuriy Tseretyan <yuriy.tseretyan@grafana.com>
2022-04-20 18:26:11 +02:00
Grot (@grafanabot)
da335ce32e Fix: Parse exemplars before parsing heatmap data (#47463) (#47469)
* parse exemplars before parsing the heatmap. Exemplars are overlayed. Not part of the main heatmap

* added tests

(cherry picked from commit 222325c7f1)

Co-authored-by: Stephanie Closson <srclosson@gmail.com>
2022-04-20 12:59:21 -03:00
Grot (@grafanabot)
7065ee20ee Alerting: Update RBAC documentation with information about alerting (#47858) (#47982)
Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
(cherry picked from commit c1ec122f75)

Co-authored-by: Yuriy Tseretyan <yuriy.tseretyan@grafana.com>
2022-04-20 11:48:44 -04:00
Grot (@grafanabot)
5c64c3559d Transformations: Support escaped characters in key-value pair parsing (#47901) (#47973)
* Additional logic to handle quoted values

* Simple test with quoted values that include spaces

* Update test description

* Updating logic to account for nested quotes

- Adding additional test for nested quotes

* Strip out line breaks and carriage returns pre-processing

* Fix typo in test result

* Update key-value logic to avoid regexp

- Minor changes to account for null values

* Correct escaping on test

* Additional tests

- Test for null values
- Test for nested separator characters
- Update quoting

(cherry picked from commit d0b41f882e)

Co-authored-by: Andreas Christou <andreas.christou@grafana.com>
2022-04-20 16:37:55 +01:00
Grot (@grafanabot)
e9c13f824c Alerting: Notification URL points to alert view page instead of alert edit page (#47752) (#47960)
Before this change, notifications generated by the Grafana Alertmanager
pointed to '/alerting/:ruleID/edit'. This change instead points them to
the view path '/alerting/grafana/:ruleID/view'. The view page has a
better UX, including timeseries display. It's also where many alert
state improvements will land in the next few versions of Grafana.

Fixes #45301

Signed-off-by: Joe Blubaugh <joe.blubaugh@grafana.com>
(cherry picked from commit 3d91047e6e)

Co-authored-by: Joe Blubaugh <joe.blubaugh@grafana.com>
2022-04-20 17:32:26 +02:00
Grot (@grafanabot)
c0e157300b DashboardPage: Remember scroll position when coming back panel edit / view panel (#47639) (#47792)
* DashboardPage: Remember scroll position when coming back panel edit / view panel

* Use scollElement callback

* Fixed ts issue

(cherry picked from commit a126c07e54)

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2022-04-20 17:25:40 +02:00
Grot (@grafanabot)
9c1c446b44 UserListAdminPage: Reset page when changing filters (#47958) (#47976)
(cherry picked from commit e19e934178)

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
2022-04-20 16:20:56 +01:00
Grot (@grafanabot)
d2feeb8455 PanelQueryRunner: use refId from results if the key value was not set in the packet (#47598) (#47647)
(cherry picked from commit f80a0d2a9b)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2022-04-20 19:14:57 +04:00
Grot (@grafanabot)
f08c5a796c Modify the Server Error scenario to return frontend errors (#47157) (#47425)
(cherry picked from commit 41642fd5f4)

Co-authored-by: Josh Hunt <joshhunt@users.noreply.github.com>
2022-04-20 16:11:23 +01:00
Grot (@grafanabot)
690c08b80d Chore: Bump minimist to 1.2.6 (#47401) (#47681)
(cherry picked from commit b8e3c77adb)

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
2022-04-20 17:01:52 +02:00
Grot (@grafanabot)
1bd1838363 Alerting: adds variable replacement to panel filters (#47962) (#47967)
(cherry picked from commit 459c64fd44)

Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
2022-04-20 16:58:41 +02:00
Grot (@grafanabot)
b37047eadb Chore: Try to fix flaky reverse proxy test (#47957) (#47966)
(cherry picked from commit 0afc542998)

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
2022-04-20 16:38:41 +02:00
Grot (@grafanabot)
9841f1d488 ReleaseNotes: Updated changelog and release notes for 8.4.7 (#47952) (#47955)
(cherry picked from commit 6eb41f9cb3)
2022-04-20 15:33:16 +02:00
Grot (@grafanabot)
7b7f7facca Folders: Fix flaky test (#47953) (#47954)
(cherry picked from commit db8d85e2f2)

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
2022-04-20 15:14:25 +02:00
Grot (@grafanabot)
52fe9ba3fe Wrap individual meta items in zIndex: 0 (#47935) (#47941)
(cherry picked from commit e8fc6637ec)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2022-04-20 14:38:51 +02:00
Grot (@grafanabot)
f7683af8b5 Prometheus: Remove running of query on raw query toggle (#47938) (#47939)
(cherry picked from commit 5c4459a723)

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
2022-04-20 14:37:54 +02:00
Grot (@grafanabot)
0508656182 Prometheus: Move count_values from function to aggregation (#47260) (#47645)
* Move count_values from function to aggregation

* Fix typos

* Fix loki operations

* Fix error that change the aggregation variant on blur

* Fix loki ops

(cherry picked from commit 95009995e4)

Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
2022-04-20 14:30:57 +02:00
Grot (@grafanabot)
0a4b6dccfc Feature Highlights: update upgrade components UI (#47885) (#47948)
* Highlights: add action prop

* Highlight team sync for trial users

* Add badges for trial highlights

* Move events to UpgradeBox

* Fix undefined license settings

* Update snapshot

* Update public/app/features/datasources/state/navModel.ts

Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>

* Update public/app/features/datasources/state/navModel.ts

Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>

* Update public/app/features/datasources/state/navModel.ts

Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>

* Update copy and event handling

Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>
(cherry picked from commit 9c0aa09a85)

Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
2022-04-20 14:24:51 +02:00
Grot (@grafanabot)
f682ad8c60 Prometheus/Loki: Fixes the query type option when multiple queries present (#47846) (#47944)
(cherry picked from commit 68aac0bd90)

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2022-04-20 14:17:38 +02:00
Grot (@grafanabot)
4ea9193696 Alerting: grafana managed group names (#47785) (#47929)
(cherry picked from commit be3f52abb1)

Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
2022-04-20 13:12:50 +02:00
Grot (@grafanabot)
ffab9f6587 Library panels: Fix issue where query editor options wouldn't be updated (#47242) (#47421)
Closes #47241

(cherry picked from commit 0cff2d5980)

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
2022-04-20 11:19:04 +01:00
Grot (@grafanabot)
37b762fe9c Don't set autofocus on logarithmic base select (#47927) (#47933)
(cherry picked from commit 8b4b57a6c6)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2022-04-20 12:18:37 +02:00
kay delaney
45d8bbba3a Dashboards: Defer loading of plugin exports until panel is visible (#47361) (#47932)
(cherry picked from commit 8ae5dd74e6)
2022-04-20 11:18:27 +01:00
Grot (@grafanabot)
78659b6814 Explore: minor copy changes (#47922) (#47928)
* Explore: minor copy changes

* fix tests

(cherry picked from commit c1490a464a)

Co-authored-by: Giordano Ricci <me@giordanoricci.com>
2022-04-20 10:57:36 +01:00
Grot (@grafanabot)
e59f21a22e Frontend: Add notification persistence behind feature flag (#47871) (#47931)
(cherry picked from commit c48d8d1d48)

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
2022-04-20 11:57:15 +02:00
Grot (@grafanabot)
f62e76b5d5 Export: Explicitly include default datasources in exported dashboard json (#47244) (#47924)
* Export: Explicitly include default datasources in exported dashboard json

(cherry picked from commit f9f4a4cbf6)

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
2022-04-20 10:48:19 +01:00
Grot (@grafanabot)
5ba2aafa88 Revert "DataLinks: Encode variable params for data link URLs (#46100)" (#47763) (#47926)
This reverts commit b47f5433d7.

(cherry picked from commit ff60f39e96)

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
2022-04-20 11:45:54 +02:00
Grot (@grafanabot)
b53f5675f4 check that the user has RBAC permissions to save dashboard annotation (#47882) (#47920)
(cherry picked from commit 1588cd393a)

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2022-04-20 10:49:32 +02:00
Grot (@grafanabot)
5de766a202 Update API Keys UI to adjust based on users permissions (#47802) (#47921)
* Update API Keys UI to adjust based on users permissions

Since API Keys support now RBAC we need to ensure that UI
is adjusted based on the user permissions.

* Applying PR suggestions

(cherry picked from commit cbd2d09d70)

Co-authored-by: Vardan Torosyan <vardants@gmail.com>
2022-04-20 10:45:54 +02:00
Grot (@grafanabot)
8888dac836 Alerting: Fix issue with Slack contact point validation (#47559) (#47919)
Co-authored-by: gillesdemey <gilles.de.mey@gmail.com>
(cherry picked from commit 39d3c8afd7)

Co-authored-by: Peter Holmberg <peterholmberg@users.noreply.github.com>
2022-04-20 10:18:41 +02:00
Grot (@grafanabot)
17c51dcd32 AzureMonitor: Update kusto-monaco package (#47897) (#47916)
(cherry picked from commit 70802447a5)

Co-authored-by: Andres Martinez Gotor <andres.mgotor@gmail.com>
2022-04-20 09:41:15 +02:00
Grot (@grafanabot)
482ca1b0f7 Chore: Update Go version (#47914) (#47915)
* Remove makefile (it's not used anymore)

* Directly download and install nodejs package

The Packages file for deb.nodesource.com/node_16.x doesn't list older
versions, so only the most recent version of nodejs is available. We
don't want to require every update to the build container to have to
update nodejs if a never version is available, so I manually download
the deb and install it with dpkg.

* Update Go version

* Update lib.star

* Generate new .drone.yml

(cherry picked from commit 24038d9985)

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
2022-04-20 09:30:59 +02:00
Grot (@grafanabot)
cd66d6cdc4 TimeSeries: always try to detect & fix reverse-sorted time field (#47906) (#47910)
(cherry picked from commit aa98123e72)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
2022-04-20 06:36:52 +02:00
Grot (@grafanabot)
548145691b Timeseries: add zoom to data button (#47862) (#47908)
(cherry picked from commit f4e285b8b4)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2022-04-19 18:27:13 -07:00
Grot (@grafanabot)
aa5bc10bcf Instrumentation: Proxy status code correction and various improvements (#47473) (#47903)
For a proxied request, e.g. Grafana's datasource or plugin proxy:
If the request is cancelled, e.g. from the browser, the HTTP status code is
now 499 Client closed request instead of 502 Bad gateway.
If the request times out, e.g. takes longer time than allowed, the HTTP status
code is now 504 Gateway timeout instead of 502 Bad gateway.
This also means that request metrics and logs will get their status codes
adjusted according to above.

Fixes #46337
Fixes #46338

(cherry picked from commit 4bc582570e)

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
2022-04-19 20:41:57 +02:00
Grot (@grafanabot)
39cab9c066 Alerting: FGAC bug bash fixes (#47873) (#47900)
* Improve new alert and new silence buttons permission handling

* Prevent loading alert rules when no sufficient permissions provided

* Improve add and edit rule permissions

* Add new rule CTA button for non-editors

* Update mock

* Fix imports

(cherry picked from commit 785145c045)

Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
2022-04-19 18:55:25 +02:00
Grot (@grafanabot)
bc9621699b Alerting: Fix nav-links for RBAC and other (#47798) (#47895)
(cherry picked from commit 0c31399e34)

Co-authored-by: Yuriy Tseretyan <yuriy.tseretyan@grafana.com>
2022-04-19 12:12:55 -04:00
Grot (@grafanabot)
2dbe8dfcbc AzureMonitor: Fix Microsoft.ClassicCompute/domainNames namespace (#47877) (#47888)
(cherry picked from commit 2d8d9bc137)

Co-authored-by: Andres Martinez Gotor <andres.mgotor@gmail.com>
2022-04-19 16:36:57 +02:00
Grot (@grafanabot)
73a0da54e4 Alerting: add confirmation modal for deleting notification policies (#47819) (#47886)
(cherry picked from commit 69eb6efd6e)

Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
2022-04-19 16:07:05 +02:00
Grot (@grafanabot)
c724639f56 Alerting: Display alert instances instead of alert rules when creating silence (#47396) (#47881)
* modify matchers util for instances

* filter alerts

* change label to include instances

* re add empty case

(cherry picked from commit 4570615afc)

Co-authored-by: Peter Holmberg <peterholmberg@users.noreply.github.com>
2022-04-19 15:28:05 +02:00
Grot (@grafanabot)
1bffc1f4ad prevent horizontal scrollbar on firefox during expand animation (#47758) (#47869)
(cherry picked from commit bb5f77703c)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2022-04-19 11:49:03 +02:00
Grot (@grafanabot)
7f4a77fb75 Prometheus: Add documentation for the query builder (#47324) (#47868)
* Update documentation for the query builder

* Review feedback

* Update docs and add pictures

(cherry picked from commit 378ce4f685)

Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
2022-04-19 11:31:26 +02:00
Grot (@grafanabot)
8bb7c88e07 Alerting: Sort StateHistoryItem after fetch instead of on render. (#47842) (#47864)
PR #47674 attempted to sort a read-only managed async array. This change
moves the sort logic to the fetch code so sort happens once on fetch, to
a mutable array, rather than trying on each render for an immutable
array.

Signed-off-by: Joe Blubaugh <joe.blubaugh@grafana.com>
(cherry picked from commit 7d5cb170c6)

Co-authored-by: Joe Blubaugh <joe.blubaugh@grafana.com>
2022-04-19 16:22:35 +08:00
Grot (@grafanabot)
190766fed1 Create fixed roles for reading API Keys and service accounts and fix listing of service account tokens (#47767) (#47777)
* Create fixed roles for reading API Keys and service accounts

* Handle PR comments and fix the listing of token

(cherry picked from commit 782ec05d8c)

Co-authored-by: Vardan Torosyan <vardants@gmail.com>
2022-04-18 20:34:12 +02:00
Grot (@grafanabot)
8dd16fb871 CloudWatch: Run query on blur in logs query field (#47454) (#47855)
* Cloudwatch: add test to ensure RunQuery is fired onBlur of LogQuery

Also remove the `onBlur` prop for the `QueryField` in `LogsQueryField` since `QueryField` is configured to re-run
queries `onBlur` by default.

Co-authored-by: Sarah Zinger <sarah.zinger@grafana.com>
Co-authored-by: Isabella Siu <isabella.siu@grafana.com>
Co-authored-by: Adam Simpson <adam@adamsimpson.net>
(cherry picked from commit 4f26129aa4)

Co-authored-by: Shirley <4163034+fridgepoet@users.noreply.github.com>
2022-04-18 20:22:10 +02:00
Grot (@grafanabot)
2250c245ef Update grabpl (#47824) (#47825)
(cherry picked from commit 4510519941)

Co-authored-by: Dimitris Sotirakis <sotirakis.dim@gmail.com>
2022-04-15 16:12:44 +02:00
Grot (@grafanabot)
ee066425ee Docs: Addition of more prominent link to docker conf (#46504) (#47822)
* Docs: Addition of more prominent link to docker conf

Saw a comment on Reddit indicating a lack of information - docs were available, just a little hard to find.

Added a note pointing our community to where they might find docker image configuration help.

* Update docs/sources/installation/docker.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
(cherry picked from commit 67ff5f5815)

Co-authored-by: Petros Kolyvas <code@petros.io>
2022-04-15 09:26:18 -04:00
Grot (@grafanabot)
c8987a040c Navigation: Add shortcut to add new alert rule to alerting section behind feature toggle (#47713) (#47814)
* Add new alert rule to alerting section

* Check access control for ability to create

(cherry picked from commit 7905957ee8)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2022-04-15 11:43:55 +02:00
Grot (@grafanabot)
c8aeb049ab CI: Remove unused --github-token flag (#47773) (#47809)
* Remove github token flag

* Minor cleanup

* Update grabpl

(cherry picked from commit 508cc58a63)

Co-authored-by: Dimitris Sotirakis <sotirakis.dim@gmail.com>
2022-04-15 11:53:26 +03:00
Grot (@grafanabot)
c2a6188334 Service accounts: RBAC the service account UI (#47788) (#47804)
* WIP

* fix: bug for saving name did not remove edit

* refactor: better error msg

* Display the column Roles even when user can't see the role picker

* Remove spaces when building the search query request

* Disable Edit button and fix token addition and deletion

* Fix the error message text

Co-authored-by: Vardan Torosyan <vardants@gmail.com>
(cherry picked from commit b43e9b50b4)

Co-authored-by: Eric Leijonmarck <eric.leijonmarck@gmail.com>
2022-04-15 00:21:51 +02:00
Grot (@grafanabot)
00e572dc3b Table panel: Fix horizontal scrolling when pagination is enabled (#47776) (#47787) 2022-04-14 19:56:27 +02:00
Grot (@grafanabot)
c8327d04a8 Tracing: Use common traceID context value for opentracing and opentelemetry (#46411) (#47786)
* use common traceID context value for opentracing and opentelemetry

* support sampled trace IDs as well

* inject traceID into NormalResponse on errors

* Finally the test passed

* fix the test

* fix linter

* change the function parameter

Co-authored-by: Ying WANG <ying.wang@grafana.com>
(cherry picked from commit 41012af997)

Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com>
2022-04-14 18:08:51 +02:00
Grot (@grafanabot)
a0ff246fcb Alerting: Add FGAC to alerts tab in panel editor (#47732) (#47771)
* add FGAC for alert panel tab

* add test for panel tabs

* Refactor condition

Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
(cherry picked from commit c63086822d)

Co-authored-by: Nathan Rodman <nathanrodman@gmail.com>
2022-04-14 15:59:05 +02:00
Grot (@grafanabot)
05fb17c3f3 Prometheus: Query builder UX tweaks and feedback link (#47655) (#47778)
* Prometheus: Query builder UX tweaks and feedback link

* Remove .

* Fixed link

* added option to hide feedback links

* feedback link setting name change

* move config check

* fixed ts issue

(cherry picked from commit 057ff5bcf5)

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2022-04-14 15:40:57 +02:00
Grot (@grafanabot)
2a34264cdd Docs: remove outdated section from Explore docs (#47768) (#47775)
(cherry picked from commit f79e0c68cc)

Co-authored-by: Giordano Ricci <me@giordanoricci.com>
2022-04-14 09:04:53 -04:00
Grot (@grafanabot)
91f7cd2307 Alerting: unwrap upsert into insert and update function (#47731) (#47772)
* Alerting: unwrap upsert into insert and update function

* add changelog entry

* remove changelog entry

* rename upsertrule to updaterule

* use directly alertrule model for inserts

* add test for updating a rule with a conflicting name

(cherry picked from commit 060ccacbf9)

Co-authored-by: Jean-Philippe Quéméner <JohnnyQQQQ@users.noreply.github.com>
2022-04-14 14:37:54 +02:00
Dimitris Sotirakis
ff6a7b0a7f CI: Remove initialize step (#47622) (#47770)
* Remove initialize step from pr.star

* Remove initialize step from main.star

* Remove initialize step from release.star

* Reorder dependencies

* Re-add identify runner step

* Add enterprise steps

* Remove is_downstream variable from starlark logic (#47715)

* Make init steps depend on init-enterprise for enterprise pipelines

* Exclude shellcheck from enterprise pipelines

* Add init steps to enterprise integration tests pipeline

* Add dependency for windows enterprise step

* Update grabpl version

(cherry picked from commit 90892050d0)
2022-04-14 15:36:16 +03:00
Grot (@grafanabot)
5c41d84f88 Access control: expose SA frontend to users with the right permissions (#47727) (#47766)
* expose frontend to users with permissions

* cover the ui endpoints

* fix permissions

(cherry picked from commit e50bd5cac8)

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2022-04-14 14:13:30 +02:00
Grot (@grafanabot)
37e5b0fbc7 Alerting: Remove mis-behaving fake and fix masked test failure in AM config API (#47747) (#47751)
* Remove misbehaving fake

* Fix bug and inject logger

(cherry picked from commit c266a4ac81)

Co-authored-by: Alexander Weaver <weaver.alex.d@gmail.com>
2022-04-14 02:49:37 +02:00
Yuriy Tseretyan
0e8ba02479 Alerting: Support OK option for Error state (#47670) (#47741)
* support OK state for Error
* add CHANGELOG to ngalert package
2022-04-13 22:39:26 +02:00
Grot (@grafanabot)
21404f4169 Alerting: Provisioning API - Contact points (#47197) (#47743)
(cherry picked from commit 388ecb4037)

Co-authored-by: Jean-Philippe Quéméner <JohnnyQQQQ@users.noreply.github.com>
2022-04-13 22:28:20 +02:00
Grot (@grafanabot)
b8039a80b7 Live: collect more usage stats – enabled, ha enabled, num nodes, channels, subs (#47378) (#47738)
(cherry picked from commit e00db6a826)

Co-authored-by: Alexander Emelin <frvzmb@gmail.com>
2022-04-13 13:07:19 -07:00
Grot (@grafanabot)
e8e03d7b1f Alerting: Add check for datasource permission in alert rule read API (#47087) (#47595)
* add check for access to rule's data source in GET APIs

* use more general method GetAlertRules instead of GetNamespaceAlertRules.
* remove unused GetNamespaceAlertRules.

Tests:
* create a method to generate permissions for rules
* extract method to create RuleSrv
* add tests for RouteGetNamespaceRulesConfig

(cherry picked from commit af9353caec)

Co-authored-by: Yuriy Tseretyan <yuriy.tseretyan@grafana.com>
2022-04-13 15:54:24 -04:00
Grot (@grafanabot)
f01f1073b8 Chore: Remove bus.Bus field (#47695) (#47704)
* Chore: Remove bus.Bus field

* fix integration test

(cherry picked from commit e86b6662a1)

Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com>
2022-04-13 19:11:20 +02:00
Grot (@grafanabot)
5332a2db05 fix: bug where disabled didnt disable the use of service account (#47688) (#47725)
(cherry picked from commit 673a2ab49e)

Co-authored-by: Eric Leijonmarck <eric.leijonmarck@gmail.com>
2022-04-13 18:47:08 +02:00
Grot (@grafanabot)
67d42fc51c Access control: service account role check (#47710) (#47724)
* forbid setting role higher than user's role

* change response code

* can assign API key permissions to non-admin users

* add: assign viewer role directly upon creation

* refactor: add AddSATcommand infavor of AddAPIkey

* refactor: frontend fixes for ServiceAccountToken

Co-authored-by: eleijonmarck <eric.leijonmarck@gmail.com>
(cherry picked from commit a245531f0c)

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2022-04-13 17:27:23 +01:00
Grot (@grafanabot)
a8bfaafeb5 Alerting: FGAC for alert rule view and edit page (#47441) (#47717)
* Add permission check to the find route, add query not accessible warning for the view page

* Hide grafana or mimir rule buttons depending on user's permissions

* Add grafana and cloud read rules checking on the alert rules list view

* Improve missing data source handling, refactor edit and remove permissions handling

* Add tests for rule edit permissions

* PR feedback

(cherry picked from commit a30ab51550)

Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
2022-04-13 18:09:25 +02:00
Grot (@grafanabot)
7366fa2530 fix terminal format (#47718) (#47721)
(cherry picked from commit f62c261900)

Co-authored-by: ying-jeanne <74549700+ying-jeanne@users.noreply.github.com>
2022-04-13 17:57:20 +02:00
Grot (@grafanabot)
6aa95a6b86 TimeSeries & BarChart: refactor stacking (#47373) (#47720)
Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
(cherry picked from commit dfdfe3f428)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
2022-04-13 17:47:04 +02:00
Grot (@grafanabot)
9534cebdc1 Chore: Bump moment to 2.29.2 (#47680) (#47699)
* chore: bump moment to 2.29.2

* chore(yarn): clean up remaining moment nested deps

(cherry picked from commit c3db3121b0)

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
2022-04-13 16:19:29 +02:00
Grot (@grafanabot)
e8517ecf15 AzureMonitor: Include macros and template variables as suggestions (#47689) (#47708)
(cherry picked from commit a57716f868)

Co-authored-by: Andres Martinez Gotor <andres.mgotor@gmail.com>
2022-04-13 16:13:24 +02:00
Grot (@grafanabot)
59886859c0 Nav: Icon clickable area fills all available space (#47334) (#47706)
Closes #47255

(cherry picked from commit 38809d73c2)

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
2022-04-13 16:12:57 +02:00
Grot (@grafanabot)
5379941bf9 Alerting: Add permission checking to alert rules in the folders view (#47686) (#47692)
(cherry picked from commit d7516f11b3)

Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
2022-04-13 15:15:04 +02:00
Grot (@grafanabot)
ba4af4a7f6 Reload permissions cache when adding a datasource (#47658) (#47685)
(cherry picked from commit 463f00b93f)

Co-authored-by: Vardan Torosyan <vardants@gmail.com>
2022-04-13 12:34:36 +02:00
Serge Zaitsev
7729b14da3 Chore: Remove bus (#47511) (#47677)
* Chore: Remove bus

* remove unused const

(cherry picked from commit 18e93c7077)
2022-04-13 11:51:21 +02:00
Dimitris Sotirakis
691e94ebe4 CI: Remove unused downstream build pipelines (#47646) (#47652)
* Remove unused downstream build pipelines

* Prettify

(cherry picked from commit 3e030d137f)
2022-04-13 09:59:38 +01:00
Grot (@grafanabot)
5721ff9689 Chore: Remove bus from contexthandler (#47458) (#47636)
* Chore: remove bus from contexthandler

* remove bus from orgredirect

(cherry picked from commit 2cf88cfec8)

Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com>
2022-04-12 22:31:53 +02:00
Grot (@grafanabot)
d6b166223f Annotation FGAC checks for comments (#47468) (#47654)
* typo

* remove unwanted change

* remove unwanted change

(cherry picked from commit d0abe1bb3d)

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2022-04-12 18:49:03 +02:00
Grot (@grafanabot)
c7696261fb Temporarily skip a test that is intermittently timing out and causing build failures. (#47637) (#47651)
(cherry picked from commit 7f5e8bb00f)

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>
2022-04-12 17:03:30 +02:00
Grot (@grafanabot)
0cb55ee544 AzureMonitor: Fix Kusto editor schema (#47576) (#47650)
(cherry picked from commit 35f52aee55)

Co-authored-by: Andres Martinez Gotor <andres.mgotor@gmail.com>
2022-04-12 16:54:59 +02:00
Grot (@grafanabot)
73f5ca876f Prometheus: Fix executing query onBlur in Explore (#47561) (#47638)
(cherry picked from commit c449aadc1b)

Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
2022-04-12 16:08:18 +02:00
Grot (@grafanabot)
09d6461ce5 Navigation: Show only + icons in overlay menu for new NavBar (#47347) (#47644)
* Nav: Show overlay icons based on allowed list

* user essentials mob! 🔱

* Navigation: clean up and use new backend prop to show plus icons and
improve visual styling

* Nav: Fix top padding

* refactor to not use showIconInNavbar in NavBarMenuItem

* remove a missed bit

* refactor icon into const

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
(cherry picked from commit 85de0d88c7)

Co-authored-by: Maria Alexandra <239999+axelavargas@users.noreply.github.com>
2022-04-12 17:04:28 +03:00
Grot (@grafanabot)
ef592f1a66 fix: postgresql ipv6 host should not be enclosed in square brackets (#47522) (#47619)
(cherry picked from commit 9024340487)

Co-authored-by: felixdoerre <felixdoerre@users.noreply.github.com>
2022-04-12 13:38:44 +01:00
Grot (@grafanabot)
31f4d88206 Chore: update latest.json (#47627) (#47629)
(cherry picked from commit 3b2ca399e2)

Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>
2022-04-12 14:30:02 +02:00
Grot (@grafanabot)
ee9807d631 Allow queries import when changing data source type (#47435) (#47630)
* Enable queries import when changing datasource

* Supporting empty imports

* Review applied

(cherry picked from commit 98cbecc4a5)

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
2022-04-12 14:29:42 +02:00
Grot (@grafanabot)
9af9cd39cd chore: remove all remaining uses of golang.org/x/net/context (#47564) (#47585)
* chore: remove all remaining uses of  golang.org/x/net/context

This PR finishes the work started in #47532, replacing all calls to that package with the stdlib context and using http.NewRequestWithContext to include the context where necessary.

Bonus: small formatting fixes to goimports in these files.

closes #44178

* tweak: use context.Background in favor of TODO for tests

(cherry picked from commit 8f6877e12a)

Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2022-04-12 08:15:28 -04:00
Grot (@grafanabot)
cce492b246 Chore: Remove bus from alerting rule (#47508) (#47510)
* Chore: Remove bus from alerting rule

* fix alerting tests

* fix provide service

(cherry picked from commit b31c7d3654)

Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com>
2022-04-12 14:05:00 +02:00
Grot (@grafanabot)
be0d1a8d57 Chore: Remove bus from dashboards provisioning (#47495) (#47507)
* Chore: Remove bus from dashboards provisioning

* fix symlink test, make it run on darwin

* remove unused mock

(cherry picked from commit ad432108e6)

Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com>
2022-04-12 14:04:40 +02:00
Grot (@grafanabot)
f438ffacf6 Explore: Allow users to save Explore queries to dashboards (#47083) (#47626)
* Select: Expose AsyncSelectProps interface

* DashboardPicker: Add a generic DashboardPicker component

* Dashboard Service: improve types

* Explore: allow saving explore state in a new panel in an existing dashboard

* Handle saving provisioned dashboards error

* Improve test coverage

* simplify test setup

* Strip base path from url when redirecting to a dashboard

* Keep existing variables when saving to an existing dashboard

* group assertions in test

* SearchCard: handle undefined in meta.updated

* Change required error message

* Add to dashboard alternative

* Add to existing is working

* Add to dashboard form

* remove default add-panel when creating a dashboard from explore

* types cleanup

* remove unneeded BE change

* simplify selector

* Add explore2Dashboard feature toggle

* add tests

* Small refactor & add tests

* small DashboardPicker improvements

* use partial from lodash

* Better error handling

* improve tests & disable button when there are no queries

* rename addPanelToDashboard function

* remove localStorage item if opening tab fails

* UI touchups & tracking

* Fix tests & remove close reporting

* remove echologger debug

* fix adding a panel to an existing dashboard

* Enable explore2Dashboard by default and add docs

* Ensure each panel in dashboards has a valid ID

* force CI restart

Co-authored-by: Elfo404 <me@giordanoricci.com>
(cherry picked from commit 7181efd1cf)

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2022-04-12 13:52:45 +02:00
Grot (@grafanabot)
aa412e9678 Unified Alerting: UI explains "match all" case in Notification Policies. (#47574) (#47603)
This change makes it explicit in the UI that a notification policy with no matching labels matches all alerts that it processes. There are visual changes in both the Notification Policy editor, and in the Notification Policy table where matching columns are shown.

It's valid to have a notification policy with no label matchers attached. Such a policy matches all alerts sent to it. It's a common stumbling block for users. Where are all my alerts going?

Co-authored-by: gillesdemey <gilles.de.mey@gmail.com>
(cherry picked from commit 51c98b182d)

Co-authored-by: Joe Blubaugh <joe.blubaugh@grafana.com>
2022-04-12 13:10:21 +02:00
Grot (@grafanabot)
b477c7eba9 Access-control: documentation for general folder uid restrictions (#47567) (#47620)
* add caveats for general folder

* expand the list of resources covered by fgac

* Update folder.md

remove unneeded comment

(cherry picked from commit a5530d36a7)

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2022-04-12 12:21:59 +02:00
Grot (@grafanabot)
fb9a67f34d ReleaseNotes: Updated changelog and release notes for 8.4.6 (#47612) (#47617)
* ReleaseNotes: Updated changelog and release notes for 8.4.6

* update changelog and release notes

Co-authored-by: Agnès Toulet <agnes.toulet@gmail.com>
(cherry picked from commit 9a0f626948)
2022-04-12 12:20:32 +02:00
Grot (@grafanabot)
556178c714 Chore: Remove bus from contexthandler (#47374) (#47616)
* Chore: Remove bus from contexthandler

* fix tests

* try different wire binding

* maybe remove a few more dispatches

* fix tests

(cherry picked from commit d153d896c5)

Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com>
2022-04-12 12:05:27 +02:00
Grot (@grafanabot)
68c6e7514a Storybook: Add store-storybook command (#47557) (#47614)
* Add store-storybook command

* Update grabpl version

(cherry picked from commit cfaf058b20)

Co-authored-by: Dimitris Sotirakis <sotirakis.dim@gmail.com>
2022-04-12 11:53:48 +02:00
Grot (@grafanabot)
bd76bd0d0e Fix unmarshal of double pointer (#47586) (#47607)
* Fix unmaarshal of double pointer

* update sdk version

(cherry picked from commit 0bf889e058)

Co-authored-by: ying-jeanne <74549700+ying-jeanne@users.noreply.github.com>
2022-04-12 09:42:56 +02:00
Grot (@grafanabot)
be54ab63cb Explore/Logs: Improve and add descriptions of Explore's Escape newlines feature (#46709) (#47606)
* Explore/Logs: Clarify phrasing of newline escape fix tooltip

Rewrite the tooltip for the smart feature in PR #31352 that
replaces incorrectly escaped newlines in log lines.

-   Clarify the functionality of the feature to emphasize its
    interactivity.
-   Remove language suggesting that the feature is experimental,
    when we intended to suggest reviewing the results manually for
    correctness.

* Docs: Document escape newlines feature

Describe and provide steps for using the "Escape newlines" feature
in Explore added in PR #31352.

* Rewrite topic lead to clarify conditional behavior

* Describe reversion of replacements as a standalone task

Reverting the replacements is a separate action, so it should have
its own task. This also describes the option's transformation and
tightens the task language.

* Remove Grafana 7 version qualifier

* Clarify escape sequence detection lede on task

Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>

* Clarify "Remove escaping" state change

Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>

* Clarify confidence of tooltip content

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
(cherry picked from commit 42431e6ad4)

Co-authored-by: Garrett Guillotte <100453168+gguillotte-grafana@users.noreply.github.com>
2022-04-12 09:40:25 +02:00
Grot (@grafanabot)
abc9678589 Azure Monitor: Bug Fix for for template variables (#47478) (#47599)
(cherry picked from commit 7a8437020d)

Co-authored-by: jcolladokuri <jcolladokuri@microsoft.com>
2022-04-12 09:13:00 +02:00
Grot (@grafanabot)
fd703d9d87 Alerting: reduce database calls in prometheus-comptible rules API (#47080) (#47569)
* move validation at the beginning of method
* remove usage of GetOrgRuleGroups because it is not necessary. All information is already available in memory.
* remove unused method

(cherry picked from commit 48519f9ebb)

Co-authored-by: Yuriy Tseretyan <yuriy.tseretyan@grafana.com>
2022-04-11 13:37:40 -04:00
Grot (@grafanabot)
cb0f520a69 add feature toggle with new format (#47336) (#47581)
* add feature toggle with new format

* fix some comments ❤️

* Update pkg/infra/log/term/terminal_logger.go

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
(cherry picked from commit 03ba91e8a4)

Co-authored-by: ying-jeanne <74549700+ying-jeanne@users.noreply.github.com>
2022-04-11 19:00:34 +02:00
Grot (@grafanabot)
f5dd8de077 Navigation: wrap long items in the menu correctly (#47431) (#47577)
* start implementing wrapping behaviour

* handle more cases

* minWidth > width to preserve mobile behaviour

* Better css

(cherry picked from commit fb0f30e30b)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2022-04-11 18:43:34 +02:00
Grot (@grafanabot)
b31aaf444f Add docs for service accounts 8.5 (#46801) (#47568)
* initial doc for service accounts

* service account token calls complete

* service account tasks

* Update docs/sources/http_api/serviceaccount.md

* adding a token to the service account

* removed unused file

* refactor: review comments

* feat: add API key documentation

* fix: spelling

* Update docs/sources/administration/service-accounts/about-service-accounts.md

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

* Update docs/sources/administration/service-accounts/about-service-accounts.md

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

* Update docs/sources/http_api/serviceaccount.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/administration/service-accounts/enable-service-accounts.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/administration/service-accounts/enable-service-accounts.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/administration/service-accounts/enable-service-accounts.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/administration/service-accounts/enable-service-accounts.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/administration/service-accounts/enable-service-accounts.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/administration/api-keys/about-api-keys.md

* refactor: based on review

* removed the permissions for apikeys, as they are not necessary

* Apply suggestions from code review

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Update docs/sources/administration/service-accounts/create-service-account.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* update based on review

* Fix formatting of bullet points

* formatting

* refcator

Co-authored-by: Jguer <joao.guerreiro@grafana.com>
Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
Co-authored-by: Vardan Torosyan <vardants@gmail.com>
(cherry picked from commit 7be8fe027f)

Co-authored-by: Eric Leijonmarck <eric.leijonmarck@gmail.com>
2022-04-11 16:51:03 +02:00
Grot (@grafanabot)
c3f6e1e224 SAML: Allow disabling of SAML signups (#47481) (#47533)
* Add new error message for signup not allowed errors

* Add documentation on new SAML signup option

* Accept documentation feedback

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* Accept documentation feedback

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* run prettier:write

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
(cherry picked from commit 4318ffdd46)

Co-authored-by: Michael Mandrus <41969079+mmandrus@users.noreply.github.com>
2022-04-11 09:11:08 -04:00
Grot (@grafanabot)
9597cc68a7 chore: remove golang.org/x/net/context in favor of stdlib (#47532) (#47558)
This PR removes golang.org context imports under pkg/services/* and replaces them with the stdlib context.

Closes #44178

(cherry picked from commit bda3dd24e4)

Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2022-04-11 09:08:36 -04:00
Grot (@grafanabot)
7a1e09631f Access control: SQL filtering for annotation listing (#47467) (#47555)
* pass in user to attribute scope resolver

* add SQL filter to annotation listing

* check annotation FGAC permissions before exposing them for commenting

* remove the requirement to be able to list all annotations from annotation listing endpoint

* adding tests for annotation listing

* remove changes that got moved to a different PR

* unused var

* Update pkg/services/sqlstore/annotation.go

Co-authored-by: Ezequiel Victorero <evictorero@gmail.com>

* remove unneeded check

* remove unneeded check

* undo accidental change

* undo accidental change

* doc update

* move tests

* redo the approach for passing the user in for scope resolution

* accidental change

* cleanup

* error handling

Co-authored-by: Ezequiel Victorero <evictorero@gmail.com>
(cherry picked from commit ef4c2672b3)

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2022-04-11 14:29:54 +02:00
Grot (@grafanabot)
9c5e459f0b bugfix: fix proper type of setting maxConcurrentShardRequests (#47120) (#47548)
(cherry picked from commit c0104db72d)

Co-authored-by: Aleksandr Stepanov <alexandrst88@gmail.com>
2022-04-11 10:45:04 +02:00
Grot (@grafanabot)
dd2663352c Service accounts: UI polish improvements (#47461) (#47526)
* ui polishes

* change icon

(cherry picked from commit 883ce5ad30)

Co-authored-by: Eric Leijonmarck <eric.leijonmarck@gmail.com>
2022-04-08 17:35:35 +01:00
Grot (@grafanabot)
58f27b980d GrafanaUI: Change chevron directions for open/closed state (#47386) (#47529)
(cherry picked from commit e3b123c3fc)

Co-authored-by: Josh Hunt <joshhunt@users.noreply.github.com>
2022-04-08 17:49:48 +02:00
Grot (@grafanabot)
283b93bc15 Alerting: fix collapsable toggle text regression (#47517) (#47524)
(cherry picked from commit 7e844064a4)

Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
2022-04-08 17:25:39 +02:00
Grot (@grafanabot)
cb0093d27f fixed dashboard spelling (#47515) (#47520)
(cherry picked from commit 1a88612e82)

Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com>
2022-04-08 16:23:38 +02:00
Grot (@grafanabot)
056ff9cf32 fix: don't allow editing rule types for existing rules (#47512) (#47516)
(cherry picked from commit c530100d45)

Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
2022-04-08 15:45:42 +02:00
Grot (@grafanabot)
df94aa979c Routing: Fix links to different port from being treated as internal links (#45192) (#47498)
* Add some failing tests that trigger the bug

* start at refactor of stripBaseFromUrl

(cherry picked from commit 416da59c43)

Co-authored-by: Josh Hunt <joshhunt@users.noreply.github.com>
2022-04-08 13:37:08 +01:00
Grot (@grafanabot)
776d2cc03e Alerting: FGAC for alert rules page (#47418) (#47503)
* Apply FGAC on the alert rules list page

* Add tests for edit, delete and silence buttons

* Unify access-control helpers

* Fix import

* Add route permissions for alert groups, unify access control helpers

* Improve buttons with data source explore permission

* Fix test

(cherry picked from commit 87383b1c8b)

Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
2022-04-08 13:50:40 +02:00
Grot (@grafanabot)
3fa4ae500a UsageStats: track enabled features (#47407) (#47501)
* #47127: include enabled features in the usage stats reports

* #47127: convert feature names to snake cased metric names

* #47127: remove dead code

* #47127: lint fix

* #47127: convert GetUsageStats to return `map[string]interface{}`

* #47127: fix testssssssss

* #47127: fix testssssssss

(cherry picked from commit f1a1070d41)

Co-authored-by: Artur Wierzbicki <wierzbicki.artur.94@gmail.com>
2022-04-08 13:18:42 +02:00
Grot (@grafanabot)
a8ab8a570b Replace with secret (#47488) (#47489)
(cherry picked from commit ce2a9252c2)

Co-authored-by: Dimitris Sotirakis <sotirakis.dim@gmail.com>
2022-04-08 11:32:39 +03:00
Grot (@grafanabot)
99dbf0502a Config: fix buildInfo initialization (#47402) (#47456)
* Config: fix buildInfo initialization

* change default to be parseable

* apply default values for buildInfo

(cherry picked from commit 59388bf546)

Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com>
2022-04-08 09:20:44 +02:00
Grot (@grafanabot)
c7877e09d0 Temporarily skip intermittent test (#47471) (#47472)
(cherry picked from commit c3ad36ba72)

Co-authored-by: Alexander Weaver <weaver.alex.d@gmail.com>
2022-04-07 13:22:21 -05:00
Grot (@grafanabot)
993ed70e34 Docs: corrects typos (#47464) (#47466)
(cherry picked from commit 883f9f718f)

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
2022-04-07 18:37:25 +02:00
Grot (@grafanabot)
0f27624e37 remove the parallele to avoid concurrency (#47447) (#47449)
(cherry picked from commit 29b8d5d295)

Co-authored-by: ying-jeanne <74549700+ying-jeanne@users.noreply.github.com>
2022-04-07 16:02:42 +02:00
Grot (@grafanabot)
ec1ae002ea TestData: Interpolate variables in more fields (#47158) (#47450)
* TestData: Interpolate variables in more fields

* only interpolate variables if the field is set

* Correctly type scenarioId which can be undefined

(cherry picked from commit b857f8339e)

Co-authored-by: Josh Hunt <joshhunt@users.noreply.github.com>
2022-04-07 16:00:18 +02:00
Grot (@grafanabot)
5965a7e479 Update latest json (#47437) (#47440)
(cherry picked from commit 4b8a202232)

Co-authored-by: Tania <yalyna.ts@gmail.com>
2022-04-07 14:32:01 +02:00
Grot (@grafanabot)
b7b2149b76 Navigation: add aria-label to NavBarToggle (#47434) (#47439)
* add aria-label to NavBarToggle

* use open/close instead

(cherry picked from commit d95782845d)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2022-04-07 14:31:48 +02:00
Grot (@grafanabot)
b4a0436af8 ReleaseNotes: Updated changelog and release notes for 8.5.0-beta1 (#47433) (#47436)
* ReleaseNotes: Updated changelog and release notes for 8.5.0-beta1

* Update release notes

* Update changelog

Co-authored-by: Tania <yalyna.ts@gmail.com>
(cherry picked from commit af71e77e6c)
2022-04-07 13:54:33 +02:00
Grot (@grafanabot)
1a4ecfde4e "Release: Updated versions in package to 8.5.0-beta.1" (#47432) 2022-04-07 13:33:09 +02:00
Grot (@grafanabot)
52b8e4991c Build: bump gonum library version to fix #46759 (#46997) (#47419)
(cherry picked from commit 460b8e85d7)

Co-authored-by: Martin <mahe@quantentunnel.de>
2022-04-07 11:36:43 +02:00
Grot (@grafanabot)
f3a704de5f Explore: Add explore2Dashboard feature toggle (#47395) (#47420)
(cherry picked from commit ca286a238d)

Co-authored-by: Giordano Ricci <me@giordanoricci.com>
2022-04-07 09:45:12 +01:00
Grot (@grafanabot)
4dcf1a8464 Make gsutil command don't fail if dir doesn't exist (#47394) (#47416)
(cherry picked from commit 0845ba1abb)

Co-authored-by: Dimitris Sotirakis <sotirakis.dim@gmail.com>
2022-04-07 09:48:33 +03:00
achatterjee-grafana
f497164472 backport changes (#47414) 2022-04-06 16:54:02 -04:00
Grot (@grafanabot)
8280aa5636 Alerting: fgac for notification policies and contact points (#46939) (#47406)
* add FGAC actions for silences table

* redirect users without permissions

* add permissions checks to routes

* add fgac to notifications and contact points

* fgac for notification policies

* fix mute timing authorization

* use consistent naming for checking grafana alertmanager

* tests for fgac in contact points and notification policies

* bump up timeout on rule editor test

* use new permissions util

* break out route evaluation into util

* Remove test timeout

* Change permissions for the alert-notifiers endpoint

* Use signed in handler for alert-notifiers when unified alerting enabled

Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
(cherry picked from commit 49505b9a3b)

Co-authored-by: Nathan Rodman <nathanrodman@gmail.com>
2022-04-06 22:34:14 +02:00
Grot (@grafanabot)
50712b5782 Navigation: implement full-width mobile menu (#47383) (#47405)
* navigation: implement proper mobile menu

* Update public/app/core/components/NavBar/Next/NavBarMenu.tsx

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>

* animation feedback

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
(cherry picked from commit 04c037b2cb)

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2022-04-06 18:12:24 +02:00
508 changed files with 18550 additions and 5700 deletions

View File

@@ -302,7 +302,7 @@ exports[`no enzyme tests`] = {
"public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:2974837543": [
[1, 19, 13, "RegExp match", "2409514259"]
],
"public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx:132770839": [
"public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx:3888529428": [
[1, 19, 13, "RegExp match", "2409514259"]
],
"public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx:1089831034": [

1078
.drone.yml

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,54 @@
<!-- 8.5.0-beta1 START -->
# 8.5.0-beta1 (2022-04-06)
### Features and enhancements
- Add config option to enable/disable reporting. (Enterprise)
- **Alerting:** Accurately set value for prom-compatible APIs. [#47216](https://github.com/grafana/grafana/pull/47216), [@gotjosh](https://github.com/gotjosh)
- **Alerting:** Provisioning API - Notification Policies. [#46755](https://github.com/grafana/grafana/pull/46755), [@alexweav](https://github.com/alexweav)
- **Alerting:** Notification URL points to alert view page instead of alert edit page. [#47752](https://github.com/grafana/grafana/pull/47752), [@joeblubaugh](https://github.com/joeblubaugh)
- **Analytics:** Enable grafana and plugin update checks to be operated independently. [#46352](https://github.com/grafana/grafana/pull/46352), [@wbrowne](https://github.com/wbrowne)
- **Azure Monitor:** Add support for multiple template variables in resource picker. [#46215](https://github.com/grafana/grafana/pull/46215), [@sarahzinger](https://github.com/sarahzinger)
- **Caching:** Add separate TTL for resources cache. (Enterprise)
- **Caching:** add support for TLS configuration for Redis Cluster. (Enterprise)
- **NewsPanel:** Remove Use Proxy option and update documentation with recommendations. [#47189](https://github.com/grafana/grafana/pull/47189), [@joshhunt](https://github.com/joshhunt)
- **OAuth:** Sync GitHub OAuth user name to Grafana if it's set. [#45438](https://github.com/grafana/grafana/pull/45438), [@pallxk](https://github.com/pallxk)
### Bug fixes
- **Plugins:** Fix Default Nav URL for dashboard includes. [#47143](https://github.com/grafana/grafana/pull/47143), [@wbrowne](https://github.com/wbrowne)
### Breaking changes
When user is using Github OAuth, GitHub login is showed as both Grafana login and name. Now the GitHub name is showed as Grafana name, and GitHub login is showed as Grafana Login. Issue [#45438](https://github.com/grafana/grafana/issues/45438)
The meaning of the default data source has now changed from being a persisted property in a panel. Before when you selected the default data source for a panel and later changed the default data source to another data source it would change all panels who were configured to use the default data source. From now on the default data source is just the default for new panels and changing the default will not impact any currently saved dashboards. Issue [#45132](https://github.com/grafana/grafana/issues/45132)
<!-- 8.4.7 START -->
# 8.4.7 (2022-04-19)
### Features and enhancements
- **CloudWatch:** Added missing MemoryDB Namespace metrics. [#47290](https://github.com/grafana/grafana/pull/47290), [@james-deee](https://github.com/james-deee)
- **Histogram Panel:** Take decimal into consideration. [#47330](https://github.com/grafana/grafana/pull/47330), [@mdvictor](https://github.com/mdvictor)
- **TimeSeries:** Sort tooltip values based on raw values. [#46738](https://github.com/grafana/grafana/pull/46738), [@dprokop](https://github.com/dprokop)
### Bug fixes
- **API:** Include userId, orgId, uname in request logging middleware. [#47183](https://github.com/grafana/grafana/pull/47183), [@marefr](https://github.com/marefr)
- **Elasticsearch:** Respect maxConcurrentShardRequests datasource setting. [#47120](https://github.com/grafana/grafana/pull/47120), [@alexandrst88](https://github.com/alexandrst88)
<!-- 8.4.7 END -->
<!-- 8.5.0-beta1 END -->
<!-- 8.4.6 START -->
# 8.4.6 (2022-04-12)
- **Security:** Fixes CVE-2022-24812. For more information, see our [blog](https://grafana.com/blog/2022/04/12/grafana-enterprise-8.4.6-released-with-high-severity-security-fix/)
<!-- 8.4.6 END -->
<!-- 8.4.5 START -->
# 8.4.5 (2022-03-31)

View File

@@ -20,7 +20,7 @@ COPY emails emails
ENV NODE_ENV production
RUN yarn build
FROM golang:1.17.8-alpine3.15 as go-builder
FROM golang:1.17.9-alpine3.15 as go-builder
RUN apk add --no-cache gcc g++ make

View File

@@ -21,7 +21,7 @@ COPY emails emails
ENV NODE_ENV production
RUN yarn build
FROM golang:1.17.8 AS go-builder
FROM golang:1.17.9 AS go-builder
WORKDIR /src/grafana

View File

@@ -230,6 +230,9 @@ application_insights_connection_string =
# Optional. Specifies an Application Insights endpoint URL where the endpoint string is wrapped in backticks ``.
application_insights_endpoint_url =
# Controls if the UI contains any links to user feedback forms
feedback_links_enabled = true
#################################### Security ############################
[security]
# disable creation of admin user on first start of grafana
@@ -1131,9 +1134,12 @@ license_path =
# enable = feature1,feature2
enable =
# The new prometheus visual query builder
# The new prometheus visual query builder
promQueryBuilder = true
# Experimental Explore to Dashboard workflow
explore2Dashboard = true
# feature1 = true
# feature2 = false

View File

@@ -230,6 +230,9 @@
# Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config
;rudderstack_config_url =
# Controls if the UI contains any links to user feedback forms
;feedback_links_enabled = true
#################################### Security ####################################
[security]
# disable creation of admin user on first start of grafana

View File

@@ -16,7 +16,7 @@
"gnetId": null,
"graphTooltip": 0,
"id": null,
"iteration": 1646409057541,
"iteration": 1601526910610,
"links": [
{
"icon": "external link",
@@ -73,61 +73,6 @@
"timeShift": null,
"title": "${custom.text}",
"type": "text"
},
{
"id": 4,
"gridPos": {
"h": 9,
"w": 9,
"x": 12,
"y": 0
},
"type": "stat",
"title": "Panel Title",
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"color": {
"mode": "thresholds"
},
"links": [
{
"title": "Var Link",
"url": "/d/vmie2cmWz/bar-gauge-demo?var-custom=$custom"
}
]
},
"overrides": []
},
"options": {
"reduceOptions": {
"values": false,
"calcs": [
"lastNotNull"
],
"fields": ""
},
"orientation": "auto",
"textMode": "auto",
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto"
},
"pluginVersion": "8.5.0-pre",
"datasource": null
}
],
"schemaVersion": 26,
@@ -167,11 +112,6 @@
"selected": false,
"text": "p3",
"value": "p3"
},
{
"selected": false,
"text": "p4",
"value": "test%25value"
}
],
"query": "p1,p2,p3",

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -12,3 +12,4 @@ This section includes information for Grafana administrators, team administrator
- [Configuration]({{< relref "configuration" >}})
- [Configure Docker image]({{< relref "configure-docker" >}})
- [Security]({{< relref "security" >}})
- [Service accounts]({{< relref "service-accounts" >}})

View File

@@ -0,0 +1,17 @@
---
title: 'API keys in Grafana'
menuTitle: 'API keys'
description: 'This section contains information about API keys in Grafana'
weight: 300
keywords:
- API keys
- Service accounts
---
# API keys in Grafana
API Keys can be used to interact with Grafana HTTP APIs.
We recommend using service accounts instead of API keys if you are on Grafana 8.5+, for more information refer to [About service accounts]({{< relref "../service-accounts/about-service-accounts.md#">}}).
{{< section >}}

View File

@@ -0,0 +1,12 @@
---
title: About API keys in Grafana
menuTitle: About API keys
description: 'Learn about using API keys in Grafana'
weight: 30
---
# About API keys in Grafana
An API key is a randomly generated string that external systems use to interact with Grafana HTTP APIs.
When you create an API key, you specify a **Role** that determines the permissions associated with the API key. Role permissions control that actions the API key can perform on Grafana resources. For more information about creating API keys, refer to [Create an API key]({{< relref "./create-api-key.md#">}}).

View File

@@ -0,0 +1,34 @@
---
title: Create an API key in Grafana
menuTitle: Create an API key
description: 'How to create an API key in Grafana'
weight: 50
keywords:
- API keys
- Service accounts
---
# Create an API key in Grafana
Create an API key when you want to manage your computed workload with a user.
For more information about API keys, refer to [About API keys in Grafana]({{< relref "./about-api-keys.md">}}).
This topic shows you how to create an API key using the Grafana UI. You can also create an API key using the Grafana HTTP API. For more information about creating API keys via the API, refer to [Create API key via API]({{< relref "../../http_api/create-api-tokens-for-org.md#how-to-create-a-new-organization-and-an-api-token">}}).
## Before you begin:
- Ensure you have permission to create and edit API keys. For more information about permissions, refer to [About users and permissions]({{< relref "../manage-users-and-permissions/about-users-and-permissions.md#">}}).
**To create an API key:**
1. Sign in to Grafana, hover your cursor over **Configuration** (the gear icon), and click **API Keys**.
1. Click **New API key**.
1. Enter a unique name for the key.
1. In the **Role** field, select one of the following access levels you want to assign to the key.
- **Admin**: Enables a user to use APIs at the broadest, most powerful administrative level.
- **Editor** or **Viewer** to limit the key's users to those levels of power.
1. In the **Time to live** field, specify how long you want the key to be valid.
- The maximum length of time is 30 days (one month). You enter a number and a letter. Valid letters include `s` for seconds,`m` for minutes, `h` for hours, `d `for days, `w` for weeks, and `M `for month. For example, `12h` is 12 hours and `1M` is 1 month (30 days).
- If you are unsure about how long an API key should be valid, we recommend that you choose a short duration, such as a few hours. This approach limits the risk of having API keys that are valid for a long time.
1. Click **Add**.

View File

@@ -509,6 +509,10 @@ If you want to track Grafana usage via Azure Application Insights, then specify
<hr />
### enable_feedback_links
If set to false will remove all feedback links from the UI. Defaults to true.
## [security]
### disable_initial_admin_creation

View File

@@ -0,0 +1,15 @@
---
title: 'Service accounts in Grafana'
menuTitle: 'Service accounts'
description: 'This page contains information about service accounts in Grafana'
weight: 300
keywords:
- API keys
- Service accounts
---
# Service accounts in Grafana
You can use service accounts to run automated or compute workloads.
{{< section >}}

View File

@@ -0,0 +1,50 @@
---
title: About service accounts
menuTitle: About service accounts
description: 'This page contains detailed information about service accounts in Grafana'
weight: 30
---
# About service accounts in Grafana
A service account can be used to run automated or compute workloads. Applications use service account tokens to authorize themselves as a service account.
> **Note:** Service accounts are available in Grafana 8.5+ as a beta feature, to enable service accounts refer to [Enable service accounts]({{< relref "./enable-service-accounts.md#">}}) section.
A common use case for creating a service account is to perform operations on automated or triggered tasks. You can use service accounts to:
- Schedule reports for specific dashboards to be delivered on a daily/weekly/monthly basis
- Define alerts in your system to be used in Grafana
- Set up an external authentication provider to manage users and permissions across an organization
- Establish machine-to-machine communication
- Interact with Grafana without logging in as a user
You can also use service accounts in combination with fine-grained access control to grant users specific scopes.
You can associate a service account with multiple tokens. This is because a service account:
- can be used by multiple team members and therefore can generate their own token each
- can be used across multiple tenants and each tenant can have its own token
We recommend the you begin by creating one service account for each use case.
> **Note:** Service accounts can only act in the organization they are created for. If you have the same task that is needed for multiple organizations, we recommend creating service accounts in each organization.
---
## Service account tokens
A service account token is a generated random string that are an alternative to using passwords for authentication with Grafana, to interact with the Grafana HTTP APIs.
When you create a service account, you can associate one or more access tokens with it. You can use service access tokens the same way as API Keys, for example to access Grafana HTTP API programmatically.
Service account access tokens inherit permissions from service account directly.
### Service accounts benefits
The added benefits of service accounts to API keys include:
- Service accounts resemble Grafana users and can be enabled/disabled, granted specific permissions, and remain active until they are deleted or disabled. API keys are only valid until their expiry date.
- Service accounts can be associated with multiple tokens.
- Unlike API keys, service account tokens are not associated with a specific user, which means that applications can be authenticated even if a Grafana user is deleted.
- You can grant granular permissions to service accounts by leveraging [fine-grained access control]({{< relref "../../enterprise/access-control">}}). For more information about permissions, refer to [About users and permissions]({{< relref "../manage-users-and-permissions/about-users-and-permissions.md#">}}).

View File

@@ -0,0 +1,31 @@
---
title: 'Add a token to a service account in Grafana'
menuTitle: 'Add a token to a service account'
description: 'This topic shows you how to add a token to a service account'
weight: 60
---
# Add a token to a service account in Grafana
A service account token is a randomly generated string that external system use to authenticate into Grafana, and include specific permissions to interact with the Grafana HTTP APIs.
For more information about service accounts, refer to [About service accounts in Grafana]({{< relref "./about-service-accounts.md">}}).
You can create a service account token using the Grafana UI or via the API. For more information about creating a service account token via the API, refer to [HTTP API Create service account token]({{< relref "../../http_api/serviceaccount.md#create-service-account-tokens">}}).
## Before you begin
- Ensure you have added the `serviceAccounts` feature toggle to Grafana. For more information about adding the feature toggle, refer to [Enable service accounts]({{< relref "./enable-service-accounts.md#">}}).
- Ensure you have permission to create and edit service accounts. For more information about user roles, refer to [About users and permissions]({{< relref "../manage-users-and-permissions/about-users-and-permissions.md#">}}).
- [Create a service account in Grafana]({{< relref "./create-service-account.md#">}}).
**To add a token to a service account:**
1. Sign in to Grafana and hover your cursor over the organization icon in the sidebar.
1. Click **Service accounts**.
1. Click the service account to which you want to add a token.
1. Click **Add token**.
1. Enter a name for the token.
1. (recommended) Enter an expiry date and expiry date for the token or leave it on no expiry date option.
- The expiry date specifies how long you want the key to be valid.
- If you are unsure of an expiration date, we recommend that you set the token to expire after a short time, such as a few hours or less. This limits the risk associated with a token that is valid for a long time.
1. Click **Generate service account token**.

View File

@@ -0,0 +1,30 @@
---
title: Create a service account in Grafana
menuTitle: Create a service account
description: 'How to create a service account in Grafana'
weight: 50
keywords:
- Service accounts
---
# Create a service account in Grafana
A service account is a user account that you can use to run automated or compute workloads. For more information about how you can use service accounts, refer to [About service accounts]({{< relref "../service-accounts/about-service-accounts.md#">}}).
For more information about creating service accounts via the API, refer to [Create service account via API]({{< relref "../../http_api/serviceaccount.md#create-service-account">}}).
## Before you begin
- Ensure you have added the feature toggle for service accounts `serviceAccounts`. For more information about adding the feature toggle, refer to [Enable service accounts]({{< relref "./enable-service-accounts.md#">}}).
- Ensure you have permission to create and edit service accounts. For more information about user permissions, refer to [About users and permissions]({{< relref "../manage-users-and-permissions/about-users-and-permissions.md#">}}).
**To create a service account:**
1. Sign in to Grafana and hover your cursor over the organization icon in the sidebar.
1. Click **Service accounts**.
1. Click **New service account**.
1. Enter a **Display name**.
1. The display name must be unique as it determines the ID associated with the service account.
- We recommend that you use a consistent naming convention when you name service accounts. A consistent naming convention can help you scale and maintain service accounts in the future.
- You can change the display name at any time.
1. Click **Create service account**.

View File

@@ -0,0 +1,43 @@
---
title: 'Enable service accounts in Grafana'
menuTitle: 'Enable service accounts'
description: 'This topic shows you how to to enable the service accounts feature in Grafana'
weight: 40
keywords:
- Feature toggle
- Service accounts
---
# Enable service accounts in Grafana
Service accounts are available behind the `service-accounts` feature toggle available in Grafana 9.0+.
You can enable service accounts by:
- modifying the Grafana configuration file, or
- configuring an environment variable
## Enable service accounts with configuration file
This topic shows you how to enable service accounts by modifying the Grafana configuration file.
1. Sign in to the Grafana server and locate the configuration file. For more information about finding the configuration file, refer to LINK.
1. Open the configuration file and locate the [feature toggles] section. In your [config file]({{< relref "../../administration/configuration.md#config-file-locations" >}}), add `serviceAccounts` as a [feature_toggle]({{< relref "../../administration/configuration.md#feature_toggle" >}}).
```
[feature_toggles]
# enable features, separated by spaces
enable = serviceAccounts
```
1. Save your changes, Grafana should recognize your changes; in case of any issues we recommend restarting the Grafana server.
## Enable service accounts with an environment variable
This topic shows you how to enable service accounts by setting environment variables before starting Grafana.
> **Note:** Environment variables override any configuration file settings.
You can use `GF_FEATURE_TOGGLES_ENABLE = serviceAccounts` environment variable.
For more information regarding on how to setup environment variables refer to [Configuring with environment variables]({{< relref "../../administration/configuration.md#override-configuration-with-environment-variables" >}}).

View File

@@ -8,6 +8,8 @@ weight = 113
Grafana 8.0 has new and improved alerting that centralizes alerting information in a single, searchable view. It is enabled by default for all new OSS instances, and is an [opt-in]({{< relref "./opt-in.md" >}}) feature for older installations that still use legacy dashboard alerting. We encourage you to create issues in the Grafana GitHub repository for bugs found while testing Grafana alerting. See also, [What's New with Grafana alerting]({{< relref "./difference-old-new.md" >}}).
> Refer to [Fine-grained access control]({{< relref "../enterprise/access-control/_index.md" >}}) in Grafana Enterprise to learn more about controlling access to alerts using fine-grained permissions.
When Grafana alerting is enabled, you can:
- [Create Grafana managed alerting rules]({{< relref "alerting-rules/create-grafana-managed-rule.md" >}})

View File

@@ -4,7 +4,7 @@ aliases = ["/docs/grafana/latest/features/dashboard/dashboards/"]
weight = 80
+++
# Dshboard rows
# Dashboard rows
A dashboard row is a logical divider within a dashboard. It is used to group panels together.

View File

@@ -143,13 +143,13 @@ types of template variables.
### Query variable
The Elasticsearch data source supports two types of queries you can use in the _Query_ field of _Query_ variables. The query is written using a custom JSON string.
The Elasticsearch data source supports two types of queries you can use in the _Query_ field of _Query_ variables. The query is written using a custom JSON string. The field should be mapped as a [keyword](https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html#keyword) in the Elasticsearch index mapping. If it is [multi-field](https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-fields.html) with both a `text` and `keyword` type, then use `"field":"fieldname.keyword"`(sometimes`fieldname.raw`) to specify the keyword field in your query.
| Query | Description |
| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `{"find": "fields", "type": "keyword"}` | Returns a list of field names with the index type `keyword`. |
| `{"find": "terms", "field": "@hostname", "size": 1000}` | Returns a list of values for a field using term aggregation. Query will use current dashboard time range as time range for query. |
| `{"find": "terms", "field": "@hostname", "query": '<lucene query>'}` | Returns a list of values for a field using term aggregation and a specified lucene query filter. Query will use current dashboard time range as time range for query. |
| Query | Description |
| ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `{"find": "fields", "type": "keyword"}` | Returns a list of field names with the index type `keyword`. |
| `{"find": "terms", "field": "hostname.keyword", "size": 1000}` | Returns a list of values for a keyword using term aggregation. Query will use current dashboard time range as time range query. |
| `{"find": "terms", "field": "hostname", "query": '<lucene query>'}` | Returns a list of values for a keyword field using term aggregation and a specified lucene query filter. Query will use current dashboard time range as time range for query. |
There is a default size limit of 500 on terms queries. Set the size property in your query to set a custom limit.
You can use other variables inside the query. Example query definition for a variable named `$host`.

View File

@@ -16,76 +16,59 @@ Grafana includes built-in support for Prometheus. This topic explains options, v
To access Prometheus settings, hover your mouse over the **Configuration** (gear) icon, then click **Data Sources**, and then click the Prometheus data source.
| Name | Description |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Name` | The data source name. This is how you refer to the data source in panels and queries. |
| `Default` | Default data source that is pre-selected for new panels. |
| `Url` | The URL of your Prometheus server, for example, `http://prometheus.example.org:9090`. |
| `Access` | Server (default) = URL needs to be accessible from the Grafana backend/server, Browser = URL needs to be accessible from the browser. **Note**: Browser (direct) access is deprecated and will be removed in a future release. |
| `Basic Auth` | Enable basic authentication to the Prometheus data source. |
| `User` | User name for basic authentication. |
| `Password` | Password for basic authentication. |
| `Scrape interval` | Set this to the typical scrape and evaluation interval configured in Prometheus. Defaults to 15s. |
| `HTTP method` | Use either POST or GET HTTP method to query your data source. POST is the recommended and pre-selected method as it allows bigger queries. Change this to GET if you have a Prometheus version older than 2.1 or if POST requests are restricted in your network. |
| `Disable metrics lookup` | Checking this option will disable the metrics chooser and metric/label support in the query field's autocomplete. This helps if you have performance issues with bigger Prometheus instances. |
| `Custom Query Parameters` | Add custom parameters to the Prometheus query URL. For example `timeout`, `partial_response`, `dedup`, or `max_source_resolution`. Multiple parameters should be concatenated together with an '&amp;'. |
| `Label name` | Add the name of the field in the label object. |
| `URL` | If the link is external, then enter the full link URL. You can interpolate the value from the field with `${__value.raw }` macro. |
| `URL Label` | (Optional) Set a custom display label for the link URL. The link label defaults to the full external URL or the name of datasource and is overridden by this setting. |
| `Internal link` | Select if the link is internal or external. In the case of an internal link, a data source selector allows you to select the target data source. Supports tracing data sources only. |
| Name | Description |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Name` | The data source name. This is how you refer to the data source in panels and queries. |
| `Default` | Default data source that is pre-selected for new panels. |
| `Url` | The URL of your Prometheus server, for example, `http://prometheus.example.org:9090`. |
| `Access` | Server (default) = URL needs to be accessible from the Grafana backend/server, Browser = URL needs to be accessible from the browser. **Note**: Browser (direct) access is deprecated and will be removed in a future release. |
| `Basic Auth` | Enable basic authentication to the Prometheus data source. |
| `User` | User name for basic authentication. |
| `Password` | Password for basic authentication. |
| `Scrape interval` | Set this to the typical scrape and evaluation interval configured in Prometheus. Defaults to 15s. |
| `HTTP method` | Use either POST or GET HTTP method to query your data source. POST is the recommended and pre-selected method as it allows bigger queries. Change this to GET if you have a Prometheus version older than 2.1 or if POST requests are restricted in your network. |
| `Disable metrics lookup` | Checking this option will disable the metrics chooser and metric/label support in the query field's autocomplete. This helps if you have performance issues with bigger Prometheus instances. |
| `Custom Query Parameters` | Add custom parameters to the Prometheus query URL. For example `timeout`, `partial_response`, `dedup`, or `max_source_resolution`. Multiple parameters should be concatenated together with an '&amp;'. |
| **Exemplars configuration** | |
| `Internal link` | Enable this option is you have an internal link. When you enable this option, you will see a data source selector. Select the backend tracing data store for your exemplar data. |
| `Data source` | You will see this option only if you enable `Internal link` option. Select the backend tracing data store for your exemplar data. |
| `URL` | You will see this option only if the `Internal link` option is disabled. Enter the full URL of the external link. You can interpolate the value from the field with `${__value.raw }` macro. |
| `URL Label` | (Optional) add a custom display label to override the value of the `Label name` field. |
| `Label name` | Add a name for the exemplar traceID property. |
## Prometheus query editor
Below you can find information and options for Prometheus query editor in dashboard and in Explore.
Prometheus query editor is separated into 3 distinct modes that you can switch between. See docs for each section below.
### Query editor in dashboards
![Editor toolbar](/static/img/docs/prometheus/header-8-5.png 'Editor toolbar')
Open a graph in edit mode by clicking the title > Edit (or by pressing `e` key while hovering over panel).
At the top of the editor there is `Run query` button that will run the query and `Explain | Builder | Code` tabs to switch between the editor modes. If the query editor is in Builder mode there are additional elements explained in the Builder section.
{{< figure src="/static/img/docs/v45/prometheus_query_editor_still.png"
animated-gif="/static/img/docs/v45/prometheus_query_editor.gif" >}}
Each mode is synchronized with the other modes, so you can switch between them without losing your work, although there are some limitations. Some more complex queries are not yet supported in the builder mode. If you try to switch from `Code` to `Builder` with such query, editor will show a popup explaining that you can lose some parts of the query, and you can decide if you still want to continue to `Builder` mode or not.
| Name | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Query expression` | Prometheus query expression. For more information, refer to the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). |
| `Legend format` | Controls the name of the time series, using name or pattern. For example, `{{hostname}}` is replaced by the label value for the label `hostname`. |
| `Step` | Use 'Minimum' or 'Maximum' step mode to set the lower or upper bounds respectively on the interval between data points. For example, set "minimum 1h" to hint that measurements are not frequent (taken hourly). Use the 'Exact' step mode to set a precise interval between data points. `$__interval` and `$__rate_interval` are supported. |
| `Resolution` | `1/1` sets both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) such that each pixel corresponds to one data point. For better performance, you can pick lower resolutions. `1/2` only retrieves a data point for every other pixel, and `1/10` retrieves one data point per 10 pixels. Both _Min time interval_ and _Step_ limit the final value of `$__interval` and `step`. |
| `Metric lookup` | Search for metric names in this input field. |
| `Format as` | You can switch between `Table` `Time series` or `Heatmap` options. The `Table` option works only in the Table panel. `Heatmap` displays metrics of the Histogram type on a Heatmap panel. Under the hood, it converts cumulative histograms to regular ones and sorts series by the bucket bound. |
| `Instant` | Perform an "instant" query to return only the latest value that Prometheus has scraped for the requested time series. Instant queries can return results much faster than normal range queries. Use them to look up label sets. |
| `Min time interval` | This value multiplied by the denominator from the _Resolution_ setting sets a lower limit to both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries). Defaults to _Scrape interval_ as specified in the data source options. |
| `Exemplars` | Run and show exemplars in the graph. |
### Code mode
> **Note:** Grafana modifies the request dates for queries to align them with the dynamically calculated step. This ensures consistent display of metrics data, but it can result in a small gap of data at the right edge of a graph.
![Code mode](/static/img/docs/prometheus/code-mode-8-5.png 'Code mode')
#### Instant queries in dashboards
Code mode allows you to write raw queries in a textual editor. It implements advanced autocomplete features and syntax highlighting to help with writing complex queries. In addition, it also contains `Metrics browser` to further aid with writing queries (see more docs below).
The Prometheus data source allows you to run "instant" queries, which query only the latest value.
You can visualize the results in a table panel to see all available labels of a timeseries.
For more information about Prometheus query language, refer to the [Prometheus documentation](http://prometheus.io/docs/querying/basics/).
Instant query results are made up only of one data point per series but can be shown in the graph panel with the help of [series overrides]({{< relref "../visualizations/graph-panel.md#series-overrides" >}}).
To show them in the graph as a latest value point, add a series override and select `Points > true`.
To show a horizontal line across the whole graph, add a series override and select `Transform > constant`.
#### Autocomplete
> Support for constant series overrides is available from Grafana v6.4
![Autocomplete](/static/img/docs/prometheus/autocomplete-8-5.png 'Autocomplete')
### Query editor in Explore
Autocomplete kicks automatically in appropriate times during typing. Use `ctrl/cmd + space` to trigger autocomplete manually when needed. Autocomplete can suggest both static functions, aggregations and keywords but also dynamic items like metrics and labels. Autocomplete dropdown also shows documentation for the suggested items, either static one or dynamic metric documentation where available.
| Name | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Query expression` | Prometheus query expression, check out the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). |
| `Step` | [`Step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries). Time units can be used here, for example: 5s, 1m, 3h, 1d, 1y. Default unit if no unit specified is `s` (seconds). |
| `Query type` | `Range`, `Instant`, or `Both`. When running **Range query**, the result of the query is displayed in graph and table. Instant query returns only the latest value that Prometheus has scraped for the requested time series and it is displayed in the table. When **Both** is selected, both instant query and range query is run. Result of range query is displayed in graph and the result of instant query is displayed in the table. |
| `Exemplars` | Run and show exemplars in the graph. |
In [Explore]({{< relref "../explore/_index.md" >}}) use `shift + enter` to run the query.
### Metrics browser
#### Metrics browser
The metrics browser allows you to quickly find metrics and select relevant labels to build basic queries.
When you open the browser you will see all available metrics and labels.
If supported by your Prometheus instance, each metric will show its HELP and TYPE as a tooltip.
{{< figure src="/static/img/docs/v8/prometheus_metrics_browser.png" class="docs-image--no-shadow" max-width="800px" caption="Screenshot of the metrics browser for Prometheus" >}}
![Metrics browser](/static/img/docs/prometheus/metric-browser-8-5.png 'Metrics browser')
When you select a metric, the browser narrows down the available labels to show only the ones applicable to the metric.
You can then select one or more labels for which the available label values are shown in lists in the bottom section.
@@ -93,14 +76,77 @@ Select one or more values for each label to tighten your query scope.
> **Note:** If you do not remember a metric name to start with, you can also select a few labels first, to narrow down the list and then find relevant label values.
All lists in the metrics browser have a search field above them to quickly filter for metrics or labels that match a certain string. The values section only has one search field. Its filtering applies to all labels to help you find values across labels once they have been selected, for example, among your labels `app`, `job`, `job_name` only one might with the value you are looking for.
All lists in the metrics browser have a search field above them to quickly filter for metrics or labels that match a certain string. The values section only has one search field. It's filtering applies to all labels to help you find values across labels once they have been selected, for example, among your labels `app`, `job`, `job_name` only one might with the value you are looking for.
Once you are satisfied with your query, click "Use query" to run the query. The button "Use as rate query" adds a `rate(...)[$__interval]` around your query to help write queries for counter metrics.
The "Validate selector" button will check with Prometheus how many time series are available for that selector.
#### Limitations
#### Options
The metrics browser has a hard limit of 10,000 labels (keys) and 50,000 label values (including metric names). If your Prometheus instance returns more results, the browser will continue functioning. However, the result sets will be cut off above those maximum limits.
![Options](/static/img/docs/prometheus/options-8-5.png 'Options')
| Name | Description |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Legend` | Controls the name of the time series. Use predefined format or use custom format.<br/>`Auto` - only includes unique labels.<br/>`Verbose` - includes all labels.<br/>`Custom` - select will change to text input. Use use tamplating to select which labels will be included. For example, `{{hostname}}` is replaced by the label value for the label `hostname`. Clear the input and click outside the input to go back to select mode. |
| `Min step` | Set the lower bounds on the interval between data points. For example, set "1h" to hint that measurements are not frequent (taken hourly). `$__interval` and `$__rate_interval` are supported. |
| `Format` | You can switch between `Table` `Time series` or `Heatmap` options. The `Table` option works only in the Table panel. `Heatmap` displays metrics of the Histogram type on a Heatmap panel. Under the hood, it converts cumulative histograms to regular ones and sorts series by the bucket bound. |
| `Type` | `Range` - Query returning a Range vector, a set of time series containing a range of data points over time for each time series.<br/>`Instant` - Perform an "instant" query to return only the latest value that Prometheus has scraped for the requested time series. Instant queries can return results much faster than normal range queries. Use them to look up label sets. Instant query results are made up only of one data point per series but can be shown in the graph panel in a dashboard with the help of [series overrides]({{< relref "../visualizations/graph-panel.md#series-overrides" >}}). To show them in the graph as a latest value point, add a series override and select `Points > true`. To show a horizontal line across the whole graph, add a series override and select `Transform > constant`. <br/>`Both` - Available only in Explore. Runs both range and instant query |
| `Exemplars` | If on, run exemplars query with the regular query and show exemplars in the graph. |
> **Note:** Grafana modifies the request dates for queries to align them with the dynamically calculated step. This ensures consistent display of metrics data, but it can result in a small gap of data at the right edge of a graph.
### Builder mode
#### Toolbar
In addition to `Run query` button and mode switcher, in builder mode additional elements are available:
| Name | Description |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Query patterns | A list of useful operation patterns that can be used to quickly add multiple operations to your query to achieve a specific goal. |
| Raw query | Toggle to show raw query generated by the builder that will be sent to Prometheus instance. |
#### Metric and labels
![Metric and labels](/static/img/docs/prometheus/metric-select-8-5.png 'Metric and labels')
Select a specific metric name from the dropdown list. List of available metrics is fetched from the Prometheus server based on selected time rage. Write into the select when the dropdown is open to search and filter the list.
Select desired labels and their values from the dropdown list. When metric is selected, available labels and their values are fetched from the server. Use the `+` button to add more labels. Use the `x` button to remove a label.
#### Operations
![Operations](/static/img/docs/prometheus/operations-8-5.gif 'Operations')
Use the `+ Operations` button to add operation to your query. Operations are grouped into sections for easier navigation. When the operations dropdown is open, write into the search input to search and filter operations list.
Operations in a query are shown as boxes in the operations section. Each has a header with a name and additional action buttons. Hover over the operation header to show the action buttons. Click the `v` button to quickly replace the operation with different one of the same type. Click the `info` button to open operations' description tooltip. Click the `x` button to remove the operation.
Operation can have additional parameters under the operation header. See the operation description or Prometheus docs for more details about each operation.
Some operations make sense only in specific order, if adding an operation would result in nonsensical query, operation will be added to the correct place. To order operations manually drag operation box by the operation name and drop in appropriate place.
##### Hints
![Hint](/static/img/docs/prometheus/hint-8-5.gif 'Hint')
In same cases the query editor can detect which operations would be most appropriate for a selected metric. In such cases it will show a hint next to the `+ Operations` button. Click on the hint to add the operations to your query.
#### Raw query
![Raw query](/static/img/docs/prometheus/raw-query-8-5.gif 'Raw query')
This section is shown only if the `Raw query` switch from the query editor top toolbar is set to `on`. It shows the raw query that will be created and executed by the query editor.
#### Options
Same set of option is available as in the `Code` mode. See the [Code mode options]({{< relref "#options" >}}) for details.
### Explain mode
![Explain mode](/static/img/docs/prometheus/explain-8-5.png 'Explain mode')
Explain mode helps with understanding the query. It shows a step by step explanation of all query parts and the operations.
## Templating

View File

@@ -36,6 +36,10 @@ Fine-grained access control is available for the following capabilities:
- [Provision Grafana]({{< relref "../../administration/provisioning/_index.md" >}})
- [Manage reports]({{< relref "../reporting.md" >}})
- [View server information]({{< relref "../../administration/view-server/_index.md" >}})
- [Manage teams]({{< relref "../../administration/manage-users-and-permissions/manage-teams/_index.md" >}})
- [Manage dashboards and folders]({{< relref "../../dashboards/_index.md" >}})
- [Manage annotations]({{< relref "../../visualizations/annotations.md" >}})
- [Alerting]({{< relref "../../alerting/unified-alerting/_index.md">}})
To learn about specific endpoints where you can use fine-grained access control, refer to [Permissions]({{< relref "./permissions.md" >}}) and to the relevant [API]({{< relref "../../http_api/_index.md" >}}) documentation.
@@ -58,8 +62,13 @@ enable = accesscontrol
You can use `GF_FEATURE_TOGGLES_ENABLE = accesscontrol` environment variable to override the config file configuration and enable fine-grained access control.
Refer to [Configuring with environment variables]({{< relref "../../administration/configuration.md#configure-with-environment-variables" >}}) for more information.
Refer to [Configuring with environment variables]({{< relref "../../administration/configuration.md#/#override-configuration-with-environment-variables" >}}) for more information.
### Verify if enabled
You can verify if fine-grained access control is enabled or not by sending an HTTP request to the [Check endpoint]({{< relref "../../http_api/access_control.md#check-if-enabled" >}}).
## Caveats
If you have created a folder with unique identifier (uid) set to "general", you will not be able to manage its permissions with fine-grained access control.
Any [folder permissions]({{< relref "../../administration/manage-users-and-permissions/manage-dashboard-permissions/_index.md" >}}) set for this folder will be disregarded when fine-grained access control is enabled.

View File

@@ -54,11 +54,34 @@ The reference information that follows complements conceptual information about
| `fixed:annotations.dashboard:writer` | `annotations:write` <br>`annotations.create`<br> `annotations:delete` for scope `annotations:type:dashboard` | Create, update and delete dashboard annotations and annotation tags. |
| `fixed:annotations:writer` | `annotations:write` <br>`annotations.create`<br> `annotations:delete` for scope `annotations:type:*` | Create, update and delete all annotations and annotation tags. |
### Alerting roles
If you [enable]({{< relref "../../alerting/unified-alerting/opt-in.md" >}}) Grafana Alerting, you can use predefined roles to manage user access to alert rules, alert instances, and alert notification settings and create custom roles to limit user access to alert rules in a folder.
Access to Grafana alert rules is an intersection of many permissions:
- Permission to read a folder, for example, the fixed role `fixed:folders:reader` or action `folders:read` in the scope of a folder `folders:id:`
- Permission to manage alerts. The following table contains information about alerting fixed roles.
- Permission to query **all** data sources that the rule uses, for example, the fixed role `fixed:datasources:reader` or action `datasources:query` in the scope of `datasources:uid:`.
For more information about the permissions required to access alert rules, refer to [Create a custom role to access alerts in a folder]({{< relref "./usage-scenarios.md#create-a-custom-role-to-access-alerts-in-a-folder" >}}).
| Fixed roles | Permissions | Descriptions |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fixed:alerting.rules:reader` | `alert.rule:read` for scope `folders:*` <br> `alert.rules.external:read` for scope `datasources:*` | Read all\* Grafana, Mimir, and Loki alert rules |
| `fixed:alerting.rules:editor` | All permissions from `fixed:alerting.rules:reader` and <br> `alert.rule:create` <br> `alert.rule:update` <br> `alert.rule:delete` for scope `folders:*` <br> `alert.rules.external:write` for scope `datasources:*` | Create, update, and delete all\* Grafana, Mimir, and Loki alert rules. |
| `fixed:alerting.instances:reader` | `alert.instances:read` for organization scope <br> `alert.instances.external:read` for scope `datasources:*` | Read all alerts and silences in the organization produced by Grafana Alerts and Mimir and Loki alerts and silences. |
| `fixed:alerting.instances:editor` | All permissions from `fixed:alerting.instances:reader` and<br> `alert.instances:create`<br>`alert.instances:update` for organization scope <br> `alert.instances.external:write` for scope `datasources:*` | Create, update and expire all silences in the organization produced by Grafana, Mimir, and Loki. |
| `fixed:alerting.notifications:reader` | `alert.notifications:read` for organization scope<br>`alert.notifications.external:read` for scope `datasources:*` | Read all Grafana and Alertmanager contact points, templates, and notification policies. |
| `fixed:alerting.notifications:editor` | All permissions from `fixed:alerting.notifications:reader` and<br>`alert.notifications:create`<br>`alert.notifications:update`<br>`alert.notifications:delete` for organization scope<br>`alert.notifications.external:read` for scope `datasources:*` | Create, update, and delete contact points, templates, mute timings and notification policies for Grafana and external Alertmanager. |
| `fixed:alerting:reader` | All permissions from `fixed:alerting.rules:reader` <br>`fixed:alerting.instances:reader`<br>`fixed:alerting.notifications:reader` | Read-only permissions for all Grafana, Mimir, Loki and Alertmanager alert rules\*, alerts, contact points, and notification policies. |
| `fixed:alerting:editor` | All permissions from `fixed:alerting.rules:editor` <br>`fixed:alerting.instances:editor`<br>`fixed:alerting.notifications:editor` | Create, update, and delete Grafana, Mimir, Loki and Alertmanager alert rules\*, silences, contact points, templates, mute timings, and notification policies. |
## Default built-in role assignments
| Built-in role | Associated role | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Grafana Admin | `fixed:roles:reader`<br>`fixed:roles:writer`<br>`fixed:users:reader`<br>`fixed:users:writer`<br>`fixed:org.users:reader`<br>`fixed:org.users:writer`<br>`fixed:ldap:reader`<br>`fixed:ldap:writer`<br>`fixed:stats:reader`<br>`fixed:settings:reader`<br>`fixed:settings:writer`<br>`fixed:provisioning:writer`<br>`fixed:organization:reader`<br>`fixed:organization:maintainer`<br>`fixed:licensing:reader`<br>`fixed:licensing:writer` | Default [Grafana server administrator]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#grafana-server-administrators" >}}) assignments. |
| Admin | `fixed:reports:reader`<br>`fixed:reports:writer`<br>`fixed:datasources:reader`<br>`fixed:datasources:writer`<br>`fixed:organization:writer`<br>`fixed:datasources.permissions:reader`<br>`fixed:datasources.permissions:writer`<br>`fixed:teams:writer`<br>`fixed:dashboards:reader`<br>`fixed:dashboards:writer`<br>`fixed:dashboards.permissions:reader`<br>`fixed:dashboards.permissions:writer`<br>`fixed:folders:reader`<br>`fixes:folders:writer`<br>`fixed:folders.permissions:reader`<br>`fixed:folders.permissions:writer` | Default [Grafana organization administrator]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. |
| Editor | `fixed:datasources:explorer`<br>`fixed:dashboards:creator`<br>`fixed:folders:creator`<br>`fixed:annotations:writer`<br>`fixed:teams:creator` if the `editors_can_admin` configuration flag is enabled | Default [Editor]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. |
| Viewer | `fixed:datasources:id:reader`<br>`fixed:organization:reader`<br>`fixed:annotations:reader`<br>`fixed:annotations.dashboard:writer` | Default [Viewer]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. |
| Built-in role | Associated role | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Grafana Admin | `fixed:roles:reader`<br>`fixed:roles:writer`<br>`fixed:users:reader`<br>`fixed:users:writer`<br>`fixed:org.users:reader`<br>`fixed:org.users:writer`<br>`fixed:ldap:reader`<br>`fixed:ldap:writer`<br>`fixed:stats:reader`<br>`fixed:settings:reader`<br>`fixed:settings:writer`<br>`fixed:provisioning:writer`<br>`fixed:organization:reader`<br>`fixed:organization:maintainer`<br>`fixed:licensing:reader`<br>`fixed:licensing:writer` | Default [Grafana server administrator]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#grafana-server-administrators" >}}) assignments. |
| Admin | `fixed:reports:reader`<br>`fixed:reports:writer`<br>`fixed:datasources:reader`<br>`fixed:datasources:writer`<br>`fixed:organization:writer`<br>`fixed:datasources.permissions:reader`<br>`fixed:datasources.permissions:writer`<br>`fixed:teams:writer`<br>`fixed:dashboards:reader`<br>`fixed:dashboards:writer`<br>`fixed:dashboards.permissions:reader`<br>`fixed:dashboards.permissions:writer`<br>`fixed:folders:reader`<br>`fixes:folders:writer`<br>`fixed:folders.permissions:reader`<br>`fixed:folders.permissions:writer`<br>`fixed:alerting:editor` | Default [Grafana organization administrator]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. |
| Editor | `fixed:datasources:explorer`<br>`fixed:dashboards:creator`<br>`fixed:folders:creator`<br>`fixed:annotations:writer`<br>`fixed:teams:creator` if the `editors_can_admin` configuration flag is enabled<br>`fixed:alerting:editor` | Default [Editor]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. |
| Viewer | `fixed:datasources:id:reader`<br>`fixed:organization:reader`<br>`fixed:annotations:reader`<br>`fixed:annotations.dashboard:writer`<br>`fixed:alerting:reader` | Default [Viewer]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. |

View File

@@ -23,100 +23,117 @@ scope
The following list contains fine-grained access control actions.
| Action | Applicable scope | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `roles:list` | `roles:*` | List available roles without permissions. |
| `roles:read` | `roles:*` <br> `roles:uid:*` | Read a specific role with its permissions. |
| `roles:write` | `permissions:delegate` | Create or update a custom role. |
| `roles:delete` | `permissions:delegate` | Delete a custom role. |
| `roles.builtin:list` | `roles:*` | List built-in role assignments. |
| `roles.builtin:add` | `permissions:delegate` | Create a built-in role assignment. |
| `roles.builtin:remove` | `permissions:delegate` | Delete a built-in role assignment. |
| `reports.admin:create` | n/a | Create reports. |
| `reports.admin:write` | `reports:*` <br> `reports:id:*` | Update reports. |
| `reports:delete` | `reports:*` <br> `reports:id:*` | Delete reports. |
| `reports:read` | `reports:*` | List all available reports or get a specific report. |
| `reports:send` | `reports:*` | Send a report email. |
| `reports.settings:write` | n/a | Update report settings. |
| `reports.settings:read` | n/a | Read report settings. |
| `provisioning:reload` | `provisioners:*` | Reload provisioning files. To find the exact scope for specific provisioner, see [Scope definitions]({{< relref "./permissions.md#scope-definitions" >}}). |
| `teams.roles:list` | `teams:*` | List roles assigned directly to a team. |
| `teams.roles:add` | `permissions:delegate` | Assign a role to a team. |
| `teams.roles:remove` | `permissions:delegate` | Unassign a role from a team. |
| `users:read` | `global.users:*` | Read or search user profiles. |
| `users:write` | `global.users:*` <br> `global.users:id:*` | Update a users profile. |
| `users.teams:read` | `global.users:*` <br> `global.users:id:*` | Read a users teams. |
| `users.authtoken:list` | `global.users:*` <br> `global.users:id:*` | List authentication tokens that are assigned to a user. |
| `users.authtoken:update` | `global.users:*` <br> `global.users:id:*` | Update authentication tokens that are assigned to a user. |
| `users.password:update` | `global.users:*` <br> `global.users:id:*` | Update a users password. |
| `users:delete` | `global.users:*` <br> `global.users:id:*` | Delete a user. |
| `users:create` | n/a | Create a user. |
| `users:enable` | `globa.users:*` <br> `global.users:id:*` | Enable a user. |
| `users:disable` | `global.users:*` <br> `global.users:id:*` | Disable a user. |
| `users.permissions:update` | `global.users:*` <br> `global.users:id:*` | Update a users organization-level permissions. |
| `users:logout` | `global.users:*` <br> `global.users:id:*` | Sign out a user. |
| `users.quotas:list` | `global.users:*` <br> `global.users:id:*` | List a users quotas. |
| `users.quotas:update` | `global.users:*` <br> `global.users:id:*` | Update a users quotas. |
| `users.roles:list` | `users:*` | List roles assigned directly to a user. |
| `users.roles:add` | `permissions:delegate` | Assign a role to a user. |
| `users.roles:remove` | `permissions:delegate` | Unassign a role from a user. |
| `users.permissions:list` | `users:*` | List permissions of a user. |
| `org.users:read` | `users:*` <br> `users:id:*` | Get user profiles within an organization. |
| `org.users:add` | `users:*` | Add a user to an organization. |
| `org.users:remove` | `users:*` <br> `users:id:*` | Remove a user from an organization. |
| `org.users.role:update` | `users:*` <br> `users:id:*` | Update the organization role (`Viewer`, `Editor`, or `Admin`) of an organization. |
| `orgs:read` | `orgs:*` <br> `orgs:id:*` | Read one or more organizations. |
| `orgs:write` | `orgs:*` <br> `orgs:id:*` | Update one or more organizations. |
| `org:create` | n/a | Create an organization. |
| `orgs:delete` | `orgs:*` <br> `orgs:id:*` | Delete one or more organizations. |
| `orgs.quotas:read` | `orgs:*` <br> `orgs:id:*` | Read organization quotas. |
| `orgs.quotas:write` | `orgs:*` <br> `orgs:id:*` | Update organization quotas. |
| `orgs.preferences:read` | `orgs:*` <br> `orgs:id:*` | Read organization preferences. |
| `orgs.preferences:write` | `orgs:*` <br> `orgs:id:*` | Update organization preferences. |
| `ldap.user:read` | n/a | Read users via LDAP. |
| `ldap.user:sync` | n/a | Sync users via LDAP. |
| `ldap.status:read` | n/a | Verify the availability of the LDAP server or servers. |
| `ldap.config:reload` | n/a | Reload the LDAP configuration. |
| `status:accesscontrol` | `services:accesscontrol` | Get access-control enabled status. |
| `settings:read` | `settings:*`<br>`settings:auth.saml:*`<br>`settings:auth.saml:enabled` (property level) | Read the [Grafana configuration settings]({{< relref "../../administration/configuration/_index.md" >}}) |
| `settings:write` | `settings:*`<br>`settings:auth.saml:*`<br>`settings:auth.saml:enabled` (property level) | Update any Grafana configuration settings that can be [updated at runtime]({{< relref "../../enterprise/settings-updates/_index.md" >}}). |
| `server.stats:read` | n/a | Read Grafana instance statistics. |
| `datasources:explore` | n/a | Enable access to the **Explore** tab. |
| `datasources:read` | n/a<br>`datasources:*`<br>`datasources:id:*`<br>`datasources:uid:*`<br>`datasources:name:*` | List data sources. |
| `datasources:query` | n/a<br>`datasources:*`<br>`datasources:id:*` | Query data sources. |
| `datasources.id:read` | `datasources:*`<br>`datasources:name:*` | Read data source IDs. |
| `datasources:create` | n/a | Create data sources. |
| `datasources:write` | `datasources:*`<br>`datasources:id:*` | Update data sources. |
| `datasources:delete` | `datasources:id:*`<br>`datasources:uid:*`<br>`datasources:name:*` | Delete data sources. |
| `datasources.permissions:read` | `datasources:*`<br>`datasources:id:*` | List data source permissions. |
| `datasources.permissions:write` | `datasources:*`<br>`datasources:id:*` | Update data source permissions. |
| `licensing:read` | n/a | Read licensing information. |
| `licensing:update` | n/a | Update the license token. |
| `licensing:delete` | n/a | Delete the license token. |
| `licensing.reports:read` | n/a | Get custom permission reports. |
| `teams:create` | n/a | Create teams. |
| `teams:read` | `teams:*`<br>`teams:id:*` | Read one or more teams and team preferences. |
| `teams:write` | `teams:*`<br>`teams:id:*` | Update one or more teams and team preferences. |
| `teams:delete` | `teams:*`<br>`teams:id:*` | Delete one or more teams. |
| `teams.permissions:read` | `teams:*`<br>`teams:id:*` | Read members and External Group Synchronization setup for teams. |
| `teams.permissions:write` | `teams:*`<br>`teams:id:*` | Add, remove and update members and manage External Group Synchronization setup for teams. |
| `dashboards:read` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Read one or more dashboards. |
| `dashboards:create` | `folders:*`<br>`folders:id:*` | Create dashboards in one or more folders. |
| `dashboards:write` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Update one or more dashboards. |
| `dashboards:edit` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Edit one or more dashboards (only in ui). |
| `dashboards:delete` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Delete one or more dashboards. |
| `dashboards.permissions:read` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Read permissions for one or more dashboards. |
| `dashboards.permissions:write` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Update permissions for one or more dashboards. |
| `folders:read` | `folders:*`<br>`folders:id:*` | Read one or more folders. |
| `folders:create` | n/a | Create folders. |
| `folders:write` | `folders:*`<br>`folders:id:*` | Update one or more folders. |
| `folders:delete` | `folders:*`<br>`folders:id:*` | Delete one or more folders. |
| `folers.permissions:read` | `folders:*`<br>`folders:id:*` | Read permissions for one or more folders. |
| `folders.permissions:write` | `folders:*`<br>`folders:id:*` | Update permissions for one or more folders. |
| `annotations.read` | `annotations:*`<br>`annotations:type:*` | Read annotations and annotation tags. |
| `annotations.create` | `annotations:*`<br>`annotations:type:*` | Create annotations. |
| `annotations.write` | `annotations:*`<br>`annotations:type:*` | Update annotations. |
| `annotations.delete` | `annotations:*`<br>`annotations:type:*` | Delete annotations. |
| Action | Applicable scope | Description |
| ------------------------------------ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `roles:list` | `roles:*` | List available roles without permissions. |
| `roles:read` | `roles:*` <br> `roles:uid:*` | Read a specific role with its permissions. |
| `roles:write` | `permissions:delegate` | Create or update a custom role. |
| `roles:delete` | `permissions:delegate` | Delete a custom role. |
| `roles.builtin:list` | `roles:*` | List built-in role assignments. |
| `roles.builtin:add` | `permissions:delegate` | Create a built-in role assignment. |
| `roles.builtin:remove` | `permissions:delegate` | Delete a built-in role assignment. |
| `reports.admin:create` | n/a | Create reports. |
| `reports.admin:write` | `reports:*` <br> `reports:id:*` | Update reports. |
| `reports:delete` | `reports:*` <br> `reports:id:*` | Delete reports. |
| `reports:read` | `reports:*` | List all available reports or get a specific report. |
| `reports:send` | `reports:*` | Send a report email. |
| `reports.settings:write` | n/a | Update report settings. |
| `reports.settings:read` | n/a | Read report settings. |
| `provisioning:reload` | `provisioners:*` | Reload provisioning files. To find the exact scope for specific provisioner, see [Scope definitions]({{< relref "./permissions.md#scope-definitions" >}}). |
| `teams.roles:list` | `teams:*` | List roles assigned directly to a team. |
| `teams.roles:add` | `permissions:delegate` | Assign a role to a team. |
| `teams.roles:remove` | `permissions:delegate` | Unassign a role from a team. |
| `users:read` | `global.users:*` | Read or search user profiles. |
| `users:write` | `global.users:*` <br> `global.users:id:*` | Update a users profile. |
| `users.teams:read` | `global.users:*` <br> `global.users:id:*` | Read a users teams. |
| `users.authtoken:list` | `global.users:*` <br> `global.users:id:*` | List authentication tokens that are assigned to a user. |
| `users.authtoken:update` | `global.users:*` <br> `global.users:id:*` | Update authentication tokens that are assigned to a user. |
| `users.password:update` | `global.users:*` <br> `global.users:id:*` | Update a users password. |
| `users:delete` | `global.users:*` <br> `global.users:id:*` | Delete a user. |
| `users:create` | n/a | Create a user. |
| `users:enable` | `globa.users:*` <br> `global.users:id:*` | Enable a user. |
| `users:disable` | `global.users:*` <br> `global.users:id:*` | Disable a user. |
| `users.permissions:update` | `global.users:*` <br> `global.users:id:*` | Update a users organization-level permissions. |
| `users:logout` | `global.users:*` <br> `global.users:id:*` | Sign out a user. |
| `users.quotas:list` | `global.users:*` <br> `global.users:id:*` | List a users quotas. |
| `users.quotas:update` | `global.users:*` <br> `global.users:id:*` | Update a users quotas. |
| `users.roles:list` | `users:*` | List roles assigned directly to a user. |
| `users.roles:add` | `permissions:delegate` | Assign a role to a user. |
| `users.roles:remove` | `permissions:delegate` | Unassign a role from a user. |
| `users.permissions:list` | `users:*` | List permissions of a user. |
| `org.users:read` | `users:*` <br> `users:id:*` | Get user profiles within an organization. |
| `org.users:add` | `users:*` | Add a user to an organization. |
| `org.users:remove` | `users:*` <br> `users:id:*` | Remove a user from an organization. |
| `org.users.role:update` | `users:*` <br> `users:id:*` | Update the organization role (`Viewer`, `Editor`, or `Admin`) of an organization. |
| `orgs:read` | `orgs:*` <br> `orgs:id:*` | Read one or more organizations. |
| `orgs:write` | `orgs:*` <br> `orgs:id:*` | Update one or more organizations. |
| `org:create` | n/a | Create an organization. |
| `orgs:delete` | `orgs:*` <br> `orgs:id:*` | Delete one or more organizations. |
| `orgs.quotas:read` | `orgs:*` <br> `orgs:id:*` | Read organization quotas. |
| `orgs.quotas:write` | `orgs:*` <br> `orgs:id:*` | Update organization quotas. |
| `orgs.preferences:read` | `orgs:*` <br> `orgs:id:*` | Read organization preferences. |
| `orgs.preferences:write` | `orgs:*` <br> `orgs:id:*` | Update organization preferences. |
| `ldap.user:read` | n/a | Read users via LDAP. |
| `ldap.user:sync` | n/a | Sync users via LDAP. |
| `ldap.status:read` | n/a | Verify the availability of the LDAP server or servers. |
| `ldap.config:reload` | n/a | Reload the LDAP configuration. |
| `status:accesscontrol` | `services:accesscontrol` | Get access-control enabled status. |
| `settings:read` | `settings:*`<br>`settings:auth.saml:*`<br>`settings:auth.saml:enabled` (property level) | Read the [Grafana configuration settings]({{< relref "../../administration/configuration/_index.md" >}}) |
| `settings:write` | `settings:*`<br>`settings:auth.saml:*`<br>`settings:auth.saml:enabled` (property level) | Update any Grafana configuration settings that can be [updated at runtime]({{< relref "../../enterprise/settings-updates/_index.md" >}}). |
| `server.stats:read` | n/a | Read Grafana instance statistics. |
| `datasources:explore` | n/a | Enable access to the **Explore** tab. |
| `datasources:read` | n/a<br>`datasources:*`<br>`datasources:id:*`<br>`datasources:uid:*`<br>`datasources:name:*` | List data sources. |
| `datasources:query` | n/a<br>`datasources:*`<br>`datasources:id:*` | Query data sources. |
| `datasources.id:read` | `datasources:*`<br>`datasources:name:*` | Read data source IDs. |
| `datasources:create` | n/a | Create data sources. |
| `datasources:write` | `datasources:*`<br>`datasources:id:*` | Update data sources. |
| `datasources:delete` | `datasources:id:*`<br>`datasources:uid:*`<br>`datasources:name:*` | Delete data sources. |
| `datasources.permissions:read` | `datasources:*`<br>`datasources:id:*` | List data source permissions. |
| `datasources.permissions:write` | `datasources:*`<br>`datasources:id:*` | Update data source permissions. |
| `licensing:read` | n/a | Read licensing information. |
| `licensing:update` | n/a | Update the license token. |
| `licensing:delete` | n/a | Delete the license token. |
| `licensing.reports:read` | n/a | Get custom permission reports. |
| `teams:create` | n/a | Create teams. |
| `teams:read` | `teams:*`<br>`teams:id:*` | Read one or more teams and team preferences. |
| `teams:write` | `teams:*`<br>`teams:id:*` | Update one or more teams and team preferences. |
| `teams:delete` | `teams:*`<br>`teams:id:*` | Delete one or more teams. |
| `teams.permissions:read` | `teams:*`<br>`teams:id:*` | Read members and External Group Synchronization setup for teams. |
| `teams.permissions:write` | `teams:*`<br>`teams:id:*` | Add, remove and update members and manage External Group Synchronization setup for teams. |
| `dashboards:read` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Read one or more dashboards. |
| `dashboards:create` | `folders:*`<br>`folders:id:*` | Create dashboards in one or more folders. |
| `dashboards:write` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Update one or more dashboards. |
| `dashboards:edit` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Edit one or more dashboards (only in ui). |
| `dashboards:delete` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Delete one or more dashboards. |
| `dashboards.permissions:read` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Read permissions for one or more dashboards. |
| `dashboards.permissions:write` | `dashboards:*`<br>`dashboards:id:*`<br>`folders:*`<br>`folders:id:*` | Update permissions for one or more dashboards. |
| `folders:read` | `folders:*`<br>`folders:id:*` | Read one or more folders. |
| `folders:create` | n/a | Create folders. |
| `folders:write` | `folders:*`<br>`folders:id:*` | Update one or more folders. |
| `folders:delete` | `folders:*`<br>`folders:id:*` | Delete one or more folders. |
| `folers.permissions:read` | `folders:*`<br>`folders:id:*` | Read permissions for one or more folders. |
| `folders.permissions:write` | `folders:*`<br>`folders:id:*` | Update permissions for one or more folders. |
| `annotations.read` | `annotations:*`<br>`annotations:type:*` | Read annotations and annotation tags. |
| `annotations.create` | `annotations:*`<br>`annotations:type:*` | Create annotations. |
| `annotations.write` | `annotations:*`<br>`annotations:type:*` | Update annotations. |
| `annotations.delete` | `annotations:*`<br>`annotations:type:*` | Delete annotations. |
| `alert.rules:read` | `folders:*`<br>`folders:id:*` | Read Grafana alert rules in a folder. Combine this permission with `folders:read` in a scope that includes the folder and `datasources:query` in the scope of data sources the user can query. |
| `alert.rules:create` | `folders:*`<br>`folders:id:*` | Create Grafana alert rules in a folder. Combine this permission with `folders:read` in a scope that includes the folder and `datasources:query` in the scope of data sources the user can query. |
| `alert.rules:update` | `folders:*`<br>`folders:id:*` | Update Grafana alert rules in a folder. Combine this permission with `folders:read` in a scope that includes the folder and `datasources:query` in the scope of data sources the user can query. |
| `alert.rules:delete` | `folders:*`<br>`folders:id:*` | Delete Grafana alert rules in a folder. Combine this permission with `folders:read` in a scope that includes the folder and `datasources:query` in the scope of data sources the user can query. |
| `alert.rules.external:read` | `datasources:*`<br>`datasources:uid:*` | Read alert rules in data sources that support alerting (Prometheus, Mimir, and Loki) |
| `alert.rules.external:write` | `datasources:*`<br>`datasources:uid:*` | Create, update, and delete alert rules in data sources that support alerting (Mimir and Loki). |
| `alert.instances:read` | n/a | Read alerts and silences in the current organization. |
| `alert.instances:create` | n/a | Create silences in the current organization. |
| `alert.instances:update` | n/a | Update and expire silences in the current organization. |
| `alert.instances.external:read` | `datasources:*`<br>`datasources:uid:*` | Read alerts and silences in data sources that support alerting. |
| `alert.instances.external:write` | `datasources:*`<br>`datasources:uid:*` | Manage alerts and silences in data sources that support alerting. |
| `alert.notifications:create` | n/a | Create templates, contact points, notification policies, and mute timings in the current organization. |
| `alert.notifications:read` | n/a | Read all templates, contact points, notification policies, and mute timings in the current organization. |
| `alert.notifications:update` | n/a | Update templates, contact points, notification policies, and mute timings in the current organization. |
| `alert.notifications:delete` | n/a | Delete templates, contact points, notification policies, and mute timings in the current organization. |
| `alert.notifications.external:read` | `datasources:*`<br>`datasources:uid:*` | Read templates, contact points, notification policies, and mute timings in data sources that support alerting. |
| `alert.notifications.external:write` | `datasources:*`<br>`datasources:uid:*` | Manage templates, contact points, notification policies, and mute timings in data sources that support alerting. |
## Scope definitions

View File

@@ -231,3 +231,46 @@ By default, the Grafana Server Admin is the only user who can create and manage
1. [Create a custom role]({{< ref "#create-your-custom-role" >}}) with `roles.builtin:add` and `roles:write` permissions, then create a built-in role assignment for `Editor` organization role.
Note that any user with the ability to modify roles can only create, update or delete roles with permissions they themselves have been granted. For example, a user with the `Editor` role would be able to create and manage roles only with the permissions they have, or with a subset of them.
## Create a custom role to access alerts in a folder
To see an alert rule in Grafana, the user must have read access to the folder that stores the alert rule, permission to read alerts in the folder, and permission to query all data sources that the rule uses.
The API command in this example is based on the following:
- A `Test-Folder` with ID `92`
- Two data sources: `DS1` with UID `_oAfGYUnk`, and `DS2` with UID `YYcBGYUnk`
- An alert rule that is stored in `Test-Folder` and queries the two data sources.
The following request creates a custom role that includes permissions to access the alert rule:
```
curl --location --request POST '<grafana_url>/api/access-control/roles/' \
--header 'Authorization: Basic YWRtaW46cGFzc3dvcmQ=' \
--header 'Content-Type: application/json' \
--data-raw '{
"version": 1,
"name": "custom:alerts.reader.in.folder.123",
"displayName": "Read-only access to alerts in folder Test-Folder",
"description": "Let user query DS1 and DS2, and read alerts in folder Test-Folders",
"group":"Custom",
"global": true,
"permissions": [
{
"action": "folders:read",
"scope": "folders:id:92"
},
{
"action": "alert.rules:read",
"scope": "folders:id:92"
},
{
"action": "datasources:query",
"scope": "datasources:uid:_oAfGYUnk"
},
{
"action": "datasources:query",
"scope": "datasources:uid:YYcBGYUnk"
}
]
}'
```

View File

@@ -29,7 +29,7 @@ Your Grafana license includes a maximum number of _Viewer_ and _Editor/Admin_ ac
- An _active user_ is a user who has signed in to Grafana within the last 30 days. This is a rolling window that is updated daily.
- When you reach the number of maximum active viewers or editor/admins, only currently active users can sign in; new users and non-active users cannot sign in when you reach the limit.
- Grafana applies sign-in restrictions separately for viewers and editor/admins. If your Grafana license reaches its limit of active viewers but not its limit of active editor/ddmins, new editors and admins can still sign in.
- Grafana applies sign-in restrictions separately for viewers and editor/admins. If your Grafana license reaches its limit of active viewers but not its limit of active editor/admins, new editors and admins can still sign in.
- The number of dashboards that a user can view or edit, and the number of organizations that they can access does not affect the active user count. A user with editor permissions for many dashboards across many different organizations counts as one editor.
- A license limit banner appears to administrators when Grafana reaches its active user limit; editors and viewers do not see the banner.
To change user roles to make better use of your licenses, refer to [Optimize your tiered license](#optimize-your-tiered-license).
@@ -123,7 +123,7 @@ After you apply the token, Grafana Enterprise resets your license and updates th
> If you are running Grafana Enterprise 8.2 or earlier, the license grants you the total number of licensed users _for each user type_.
For example, if your current license includes 60 viewers and 40 dditor/admins, the new license includes 100 viewers and 100 editor/admins. Grafana Enterprise 8.3 removes the distinction between viewers and editor/admins as shown on the **Utilization** panel.
For example, if your current license includes 60 viewers and 40 editor/admins, the new license includes 100 viewers and 100 editor/admins. Grafana Enterprise 8.3 removes the distinction between viewers and editor/admins as shown on the **Utilization** panel.
Before you upgrade to Grafana 8.3, ensure that the total number of active users in Grafana does not exceed the number of users in your combined license. If it does, then new users cannot sign in to Grafana 8.3 until the active user count returns below the licensed limit.

View File

@@ -46,6 +46,7 @@ The table below describes all SAML configuration options. Continue reading below
| ---------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `enabled` | No | Whether SAML authentication is allowed | `false` |
| `single_logout` | No | Whether SAML Single Logout enabled | `false` |
| `allow_sign_up` | No | Whether to allow new Grafana user creation through SAML login. If set to `false`, then only existing Grafana users can log in with SAML. | `true` |
| `allow_idp_initiated` | No | Whether SAML IdP-initiated login is allowed | `false` |
| `certificate` or `certificate_path` | Yes | Base64-encoded string or Path for the SP X.509 certificate | |
| `private_key` or `private_key_path` | Yes | Base64-encoded string or Path for the SP private key | |
@@ -142,6 +143,10 @@ For Grafana to map the user information, it looks at the individual attributes w
Grafana provides configuration options that let you modify which keys to look at for these values. The data we need to create the user in Grafana is Name, Login handle, and email.
### Allow new user signups
By default, new Grafana users using SAML authentication will have an account created for them automatically. To decouple authentication and account creation and ensure only users with existing accounts can log in with SAML, set the `allow_sign_up` option to false.
### Configure team sync
> Team sync support for SAML only available in Grafana v7.0+

View File

@@ -55,16 +55,16 @@ In split view, timepickers for both panels can be linked (if you change one, the
To close the newly created query, click on the Close Split button.
## Navigate between Explore and a dashboard
To help accelerate workflows that involve regularly switching from Explore to a dashboard and vice-versa, Grafana provides you with the ability to return to the origin dashboard after navigating to Explore from the panel's dropdown.
After you've navigated to Explore, you should notice a "Back" button in the Explore toolbar. Simply click it to return to the origin dashboard. To bring changes you make in Explore back to the dashboard, click the arrow next to the button to reveal a "Return to panel with changes" menu item.
{{< figure src="/static/img/docs/explore/explore_return_dropdown-7-4.png" class="docs-image--no-shadow" max-width= "400px" caption="Screenshot of the expanded explore return dropdown" >}}
## Share shortened link
> **Note:** Available in Grafana 7.3 and later versions.
The Share shortened link capability allows you to create smaller and simpler URLs of the format /goto/:uid instead of using longer URLs with query parameters. To create a shortened link to the executed query, click the **Share** option in the Explore toolbar. A shortened link that is never used will automatically get deleted after seven (7) days.
## Available feature toggles
### explore2Dashboard
> **Note:** Available in Grafana 8.5.0 and later versions.
Enabled by default, allows users to create panels in dashboards from within Explore.

View File

@@ -97,6 +97,17 @@ You can change the order of received logs from the default descending order (new
Each log row has an extendable area with its labels and detected fields, for more robust interaction. For all labels we have added the ability to filter for (positive filter) and filter out (negative filter) selected labels. Each field or label also has a stats icon to display ad-hoc statistics in relation to all displayed logs.
### Escaping newlines
Explore automatically detects some incorrectly escaped sequences in log lines, such as newlines (`\n`, `\r`) or tabs (`\t`). When it detects such sequences, Explore provides an "Escape newlines" option.
To automatically fix incorrectly escaped sequences that Explore has detected:
1. Click "Escape newlines" to replace the sequences.
2. Manually review the replacements to confirm their correctness.
Explore replaces these sequences. When it does so, the option will change from "Escape newlines" to "Remove escaping". Evaluate the changes as the parsing may not be accurate based on the input received. You can revert the replacements by clicking "Remove escaping".
#### Derived fields links
By using Derived fields, you can turn any part of a log message into an internal or external link. The created link is visible as a button next to the Detected field in the Log details view.

View File

@@ -20,9 +20,9 @@ This is the API documentation for the new Grafana Annotations feature released i
See note in the [introduction]({{< ref "#annotations-api" >}}) for an explanation.
| Action | Scope |
| ---------------- | -------------- |
| annotations:read | annotations:\* |
| Action | Scope |
| ---------------- | ----------------------- |
| annotations:read | annotations:type:<type> |
**Example Request**:

View File

@@ -0,0 +1,338 @@
+++
title = "Service account HTTP API "
description = "Grafana service account HTTP API"
keywords = ["grafana", "http", "documentation", "api", "serviceaccount"]
aliases = ["/docs/grafana/latest/http_api/serviceaccount/"]
+++
# Service account API
> If you are running Grafana Enterprise and have [Fine-grained access control]({{< relref "../enterprise/access-control/_index.md" >}}) enabled, for some endpoints you would need to have relevant permissions.
> Refer to specific resources to understand what permissions are required.
## Search service accounts with Paging
`GET /api/serviceaccounts/search?perpage=10&page=1&query=myserviceaccount`
#### Required permissions
See note in the [introduction]({{< ref "#user-api" >}}) for an explanation.
| Action | Scope |
| -------------------- | ------------------------- |
| serviceaccounts:read | global:serviceaccounts:\* |
**Example Request**:
```http
GET /api/serviceaccounts/search?perpage=10&page=1&query=mygraf HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
```
Default value for the `perpage` parameter is `1000` and for the `page` parameter is `1`. The `totalCount` field in the response can be used for pagination of the user list E.g. if `totalCount` is equal to 100 users and the `perpage` parameter is set to 10 then there are 10 pages of users. The `query` parameter is optional and it will return results where the query value is contained in one of the `name`. Query values with spaces need to be URL encoded e.g. `query=Jane%20Doe`.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
{
```
## Create service account
`POST /api/serviceaccounts`
#### Required permissions
See note in the [introduction]({{< ref "#serviceaccount-api" >}}) for an explanation.
| Action | Scope |
| --------------------- | ------------------ |
| serviceaccounts:write | serviceaccounts:\* |
**Example Request**:
```http
POST /api/serviceaccounts HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
```
Requires basic authentication and that the authenticated user is a Grafana Admin.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
```
## Get single serviceaccount by Id
`GET /api/serviceaccounts/:id`
#### Required permissions
See note in the [introduction]({{< ref "#serviceaccount-api" >}}) for an explanation.
| Action | Scope |
| -------------------- | ------------------ |
| serviceaccounts:read | serviceaccounts:\* |
**Example Request**:
```http
GET /api/serviceaccounts/1 HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
```
Requires basic authentication and that the authenticated user is a Grafana Admin.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
```
## Update service account
`PATCH /api/serviceaccounts/:id`
#### Required permissions
See note in the [introduction]({{< ref "#serviceaccount-api" >}}) for an explanation.
| Action | Scope |
| --------------------- | ------------------ |
| serviceaccounts:write | serviceaccounts:\* |
**Example Request**:
```http
PUT /api/serviceaccounts/2 HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
```
Requires basic authentication and that the authenticated user is a Grafana Admin.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
```
---
## Service account tokens
## Get service account tokens
`GET /api/serviceaccounts/:id/tokens`
#### Required permissions
See note in the [introduction]({{< ref "#serviceaccount-api" >}}) for an explanation.
| Action | Scope |
| -------------------- | ------------------ |
| serviceaccounts:read | serviceaccounts:\* |
**Example Request**:
```http
GET /api/serviceaccounts/2/tokens HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
```
Requires basic authentication and that the authenticated user is a Grafana Admin.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
```
## Create service account tokens
`POST /api/serviceaccounts/:id/tokens`
#### Required permissions
See note in the [introduction]({{< ref "#serviceaccount-api" >}}) for an explanation.
| Action | Scope |
| --------------------- | ------------------ |
| serviceaccounts:write | serviceaccounts:\* |
**Example Request**:
```http
POST /api/serviceaccounts/2/tokens HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
```
Requires basic authentication and that the authenticated user is a Grafana Admin.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
```
## Delete service account tokens
`DELETE /api/serviceaccounts/:id/tokens/:tokenId`
#### Required permissions
See note in the [introduction]({{< ref "#serviceaccount-api" >}}) for an explanation.
| Action | Scope |
| --------------------- | ------------------ |
| serviceaccounts:write | serviceaccounts:\* |
**Example Request**:
```http
DELETE /api/serviceaccounts/2/tokens/1 HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
```
Requires basic authentication and that the authenticated user is a Grafana Admin.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
```
```http
GET /api/serviceaccounts/2/tokens HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
```
Requires basic authentication and that the authenticated user is a Grafana Admin.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
[
{
"id": 1,
"name": "grafana",
"role": "Viewer",
"created": "2022-03-23T10:31:02Z",
"expiration": null,
"secondsUntilExpiration": 0,
"hasExpired": false
}
]
```
## Create service account tokens
`POST /api/serviceaccounts/:id/tokens`
#### Required permissions
See note in the [introduction]({{< ref "#serviceaccount-api" >}}) for an explanation.
| Action | Scope |
| --------------------- | ------------------ |
| serviceaccounts:write | serviceaccounts:\* |
**Example Request**:
```http
POST /api/serviceaccounts/2/tokens HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
{
"name": "grafana",
"role": "Viewer"
}
```
Requires basic authentication and that the authenticated user is a Grafana Admin.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
{
"id": 7,
"name": "grafana",
"key": "eyJrIjoiVjFxTHZ6dGdPSjg5Um92MjN1RlhjMkNqYkZUbm9jYkwiLCJuIjoiZ3JhZmFuYSIsImlkIjoxfQ=="
}
```
## Delete service account tokens
`DELETE /api/serviceaccounts/:id/tokens/:tokenId`
#### Required permissions
See note in the [introduction]({{< ref "#serviceaccount-api" >}}) for an explanation.
| Action | Scope |
| --------------------- | ------------------ |
| serviceaccounts:write | serviceaccounts:\* |
**Example Request**:
```http
DELETE /api/serviceaccounts/2/tokens/1 HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
```
Requires basic authentication and that the authenticated user is a Grafana Admin.
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
{
"message": "API key deleted"
}
```

View File

@@ -16,6 +16,8 @@ You can install and run Grafana using the official Docker images. Our docker ima
Each edition is available in two variants: Alpine and Ubuntu. See below.
For documentation regarding the configuration of a docker image, refer to [configure a Grafana Docker image](https://grafana.com/docs/grafana/latest/administration/configure-docker/).
This topic also contains important information about [migrating from earlier Docker image versions](#migrate-from-previous-docker-containers-versions).
> **Note:** You can use [Grafana Cloud](https://grafana.com/products/cloud/features/#cloud-logs) to avoid the overhead of installing, maintaining, and scaling your observability stack. The free forever plan includes Grafana, 10K Prometheus series, 50 GB logs, and more.[Create a free account to get started](https://grafana.com/auth/sign-up/create-user?pg=docs-grafana-install&plcmt=in-text).

View File

@@ -8,6 +8,9 @@ weight = 10000
Here you can find detailed release notes that list everything that is included in every release as well as notices
about deprecations, breaking changes as well as changes that relate to plugin development.
- [Release notes for 8.5.0-beta1]({{< relref "release-notes-8-5-0-beta1" >}})
- [Release notes for 8.4.7]({{< relref "release-notes-8-4-7" >}})
- [Release notes for 8.4.6]({{< relref "release-notes-8-4-6" >}})
- [Release notes for 8.4.5]({{< relref "release-notes-8-4-5" >}})
- [Release notes for 8.4.4]({{< relref "release-notes-8-4-4" >}})
- [Release notes for 8.4.3]({{< relref "release-notes-8-4-3" >}})

View File

@@ -0,0 +1,10 @@
+++
title = "Release notes for Grafana 8.4.6"
hide_menu = true
+++
<!-- Auto generated by update changelog github action -->
# Release notes for Grafana 8.4.6
- **Security:** Fixes CVE-2022-24812. For more information, see our [blog](https://grafana.com/blog/2022/04/12/grafana-enterprise-8.4.6-released-with-high-severity-security-fix/)

View File

@@ -0,0 +1,19 @@
+++
title = "Release notes for Grafana 8.4.7"
hide_menu = true
+++
<!-- Auto generated by update changelog github action -->
# Release notes for Grafana 8.4.7
### Features and enhancements
- **CloudWatch:** Added missing MemoryDB Namespace metrics. [#47290](https://github.com/grafana/grafana/pull/47290), [@james-deee](https://github.com/james-deee)
- **Histogram Panel:** Take decimal into consideration. [#47330](https://github.com/grafana/grafana/pull/47330), [@mdvictor](https://github.com/mdvictor)
- **TimeSeries:** Sort tooltip values based on raw values. [#46738](https://github.com/grafana/grafana/pull/46738), [@dprokop](https://github.com/dprokop)
### Bug fixes
- **API:** Include userId, orgId, uname in request logging middleware. [#47183](https://github.com/grafana/grafana/pull/47183), [@marefr](https://github.com/marefr)
- **Elasticsearch:** Respect maxConcurrentShardRequests datasource setting. [#47120](https://github.com/grafana/grafana/pull/47120), [@alexandrst88](https://github.com/alexandrst88)

View File

@@ -0,0 +1,30 @@
+++
title = "Release notes for Grafana 8.5.0-beta1"
hide_menu = true
+++
<!-- Auto generated by update changelog github action -->
# Release notes for Grafana 8.5.0-beta1
### Features and enhancements
- Add config option to enable/disable reporting. (Enterprise)
- **Alerting:** Accurately set value for prom-compatible APIs. [#47216](https://github.com/grafana/grafana/pull/47216), [@gotjosh](https://github.com/gotjosh)
- **Alerting:** Provisioning API - Notification Policies. [#46755](https://github.com/grafana/grafana/pull/46755), [@alexweav](https://github.com/alexweav)
- **Analytics:** Enable grafana and plugin update checks to be operated independently. [#46352](https://github.com/grafana/grafana/pull/46352), [@wbrowne](https://github.com/wbrowne)
- **Azure Monitor:** Add support for multiple template variables in resource picker. [#46215](https://github.com/grafana/grafana/pull/46215), [@sarahzinger](https://github.com/sarahzinger)
- **Caching:** Add separate TTL for resources cache. (Enterprise)
- **Caching:** add support for TLS configuration for Redis Cluster. (Enterprise)
- **NewsPanel:** Remove Use Proxy option and update documentation with recommendations. [#47189](https://github.com/grafana/grafana/pull/47189), [@joshhunt](https://github.com/joshhunt)
- **OAuth:** Sync GitHub OAuth user name to Grafana if it's set. [#45438](https://github.com/grafana/grafana/pull/45438), [@pallxk](https://github.com/pallxk)
### Bug fixes
- **Plugins:** Fix Default Nav URL for dashboard includes. [#47143](https://github.com/grafana/grafana/pull/47143), [@wbrowne](https://github.com/wbrowne)
### Breaking changes
When user is using Github OAuth, GitHub login is showed as both Grafana login and name. Now the GitHub name is showed as Grafana name, and GitHub login is showed as Grafana Login. Issue [#45438](https://github.com/grafana/grafana/issues/45438)
The meaning of the default data source has now changed from being a persisted property in a panel. Before when you selected the default data source for a panel and later changed the default data source to another data source it would change all panels who were configured to use the default data source. From now on the default data source is just the default for new panels and changing the default will not impact any currently saved dashboards. Issue [#45132](https://github.com/grafana/grafana/issues/45132)

View File

@@ -11,6 +11,7 @@ as info on deprecations, breaking changes and plugin development read the [relea
## Grafana 8
- [What's new in 8.5]({{< relref "whats-new-in-v8-5" >}})
- [What's new in 8.4]({{< relref "whats-new-in-v8-4" >}})
- [What's new in 8.3]({{< relref "whats-new-in-v8-3" >}})
- [What's new in 8.2]({{< relref "whats-new-in-v8-2" >}})

View File

@@ -0,0 +1,126 @@
+++
title = "What's new in Grafana v8.5"
description = "Feature and improvement highlights for Grafana v8.5"
keywords = ["grafana", "new", "documentation", "8.5", "release notes"]
weight = -33
aliases = ["/docs/grafana/latest/guides/whats-new-in-v8-5/"]
[_build]
list = false
+++
# Whats new in Grafana v8.5
Were excited to announce Grafana v8.5, with a variety of improvements that focus on Grafanas usability, performance, and security.
Weve summarized whats new in the release here, but you might also be interested in the announcement blog post as well. If youd like all the details you can check out the complete [changelog](https://github.com/grafana/grafana/blob/main/CHANGELOG.md).
# OSS
## Alerting - group names for Grafana-managed alert rules
Its been tricky to work with more than a small number of Grafana-managed alert rules without groups in namespaces. Theyve also been inconsistent with the [Prometheus Alert Generator Compliance Specification](https://github.com/prometheus/compliance/blob/main/alert_generator/specification.md), which made working with Grafana-managed and Prometheus-managed alerts a confusing experience. With this release, you can still see flattened Grafana-managed alerts in the “List” tab, or use the new “Grouped” tab to work with groups:
Choose useful group names, and move alert rules between groups.
{{< figure src="/static/img/docs/alerting/unified/rule-grouping-8-5.png" max-width="38000px" caption="Rule group" >}}
Rules in a group are evaluated together, so you can also set the interval for the entire group.
{{< figure src="/static/img/docs/alerting/unified/rule-grouping-details-8-5.png" max-width="38000px" caption="Rule group details" >}}
## Analytics
You can now enable Grafana version update checking and Grafana plugins version update checking separately using [a new configuration option to separately enable plugin version update checks](https://grafana.com/docs/grafana/latest/administration/configuration/#check_for_plugin_updates). The [prior version update checking configuration](https://grafana.com/docs/grafana/latest/administration/configuration/#check_for_updates) now only controls Grafana version update checks.
When enabled, a check runs every 10 minutes. It will notify, via the UI, when a new version is available. The checks will not prompt any auto-updates of the Grafana software, nor will it send any sensitive information.
Plugin update available:
{{< figure src="/static/img/docs/analytics/github-plugin-update-available-8-5.png" max-width="1200px" caption="Plugin-available" >}}
Grafana update available:
{{< figure src="/static/img/docs/analytics/github-plugin-version-8-5.png" max-width="1200px" caption="Grafana-update-available" >}}
## Dashboard Panels
In addition to RSS feeds, the News panel now supports Atom feeds, allowing you to display a wider range of data and information in Grafana.
### Scrolling in the Bar gauge panel
The Bar gauge panel now supports scrolling to support displaying large datasets while maintaining the readability of labels. You can set a min width or height for the bars (depending on the charts orientation), allowing the content to overflow in the container and become scrollable.
{{< figure src="/static/img/docs/bar-gauge-panel/vertical-view-8-5.png" max-width="1200px" caption="Vertical view" >}}
{{< figure src="/static/img/docs/bar-gauge-panel/horizontal-view-8-5.png" max-width="1200px" caption="Horizontal view" >}}
## Transformations
### Template variable substitution
Weve added support to substitute template variables to transformations. This allows dashboards to be more interactive with transformations when a user inputs calculations and $**interval and $**interval_ms.
### Grouping to matrix
A new transformation is available that helps you structure data in a matrix format, using the Grafana table plugin.
## Expanding the navigation bar (Beta)
Available by switching on the newNavigation feature toggle.
You can expand the navigation bar for a better overview of Grafanas features and installed integrations.
This feature is currently in a beta version and we would appreciate your feedback. Sign up for a call with the Grafana team - it only takes 30 minutes, and you'll receive a $40 USD gift card as a token of appreciation for your time.
US, UK, Canada, Australia, France, Germany, or South Africa: sign up here
Everywhere else in the world: sign up here
{{< figure src="/static/img/docs/navigation/new-navigation-8-5.png" max-width="1200px" caption="New nav panel" >}}
## Notifications list for error alerts (Beta)
Available by switching on the persistNotifications feature toggle.
In order to support debugging issues in Grafana, error alerts that appear when viewing a dashboard now include a trace ID, and these alerts can be accessed under Profile / Notifications.
{{< figure src="/static/img/docs/navigation/nav-profile-notification-8-5.png" max-width="1200px" caption="Alert error list" >}}
## Service accounts (beta)
Service accounts are a major evolution for machine access within Grafana. You can create multiple API tokens per service account with independent expiration dates, and temporarily disable a service account without deleting it. These benefits make Service Accounts a more flexible way for Terraform and other apps to authenticate with Grafana. Service accounts also work with [fine-grained access control](https://grafana.com/docs/grafana/latest/enterprise/access-control/) in [Grafana Enterprise](https://grafana.com/docs/grafana/latest/enterprise/): you can improve security by granting service accounts specific roles to limit the functions they can perform. Service accounts are available in beta; you can try them out by enabling the `service-accounts` [feature toggle](https://grafana.com/docs/grafana/latest/administration/service-accounts/enable-service-accounts) or, if you use Grafana Cloud, [reaching out to our support team](https://grafana.com/orgs/raintank/tickets#) for early access. Learn more about Service Accounts in our [docs](https://grafana.com/docs/grafana/latest/administration/service-accounts).
{{< figure src="/static/img/docs/service-accounts/configure-8-5.png" max-width="1200px" caption="Configure service accounts" >}}
## Observability
### Trace to Logs for Splunk
With Trace to Logs, you can view relevant logs for a trace or span with one click. You can now link to Splunk logs from your tracing datasource. In your tracing datasource, configure Trace to Logs by selecting the Splunk datasource and relevant query options like tags to include in the query.
## Experimental Explore to Dashboard workflow
Allows users to create panels directly from within explore.
All queries in Explore get copied over to the new panel, the panel type is automatically selected based on queries response
With multiple queries, it will view the response from the first, non hidden query to determine the visualization type
This feature is behind the `explore2Dashboard` feature toggle and is enabled by default.
# Grafana Enterprise
## Security
### Fine-Grained Access Control comes to Alerting (beta)
Check the Grafana Enterprise / Security section below for more details, including how to enable this beta feature; weve implemented
[fine-grained access control](https://grafana.com/docs/grafana/latest/enterprise/access-control/) for alerting rules, notification policies, and contact points in [Grafana Enterprise](https://grafana.com/docs/grafana/latest/enterprise/). You can turn on fine-grained access control using the `accesscontrol` [feature toggle](https://grafana.com/docs/grafana/latest/enterprise/access-control/#enable-fine-grained-access-control), or by [reaching out to our support team](https://grafana.com/orgs/raintank/tickets#) for early access if you use Grafana Cloud. For more information on fine-grained access control, visit our [docs](https://grafana.com/docs/grafana/latest/enterprise/access-control/).
### Control access to dashboards, folders, and annotations (beta)
You can now use fine-grained access control to manage which specific users, teams, and roles can create, read, update, or delete dashboards, folders, or annotations. These are the latest services to incorporate fine-grained access control, which helps you dial in the specific access your users should have in Grafana. Fine-grained access control is currently in beta, but general availability is just around the corner, planned for our 9.0 release. You can turn on fine-grained access control using the `accesscontrol` [feature toggle](https://grafana.com/docs/grafana/latest/enterprise/access-control/#enable-fine-grained-access-control), or by [reaching out to our support team](https://grafana.com/orgs/raintank/tickets#) for early access if you use Grafana Cloud. For more information on fine-grained access control, visit our [docs](https://grafana.com/docs/grafana/latest/enterprise/access-control/).
### Configure Azure Key Vault using Managed Identities
You can already keep secrets in Grafanas database (like data source credentials) safer by retrieving your database encryption key from a Key Management Service, like AWS KMS or Azure Key Vault. In Grafana v8.5, you can use an Azure Managed Identity to integrate with Azure Key Vault. This simplifies the Key Vault integration and keeps it consistent with Grafana data sources, like Azure Data Explorer.
## Configure reports more easily
Reports are a great way to share Grafana dashboards by email with users who dont regularly sign in to Grafana. In 8.5, weve revamped the Report authoring UI to make it quicker and easier for you to create reports. View report details at a glance in list view, consider one configuration step at a time, and save reports for later. Also, Grafana will now emit a log every time a report is sent, so you can confirm its status or learn about send errors. Learn more about Reporting in our [docs](https://grafana.com/docs/grafana/latest/enterprise/reporting/).
{{< figure src="/static/img/docs/enterprise/report-new-report-8-5.png" max-width="1200px" caption="New report" >}}

View File

@@ -34,7 +34,7 @@ e2e.scenario({
expect(links).to.have.length.greaterThan(13);
for (let index = 0; index < links.length; index++) {
expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${encodeURI(variableValue)}`);
expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${variableValue}`);
}
});
};
@@ -44,14 +44,6 @@ e2e.scenario({
// verify all links, should have All value
verifyLinks('All');
// Data links should percent encode var values
e2e()
.get('[aria-label="Data link"]')
.should('exist')
.and((link) => {
expect(link.attr('href')).contains(encodeURI('test%25value'));
});
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All').should('be.visible').click();
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('p2').should('be.visible').click();

8
go.mod
View File

@@ -53,7 +53,7 @@ require (
github.com/grafana/cuetsy v0.0.0-20211119211437-8c25464cc9bf
github.com/grafana/grafana-aws-sdk v0.10.1
github.com/grafana/grafana-azure-sdk-go v1.1.0
github.com/grafana/grafana-plugin-sdk-go v0.129.0
github.com/grafana/grafana-plugin-sdk-go v0.131.0
github.com/grafana/loki v1.6.2-0.20211015002020-7832783b1caa
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/hashicorp/go-hclog v0.16.1
@@ -108,8 +108,8 @@ require (
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
golang.org/x/tools v0.1.5
gonum.org/v1/gonum v0.9.3
golang.org/x/tools v0.1.9
gonum.org/v1/gonum v0.11.0
google.golang.org/api v0.60.0
google.golang.org/grpc v1.42.0
google.golang.org/protobuf v1.27.1
@@ -272,7 +272,7 @@ require (
github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac // indirect
github.com/envoyproxy/go-control-plane v0.10.1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
github.com/getkin/kin-openapi v0.91.0 // indirect
github.com/getkin/kin-openapi v0.94.0 // indirect
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
github.com/imdario/mergo v0.3.12 // indirect

30
go.sum
View File

@@ -76,6 +76,7 @@ cuelang.org/go v0.4.0/go.mod h1:tz/edkPi+T37AZcb5GlPY+WJkL6KiDlDVupKwL3vvjs=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/Azure/azure-amqp-common-go/v3 v3.0.0/go.mod h1:SY08giD/XbhTz07tJdpw1SoxQXHPN30+DI3Z04SYqyg=
github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
@@ -286,7 +287,10 @@ github.com/aerospike/aerospike-client-go v1.27.0/go.mod h1:zj8LBEnWBDOVEIJt8LvaR
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -440,6 +444,7 @@ github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+Wji
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bonitoo-io/go-sql-bigquery v0.3.4-1.4.0/go.mod h1:J4Y6YJm0qTWB9aFziB7cPeSyc6dOZFyJdteSeybVpXQ=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
@@ -865,6 +870,8 @@ github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/getkin/kin-openapi v0.91.0 h1:mOSAljTAQONM0YVtI3+LvIQaa0zPwa3SH6UuiyEnbYQ=
github.com/getkin/kin-openapi v0.91.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
github.com/getkin/kin-openapi v0.94.0 h1:bAxg2vxgnHHHoeefVdmGbR+oxtJlcv5HsJJa3qmAHuo=
github.com/getkin/kin-openapi v0.94.0/go.mod h1:LWZfzOd7PRy8GJ1dJ6mCU6tNdSfOwRac1BUPam4aw6Q=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/getsentry/sentry-go v0.10.0 h1:6gwY+66NHKqyZrdi6O2jGdo7wGdo9b3B69E01NFgT5g=
github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
@@ -892,6 +899,7 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -905,6 +913,7 @@ github.com/go-kit/kit v0.11.0/go.mod h1:73/6Ixaufkvb5Osvkls8C79vuQ49Ba1rUEUYNSf+
github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@@ -1044,6 +1053,8 @@ github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9G
github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0=
github.com/go-openapi/validate v0.20.2 h1:AhqDegYV3J3iQkMPJSXkvzymHKMTw0BST3RK3hTT4ts=
github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0=
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
@@ -1359,6 +1370,8 @@ github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOax
github.com/grafana/grafana-plugin-sdk-go v0.125.0/go.mod h1:9YiJ5GUxIsIEUC0qR9+BJVP5M7mCSP6uc6Ne62YKkgc=
github.com/grafana/grafana-plugin-sdk-go v0.129.0 h1:apmA8x59QvW5Wov+FhAfM0IiNGjMi8V2Ou7xyMk1leE=
github.com/grafana/grafana-plugin-sdk-go v0.129.0/go.mod h1:4edtosZepfQF9jkQwRywJsNSJzXTHmzbmcVcAl8MEQc=
github.com/grafana/grafana-plugin-sdk-go v0.131.0 h1:8M+Qfch4WNi3PPpRhWtmcLFTCq8zlIjnxrc8iRigAY0=
github.com/grafana/grafana-plugin-sdk-go v0.131.0/go.mod h1:jmrxelOJKrIK0yrsIzcotS8pbqPZozbmJgGy7k3hK1k=
github.com/grafana/loki v1.6.2-0.20211015002020-7832783b1caa h1:+pXjAxavVR2FKKNsuuCXGCWEj8XGc1Af6SPiyBpzU2A=
github.com/grafana/loki v1.6.2-0.20211015002020-7832783b1caa/go.mod h1:0O8o/juxNSKN/e+DzWDTRkl7Zm8CkZcz0NDqEdojlrk=
github.com/grafana/saml v0.0.0-20211007135653-aed1b2edd86b h1:YiSGp34F4V0G08HHx1cJBf2GVgwYAkXQjzuVs1t8jYk=
@@ -2068,6 +2081,7 @@ github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -2264,6 +2278,7 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
@@ -2546,6 +2561,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/gopher-lua v0.0.0-20180630135845-46796da1b0b4/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
@@ -2754,6 +2770,10 @@ golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+o
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -2782,6 +2802,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -3206,8 +3227,9 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -3220,13 +3242,14 @@ gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJ
gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/gonum v0.9.3 h1:DnoIG+QAMaF5NvxnGe/oKsgKcAc6PcUyl8q0VetfQ8s=
gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=
gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E=
gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=
gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=
gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo=
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
@@ -3520,6 +3543,7 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
honnef.co/go/tools v0.2.0/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
inet.af/netaddr v0.0.0-20210707202901-70468d781e6c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=

View File

@@ -1,4 +1,4 @@
{
"stable": "8.4.5",
"testing": "8.4.5"
"stable": "8.4.6",
"testing": "8.5.0-beta1"
}

View File

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

View File

@@ -3,7 +3,7 @@
"license": "AGPL-3.0-only",
"private": true,
"name": "grafana",
"version": "8.5.0-pre",
"version": "8.5.0",
"repository": "github:grafana/grafana",
"scripts": {
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
@@ -142,7 +142,7 @@
"@types/papaparse": "5.3.2",
"@types/pluralize": "^0.0.29",
"@types/prismjs": "1.26.0",
"@types/rc-time-picker": "^3",
"@types/rc-time-picker": "3.4.1",
"@types/react": "17.0.42",
"@types/react-beautiful-dnd": "13.1.2",
"@types/react-dom": "17.0.14",
@@ -257,7 +257,7 @@
"@grafana/slate-react": "0.22.10-grafana",
"@grafana/ui": "workspace:*",
"@jaegertracing/jaeger-ui-components": "workspace:*",
"@kusto/monaco-kusto": "4.1.6",
"@kusto/monaco-kusto": "5.1.1",
"@lezer/common": "0.15.12",
"@lezer/lr": "0.15.8",
"@lingui/core": "3.13.2",
@@ -325,7 +325,7 @@
"lru-cache": "7.7.1",
"memoize-one": "6.0.0",
"minisearch": "5.0.0-beta1",
"moment": "2.29.1",
"moment": "2.29.2",
"moment-timezone": "0.5.34",
"monaco-editor": "^0.31.1",
"monaco-promql": "^1.7.2",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/data",
"version": "8.5.0-pre",
"version": "8.5.0",
"description": "Grafana Data Library",
"keywords": [
"typescript"
@@ -22,14 +22,14 @@
},
"dependencies": {
"@braintree/sanitize-url": "6.0.0",
"@grafana/schema": "8.5.0-pre",
"@grafana/schema": "8.5.0",
"@types/d3-interpolate": "^1.4.0",
"d3-interpolate": "1.4.0",
"date-fns": "2.28.0",
"eventemitter3": "4.0.7",
"lodash": "4.17.21",
"marked": "4.0.12",
"moment": "2.29.1",
"moment": "2.29.2",
"moment-timezone": "0.5.34",
"ol": "6.14.1",
"papaparse": "5.3.2",

View File

@@ -1,6 +1,7 @@
import {
ApplyFieldOverrideOptions,
DataFrame,
DataLink,
DisplayProcessor,
DisplayValue,
DynamicConfigValue,
@@ -352,7 +353,7 @@ export const getLinksSupplier =
const timeRangeUrl = locationUtil.getTimeRangeUrlParams();
const { timeField } = getTimeField(frame);
return field.config.links.map((link) => {
return field.config.links.map((link: DataLink) => {
const variablesQuery = locationUtil.getVariablesUrlParams();
let dataFrameVars = {};
let valueVars = {};
@@ -438,7 +439,7 @@ export const getLinksSupplier =
}
let href = locationUtil.assureBaseUrl(link.url.replace(/\n/g, ''));
href = replaceVariables(href, variables, encodeURIComponent);
href = replaceVariables(href, variables);
href = locationUtil.processUrl(href);
const info: LinkModel<Field> = {

View File

@@ -188,7 +188,7 @@ describe('align frames', () => {
],
});
const out = outerJoinDataFrames({ frames: [series1], enforceSort: true, keepOriginIndices: true })!;
const out = outerJoinDataFrames({ frames: [series1], keepOriginIndices: true })!;
expect(
out.fields.map((f) => ({
name: f.name,

View File

@@ -48,13 +48,6 @@ export interface JoinOptions {
*/
keep?: FieldMatcher;
/**
* When the result is a single frame, this will to a quick check to see if the values are sorted,
* and sort if necessary. If the first/last values are in order the whole vector is assumed to be
* sorted
*/
enforceSort?: boolean;
/**
* @internal -- used when we need to keep a reference to the original frame/field index
*/
@@ -65,6 +58,21 @@ function getJoinMatcher(options: JoinOptions): FieldMatcher {
return options.joinBy ?? pickBestJoinField(options.frames);
}
/**
* @internal
*/
export function maybeSortFrame(frame: DataFrame, fieldIdx: number) {
if (fieldIdx >= 0) {
let sortByField = frame.fields[fieldIdx];
if (sortByField.type !== FieldType.string && !isLikelyAscendingVector(sortByField.values)) {
frame = sortDataFrame(frame, fieldIdx);
}
}
return frame;
}
/**
* This will return a single frame joined by the first matching field. When a join field is not specified,
* the default will use the first time field
@@ -109,12 +117,8 @@ export function outerJoinDataFrames(options: JoinOptions): DataFrame | undefined
}
}
if (options.enforceSort) {
if (joinIndex >= 0) {
if (!isLikelyAscendingVector(frameCopy.fields[joinIndex].values)) {
frameCopy = sortDataFrame(frameCopy, joinIndex);
}
}
if (joinIndex >= 0) {
frameCopy = maybeSortFrame(frameCopy, joinIndex);
}
if (options.keep) {

View File

@@ -42,6 +42,7 @@ export interface LicenseInfo {
stateInfo: string;
edition: GrafanaEdition;
enabledFeatures: { [key: string]: boolean };
trialExpiry?: number;
}
/**
@@ -180,4 +181,5 @@ export interface GrafanaConfig {
geomapDisableCustomBaseLayer?: boolean;
unifiedAlertingEnabled: boolean;
angularSupportEnabled: boolean;
feedbackLinksEnabled: boolean;
}

View File

@@ -19,7 +19,7 @@ export interface FeatureToggles {
trimDefaults?: boolean;
envelopeEncryption?: boolean;
httpclientprovider_azure_auth?: boolean;
['service-accounts']?: boolean;
serviceAccounts?: boolean;
database_metrics?: boolean;
dashboardPreviews?: boolean;
dashboardPreviewsScheduler?: boolean;
@@ -52,4 +52,6 @@ export interface FeatureToggles {
alertProvisioning?: boolean;
storageLocalUpload?: boolean;
azureMonitorResourcePickerForMetrics?: boolean;
explore2Dashboard?: boolean;
persistNotifications?: boolean;
}

View File

@@ -5,8 +5,8 @@ import { eventFactory } from '../events/eventFactory';
import { BusEventBase, BusEventWithPayload } from '../events/types';
import { DataHoverPayload } from '../events';
export type AlertPayload = [string, string?];
export type AlertErrorPayload = [string, (string | Error)?];
export type AlertPayload = [string, string?, string?];
export type AlertErrorPayload = [string, (string | Error)?, string?];
export const AppEvents = {
alertSuccess: eventFactory<AlertPayload>('alert-success'),

View File

@@ -30,6 +30,7 @@ export interface NavModelItem extends NavLinkDTO {
highlightId?: string;
tabSuffix?: ComponentType<{ className?: string }>;
hideFromNavbar?: boolean;
showIconInNavbar?: boolean;
}
export enum NavSection {

View File

@@ -3,7 +3,7 @@ import { locationUtil } from './location';
describe('locationUtil', () => {
const { location } = window;
beforeAll(() => {
beforeEach(() => {
// @ts-ignore
delete window.location;
@@ -21,63 +21,97 @@ describe('locationUtil', () => {
};
});
afterAll(() => {
afterEach(() => {
window.location = location;
});
describe('strip base when appSubUrl configured', () => {
beforeEach(() => {
locationUtil.initialize({
config: { appSubUrl: '/subUrl' } as any,
getVariablesUrlParams: (() => {}) as any,
getTimeRangeForUrl: (() => {}) as any,
describe('stripBaseFromUrl', () => {
describe('when appSubUrl configured', () => {
beforeEach(() => {
locationUtil.initialize({
config: { appSubUrl: '/subUrl' } as any,
getVariablesUrlParams: (() => {}) as any,
getTimeRangeForUrl: (() => {}) as any,
});
});
});
test('relative url', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('/subUrl/thisShouldRemain/');
expect(urlWithoutMaster).toBe('/thisShouldRemain/');
});
test('relative url with multiple subUrl in path', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('/subUrl/thisShouldRemain/subUrl/');
expect(urlWithoutMaster).toBe('/thisShouldRemain/subUrl/');
});
test('relative url with subdirectory subUrl', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('/thisShouldRemain/subUrl/');
expect(urlWithoutMaster).toBe('/thisShouldRemain/subUrl/');
});
test('absolute url', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('http://www.domain.com:9877/subUrl/thisShouldRemain/');
expect(urlWithoutMaster).toBe('/thisShouldRemain/');
});
test('absolute url with multiple subUrl in path', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl(
'http://www.domain.com:9877/subUrl/thisShouldRemain/subUrl/'
);
expect(urlWithoutMaster).toBe('/thisShouldRemain/subUrl/');
});
test('absolute url with subdirectory subUrl', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('http://www.domain.com:9877/thisShouldRemain/subUrl/');
expect(urlWithoutMaster).toBe('http://www.domain.com:9877/thisShouldRemain/subUrl/');
});
});
describe('strip base when appSubUrl not configured', () => {
beforeEach(() => {
locationUtil.initialize({
config: {} as any,
getVariablesUrlParams: (() => {}) as any,
getTimeRangeForUrl: (() => {}) as any,
test('relative url', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('/subUrl/thisShouldRemain/');
expect(urlWithoutMaster).toBe('/thisShouldRemain/');
});
test('relative url with multiple subUrl in path', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('/subUrl/thisShouldRemain/subUrl/');
expect(urlWithoutMaster).toBe('/thisShouldRemain/subUrl/');
});
test('relative url with subdirectory subUrl', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('/thisShouldRemain/subUrl/');
expect(urlWithoutMaster).toBe('/thisShouldRemain/subUrl/');
});
test('absolute url', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('http://www.domain.com:9877/subUrl/thisShouldRemain/');
expect(urlWithoutMaster).toBe('/thisShouldRemain/');
});
test('absolute url with multiple subUrl in path', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl(
'http://www.domain.com:9877/subUrl/thisShouldRemain/subUrl/'
);
expect(urlWithoutMaster).toBe('/thisShouldRemain/subUrl/');
});
test('absolute url with subdirectory subUrl', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('http://www.domain.com:9877/thisShouldRemain/subUrl/');
expect(urlWithoutMaster).toBe('http://www.domain.com:9877/thisShouldRemain/subUrl/');
});
});
test('relative url', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('/subUrl/grafana/');
expect(urlWithoutMaster).toBe('/subUrl/grafana/');
describe('when appSubUrl not configured', () => {
beforeEach(() => {
locationUtil.initialize({
config: {} as any,
getVariablesUrlParams: (() => {}) as any,
getTimeRangeForUrl: (() => {}) as any,
});
});
test('relative url', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('/subUrl/grafana/');
expect(urlWithoutMaster).toBe('/subUrl/grafana/');
});
test('absolute url', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('http://www.domain.com:9877/subUrl/grafana/');
expect(urlWithoutMaster).toBe('/subUrl/grafana/');
});
});
test('absolute url', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('http://www.domain.com:9877/subUrl/grafana/');
expect(urlWithoutMaster).toBe('/subUrl/grafana/');
describe('when origin does not have a port in it', () => {
beforeEach(() => {
window.location = {
...location,
hash: '#hash',
host: 'www.domain.com',
hostname: 'www.domain.com',
href: 'http://www.domain.com/path/b?search=a&b=c&d#hash',
origin: 'http://www.domain.com',
pathname: '/path/b',
port: '',
protocol: 'http:',
search: '?search=a&b=c&d',
};
});
test('relative url', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('/subUrl/grafana/');
expect(urlWithoutMaster).toBe('/subUrl/grafana/');
});
test('URL with same host, different port', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('http://www.domain.com:9877/subUrl/grafana/');
expect(urlWithoutMaster).toBe('http://www.domain.com:9877/subUrl/grafana/');
});
test('URL of a completely different origin', () => {
const urlWithoutMaster = locationUtil.stripBaseFromUrl('http://www.another-domain.com/subUrl/grafana/');
expect(urlWithoutMaster).toBe('http://www.another-domain.com/subUrl/grafana/');
});
});
});

View File

@@ -7,22 +7,43 @@ let grafanaConfig: GrafanaConfig = { appSubUrl: '' } as any;
let getTimeRangeUrlParams: () => RawTimeRange;
let getVariablesUrlParams: (scopedVars?: ScopedVars) => UrlQueryMap;
const maybeParseUrl = (input: string): URL | undefined => {
try {
return new URL(input);
} catch {
return undefined;
}
};
/**
*
* @param url
* @internal
*/
const stripBaseFromUrl = (url: string): string => {
const stripBaseFromUrl = (urlOrPath: string): string => {
// Will only return a URL object if the input is actually a valid URL
const parsedUrl = maybeParseUrl(urlOrPath);
if (parsedUrl) {
// If the input is a URL, and for a different origin that we're on, just bail
// and return it. There's no need to strip anything from it
if (parsedUrl.origin !== window.location.origin) {
return urlOrPath;
}
}
const appSubUrl = grafanaConfig.appSubUrl ?? '';
const stripExtraChars = appSubUrl.endsWith('/') ? 1 : 0;
const isAbsoluteUrl = url.startsWith('http');
const isAbsoluteUrl = urlOrPath.startsWith('http');
let segmentToStrip = appSubUrl;
if (!url.startsWith('/') || isAbsoluteUrl) {
if (!urlOrPath.startsWith('/') || isAbsoluteUrl) {
segmentToStrip = `${window.location.origin}${appSubUrl}`;
}
return url.length > 0 && url.indexOf(segmentToStrip) === 0 ? url.slice(segmentToStrip.length - stripExtraChars) : url;
return urlOrPath.length > 0 && urlOrPath.indexOf(segmentToStrip) === 0
? urlOrPath.slice(segmentToStrip.length - stripExtraChars)
: urlOrPath;
};
/**

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e-selectors",
"version": "8.5.0-pre",
"version": "8.5.0",
"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": "8.5.0-pre",
"version": "8.5.0",
"description": "Grafana End-to-End Test Library",
"keywords": [
"cli",
@@ -48,7 +48,7 @@
"@babel/core": "7.17.8",
"@babel/preset-env": "7.16.11",
"@cypress/webpack-preprocessor": "5.11.1",
"@grafana/e2e-selectors": "8.5.0-pre",
"@grafana/e2e-selectors": "8.5.0",
"@grafana/tsconfig": "^1.2.0-rc1",
"@mochajs/json-file-reporter": "^1.2.0",
"babel-loader": "8.2.4",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/runtime",
"version": "8.5.0-pre",
"version": "8.5.0",
"description": "Grafana Runtime Library",
"keywords": [
"grafana",
@@ -22,9 +22,9 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@grafana/data": "8.5.0-pre",
"@grafana/e2e-selectors": "8.5.0-pre",
"@grafana/ui": "8.5.0-pre",
"@grafana/data": "8.5.0",
"@grafana/e2e-selectors": "8.5.0",
"@grafana/ui": "8.5.0",
"@sentry/browser": "6.19.1",
"history": "4.10.1",
"lodash": "4.17.21",

View File

@@ -36,6 +36,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
externalUserMngLinkName = '';
externalUserMngInfo = '';
allowOrgCreate = false;
feedbackLinksEnabled = true;
disableLoginForm = false;
defaultDatasource = ''; // UID
alertingEnabled = false;
@@ -119,7 +120,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
this.theme2 = createTheme({ colors: { mode } });
this.theme = this.theme2.v1;
this.bootData = options.bootData;
this.buildInfo = options.buildInfo;
const defaults = {
datasources: {},
@@ -131,7 +131,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
appUrl: '',
appSubUrl: '',
buildInfo: {
version: 'v1.0',
version: '1.0',
commit: '1',
env: 'production',
},
@@ -142,6 +142,8 @@ export class GrafanaBootConfig implements GrafanaConfig {
merge(this, defaults, options);
this.buildInfo = options.buildInfo || defaults.buildInfo;
if (this.dateFormats) {
systemDateFormats.update(this.dateFormats);
}

View File

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

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/toolkit",
"version": "8.5.0-pre",
"version": "8.5.0",
"description": "Grafana Toolkit",
"keywords": [
"grafana",
@@ -28,10 +28,10 @@
"dependencies": {
"@babel/core": "7.13.14",
"@babel/preset-env": "7.13.12",
"@grafana/data": "8.5.0-pre",
"@grafana/data": "8.5.0",
"@grafana/eslint-config": "2.5.2",
"@grafana/tsconfig": "^1.2.0-rc1",
"@grafana/ui": "8.5.0-pre",
"@grafana/ui": "8.5.0",
"@jest/core": "26.6.3",
"@rushstack/eslint-patch": "1.0.6",
"@types/command-exists": "^1.2.0",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/ui",
"version": "8.5.0-pre",
"version": "8.5.0",
"description": "Grafana Components Library",
"keywords": [
"grafana",
@@ -33,9 +33,9 @@
"@emotion/css": "11.7.1",
"@emotion/react": "11.8.2",
"@grafana/aws-sdk": "0.0.35",
"@grafana/data": "8.5.0-pre",
"@grafana/e2e-selectors": "8.5.0-pre",
"@grafana/schema": "8.5.0-pre",
"@grafana/data": "8.5.0",
"@grafana/e2e-selectors": "8.5.0",
"@grafana/schema": "8.5.0",
"@grafana/slate-react": "0.22.10-grafana",
"@monaco-editor/react": "4.3.1",
"@popperjs/core": "2.11.4",
@@ -58,7 +58,7 @@
"jquery": "3.6.0",
"lodash": "4.17.21",
"memoize-one": "6.0.0",
"moment": "2.29.1",
"moment": "2.29.2",
"monaco-editor": "^0.31.1",
"ol": "6.14.1",
"prismjs": "1.27.0",

View File

@@ -22,7 +22,7 @@ export interface Props extends HTMLAttributes<HTMLDivElement> {
topSpacing?: number;
}
function getIconFromSeverity(severity: AlertVariant): string {
export function getIconFromSeverity(severity: AlertVariant): string {
switch (severity) {
case 'error':
case 'warning':
@@ -150,7 +150,7 @@ const getStyles = (
color: ${theme.colors.text.secondary};
padding-top: ${theme.spacing(1)};
max-height: 50vh;
overflow-y: scroll;
overflow-y: auto;
`,
buttonWrapper: css`
padding: ${theme.spacing(1)};

View File

@@ -232,12 +232,17 @@ const Meta = memo(({ children, className, separator = '|' }: ChildProps & { sepa
const styles = useStyles2(getMetaStyles);
let meta = children;
const filtered = React.Children.toArray(children).filter(Boolean);
if (!filtered.length) {
return null;
}
meta = filtered.map((element, i) => (
<div key={`element_${i}`} className={styles.metadataItem}>
{element}
</div>
));
// Join meta data elements by separator
if (Array.isArray(children) && separator) {
const filtered = React.Children.toArray(children).filter(Boolean);
if (!filtered.length) {
return null;
}
if (filtered.length > 1 && separator) {
meta = filtered.reduce((prev, curr, i) => [
prev,
<span key={`separator_${i}`} className={styles.separator}>
@@ -261,6 +266,9 @@ const getMetaStyles = (theme: GrafanaTheme2) => ({
margin: theme.spacing(0.5, 0, 0),
lineHeight: theme.typography.bodySmall.lineHeight,
overflowWrap: 'anywhere',
}),
metadataItem: css({
// Needed to allow for clickable children in metadata
zIndex: 0,
}),
separator: css({

View File

@@ -60,7 +60,7 @@ export const CollapsableSection: FC<Props> = ({
{loading ? (
<Spinner className={styles.spinner} />
) : (
<Icon name={open ? 'angle-down' : 'angle-right'} className={styles.icon} />
<Icon name={open ? 'angle-up' : 'angle-down'} className={styles.icon} />
)}
</button>
<div className={styles.label} id={`collapse-label-${id}`}>

View File

@@ -82,7 +82,7 @@ function sameProps(prevProps: any, nextProps: any, propsToDiff: Array<string | P
*/
export interface GraphNGState {
alignedFrame: DataFrame;
alignedData: AlignedData;
alignedData?: AlignedData;
config?: UPlotConfigBuilder;
}
@@ -98,7 +98,9 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
constructor(props: GraphNGProps) {
super(props);
this.state = this.prepState(props);
let state = this.prepState(props);
state.alignedData = state.config!.prepData!([state.alignedFrame]) as AlignedData;
this.state = state;
this.plotInstance = React.createRef();
}
@@ -131,7 +133,6 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
state = {
alignedFrame,
alignedData: config!.prepData!([alignedFrame]) as AlignedData,
config,
};
@@ -229,12 +230,13 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
if (shouldReconfig) {
newState.config = this.props.prepConfig(newState.alignedFrame, this.props.frames, this.getTimeRange);
newState.alignedData = newState.config.prepData!([newState.alignedFrame]) as AlignedData;
pluginLog('GraphNG', false, 'config recreated', newState.config);
}
}
newState && this.setState(newState);
newState.alignedData = newState.config!.prepData!([newState.alignedFrame]) as AlignedData;
this.setState(newState);
}
}
}
@@ -255,7 +257,7 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
{(vizWidth: number, vizHeight: number) => (
<UPlotChart
config={config}
data={alignedData}
data={alignedData!}
width={vizWidth}
height={vizHeight}
timeRange={timeRange}

View File

@@ -58,22 +58,6 @@ Object {
"values": [Function],
},
],
"bands": Array [
Object {
"dir": -1,
"series": Array [
2,
1,
],
},
Object {
"dir": -1,
"series": Array [
4,
3,
],
},
],
"cursor": Object {
"dataIdx": [Function],
"drag": Object {

View File

@@ -13,7 +13,7 @@ export function MultiSelect<T>(props: MultiSelectCommonProps<T>) {
return <SelectBase {...props} isMulti />;
}
interface AsyncSelectProps<T> extends Omit<SelectCommonProps<T>, 'options'>, SelectAsyncProps<T> {
export interface AsyncSelectProps<T> extends Omit<SelectCommonProps<T>, 'options'>, SelectAsyncProps<T> {
// AsyncSelect has options stored internally. We cannot enable plain values as we don't have access to the fetched options
value?: SelectableValue<T> | null;
invalid?: boolean;

View File

@@ -20,7 +20,7 @@ import {
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
import { UPlotChart } from '../uPlot/Plot';
import { Themeable2 } from '../../types';
import { preparePlotData } from '../uPlot/utils';
import { preparePlotData2, getStackingGroups } from '../uPlot/utils';
import { preparePlotFrame } from './utils';
import { isEqual } from 'lodash';
@@ -50,7 +50,7 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
const alignedDataFrame = preparePlotFrame(props.sparkline, props.config);
this.state = {
data: preparePlotData([alignedDataFrame]),
data: preparePlotData2(alignedDataFrame, getStackingGroups(alignedDataFrame)),
alignedDataFrame,
configBuilder: this.prepareConfig(alignedDataFrame),
};
@@ -64,7 +64,7 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
return {
...state,
data: preparePlotData([frame]),
data: preparePlotData2(frame, getStackingGroups(frame)),
alignedDataFrame: frame,
};
}

View File

@@ -304,9 +304,9 @@ export const Table: FC<Props> = memo((props: Props) => {
totalColumnsWidth={totalColumnsWidth}
/>
)}
{paginationEl}
</div>
</CustomScrollbar>
{paginationEl}
</div>
);
});

View File

@@ -184,6 +184,9 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
div:not(:only-child):first-child {
flex-grow: 0.6;
}
position: absolute;
bottom: 0;
left: 0;
`,
paginationSummary: css`
color: ${theme.colors.text.secondary};
@@ -191,11 +194,16 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
margin-left: auto;
`,
tableContentWrapper: (totalColumnsWidth: number) => css`
width: ${totalColumnsWidth ?? '100%'};
display: flex;
flex-direction: column;
`,
tableContentWrapper: (totalColumnsWidth: number) => {
const width = totalColumnsWidth !== undefined ? `${totalColumnsWidth}px` : '100%';
return css`
label: tableContentWrapper;
width: ${width};
display: flex;
flex-direction: column;
`;
},
row: css`
label: row;
border-bottom: 1px solid ${borderColor};

View File

@@ -19,7 +19,7 @@ export class UnthemedTimeSeries extends React.Component<TimeSeriesProps> {
prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => {
const { eventBus, sync } = this.context as PanelContext;
const { theme, timeZone, legend, renderers, tweakAxis, tweakScale } = this.props;
const { theme, timeZone, renderers, tweakAxis, tweakScale } = this.props;
return preparePlotConfigBuilder({
frame: alignedFrame,
@@ -29,7 +29,6 @@ export class UnthemedTimeSeries extends React.Component<TimeSeriesProps> {
eventBus,
sync,
allFrames,
legend,
renderers,
tweakScale,
tweakAxis,

View File

@@ -23,10 +23,9 @@ import {
VisibilityMode,
ScaleDirection,
ScaleOrientation,
VizLegendOptions,
StackingMode,
} from '@grafana/schema';
import { collectStackingGroups, INTERNAL_NEGATIVE_Y_PREFIX, orderIdsByCalcs, preparePlotData } from '../uPlot/utils';
import { getStackingGroups, preparePlotData2 } from '../uPlot/utils';
import uPlot from 'uplot';
import { buildScaleKey } from '../GraphNG/utils';
@@ -40,7 +39,6 @@ const defaultConfig: GraphFieldConfig = {
export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
sync?: () => DashboardCursorSync;
legend?: VizLegendOptions;
}> = ({
frame,
theme,
@@ -50,13 +48,12 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
sync,
allFrames,
renderers,
legend,
tweakScale = (opts) => opts,
tweakAxis = (opts) => opts,
}) => {
const builder = new UPlotConfigBuilder(timeZone);
builder.setPrepData((prepData) => preparePlotData(prepData, undefined, legend));
builder.setPrepData((frames) => preparePlotData2(frames[0], builder.getStackingGroups()));
// X is the first field in the aligned frame
const xField = frame.fields[0];
@@ -122,8 +119,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
let customRenderedFields =
renderers?.flatMap((r) => Object.values(r.fieldMap).filter((name) => r.indicesOnly.indexOf(name) === -1)) ?? [];
const stackingGroups: Map<string, number[]> = new Map();
let indexByName: Map<string, number> | undefined;
for (let i = 1; i < frame.fields.length; i++) {
@@ -328,20 +323,11 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
});
}
}
collectStackingGroups(field, stackingGroups, seriesIndex);
}
if (stackingGroups.size !== 0) {
for (const [group, seriesIds] of stackingGroups.entries()) {
const seriesIdxs = orderIdsByCalcs({ ids: seriesIds, legend, frame });
for (let j = seriesIdxs.length - 1; j > 0; j--) {
builder.addBand({
series: [seriesIdxs[j], seriesIdxs[j - 1]],
dir: group.startsWith(INTERNAL_NEGATIVE_Y_PREFIX) ? 1 : -1,
});
}
}
}
let stackingGroups = getStackingGroups(frame);
builder.setStackingGroups(stackingGroups);
// hook up custom/composite renderers
renderers?.forEach((r) => {

View File

@@ -6,7 +6,7 @@ import { GraphFieldConfig, GraphDrawStyle } from '@grafana/schema';
import uPlot from 'uplot';
import createMockRaf from 'mock-raf';
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
import { preparePlotData } from './utils';
import { preparePlotData2, getStackingGroups } from './utils';
import { SeriesProps } from './config/UPlotSeriesBuilder';
const mockRaf = createMockRaf();
@@ -55,7 +55,7 @@ const mockData = () => {
const config = new UPlotConfigBuilder();
config.addSeries({} as SeriesProps);
return { data: [data], timeRange, config };
return { data: data, timeRange, config };
};
describe('UPlotChart', () => {
@@ -75,7 +75,7 @@ describe('UPlotChart', () => {
const { unmount } = render(
<UPlotChart
data={preparePlotData(data)} // mock
data={preparePlotData2(data, getStackingGroups(data))} // mock
config={config}
timeRange={timeRange}
width={100}
@@ -94,7 +94,7 @@ describe('UPlotChart', () => {
const { rerender } = render(
<UPlotChart
data={preparePlotData(data)} // mock
data={preparePlotData2(data, getStackingGroups(data))} // mock
config={config}
timeRange={timeRange}
width={100}
@@ -104,11 +104,11 @@ describe('UPlotChart', () => {
expect(uPlot).toBeCalledTimes(1);
data[0].fields[1].values.set(0, 1);
data.fields[1].values.set(0, 1);
rerender(
<UPlotChart
data={preparePlotData(data)} // changed
data={preparePlotData2(data, getStackingGroups(data))} // changed
config={config}
timeRange={timeRange}
width={100}
@@ -124,7 +124,13 @@ describe('UPlotChart', () => {
it('skips uPlot intialization for width and height equal 0', async () => {
const { data, timeRange, config } = mockData();
const { queryAllByTestId } = render(
<UPlotChart data={preparePlotData(data)} config={config} timeRange={timeRange} width={0} height={0} />
<UPlotChart
data={preparePlotData2(data, getStackingGroups(data))}
config={config}
timeRange={timeRange}
width={0}
height={0}
/>
);
expect(queryAllByTestId('uplot-main-div')).toHaveLength(1);
@@ -136,7 +142,7 @@ describe('UPlotChart', () => {
const { rerender } = render(
<UPlotChart
data={preparePlotData(data)} // frame
data={preparePlotData2(data, getStackingGroups(data))} // frame
config={config}
timeRange={timeRange}
width={100}
@@ -150,7 +156,13 @@ describe('UPlotChart', () => {
nextConfig.addSeries({} as SeriesProps);
rerender(
<UPlotChart data={preparePlotData(data)} config={nextConfig} timeRange={timeRange} width={100} height={100} />
<UPlotChart
data={preparePlotData2(data, getStackingGroups(data))}
config={nextConfig}
timeRange={timeRange}
width={100}
height={100}
/>
);
expect(destroyMock).toBeCalledTimes(1);
@@ -162,7 +174,7 @@ describe('UPlotChart', () => {
const { rerender } = render(
<UPlotChart
data={preparePlotData(data)} // frame
data={preparePlotData2(data, getStackingGroups(data))} // frame
config={config}
timeRange={timeRange}
width={100}
@@ -173,7 +185,7 @@ describe('UPlotChart', () => {
// we wait 1 frame for plugins initialisation logic to finish
rerender(
<UPlotChart
data={preparePlotData(data)} // frame
data={preparePlotData2(data, getStackingGroups(data))} // frame
config={config}
timeRange={timeRange}
width={200}

View File

@@ -15,7 +15,7 @@ import { ScaleProps, UPlotScaleBuilder } from './UPlotScaleBuilder';
import { SeriesProps, UPlotSeriesBuilder } from './UPlotSeriesBuilder';
import { AxisProps, UPlotAxisBuilder } from './UPlotAxisBuilder';
import { AxisPlacement } from '@grafana/schema';
import { pluginLog } from '../utils';
import { getStackingBands, pluginLog, StackingGroup } from '../utils';
import { getThresholdsDrawHook, UPlotThresholdOptions } from './UPlotThresholds';
const cursorDefaults: Cursor = {
@@ -33,12 +33,14 @@ const cursorDefaults: Cursor = {
};
type PrepData = (frames: DataFrame[]) => AlignedData | FacetedData;
type PreDataStacked = (frames: DataFrame[], stackingGroups: StackingGroup[]) => AlignedData | FacetedData;
export class UPlotConfigBuilder {
private series: UPlotSeriesBuilder[] = [];
private axes: Record<string, UPlotAxisBuilder> = {};
private scales: UPlotScaleBuilder[] = [];
private bands: Band[] = [];
private stackingGroups: StackingGroup[] = [];
private cursor: Cursor | undefined;
private select: uPlot.Select | undefined;
private hasLeftAxis = false;
@@ -143,6 +145,14 @@ export class UPlotConfigBuilder {
this.bands.push(band);
}
setStackingGroups(groups: StackingGroup[]) {
this.stackingGroups = groups;
}
getStackingGroups() {
return this.stackingGroups;
}
setTooltipInterpolator(interpolator: PlotTooltipInterpolator) {
this.tooltipInterpolator = interpolator;
}
@@ -151,10 +161,10 @@ export class UPlotConfigBuilder {
return this.tooltipInterpolator;
}
setPrepData(prepData: PrepData) {
setPrepData(prepData: PreDataStacked) {
this.prepData = (frames) => {
this.frames = frames;
return prepData(frames);
return prepData(frames, this.getStackingGroups());
};
}
@@ -221,6 +231,14 @@ export class UPlotConfigBuilder {
config.tzDate = this.tzDate;
config.padding = this.padding;
if (this.stackingGroups.length) {
this.stackingGroups.forEach((group) => {
getStackingBands(group).forEach((band) => {
this.addBand(band);
});
});
}
if (this.bands.length) {
config.bands = this.bands;
}

View File

@@ -213,7 +213,7 @@ function mapDrawStyleToPathBuilder(
lineInterpolation?: LineInterpolation,
barAlignment = 0,
barWidthFactor = 0.6,
barMaxWidth = Infinity
barMaxWidth = 200
): Series.PathBuilder {
const pathBuilders = uPlot.paths;

View File

@@ -1,6 +1,7 @@
import { orderIdsByCalcs, preparePlotData, timeFormatToTemplate } from './utils';
import { getStackingGroups, preparePlotData2, timeFormatToTemplate } from './utils';
import { FieldType, MutableDataFrame } from '@grafana/data';
import { GraphTransform, StackingMode } from '@grafana/schema';
import { BarAlignment, GraphDrawStyle, GraphTransform, LineInterpolation, StackingMode } from '@grafana/schema';
import Units from 'ol/proj/Units';
describe('timeFormatToTemplate', () => {
it.each`
@@ -16,7 +17,7 @@ describe('timeFormatToTemplate', () => {
});
});
describe('preparePlotData', () => {
describe('preparePlotData2', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
@@ -27,7 +28,7 @@ describe('preparePlotData', () => {
});
it('creates array from DataFrame', () => {
expect(preparePlotData([df])).toMatchInlineSnapshot(`
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
Array [
Array [
9997,
@@ -63,7 +64,7 @@ describe('preparePlotData', () => {
{ name: 'c', values: [20, 20, 20], config: { custom: { transform: GraphTransform.NegativeY } } },
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
Array [
Array [
9997,
@@ -104,7 +105,7 @@ describe('preparePlotData', () => {
{ name: 'i', values: [20, undefined, 20, 20], config: { custom: { transform: GraphTransform.NegativeY } } },
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
Array [
Array [
9997,
@@ -178,7 +179,7 @@ describe('preparePlotData', () => {
{ name: 'c', values: [20, 20, 20] },
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
Array [
Array [
9997,
@@ -226,7 +227,7 @@ describe('preparePlotData', () => {
},
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
Array [
Array [
9997,
@@ -273,7 +274,7 @@ describe('preparePlotData', () => {
},
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
Array [
Array [
9997,
@@ -329,7 +330,7 @@ describe('preparePlotData', () => {
},
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
Array [
Array [
9997,
@@ -397,7 +398,7 @@ describe('preparePlotData', () => {
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
Array [
Array [
9997,
@@ -472,7 +473,7 @@ describe('preparePlotData', () => {
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
expect(preparePlotData2(df, getStackingGroups(df))).toMatchInlineSnapshot(`
Array [
Array [
9997,
@@ -507,239 +508,330 @@ describe('preparePlotData', () => {
]
`);
});
describe('ignores nullish-only stacks', () => {
test('single stacking group', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
{
name: 'a',
values: [-10, null, 10],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
},
{
name: 'b',
values: [10, null, null],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
},
{
name: 'c',
values: [20, undefined, 20],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
},
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
Array [
Array [
9997,
9998,
9999,
],
Array [
-10,
null,
10,
],
Array [
0,
null,
10,
],
Array [
20,
null,
30,
],
]
`);
});
test('multiple stacking groups', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
{
name: 'a',
values: [-10, undefined, 10],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
},
{
name: 'b',
values: [10, undefined, 10],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
},
{
name: 'c',
values: [20, undefined, 20],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
},
{
name: 'd',
values: [1, 2, null],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackB' } } },
},
{
name: 'e',
values: [1, 2, null],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackB' } } },
},
{
name: 'f',
values: [1, 2, null],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackB' } } },
},
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
Array [
Array [
9997,
9998,
9999,
],
Array [
-10,
null,
10,
],
Array [
0,
null,
20,
],
Array [
20,
null,
40,
],
Array [
1,
2,
null,
],
Array [
2,
4,
null,
],
Array [
3,
6,
null,
],
]
`);
});
});
describe('with legend sorted', () => {
it('should affect when single group', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
{
name: 'a',
values: [-10, 20, 10],
state: { calcs: { max: 20 } },
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
},
{
name: 'b',
values: [10, 10, 10],
state: { calcs: { max: 10 } },
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
},
{
name: 'c',
values: [20, 20, 20],
state: { calcs: { max: 20 } },
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
},
],
});
expect(preparePlotData([df], undefined, { sortBy: 'Max', sortDesc: false } as any)).toMatchInlineSnapshot(`
Array [
Array [
9997,
9998,
9999,
],
Array [
0,
30,
20,
],
Array [
10,
10,
10,
],
Array [
20,
50,
40,
],
]
`);
expect(preparePlotData([df], undefined, { sortBy: 'Max', sortDesc: true } as any)).toMatchInlineSnapshot(`
Array [
Array [
9997,
9998,
9999,
],
Array [
-10,
20,
10,
],
Array [
20,
50,
40,
],
Array [
10,
40,
30,
],
]
`);
});
});
});
});
describe('orderIdsByCalcs', () => {
const ids = [1, 2, 3, 4];
const frame = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
{ name: 'a', values: [-10, 20, 10], state: { calcs: { min: -10 } } },
{ name: 'b', values: [20, 20, 20], state: { calcs: { min: 20 } } },
{ name: 'c', values: [10, 10, 10], state: { calcs: { min: 10 } } },
{ name: 'd', values: [30, 30, 30] },
],
describe('auto stacking groups', () => {
test('split on stacking mode', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
{
name: 'b',
values: [1, 2, 3],
config: { custom: { stacking: { mode: StackingMode.Percent } } },
},
{
name: 'c',
values: [4, 5, 6],
config: { custom: { stacking: { mode: StackingMode.Normal } } },
},
],
});
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
Array [
Object {
"dir": 1,
"series": Array [
1,
],
},
Object {
"dir": 1,
"series": Array [
2,
],
},
]
`);
});
it.each([
{ legend: undefined },
{ legend: { sortBy: 'Min' } },
{ legend: { sortDesc: false } },
{ legend: {} },
{ sortBy: 'Mik', sortDesc: true },
])('should return without ordering if legend option is %o', (legend: any) => {
const result = orderIdsByCalcs({ ids, frame, legend });
expect(result).toEqual([1, 2, 3, 4]);
test('split pos/neg', () => {
// since we expect most series to be Pos, we try to bail early when scanning all values
// as soon as we find a value >= 0, it's assumed Pos, else Neg
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
{
name: 'a',
values: [-1, null, -3],
config: { custom: { stacking: { mode: StackingMode.Normal } } },
},
{
name: 'b',
values: [1, 2, 3],
config: { custom: { stacking: { mode: StackingMode.Normal } } },
},
{
name: 'c',
values: [0, 0, 0],
config: { custom: { stacking: { mode: StackingMode.Normal } } },
},
],
});
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
Array [
Object {
"dir": -1,
"series": Array [
1,
3,
],
},
Object {
"dir": 1,
"series": Array [
2,
],
},
]
`);
});
it('should order the ids based on the frame stat', () => {
const resultDesc = orderIdsByCalcs({ ids, frame, legend: { sortBy: 'Min', sortDesc: true } as any });
expect(resultDesc).toEqual([4, 2, 3, 1]);
const resultAsc = orderIdsByCalcs({ ids, frame, legend: { sortBy: 'Min', sortDesc: false } as any });
expect(resultAsc).toEqual([1, 3, 2, 4]);
test('split pos/neg with NegY', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
{
name: 'a',
values: [-1, null, -3],
config: { custom: { stacking: { mode: StackingMode.Normal }, transform: GraphTransform.NegativeY } },
},
{
name: 'b',
values: [1, 2, 3],
config: { custom: { stacking: { mode: StackingMode.Normal } } },
},
{
name: 'c',
values: [0, 0, 0],
config: { custom: { stacking: { mode: StackingMode.Normal } } },
},
],
});
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
Array [
Object {
"dir": 1,
"series": Array [
1,
2,
],
},
Object {
"dir": -1,
"series": Array [
3,
],
},
]
`);
});
test('split on drawStyle, lineInterpolation, barAlignment', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
{
name: 'a',
values: [1, 2, 3],
config: {
custom: {
drawStyle: GraphDrawStyle.Bars,
barAlignment: BarAlignment.After,
stacking: { mode: StackingMode.Normal },
},
},
},
{
name: 'b',
values: [1, 2, 3],
config: {
custom: {
drawStyle: GraphDrawStyle.Bars,
barAlignment: BarAlignment.Before,
stacking: { mode: StackingMode.Normal },
},
},
},
{
name: 'c',
values: [1, 2, 3],
config: {
custom: {
drawStyle: GraphDrawStyle.Line,
lineInterpolation: LineInterpolation.Linear,
stacking: { mode: StackingMode.Normal },
},
},
},
{
name: 'd',
values: [1, 2, 3],
config: {
custom: {
drawStyle: GraphDrawStyle.Line,
lineInterpolation: LineInterpolation.Smooth,
stacking: { mode: StackingMode.Normal },
},
},
},
{
name: 'e',
values: [1, 2, 3],
config: { custom: { drawStyle: GraphDrawStyle.Points, stacking: { mode: StackingMode.Normal } } },
},
],
});
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
Array [
Object {
"dir": 1,
"series": Array [
1,
],
},
Object {
"dir": 1,
"series": Array [
2,
],
},
Object {
"dir": 1,
"series": Array [
3,
],
},
Object {
"dir": 1,
"series": Array [
4,
],
},
Object {
"dir": 1,
"series": Array [
5,
],
},
]
`);
});
test('split on axis & units (scaleKey)', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
{
name: 'a',
values: [1, 2, 3],
config: { custom: { stacking: { mode: StackingMode.Normal } }, unit: Units.FEET },
},
{
name: 'b',
values: [1, 2, 3],
config: { custom: { stacking: { mode: StackingMode.Normal } }, unit: Units.DEGREES },
},
],
});
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
Array [
Object {
"dir": 1,
"series": Array [
1,
],
},
Object {
"dir": 1,
"series": Array [
2,
],
},
]
`);
});
test('split on explicit stacking group & mode & pos/neg w/NegY', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [0, 1, 2] },
{
name: 'a',
values: [1, 2, 3],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'A' } } },
},
{
name: 'b',
values: [1, 2, 3],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'A' } } },
},
{
name: 'c',
values: [1, 2, 3],
config: { custom: { stacking: { mode: StackingMode.Percent, group: 'A' } } },
},
{
name: 'd',
values: [1, 2, 3],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'B' } } },
},
{
name: 'e',
values: [1, 2, 3],
config: { custom: { stacking: { mode: StackingMode.Percent, group: 'B' } } },
},
{
name: 'e',
values: [1, 2, 3],
config: {
custom: { stacking: { mode: StackingMode.Percent, group: 'B' }, transform: GraphTransform.NegativeY },
},
},
],
});
expect(getStackingGroups(df)).toMatchInlineSnapshot(`
Array [
Object {
"dir": 1,
"series": Array [
1,
2,
],
},
Object {
"dir": 1,
"series": Array [
3,
],
},
Object {
"dir": 1,
"series": Array [
4,
],
},
Object {
"dir": 1,
"series": Array [
5,
],
},
Object {
"dir": -1,
"series": Array [
6,
],
},
]
`);
});
});

View File

@@ -1,12 +1,11 @@
import { DataFrame, ensureTimeField, Field, FieldType } from '@grafana/data';
import { GraphFieldConfig, GraphTransform, StackingMode, VizLegendOptions } from '@grafana/schema';
import { orderBy } from 'lodash';
import { DataFrame, ensureTimeField, FieldType } from '@grafana/data';
import { BarAlignment, GraphDrawStyle, GraphTransform, LineInterpolation, StackingMode } from '@grafana/schema';
import uPlot, { AlignedData, Options, PaddingSide } from 'uplot';
import { attachDebugger } from '../../utils';
import { createLogger } from '../../utils/logger';
import { buildScaleKey } from '../GraphNG/utils';
const ALLOWED_FORMAT_STRINGS_REGEX = /\b(YYYY|YY|MMMM|MMM|MM|M|DD|D|WWWW|WWW|HH|H|h|AA|aa|a|mm|m|ss|s|fff)\b/g;
export const INTERNAL_NEGATIVE_Y_PREFIX = '__internalNegY';
export function timeFormatToTemplate(f: string) {
return f.replace(ALLOWED_FORMAT_STRINGS_REGEX, (match) => `{${match}}`);
@@ -41,115 +40,225 @@ interface StackMeta {
}
/** @internal */
export function preparePlotData(
frames: DataFrame[],
onStackMeta?: (meta: StackMeta) => void,
legend?: VizLegendOptions
): AlignedData {
const frame = frames[0];
const result: any[] = [];
const stackingGroups: Map<string, number[]> = new Map();
let seriesIndex = 0;
for (let i = 0; i < frame.fields.length; i++) {
const f = frame.fields[i];
if (f.type === FieldType.time) {
result.push(ensureTimeField(f).values.toArray());
seriesIndex++;
continue;
}
collectStackingGroups(f, stackingGroups, seriesIndex);
const customConfig: GraphFieldConfig = f.config.custom || {};
const values = f.values.toArray();
if (customConfig.transform === GraphTransform.NegativeY) {
result.push(values.map((v) => (v == null ? v : v * -1)));
} else if (customConfig.transform === GraphTransform.Constant) {
result.push(new Array(values.length).fill(values[0]));
} else {
result.push(values);
}
seriesIndex++;
}
// Stacking
if (stackingGroups.size !== 0) {
const byPct = frame.fields[1].config.custom?.stacking?.mode === StackingMode.Percent;
const dataLength = result[0].length;
const alignedTotals = Array(stackingGroups.size);
alignedTotals[0] = null;
// array or stacking groups
for (const [_, seriesIds] of stackingGroups.entries()) {
const seriesIdxs = orderIdsByCalcs({ ids: seriesIds, legend, frame });
const noValueStack = Array(dataLength).fill(true);
const groupTotals = byPct ? Array(dataLength).fill(0) : null;
if (byPct) {
for (let j = 0; j < seriesIdxs.length; j++) {
const currentlyStacking = result[seriesIdxs[j]];
for (let k = 0; k < dataLength; k++) {
const v = currentlyStacking[k];
groupTotals![k] += v == null ? 0 : +v;
}
}
}
const acc = Array(dataLength).fill(0);
for (let j = 0; j < seriesIdxs.length; j++) {
let seriesIdx = seriesIdxs[j];
alignedTotals[seriesIdx] = groupTotals;
const currentlyStacking = result[seriesIdx];
for (let k = 0; k < dataLength; k++) {
const v = currentlyStacking[k];
if (v != null && noValueStack[k]) {
noValueStack[k] = false;
}
acc[k] += v == null ? 0 : v / (byPct ? groupTotals![k] : 1);
}
result[seriesIdx] = acc.slice().map((v, i) => (noValueStack[i] ? null : v));
}
}
onStackMeta &&
onStackMeta({
totals: alignedTotals as AlignedData,
});
}
return result as AlignedData;
export interface StackingGroup {
series: number[];
dir: StackDirection;
}
export function collectStackingGroups(f: Field, groups: Map<string, number[]>, seriesIdx: number) {
const customConfig = f.config.custom;
if (!customConfig) {
return;
}
if (
customConfig.stacking?.mode !== StackingMode.None &&
customConfig.stacking?.group &&
!customConfig.hideFrom?.viz
) {
const group =
customConfig.transform === GraphTransform.NegativeY
? `${INTERNAL_NEGATIVE_Y_PREFIX}-${customConfig.stacking.group}`
: customConfig.stacking.group;
/** @internal */
const enum StackDirection {
Pos = 1,
Neg = -1,
}
if (!groups.has(group)) {
groups.set(group, [seriesIdx]);
} else {
groups.set(group, groups.get(group)!.concat(seriesIdx));
// generates bands between adjacent group series
/** @internal */
export function getStackingBands(group: StackingGroup) {
let bands: uPlot.Band[] = [];
let { series, dir } = group;
let lastIdx = series.length - 1;
let rSeries = series.slice().reverse();
rSeries.forEach((si, i) => {
if (i !== lastIdx) {
let nextIdx = rSeries[i + 1];
bands.push({
series: [si, nextIdx],
// fill direction is inverted from stack direction
dir: (-1 * dir) as 1 | -1,
});
}
});
return bands;
}
// expects an AlignedFrame
/** @internal */
export function getStackingGroups(frame: DataFrame) {
let groups: Map<string, StackingGroup> = new Map();
frame.fields.forEach(({ config, values }, i) => {
// skip x or time field
if (i === 0) {
return;
}
let { custom } = config;
if (custom == null) {
return;
}
// TODO: currently all AlignedFrame fields end up in uplot series & data, even custom.hideFrom?.viz
// ideally hideFrom.viz fields would be excluded so we can remove this
if (custom.hideFrom?.viz) {
return;
}
let { stacking } = custom;
if (stacking == null) {
return;
}
let { mode: stackingMode, group: stackingGroup } = stacking;
// not stacking
if (stackingMode === StackingMode.None) {
return;
}
// will this be stacked up or down after any transforms applied
let vals = values.toArray();
let transform = custom.transform;
let stackDir =
transform === GraphTransform.Constant
? vals[0] > 0
? StackDirection.Pos
: StackDirection.Neg
: transform === GraphTransform.NegativeY
? vals.some((v) => v > 0)
? StackDirection.Neg
: StackDirection.Pos
: vals.some((v) => v > 0)
? StackDirection.Pos
: StackDirection.Neg;
let drawStyle = custom.drawStyle as GraphDrawStyle;
let drawStyle2 =
drawStyle === GraphDrawStyle.Bars
? (custom.barAlignment as BarAlignment)
: drawStyle === GraphDrawStyle.Line
? (custom.lineInterpolation as LineInterpolation)
: null;
let stackKey = `${stackDir}|${stackingMode}|${stackingGroup}|${buildScaleKey(config)}|${drawStyle}|${drawStyle2}`;
let group = groups.get(stackKey);
if (group == null) {
group = {
series: [],
dir: stackDir,
};
groups.set(stackKey, group);
}
group.series.push(i);
});
return [...groups.values()];
}
/** @internal */
export function preparePlotData2(
frame: DataFrame,
stackingGroups: StackingGroup[],
onStackMeta?: (meta: StackMeta) => void
) {
let data = Array(frame.fields.length) as AlignedData;
let dataLen = frame.length;
let zeroArr = Array(dataLen).fill(0);
let accums = Array.from({ length: stackingGroups.length }, () => zeroArr.slice());
frame.fields.forEach((field, i) => {
let vals = field.values.toArray();
if (i === 0) {
if (field.type === FieldType.time) {
data[i] = ensureTimeField(field).values.toArray();
} else {
data[i] = vals;
}
return;
}
let { custom } = field.config;
if (!custom || custom.hideFrom?.viz) {
data[i] = vals;
return;
}
// apply transforms
if (custom.transform === GraphTransform.Constant) {
vals = Array(vals.length).fill(vals[0]);
} else {
vals = vals.slice();
if (custom.transform === GraphTransform.NegativeY) {
for (let i = 0; i < vals.length; i++) {
if (vals[i] != null) {
vals[i] *= -1;
}
}
}
}
let stackingMode = custom.stacking?.mode;
if (!stackingMode || stackingMode === StackingMode.None) {
data[i] = vals;
} else {
let stackIdx = stackingGroups.findIndex((group) => group.series.indexOf(i) > -1);
let accum = accums[stackIdx];
let stacked = (data[i] = Array(dataLen));
for (let i = 0; i < dataLen; i++) {
let v = vals[i];
if (v != null) {
stacked[i] = accum[i] += v;
} else {
stacked[i] = v; // we may want to coerce to 0 here
}
}
}
});
if (onStackMeta) {
let accumsBySeriesIdx = data.map((vals, i) => {
let stackIdx = stackingGroups.findIndex((group) => group.series.indexOf(i) > -1);
return stackIdx !== -1 ? accums[stackIdx] : vals;
});
onStackMeta({
totals: accumsBySeriesIdx as AlignedData,
});
}
// re-compute by percent
frame.fields.forEach((field, i) => {
if (i === 0 || field.config.custom?.hideFrom?.viz) {
return;
}
let stackingMode = field.config.custom?.stacking?.mode;
if (stackingMode === StackingMode.Percent) {
let stackIdx = stackingGroups.findIndex((group) => group.series.indexOf(i) > -1);
let accum = accums[stackIdx];
let group = stackingGroups[stackIdx];
let stacked = data[i];
for (let i = 0; i < dataLen; i++) {
let v = stacked[i];
if (v != null) {
// v / accum will always be pos, so properly (re)sign by group stacking dir
stacked[i] = group.dir * (v / accum[i]);
}
}
}
});
return data;
}
/**
@@ -215,23 +324,3 @@ export const pluginLogger = createLogger('uPlot');
export const pluginLog = pluginLogger.logger;
// pluginLogger.enable();
attachDebugger('graphng', undefined, pluginLogger);
type OrderIdsByCalcsOptions = {
legend?: VizLegendOptions;
ids: number[];
frame: DataFrame;
};
export function orderIdsByCalcs({ legend, ids, frame }: OrderIdsByCalcsOptions) {
if (!legend?.sortBy || legend.sortDesc == null) {
return ids;
}
const orderedIds = orderBy<number>(
ids,
(id) => {
return frame.fields[id].state?.calcs?.[legend.sortBy!.toLowerCase()];
},
legend.sortDesc ? 'desc' : 'asc'
);
return orderedIds;
}

View File

@@ -143,7 +143,6 @@ const ScaleDistributionEditor: React.FC<FieldOverrideEditorProps<ScaleDistributi
<Select
menuShouldPortal
allowCustomValue={false}
autoFocus
options={LOG_DISTRIBUTION_OPTIONS}
value={value.log || 2}
prefix={'base'}

View File

@@ -1,6 +1,6 @@
{
"name": "@jaegertracing/jaeger-ui-components",
"version": "8.5.0-pre",
"version": "8.5.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@emotion/css": "11.7.1",
"@grafana/data": "8.5.0-pre",
"@grafana/ui": "8.5.0-pre",
"@grafana/data": "8.5.0",
"@grafana/ui": "8.5.0",
"chance": "^1.0.10",
"classnames": "^2.2.5",
"combokeys": "^3.0.0",
@@ -39,7 +39,7 @@
"lodash": "4.17.21",
"lru-memoize": "^1.1.0",
"memoize-one": "6.0.0",
"moment": "2.29.1",
"moment": "2.29.2",
"moment-timezone": "0.5.34",
"prop-types": "15.8.1",
"react": "17.0.2",

View File

@@ -5,6 +5,7 @@ import (
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/setting"
)
@@ -149,6 +150,23 @@ func (hs *HTTPServer) declareFixedRoles() error {
Grants: []string{string(models.ROLE_VIEWER)},
}
apikeyReaderRole := ac.RoleRegistration{
Role: ac.RoleDTO{
Version: 1,
Name: "fixed:apikeys:reader",
DisplayName: "APIKeys reader",
Description: "Gives access to read api keys.",
Group: "API Keys",
Permissions: []ac.Permission{
{
Action: ac.ActionAPIKeyRead,
Scope: ac.ScopeAPIKeysAll,
},
},
},
Grants: []string{string(models.ROLE_ADMIN)},
}
apikeyWriterRole := ac.RoleRegistration{
Role: ac.RoleDTO{
Version: 1,
@@ -156,19 +174,15 @@ func (hs *HTTPServer) declareFixedRoles() error {
DisplayName: "APIKeys writer",
Description: "Gives access to add and delete api keys.",
Group: "API Keys",
Permissions: []ac.Permission{
Permissions: ac.ConcatPermissions(apikeyReaderRole.Role.Permissions, []ac.Permission{
{
Action: ac.ActionAPIKeyCreate,
},
{
Action: ac.ActionAPIKeyRead,
Scope: ac.ScopeAPIKeysAll,
},
{
Action: ac.ActionAPIKeyDelete,
Scope: ac.ScopeAPIKeysAll,
},
},
}),
},
Grants: []string{string(models.ROLE_ADMIN)},
}
@@ -410,7 +424,7 @@ func (hs *HTTPServer) declareFixedRoles() error {
orgMaintainerRole, teamsCreatorRole, teamsWriterRole, datasourcesExplorerRole,
annotationsReaderRole, dashboardAnnotationsWriterRole, annotationsWriterRole,
dashboardsCreatorRole, dashboardsReaderRole, dashboardsWriterRole,
foldersCreatorRole, foldersReaderRole, foldersWriterRole, apikeyWriterRole,
foldersCreatorRole, foldersReaderRole, foldersWriterRole, apikeyReaderRole, apikeyWriterRole,
)
}
@@ -462,6 +476,12 @@ var teamsEditAccessEvaluator = ac.EvalAll(
),
)
// apiKeyAccessEvaluator is used to protect the "Configuration > API keys" page access
var apiKeyAccessEvaluator = ac.EvalPermission(ac.ActionAPIKeyRead)
// serviceAccountAccessEvaluator is used to protect the "Configuration > Service accounts" page access
var serviceAccountAccessEvaluator = ac.EvalPermission(serviceaccounts.ActionRead)
// Metadata helpers
// getAccessControlMetadata returns the accesscontrol metadata associated with a given resource
func (hs *HTTPServer) getAccessControlMetadata(c *models.ReqContext,

View File

@@ -1,14 +1,12 @@
package api
import (
"context"
"fmt"
"testing"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth"
@@ -49,11 +47,6 @@ func TestAdminAPIEndpoint(t *testing.T) {
mock := mockstore.NewSQLStoreMock()
adminLogoutUserScenario(t, "Should not be allowed when calling POST on",
"/api/admin/users/1/logout", "/api/admin/users/:id/logout", func(sc *scenarioContext) {
bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error {
cmd.Result = &models.User{Id: testUserID}
return nil
})
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 400, sc.resp.Code)
}, mock)
@@ -182,8 +175,6 @@ func TestAdminAPIEndpoint(t *testing.T) {
}
adminCreateUserScenario(t, "Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
bus.ClearBusHandlers()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
@@ -202,8 +193,6 @@ func TestAdminAPIEndpoint(t *testing.T) {
}
adminCreateUserScenario(t, "Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
bus.ClearBusHandlers()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
@@ -222,8 +211,6 @@ func TestAdminAPIEndpoint(t *testing.T) {
}
adminCreateUserScenario(t, "Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
bus.ClearBusHandlers()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 400, sc.resp.Code)
@@ -241,8 +228,6 @@ func TestAdminAPIEndpoint(t *testing.T) {
}
adminCreateUserScenario(t, "Should return an error", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
bus.ClearBusHandlers()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 412, sc.resp.Code)
@@ -256,8 +241,6 @@ func TestAdminAPIEndpoint(t *testing.T) {
func putAdminScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
cmd dtos.AdminUpdateUserPermissionsForm, fn scenarioFunc, sqlStore sqlstore.Store) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
hs := &HTTPServer{
Cfg: setting.NewCfg(),
SQLStore: sqlStore,
@@ -284,10 +267,7 @@ func putAdminScenario(t *testing.T, desc string, url string, routePattern string
func adminLogoutUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc, sqlStore sqlstore.Store) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
hs := HTTPServer{
Bus: bus.GetBus(),
AuthTokenService: auth.NewFakeUserAuthTokenService(),
SQLStore: sqlStore,
}
@@ -312,12 +292,9 @@ func adminLogoutUserScenario(t *testing.T, desc string, url string, routePattern
func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePattern string, cmd models.RevokeAuthTokenCmd, fn scenarioFunc, sqlStore sqlstore.Store) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
hs := HTTPServer{
Bus: bus.GetBus(),
AuthTokenService: fakeAuthTokenService,
SQLStore: sqlStore,
}
@@ -343,12 +320,9 @@ func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, rou
func adminGetUserAuthTokensScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc, sqlStore sqlstore.Store) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
hs := HTTPServer{
Bus: bus.GetBus(),
AuthTokenService: fakeAuthTokenService,
SQLStore: sqlStore,
}
@@ -372,14 +346,11 @@ func adminGetUserAuthTokensScenario(t *testing.T, desc string, url string, route
func adminDisableUserScenario(t *testing.T, desc string, action string, url string, routePattern string, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
authInfoService := &logintest.AuthInfoServiceFake{}
hs := HTTPServer{
Bus: bus.GetBus(),
SQLStore: mockstore.NewSQLStoreMock(),
AuthTokenService: fakeAuthTokenService,
authInfoService: authInfoService,
@@ -410,8 +381,6 @@ func adminDeleteUserScenario(t *testing.T, desc string, url string, routePattern
SQLStore: mockstore.NewSQLStoreMock(),
}
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
sc := setupScenarioContext(t, url)
sc.sqlStore = hs.SQLStore
sc.authInfoService = &logintest.AuthInfoServiceFake{}
@@ -430,10 +399,7 @@ func adminDeleteUserScenario(t *testing.T, desc string, url string, routePattern
func adminCreateUserScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.AdminCreateUserForm, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
hs := HTTPServer{
Bus: bus.GetBus(),
Login: loginservice.LoginServiceMock{
ExpectedUserForm: cmd,
NoExistingOrgId: nonExistingOrgID,

View File

@@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/guardian"
@@ -491,7 +490,7 @@ func (hs *HTTPServer) NotificationTest(c *models.ReqContext) response.Response {
SecureSettings: dto.SecureSettings,
}
if err := bus.Dispatch(c.Req.Context(), cmd); err != nil {
if err := hs.AlertNotificationService.HandleNotificationTestCommand(c.Req.Context(), cmd); err != nil {
if errors.Is(err, models.ErrSmtpNotEnabled) {
return response.Error(412, err.Error(), err)
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search"
@@ -124,18 +123,12 @@ func TestAlertingAPIEndpoint(t *testing.T) {
}
func callPauseAlert(sc *scenarioContext) {
bus.AddHandler("test", func(ctx context.Context, cmd *models.PauseAlertCommand) error {
return nil
})
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func postAlertScenario(t *testing.T, hs *HTTPServer, desc string, url string, routePattern string, role models.RoleType,
cmd dtos.PauseAlertCommand, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)

View File

@@ -20,17 +20,18 @@ import (
func (hs *HTTPServer) GetAnnotations(c *models.ReqContext) response.Response {
query := &annotations.ItemQuery{
From: c.QueryInt64("from"),
To: c.QueryInt64("to"),
OrgId: c.OrgId,
UserId: c.QueryInt64("userId"),
AlertId: c.QueryInt64("alertId"),
DashboardId: c.QueryInt64("dashboardId"),
PanelId: c.QueryInt64("panelId"),
Limit: c.QueryInt64("limit"),
Tags: c.QueryStrings("tags"),
Type: c.Query("type"),
MatchAny: c.QueryBool("matchAny"),
From: c.QueryInt64("from"),
To: c.QueryInt64("to"),
OrgId: c.OrgId,
UserId: c.QueryInt64("userId"),
AlertId: c.QueryInt64("alertId"),
DashboardId: c.QueryInt64("dashboardId"),
PanelId: c.QueryInt64("panelId"),
Limit: c.QueryInt64("limit"),
Tags: c.QueryStrings("tags"),
Type: c.Query("type"),
MatchAny: c.QueryBool("matchAny"),
SignedInUser: c.SignedInUser,
}
repo := annotations.GetRepository()
@@ -63,22 +64,7 @@ func (hs *HTTPServer) PostAnnotation(c *models.ReqContext) response.Response {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
var canSave bool
var err error
if cmd.DashboardId != 0 {
canSave, err = canSaveDashboardAnnotation(c, cmd.DashboardId)
} else { // organization annotations
if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) {
canSave = canSaveOrganizationAnnotation(c)
} else {
// This is an additional validation needed only for FGAC Organization Annotations.
// It is not possible to do it in the middleware because we need to look
// into the request to determine if this is a Organization annotation or not
canSave, err = hs.canCreateOrganizationAnnotation(c)
}
}
if err != nil || !canSave {
if canSave, err := hs.canCreateAnnotation(c, cmd.DashboardId); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
@@ -192,21 +178,12 @@ func (hs *HTTPServer) UpdateAnnotation(c *models.ReqContext) response.Response {
repo := annotations.GetRepository()
annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.OrgId)
annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.SignedInUser)
if resp != nil {
return resp
}
canSave := true
if annotation.GetType() == annotations.Dashboard {
canSave, err = canSaveDashboardAnnotation(c, annotation.DashboardId)
} else {
if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) {
canSave = canSaveOrganizationAnnotation(c)
}
}
if err != nil || !canSave {
if canSave, err := hs.canSaveAnnotation(c, annotation); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
@@ -239,20 +216,12 @@ func (hs *HTTPServer) PatchAnnotation(c *models.ReqContext) response.Response {
repo := annotations.GetRepository()
annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.OrgId)
annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.SignedInUser)
if resp != nil {
return resp
}
canSave := true
if annotation.GetType() == annotations.Dashboard {
canSave, err = canSaveDashboardAnnotation(c, annotation.DashboardId)
} else {
if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) {
canSave = canSaveOrganizationAnnotation(c)
}
}
if err != nil || !canSave {
if canSave, err := hs.canSaveAnnotation(c, annotation); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
@@ -310,7 +279,7 @@ func (hs *HTTPServer) MassDeleteAnnotations(c *models.ReqContext) response.Respo
var dashboardId int64
if cmd.AnnotationId != 0 {
annotation, respErr := findAnnotationByID(c.Req.Context(), repo, cmd.AnnotationId, c.OrgId)
annotation, respErr := findAnnotationByID(c.Req.Context(), repo, cmd.AnnotationId, c.SignedInUser)
if respErr != nil {
return respErr
}
@@ -358,21 +327,12 @@ func (hs *HTTPServer) DeleteAnnotationByID(c *models.ReqContext) response.Respon
repo := annotations.GetRepository()
annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.OrgId)
annotation, resp := findAnnotationByID(c.Req.Context(), repo, annotationID, c.SignedInUser)
if resp != nil {
return resp
}
canSave := true
if annotation.GetType() == annotations.Dashboard {
canSave, err = canSaveDashboardAnnotation(c, annotation.DashboardId)
} else {
if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) {
canSave = canSaveOrganizationAnnotation(c)
}
}
if err != nil || !canSave {
if canSave, err := hs.canSaveAnnotation(c, annotation); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
@@ -387,7 +347,18 @@ func (hs *HTTPServer) DeleteAnnotationByID(c *models.ReqContext) response.Respon
return response.Success("Annotation deleted")
}
func canSaveDashboardAnnotation(c *models.ReqContext, dashboardID int64) (bool, error) {
func (hs *HTTPServer) canSaveAnnotation(c *models.ReqContext, annotation *annotations.ItemDTO) (bool, error) {
if annotation.GetType() == annotations.Dashboard {
return canEditDashboard(c, annotation.DashboardId)
} else {
if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) {
return c.SignedInUser.HasRole(models.ROLE_EDITOR), nil
}
return true, nil
}
}
func canEditDashboard(c *models.ReqContext, dashboardID int64) (bool, error) {
guard := guardian.New(c.Req.Context(), dashboardID, c.OrgId, c.SignedInUser)
if canEdit, err := guard.CanEdit(); err != nil || !canEdit {
return false, err
@@ -396,12 +367,13 @@ func canSaveDashboardAnnotation(c *models.ReqContext, dashboardID int64) (bool,
return true, nil
}
func canSaveOrganizationAnnotation(c *models.ReqContext) bool {
return c.SignedInUser.HasRole(models.ROLE_EDITOR)
}
func findAnnotationByID(ctx context.Context, repo annotations.Repository, annotationID int64, orgID int64) (*annotations.ItemDTO, response.Response) {
items, err := repo.Find(ctx, &annotations.ItemQuery{AnnotationId: annotationID, OrgId: orgID})
func findAnnotationByID(ctx context.Context, repo annotations.Repository, annotationID int64, user *models.SignedInUser) (*annotations.ItemDTO, response.Response) {
query := &annotations.ItemQuery{
AnnotationId: annotationID,
OrgId: user.OrgId,
SignedInUser: user,
}
items, err := repo.Find(ctx, query)
if err != nil {
return nil, response.Error(500, "Failed to find annotation", err)
@@ -446,9 +418,21 @@ func AnnotationTypeScopeResolver() (string, accesscontrol.AttributeScopeResolveF
return "", accesscontrol.ErrInvalidScope
}
annotation, resp := findAnnotationByID(ctx, annotations.GetRepository(), int64(annotationId), orgID)
// tempUser is used to resolve annotation type.
// The annotation doesn't get returned to the real user, so real user's permissions don't matter here.
tempUser := &models.SignedInUser{
OrgId: orgID,
Permissions: map[int64]map[string][]string{
orgID: {
accesscontrol.ActionDashboardsRead: {accesscontrol.ScopeDashboardsAll},
accesscontrol.ActionAnnotationsRead: {accesscontrol.ScopeAnnotationsAll},
},
},
}
annotation, resp := findAnnotationByID(ctx, annotations.GetRepository(), int64(annotationId), tempUser)
if resp != nil {
return "", err
return "", errors.New("could not resolve annotation type")
}
if annotation.GetType() == annotations.Organization {
@@ -460,9 +444,23 @@ func AnnotationTypeScopeResolver() (string, accesscontrol.AttributeScopeResolveF
return accesscontrol.ScopeAnnotationsProvider.GetResourceScope(""), annotationTypeResolver
}
func (hs *HTTPServer) canCreateOrganizationAnnotation(c *models.ReqContext) (bool, error) {
evaluator := accesscontrol.EvalPermission(accesscontrol.ActionAnnotationsCreate, accesscontrol.ScopeAnnotationsTypeOrganization)
return hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator)
func (hs *HTTPServer) canCreateAnnotation(c *models.ReqContext, dashboardId int64) (bool, error) {
if dashboardId != 0 {
if hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) {
evaluator := accesscontrol.EvalPermission(accesscontrol.ActionAnnotationsCreate, accesscontrol.ScopeAnnotationsTypeDashboard)
if canSave, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator); err != nil || !canSave {
return canSave, err
}
}
return canEditDashboard(c, dashboardId)
} else { // organization annotations
if hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) {
evaluator := accesscontrol.EvalPermission(accesscontrol.ActionAnnotationsCreate, accesscontrol.ScopeAnnotationsTypeOrganization)
return hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator)
} else {
return c.SignedInUser.HasRole(models.ROLE_EDITOR), nil
}
}
}
func (hs *HTTPServer) canMassDeleteAnnotations(c *models.ReqContext, dashboardID int64) (bool, error) {
@@ -476,7 +474,7 @@ func (hs *HTTPServer) canMassDeleteAnnotations(c *models.ReqContext, dashboardID
return false, err
}
canSave, err = canSaveDashboardAnnotation(c, dashboardID)
canSave, err = canEditDashboard(c, dashboardID)
if err != nil || !canSave {
return false, err
}

View File

@@ -13,7 +13,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations"
@@ -293,8 +292,6 @@ var fakeAnnoRepo *fakeAnnotationsRepo
func postAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
cmd dtos.PostAnnotationsCmd, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
hs := setupSimpleHTTPServer(nil)
store := sqlstore.InitTestDB(t)
store.Cfg = hs.Cfg
@@ -324,8 +321,6 @@ func postAnnotationScenario(t *testing.T, desc string, url string, routePattern
func putAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
cmd dtos.UpdateAnnotationsCmd, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
hs := setupSimpleHTTPServer(nil)
store := sqlstore.InitTestDB(t)
store.Cfg = hs.Cfg
@@ -354,8 +349,6 @@ func putAnnotationScenario(t *testing.T, desc string, url string, routePattern s
func patchAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType, cmd dtos.PatchAnnotationsCmd, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
hs := setupSimpleHTTPServer(nil)
store := sqlstore.InitTestDB(t)
store.Cfg = hs.Cfg
@@ -385,8 +378,6 @@ func patchAnnotationScenario(t *testing.T, desc string, url string, routePattern
func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
cmd dtos.MassDeleteAnnotationsCmd, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
hs := setupSimpleHTTPServer(nil)
store := sqlstore.InitTestDB(t)
store.Cfg = hs.Cfg
@@ -624,6 +615,18 @@ func TestAPI_Annotations_AccessControl(t *testing.T) {
},
want: http.StatusForbidden,
},
{
name: "AccessControl create dashboard annotation with incorrect permissions is forbidden",
args: args{
permissions: []*accesscontrol.Permission{{
Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeOrganization,
}},
url: "/api/annotations",
method: http.MethodPost,
body: mockRequestBody(postDashboardCmd),
},
want: http.StatusForbidden,
},
{
name: "AccessControl create organization annotation with permissions is allowed",
args: args{

View File

@@ -14,6 +14,8 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/web"
)
var plog = log.New("api")
@@ -61,9 +63,9 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/org/teams", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsRead)), hs.Index)
r.Get("/org/teams/edit/*", authorize(reqCanAccessTeams, teamsEditAccessEvaluator), hs.Index)
r.Get("/org/teams/new", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsCreate)), hs.Index)
r.Get("/org/serviceaccounts", middleware.ReqOrgAdmin, hs.Index)
r.Get("/org/serviceaccounts/:serviceAccountId", middleware.ReqOrgAdmin, hs.Index)
r.Get("/org/apikeys/", reqOrgAdmin, hs.Index)
r.Get("/org/serviceaccounts", authorize(reqOrgAdmin, ac.EvalPermission(serviceaccounts.ActionRead)), hs.Index)
r.Get("/org/serviceaccounts/:serviceAccountId", authorize(reqOrgAdmin, ac.EvalPermission(serviceaccounts.ActionRead)), hs.Index)
r.Get("/org/apikeys/", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyRead)), hs.Index)
r.Get("/dashboard/import/", reqSignedIn, hs.Index)
r.Get("/configuration", reqGrafanaAdmin, hs.Index)
r.Get("/admin", reqGrafanaAdmin, hs.Index)
@@ -278,7 +280,7 @@ func (hs *HTTPServer) registerRoutes() {
keysRoute.Get("/", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyRead, ac.ScopeAPIKeysAll)), routing.Wrap(hs.GetAPIKeys))
keysRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyCreate)), quota("api_key"), routing.Wrap(hs.AddAPIKey))
keysRoute.Delete("/:id", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyDelete, apikeyIDScope)), routing.Wrap(hs.DeleteAPIKey))
}, reqOrgAdmin)
})
// Preferences
apiRoute.Group("/preferences", func(prefRoute routing.RouteRegister) {
@@ -417,7 +419,14 @@ func (hs *HTTPServer) registerRoutes() {
alertsRoute.Get("/states-for-dashboard", routing.Wrap(hs.GetAlertStatesForDashboard))
})
apiRoute.Get("/alert-notifiers", reqEditorRole, routing.Wrap(
var notifiersAuthHandler web.Handler
if hs.Cfg.UnifiedAlerting.IsEnabled() {
notifiersAuthHandler = reqSignedIn
} else {
notifiersAuthHandler = reqEditorRole
}
apiRoute.Get("/alert-notifiers", notifiersAuthHandler, routing.Wrap(
hs.GetAlertNotifiers(hs.Cfg.UnifiedAlerting.IsEnabled())),
)
@@ -438,7 +447,7 @@ func (hs *HTTPServer) registerRoutes() {
orgRoute.Get("/lookup", routing.Wrap(hs.GetAlertNotificationLookup))
})
apiRoute.Get("/annotations", authorize(reqSignedIn, ac.EvalPermission(ac.ActionAnnotationsRead, ac.ScopeAnnotationsAll)), routing.Wrap(hs.GetAnnotations))
apiRoute.Get("/annotations", authorize(reqSignedIn, ac.EvalPermission(ac.ActionAnnotationsRead)), routing.Wrap(hs.GetAnnotations))
apiRoute.Post("/annotations/mass-delete", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAnnotationsDelete)), routing.Wrap(hs.MassDeleteAnnotations))
apiRoute.Group("/annotations", func(annotationsRoute routing.RouteRegister) {

View File

@@ -70,6 +70,9 @@ func (hs *HTTPServer) AddAPIKey(c *models.ReqContext) response.Response {
if !cmd.Role.IsValid() {
return response.Error(400, "Invalid role specified", nil)
}
if !c.OrgRole.Includes(cmd.Role) {
return response.Error(http.StatusForbidden, "Cannot assign a role higher than user's role", nil)
}
if hs.Cfg.ApiKeyMaxSecondsToLive != -1 {
if cmd.SecondsToLive == 0 {

View File

@@ -27,10 +27,10 @@ func (hs *HTTPServer) initAppPluginRoutes(r *web.Mux) {
Renegotiation: tls.RenegotiateFreelyAsClient,
},
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second,
}
@@ -65,6 +65,7 @@ func AppPluginRoute(route *plugins.Route, appID string, hs *HTTPServer) web.Hand
proxy := pluginproxy.NewApiPluginProxy(c, path, route, appID, hs.Cfg, hs.PluginSettings, hs.SecretsService)
proxy.Transport = pluginProxyTransport
proxy.ServeHTTP(c.Resp, c.Req)
}
}

View File

@@ -13,7 +13,6 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/fs"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/remotecache"
@@ -53,8 +52,6 @@ func loggedInUserScenario(t *testing.T, desc string, url string, routePattern st
func loggedInUserScenarioWithRole(t *testing.T, desc string, method string, url string, routePattern string, role models.RoleType, fn scenarioFunc, sqlStore sqlstore.Store) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
sc := setupScenarioContext(t, url)
sc.sqlStore = sqlStore
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
@@ -82,8 +79,6 @@ func loggedInUserScenarioWithRole(t *testing.T, desc string, method string, url
func anonymousUserScenario(t *testing.T, desc string, method string, url string, routePattern string, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
sc.context = c
@@ -197,7 +192,9 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHa
tracer, err := tracing.InitializeTracerForTest()
require.NoError(t, err)
authProxy := authproxy.ProvideAuthProxy(cfg, remoteCacheSvc, loginservice.LoginServiceMock{}, sqlStore)
ctxHdlr := contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, sqlStore, tracer, authProxy)
loginService := &logintest.LoginServiceFake{}
authenticator := &logintest.AuthenticatorFake{}
ctxHdlr := contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, sqlStore, tracer, authProxy, loginService, authenticator)
return ctxHdlr
}
@@ -238,7 +235,6 @@ func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url strin
store := sqlstore.InitTestDB(t)
hs := &HTTPServer{
Cfg: cfg,
Bus: bus.GetBus(),
Live: newTestLive(t, store),
Features: features,
QuotaService: &quota.QuotaService{Cfg: cfg},
@@ -329,7 +325,6 @@ func setupSimpleHTTPServer(features *featuremgmt.FeatureManager) *HTTPServer {
return &HTTPServer{
Cfg: cfg,
Features: features,
Bus: bus.GetBus(),
AccessControl: accesscontrolmock.New().WithDisabled(),
}
}
@@ -380,7 +375,6 @@ func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessCo
hs := &HTTPServer{
Cfg: cfg,
Features: features,
Bus: bus.GetBus(),
Live: newTestLive(t, db),
QuotaService: &quota.QuotaService{Cfg: cfg},
RouteRegister: routeRegister,

View File

@@ -453,9 +453,10 @@ func (hs *HTTPServer) GetHomeDashboard(c *models.ReqContext) response.Response {
dash.Meta.IsHome = true
dash.Meta.CanEdit = c.SignedInUser.HasRole(models.ROLE_EDITOR)
dash.Meta.FolderTitle = "General"
dash.Dashboard = simplejson.New()
jsonParser := json.NewDecoder(file)
if err := jsonParser.Decode(&dash.Dashboard); err != nil {
if err := jsonParser.Decode(dash.Dashboard); err != nil {
return response.Error(500, "Failed to load home dashboard", err)
}

View File

@@ -1,7 +1,6 @@
package api
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
@@ -9,7 +8,6 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
@@ -52,12 +50,6 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
sqlmock.ExpectedDashboardAclInfoList = aclMockResp
sqlmock.ExpectedTeamsByUser = []*models.TeamDTO{}
// we need it here for now for the guadian service to work
bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error {
query.Result = aclMockResp
return nil
})
return mockSnapshotResult
}

View File

@@ -16,7 +16,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models"
@@ -46,7 +45,7 @@ func TestGetHomeDashboard(t *testing.T) {
cfg.StaticRootPath = "../../public/"
hs := &HTTPServer{
Cfg: cfg, Bus: bus.New(),
Cfg: cfg,
pluginStore: &fakePluginStore{},
SQLStore: mockstore.NewSQLStoreMock(),
}
@@ -96,7 +95,7 @@ func newTestLive(t *testing.T, store *sqlstore.SQLStore) *live.GrafanaLive {
nil,
&usagestats.UsageStatsMock{T: t},
nil,
features, nil)
features, accesscontrolmock.New())
require.NoError(t, err)
return gLive
}
@@ -957,31 +956,17 @@ func (hs *HTTPServer) callGetDashboard(sc *scenarioContext) {
}
func (hs *HTTPServer) callGetDashboardVersion(sc *scenarioContext) {
bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardVersionQuery) error {
query.Result = &models.DashboardVersion{}
return nil
})
sc.handlerFunc = hs.GetDashboardVersion
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
}
func (hs *HTTPServer) callGetDashboardVersions(sc *scenarioContext) {
bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardVersionsQuery) error {
query.Result = []*models.DashboardVersionDTO{}
return nil
})
sc.handlerFunc = hs.GetDashboardVersions
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
}
func (hs *HTTPServer) callDeleteDashboardByUID(t *testing.T,
sc *scenarioContext, mockDashboard *dashboards.FakeDashboardService) {
bus.AddHandler("test", func(ctx context.Context, cmd *models.DeleteDashboardCommand) error {
return nil
})
hs.dashboardService = mockDashboard
sc.handlerFunc = hs.DeleteDashboardByUID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
@@ -1003,11 +988,8 @@ func callPostDashboardShouldReturnSuccess(sc *scenarioContext) {
func postDashboardScenario(t *testing.T, desc string, url string, routePattern string, cmd models.SaveDashboardCommand, dashboardService dashboards.DashboardService, folderService dashboards.FolderService, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
cfg := setting.NewCfg()
hs := HTTPServer{
Bus: bus.GetBus(),
Cfg: cfg,
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
Live: newTestLive(t, sqlstore.InitTestDB(t)),
@@ -1040,12 +1022,9 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
func postDiffScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.CalculateDiffOptions, role models.RoleType, fn scenarioFunc, sqlmock sqlstore.Store) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
cfg := setting.NewCfg()
hs := HTTPServer{
Cfg: cfg,
Bus: bus.GetBus(),
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
Live: newTestLive(t, sqlstore.InitTestDB(t)),
QuotaService: &quota.QuotaService{Cfg: cfg},
@@ -1076,13 +1055,10 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string
func restoreDashboardVersionScenario(t *testing.T, desc string, url string, routePattern string, mock *dashboards.FakeDashboardService, cmd dtos.RestoreDashboardVersionCommand, fn scenarioFunc, sqlStore sqlstore.Store) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
cfg := setting.NewCfg()
mockSQLStore := mockstore.NewSQLStoreMock()
hs := HTTPServer{
Cfg: cfg,
Bus: bus.GetBus(),
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
Live: newTestLive(t, sqlstore.InitTestDB(t)),
QuotaService: &quota.QuotaService{Cfg: cfg},
@@ -1115,8 +1091,8 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
}
func (sc *scenarioContext) ToJSON() *simplejson.Json {
var result *simplejson.Json
err := json.NewDecoder(sc.resp.Body).Decode(&result)
result := simplejson.New()
err := json.NewDecoder(sc.resp.Body).Decode(result)
require.NoError(sc.t, err)
return result
}

View File

@@ -56,22 +56,23 @@ const (
)
type NavLink struct {
Id string `json:"id,omitempty"`
Text string `json:"text"`
Description string `json:"description,omitempty"`
Section string `json:"section,omitempty"`
SubTitle string `json:"subTitle,omitempty"`
Icon string `json:"icon,omitempty"`
Img string `json:"img,omitempty"`
Url string `json:"url,omitempty"`
Target string `json:"target,omitempty"`
SortWeight int64 `json:"sortWeight,omitempty"`
Divider bool `json:"divider,omitempty"`
HideFromMenu bool `json:"hideFromMenu,omitempty"`
HideFromTabs bool `json:"hideFromTabs,omitempty"`
Children []*NavLink `json:"children,omitempty"`
HighlightText string `json:"highlightText,omitempty"`
HighlightID string `json:"highlightId,omitempty"`
Id string `json:"id,omitempty"`
Text string `json:"text"`
Description string `json:"description,omitempty"`
Section string `json:"section,omitempty"`
SubTitle string `json:"subTitle,omitempty"`
Icon string `json:"icon,omitempty"`
Img string `json:"img,omitempty"`
Url string `json:"url,omitempty"`
Target string `json:"target,omitempty"`
SortWeight int64 `json:"sortWeight,omitempty"`
Divider bool `json:"divider,omitempty"`
HideFromMenu bool `json:"hideFromMenu,omitempty"`
HideFromTabs bool `json:"hideFromTabs,omitempty"`
ShowIconInNavbar bool `json:"showIconInNavbar,omitempty"`
Children []*NavLink `json:"children,omitempty"`
HighlightText string `json:"highlightText,omitempty"`
HighlightID string `json:"highlightId,omitempty"`
}
// NavIDCfg is the id for org configuration navigation node

View File

@@ -15,7 +15,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
service "github.com/grafana/grafana/pkg/services/dashboards/manager"
@@ -355,8 +354,6 @@ func callUpdateFolderPermissions(t *testing.T, sc *scenarioContext) {
func updateFolderPermissionScenario(t *testing.T, ctx updatePermissionContext, hs *HTTPServer) {
t.Run(fmt.Sprintf("%s %s", ctx.desc, ctx.url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
sc := setupScenarioContext(t, ctx.url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {

View File

@@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -138,10 +137,7 @@ func callCreateFolder(sc *scenarioContext) {
func createFolderScenario(t *testing.T, desc string, url string, routePattern string, folderService dashboards.FolderService,
cmd models.CreateFolderCommand, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
hs := HTTPServer{
Bus: bus.GetBus(),
Cfg: setting.NewCfg(),
folderService: folderService,
Features: featuremgmt.WithFeatures(),
@@ -170,8 +166,6 @@ func callUpdateFolder(sc *scenarioContext) {
func updateFolderScenario(t *testing.T, desc string, url string, routePattern string, folderService dashboards.FolderService,
cmd models.UpdateFolderCommand, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
hs := HTTPServer{
Cfg: setting.NewCfg(),
folderService: folderService,

View File

@@ -113,6 +113,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
"rudderstackDataPlaneUrl": setting.RudderstackDataPlaneUrl,
"rudderstackSdkUrl": setting.RudderstackSdkUrl,
"rudderstackConfigUrl": setting.RudderstackConfigUrl,
"feedbackLinksEnabled": hs.Cfg.FeedbackLinksEnabled,
"applicationInsightsConnectionString": hs.Cfg.ApplicationInsightsConnectionString,
"applicationInsightsEndpointUrl": hs.Cfg.ApplicationInsightsEndpointUrl,
"disableLoginForm": setting.DisableLoginForm,

View File

@@ -7,7 +7,6 @@ import (
"path/filepath"
"testing"
"github.com/grafana/grafana/pkg/bus"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/licensing"
@@ -45,7 +44,6 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features *featuremgmt.
hs := &HTTPServer{
Cfg: cfg,
Features: features,
Bus: bus.GetBus(),
License: &licensing.OSSLicensingService{Cfg: cfg},
RenderService: &rendering.RenderingService{
Cfg: cfg,

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