Compare commits

...

42 Commits

Author SHA1 Message Date
Arve Knudsen
fce221a7d3 Release v7.3.0-beta2
Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
2020-10-22 12:47:29 +02:00
Krzysztof Warunek
6c5e139d8f CloudWatch/Athena - valid metrics and dimensions. (#28436)
* CloudWatch/Athena - valid metrics and dimensions.
In accordance with https://docs.aws.amazon.com/athena/latest/ug/query-metrics-viewing.html.

* Athena: add ProcessedBytes dimension instead of DataScannedInBytes

(cherry picked from commit a71eadf379)
2020-10-22 12:47:29 +02:00
Carl Bergquist
194362c302 Database; Remove database metric feature flag and update changelog (#28438)
* adds note about broken feature flag

* remove code to enable feature flag

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
(cherry picked from commit 62f5641aa9)
2020-10-22 12:47:29 +02:00
Aliaksei Tuzik
11eb920e87 Prometheus: fix parsing of infinite sample values (#28287) (#28288)
* Prometheus: fix parsing of infinite sample values (#28287)

* Prometheus: Use common function to parse both sample values and histogram "le" label

(cherry picked from commit f3c09e8bcc)
2020-10-22 12:47:29 +02:00
Jack Westbrook
bb555684b1 Grafana/ui: pass html attributes to segment (#28316)
* feat(grafana-ui): introduce rest props to segment components

* docs(grafana-ui): add segment stories for html attributes

(cherry picked from commit 04c06f2286)
2020-10-22 12:47:29 +02:00
Hugo Häggmark
f2fac78c0b Docs: Adds basic frontend data request concepts (#28253)
* Docs: Adds frontend data request docs

* Update contribute/architecture/frontend-data-requests.md

Co-authored-by: Marcus Olsson <marcus.olsson@hey.com>

* Update contribute/architecture/frontend-data-requests.md

Co-authored-by: Marcus Olsson <marcus.olsson@hey.com>

* Update contribute/architecture/frontend-data-requests.md

Co-authored-by: Marcus Olsson <marcus.olsson@hey.com>

* Update contribute/architecture/frontend-data-requests.md

Co-authored-by: Marcus Olsson <marcus.olsson@hey.com>

* Docs: changes after PR comments

* Docs: changes after PR comments

* Docs: changes after PR comments

* Update contribute/architecture/frontend-data-requests.md

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>

Co-authored-by: Marcus Olsson <marcus.olsson@hey.com>
Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
Co-authored-by: achatterjee-grafana <34888589+ashishagarwal06@users.noreply.github.com>
(cherry picked from commit b497063f64)
2020-10-22 12:47:29 +02:00
Carl Bergquist
08be99fa45 Instrumentation: Add histogram for request duration (#28364)
Signed-off-by: bergquist <carl.bergquist@gmail.com>
(cherry picked from commit edbaa9d681)
2020-10-22 12:47:29 +02:00
Carl Bergquist
df2fda3f88 remove status label from histogram (#28387)
Signed-off-by: bergquist <carl.bergquist@gmail.com>
(cherry picked from commit b036112444)
2020-10-22 12:47:29 +02:00
Andrej Ocenas
983d6c8ef8 Explore: Fix date formatting in url for trace logs link (#28381)
* Fix url formatting

* Reverse the overhang buffers

* Fix range when opening split

(cherry picked from commit ad657dcdc3)
2020-10-22 12:47:29 +02:00
Carl Bergquist
12dc0ea111 Instrumentation: Add counters and histograms for database queries (#28236)
Signed-off-by: bergquist <carl.bergquist@gmail.com>

Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com>
Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
(cherry picked from commit 74d1d3c6a8)
2020-10-22 12:47:29 +02:00
Marcus Efraimsson
103765c349 CloudWatch: Adding support for additional Amazon CloudFront metrics (#28378)
Follow up to #28069 where some metrics was missed.

Ref #28069

(cherry picked from commit 0e17a15fbd)
2020-10-22 12:47:29 +02:00
Alex Khomenko
6b4fd2d33c Add unique ids to query editor fields (#28376)
* Add unique ids to query editor fields

* Update public/app/plugins/datasource/testdata/components/RandomWalkEditor.tsx

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

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
(cherry picked from commit 1cce13b501)
2020-10-22 12:47:29 +02:00
maknik
1a0500bbeb Dashboard links: Places drop down list so it's always visible (#28330)
* calculating whether to place the list on the right or left edge of the parent

* change naming and add import of createRef

(cherry picked from commit cdab6028e1)
2020-10-22 12:47:29 +02:00
Andrej Ocenas
f1b9c6cde1 Loki: Run instant query only when doing metric query (#28325)
* Run instant query only when doing metric query

* Update public/app/plugins/datasource/loki/datasource.ts

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
(cherry picked from commit 8e9181e7d1)
2020-10-22 12:47:29 +02:00
Andrej Ocenas
fd851af389 Loki: Base maxDataPoints limits on query type (#28298)
* Base maxLines and maxDataPoints based on query type

* Allow overriding the limit to higher value

(cherry picked from commit 8db5d750d0)
2020-10-22 12:47:29 +02:00
Elliot Pryde
8f7eb69db6 Explore: respect min_refresh_interval (#27988)
* Explore: respect min_refresh_interval

Fixes #27494

* fixup! Explore: respect min_refresh_interval

* fixup! Explore: respect min_refresh_interval

* UI: export defaultIntervals from refresh picker

* fixup! Explore: respect min_refresh_interval

Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
(cherry picked from commit 1760fdd55d)
2020-10-22 12:47:29 +02:00
Arve Knudsen
bf74c1fe3f Drone: Use ${DRONE_TAG} in release pipelines, since it should work (#28299)
Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
(cherry picked from commit 392c5bdf73)
2020-10-22 12:47:29 +02:00
Jack Westbrook
c60ea5f25f fix: for graph size not taking up full height or width
(cherry picked from commit 448114f649)
2020-10-22 12:47:29 +02:00
Arve Knudsen
12dc9ea49f Drone: Fix grafana-mixin linting (#28308)
* Drone: Fix Starlark script

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* grafana-mixin: Move build logic to scripts

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Drone: Use mixin scripts

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* CI build image: Install jsonnetfmt and mixtool

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Makefile: Print commands

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
(cherry picked from commit 847dc0bec7)
2020-10-22 12:47:29 +02:00
Sofia Papagiannaki
e5f12afda2 SQLStore: Run tests as integration tests (#28265)
* sqlstore: Run tests as integration tests

* Truncate database instead of re-creating it on each test

* Fix test description

See https://github.com/grafana/grafana/pull/12129

* Fix lint issues

* Fix postgres dialect after review suggestion

* Rename and document functions after review suggestion

* Add periods

* Fix auto-increment value for mysql dialect

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
(cherry picked from commit 4937f0daab)
2020-10-22 12:47:29 +02:00
Arve Knudsen
77ab9f4331 API: Fix short URLs (#28300)
* API: Fix short URLs

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
(cherry picked from commit a2c7c5b912)
2020-10-22 12:47:29 +02:00
The Rock Guy
0f0d860ed3 CloudWatch: Add EC2CapacityReservations Namespace (#28309)
(cherry picked from commit c9cc82ea55)
2020-10-22 12:47:29 +02:00
Zoltán Bedi
ec0383c9ff Jaeger: timeline collapser to show icons (#28284)
* Fix: timeline collapser to show icons

* Use IconButton

* Export named component instead of default

(cherry picked from commit e93bd23353)
2020-10-22 12:47:29 +02:00
Carl Bergquist
c510f30eb3 Add monitoring mixing for Grafana (#28285)
Co-authored-by: Tom Wilkie <tom.wilkie@gmail.com>
(cherry picked from commit 6002df580f)
2020-10-22 12:47:29 +02:00
Grot (@grafanabot)
f0dafad5cb SAML: IdP-initiated SSO docs (#28280) (#28462)
* SAML: IdP-initiated SSO docs

* Update docs/sources/enterprise/saml.md

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

* Apply suggestions from code review

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

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

Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com>
2020-10-22 12:12:00 +02:00
Grot (@grafanabot)
6b056c527f AzureMonitor: Fix capitalization of NetApp 'volumes' namespace (#28369) (#28459)
'Microsoft.NetApp/netAppAccounts/capacityPools/Volumes'  -> 'Microsoft.NetApp/netAppAccounts/capacityPools/volumes'

(cherry picked from commit d2a792ea3b)

Co-authored-by: Sean Luce <lucesean@gmail.com>
2020-10-22 12:11:44 +02:00
Grot (@grafanabot)
c9513c5e81 Loki: Visually distinguish error logs for LogQL2 (#28359) (#28460)
* Loki: Add errored logs and update UI

* Update messaging

* Add icon and tooltip for errored logs

* Update name of variable for more semantic meaning

* Add tests

* Update test

* Refactor, remove unnecessary state

* Update packages/grafana-data/src/types/logs.ts

* Update packages/grafana-ui/src/components/Logs/LogDetails.tsx

Co-authored-by: Giordano Ricci <gio.ricci@grafana.com>

Co-authored-by: Giordano Ricci <gio.ricci@grafana.com>
(cherry picked from commit 3f39b4b601)

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
2020-10-22 12:11:02 +02:00
Grot (@grafanabot)
9153d2146b Explore: Support wide data frames (#28393) (#28454)
* Change how isTimeSeries work

* Simplify the decorators and update tests

(cherry picked from commit 8f4be08b00)

Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
2020-10-22 11:52:16 +02:00
Grot (@grafanabot)
f3fa16706c Live: support real time measurements (alpha) (#28022) (#28451)
* improve reduce transformer

* add measurment classes

* sync with new grafana measure format

* use address for live

* use plural in URL

* set the field name

* fix build

* find changes

* POST http to channel

* Yarn: Update lock file (#28014)

* Loki: Run instant query only in Explore (#27974)

* Run instant query only in Explore

* Replace forEach with for loop

* don't cast

* Docs: Fixed row display in table (#28031)

* Plugins: Let descendant plugins inherit their root's signature (#27970)

* plugins: Let descendant plugins inherit their root's signature

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Registry: Fix service shutdown mode trigger location (#28025)

* Add Alex Khomenko as member (#28032)

* show history

* fix confirm

* fix confirm

* add tests

* fix lint

* add more errors

* set values

* remove unrelated changes

* unrelated changes

* Update pkg/models/live.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/models/live.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/live/live.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/live/pluginHandler.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/live/pluginHandler.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/services/live/pluginHandler.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* use measurments for testdata endpoints

* add live to testdata

* add live to testdata

* Update pkg/services/live/channel.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Apply suggestions from code review

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* update comment formats

* uprevert testdata

* Apply suggestions from code review

Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com>

* Apply suggestions from code review

* CloudWatch: Add EC2CapacityReservations Namespace (#28309)

* API: Fix short URLs (#28300)

* API: Fix short URLs

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Chore: Add cloud-middleware as code owners (#28310)

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* SQLStore: Run tests as integration tests (#28265)

* sqlstore: Run tests as integration tests

* Truncate database instead of re-creating it on each test

* Fix test description

See https://github.com/grafana/grafana/pull/12129

* Fix lint issues

* Fix postgres dialect after review suggestion

* Rename and document functions after review suggestion

* Add periods

* Fix auto-increment value for mysql dialect

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

* Drone: Fix grafana-mixin linting (#28308)

* Drone: Fix Starlark script

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* grafana-mixin: Move build logic to scripts

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Drone: Use mixin scripts

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* CI build image: Install jsonnetfmt and mixtool

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Makefile: Print commands

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* should only ignore the file in the grafana mixin root folder (#28306)

Signed-off-by: bergquist <carl.bergquist@gmail.com>

* fix: for graph size not taking up full height or width

* Graph NG: fix toggling queries and extract Graph component from graph3 panel (#28290)

* Fix issue when data and config is not in sync

* Extract GraphNG component from graph panel and add some tests coverage

* Update packages/grafana-ui/src/components/uPlot/hooks.test.ts

* Update packages/grafana-ui/src/components/uPlot/hooks.test.ts

* Update packages/grafana-ui/src/components/uPlot/hooks.test.ts

* Fix grid color and annotations refresh

* Drone: Use ${DRONE_TAG} in release pipelines, since it should work (#28299)

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Explore: respect min_refresh_interval (#27988)

* Explore: respect min_refresh_interval

Fixes #27494

* fixup! Explore: respect min_refresh_interval

* fixup! Explore: respect min_refresh_interval

* UI: export defaultIntervals from refresh picker

* fixup! Explore: respect min_refresh_interval

Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>

* Loki: Base maxDataPoints limits on query type (#28298)

* Base maxLines and maxDataPoints based on query type

* Allow overriding the limit to higher value

* Bump tree-kill from 1.2.1 to 1.2.2 (#27405)

Bumps [tree-kill](https://github.com/pkrumins/node-tree-kill) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/pkrumins/node-tree-kill/releases)
- [Commits](https://github.com/pkrumins/node-tree-kill/compare/v1.2.1...v1.2.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump handlebars from 4.4.3 to 4.7.6 (#27416)

Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.4.3 to 4.7.6.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.4.3...v4.7.6)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Build(deps): Bump http-proxy from 1.18.0 to 1.18.1 (#27507)

Bumps [http-proxy](https://github.com/http-party/node-http-proxy) from 1.18.0 to 1.18.1.
- [Release notes](https://github.com/http-party/node-http-proxy/releases)
- [Changelog](https://github.com/http-party/node-http-proxy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/http-party/node-http-proxy/compare/1.18.0...1.18.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Automation: Add backport github action (#28318)

* BackendSrv: Fixes queue countdown when unsubscribe is before response (#28323)

* GraphNG: Use AxisSide enum (#28320)

* IssueTriage: Needs more info automation and messages (#28137)

* IssueTriage: Needs more info automation and messages

* Updated

* Updated

* Updated wording

* SAML: IdP-initiated SSO docs (#28280)

* SAML: IdP-initiated SSO docs

* Update docs/sources/enterprise/saml.md

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

* Apply suggestions from code review

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

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

* Loki: Run instant query only when doing metric query (#28325)

* Run instant query only when doing metric query

* Update public/app/plugins/datasource/loki/datasource.ts

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

* Automation: Tweaks to more info message (#28332)

* AlertingNG: remove warn/crit from eval prototype (#28334)

and misc cleanup

* area/grafana/toolkit: update e2e docker image (#28335)

* add xvfb to image

* comment out toolkit inclusion

* add latest tag

* update packages for cypress

* cleanup script

* Update auth-proxy.md (#28339)

Fix a minor grammar mistake: 'handling' to 'handle'.

* Git: Create .gitattributes for windows line endings (#28340)

With this set, Windows users will have text files converted from Windows style line endings (\r\n) to Unix style line endings (\n) when they’re added to the repository.
https://www.edwardthomson.com/blog/git_for_windows_line_endings.html

* Docs: Add docs for valuepicker (#28327)

* Templating: Replace all '$tag' in tag values query (#28343)

* Docs: Add missing records from grafana-ui 7.2.1 CHANGELOG (#28302)

* Dashboard links: Places drop down list so it's always visible (#28330)

* calculating whether to place the list on the right or left edge of the parent

* change naming and add import of createRef

* Automation: Update backport github action trigger (#28352)

It seems like GitHub has solved the problem of running github actions on PRs from forks with access to secrets.

https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/#improvements-for-public-repository-forks

If I change the event that triggers it to pull_request_target the action is run in the context of the base instead of the merged PR branch

* ColorSchemes: Adds more color schemes and text colors that depend on the background (#28305)

* Adding more color modes and text colors that depend on the background color

* Updates

* Updated

* Another big value fix

* Fixing unit tests

* Updated

* Updated test

* Update

* Updated

* Updated

* Updated

* Updated

* Added new demo dashboard

* Updated

* updated

* Updated

* Updateed

* added beta notice

* Fixed e2e test

* Fix typos

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* revert pseduo code

* apply feedback

* remove HTTP for now

* fix backend test

* change to datasource

* clear input for streams

* fix docs?

* consistent measure vs measurements

* better jsdocs

* fix a few jsdoc errors

* fix comment style

* Remove commented out code

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Clean up code

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Clean up code

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Clean up code

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Clean up code

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Clean up code

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Clean up code

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Clean up code

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Update pkg/models/live.go

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix build

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* set the stringField

Co-authored-by: Torkel Ödegaard <torkel@grafana.org>
Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
Co-authored-by: ozhuang <ozhuang.95@gmail.com>
Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
Co-authored-by: Amos Law <ahlaw.dev@gmail.com>
Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com>
Co-authored-by: The Rock Guy <fabian.bracco@gvcgroup.com.au>
Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com>
Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
Co-authored-by: Carl Bergquist <carl@grafana.com>
Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
Co-authored-by: Elliot Pryde <elliot.pryde@elliotpryde.com>
Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com>
Co-authored-by: Kyle Brandt <kyle@grafana.com>
Co-authored-by: Brian Gann <briangann@users.noreply.github.com>
Co-authored-by: J-F-Far <joel.f.farthing@gmail.com>
Co-authored-by: acoder77 <73009264+acoder77@users.noreply.github.com>
Co-authored-by: Peter Holmberg <peterholmberg@users.noreply.github.com>
Co-authored-by: Krzysztof Dąbrowski <krzysdabro@live.com>
Co-authored-by: maknik <mooniczkam@gmail.com>
(cherry picked from commit 2aafa39879)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2020-10-22 09:27:49 +02:00
Grot (@grafanabot)
9d7eaedb2b TestData: multiple arrow requests should return multiple frames (#28417) (#28441)
(cherry picked from commit f32d47a535)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2020-10-21 18:16:06 +02:00
Grot (@grafanabot)
2221c2bbad Plugins: do not remount app plugin on nav change (#28105) (#28426)
* do not remount app plugin on nav change

* test for not mounting app plugin twice

(cherry picked from commit 97526fc492)

Co-authored-by: Domas <domas.lapinskas@grafana.com>
2020-10-21 16:22:47 +02:00
Grot (@grafanabot)
363d0a9588 plugins: Don't exit on duplicate plugin (#28390) (#28430)
* plugins: Don't exit on duplicate plugin

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Add missing files

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

* Fix test

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
(cherry picked from commit 4084b53f91)

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
2020-10-21 13:04:25 +02:00
Grot (@grafanabot)
d18ac27126 App Plugins: Add backend support (#28272) (#28423)
* Add backend support for app plugins

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
(cherry picked from commit 68efedfa88)

Co-authored-by: Joan López de la Franca Beltran <joanjan14@gmail.com>
2020-10-21 10:52:08 +02:00
Grot (@grafanabot)
37327d74f5 FieldColor: Remove inverted color scheme (#28408) (#28418)
(cherry picked from commit 84992adf2a)

Co-authored-by: Torkel Ödegaard <torkel@grafana.org>
2020-10-21 09:02:49 +02:00
Torkel Ödegaard
5757bd80d3 ColorSchemes: Adds more color schemes and text colors that depend on the background (#28305) (#28414)
* Adding more color modes and text colors that depend on the background color

* Updates

* Updated

* Another big value fix

* Fixing unit tests

* Updated

* Updated test

* Update

* Updated

* Updated

* Updated

* Updated

* Added new demo dashboard

* Updated

* updated

* Updated

* Updateed

* added beta notice

* Fixed e2e test

(cherry picked from commit 566cd2c6af)
2020-10-21 07:23:13 +02:00
Grot (@grafanabot)
f071817b95 CloudWatch: Fix custom metrics (#28391) (#28401)
* CloudWatch: Fix querying for custom metrics

Co-authored by Mitch McKenzie <mitch.mckenzie@outlook.com>

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
(cherry picked from commit af17f9fd9b)

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
2020-10-20 15:31:17 +02:00
Grot (@grafanabot)
2110d4e9b4 Instrumentation: Adds environment_info metric (#28355) (#28388)
Signed-off-by: bergquist <carl.bergquist@gmail.com>

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
(cherry picked from commit 89ebb97fca)

Co-authored-by: Carl Bergquist <carl@grafana.com>
2020-10-20 09:59:52 +02:00
Grot (@grafanabot)
5ac4ae37a2 BackendSrv: Fixes queue countdown when unsubscribe is before response (#28323) (#28328)
(cherry picked from commit 9305117902)

Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com>
2020-10-16 16:45:38 +02:00
Arve Knudsen
c11c8b0b4a Drone: Fixes
Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
2020-10-15 13:56:46 +02:00
Arve Knudsen
0df7b25a49 Drone: Fixes
Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
2020-10-15 13:47:14 +02:00
Arve Knudsen
68a3631ed0 Release 7.3.0-beta1
Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
2020-10-15 13:14:47 +02:00
Arve Knudsen
2e9a0d4755 Chore: Update what's new and release notes URL in package.json
Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
2020-10-15 13:09:34 +02:00
194 changed files with 4897 additions and 1661 deletions

View File

@@ -14,7 +14,7 @@ steps:
- echo $DRONE_RUNNER_NAME
- name: initialize
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
@@ -27,19 +27,21 @@ steps:
DOCKERIZE_VERSION: 0.6.1
- name: lint-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
- ./scripts/revive-strict
- ./scripts/tidy-check.sh
- ./grafana-mixin/scripts/lint.sh
- ./grafana-mixin/scripts/build.sh
environment:
CGO_ENABLED: 1
depends_on:
- initialize
- name: codespell
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
- codespell -I words_to_ignore.txt docs/
@@ -47,7 +49,7 @@ steps:
- initialize
- name: shellcheck
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
@@ -62,7 +64,7 @@ steps:
- initialize
- name: test-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl test-backend
- ./bin/grabpl integration-tests
@@ -71,7 +73,7 @@ steps:
- lint-backend
- name: test-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- yarn run ci:test-frontend
environment:
@@ -80,7 +82,7 @@ steps:
- initialize
- name: build-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --variants linux-x64,linux-x64-musl,osx64,win64 --no-pull-enterprise
depends_on:
@@ -89,7 +91,7 @@ steps:
- test-backend
- name: build-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise
depends_on:
@@ -97,7 +99,7 @@ steps:
- test-frontend
- name: build-plugins
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps
depends_on:
@@ -105,7 +107,7 @@ steps:
- lint-backend
- name: package
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- . scripts/build/gpg-test-vars.sh && ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --variants linux-x64,linux-x64-musl,osx64,win64
depends_on:
@@ -118,7 +120,7 @@ steps:
- shellcheck
- name: end-to-end-tests-server
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
detach: true
commands:
- ./e2e/start-server
@@ -136,14 +138,14 @@ steps:
- end-to-end-tests-server
- name: build-storybook
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- yarn storybook:build
depends_on:
- package
- name: build-frontend-docs
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./scripts/ci-reference-docs-lint.sh ci
depends_on:
@@ -160,7 +162,7 @@ steps:
- build-frontend-docs
- name: copy-packages-for-docker
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- cp dist/*.tar.gz* packaging/docker/
depends_on:
@@ -176,7 +178,7 @@ steps:
- copy-packages-for-docker
- name: postgres-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq postgresql-client
@@ -193,7 +195,7 @@ steps:
- test-frontend
- name: mysql-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq default-mysql-client
@@ -270,7 +272,7 @@ steps:
- echo $DRONE_RUNNER_NAME
- name: initialize
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
@@ -295,19 +297,21 @@ steps:
from_secret: drone_token
- name: lint-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
- ./scripts/revive-strict
- ./scripts/tidy-check.sh
- ./grafana-mixin/scripts/lint.sh
- ./grafana-mixin/scripts/build.sh
environment:
CGO_ENABLED: 1
depends_on:
- initialize
- name: codespell
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
- codespell -I words_to_ignore.txt docs/
@@ -315,7 +319,7 @@ steps:
- initialize
- name: shellcheck
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
@@ -330,7 +334,7 @@ steps:
- initialize
- name: test-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl test-backend
- ./bin/grabpl integration-tests
@@ -339,7 +343,7 @@ steps:
- lint-backend
- name: test-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- yarn run ci:test-frontend
environment:
@@ -348,7 +352,7 @@ steps:
- initialize
- name: publish-frontend-metrics
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./scripts/ci-frontend-metrics.sh | ./bin/grabpl publish-metrics $${GRAFANA_MISC_STATS_API_KEY}
environment:
@@ -359,7 +363,7 @@ steps:
- initialize
- name: build-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise
depends_on:
@@ -368,7 +372,7 @@ steps:
- test-backend
- name: build-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise
depends_on:
@@ -376,7 +380,7 @@ steps:
- test-frontend
- name: build-plugins
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin
environment:
@@ -387,7 +391,7 @@ steps:
- lint-backend
- name: package
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --sign
environment:
@@ -411,7 +415,7 @@ steps:
- shellcheck
- name: end-to-end-tests-server
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
detach: true
commands:
- ./e2e/start-server
@@ -429,7 +433,7 @@ steps:
- end-to-end-tests-server
- name: build-storybook
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- yarn storybook:build
depends_on:
@@ -448,7 +452,7 @@ steps:
- build-storybook
- name: build-frontend-docs
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./scripts/ci-reference-docs-lint.sh ci
depends_on:
@@ -465,7 +469,7 @@ steps:
- build-frontend-docs
- name: copy-packages-for-docker
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- cp dist/*.tar.gz* packaging/docker/
depends_on:
@@ -495,7 +499,7 @@ steps:
- copy-packages-for-docker
- name: postgres-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq postgresql-client
@@ -512,7 +516,7 @@ steps:
- test-frontend
- name: mysql-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq default-mysql-client
@@ -528,7 +532,7 @@ steps:
- test-frontend
- name: release-next-npm-packages
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./node_modules/.bin/lerna bootstrap
- echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" >> ~/.npmrc
@@ -648,7 +652,7 @@ steps:
- echo $DRONE_RUNNER_NAME
- name: initialize
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
@@ -723,13 +727,12 @@ steps:
- echo $DRONE_RUNNER_NAME
- name: initialize
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
- chmod +x bin/grabpl
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- ./bin/grabpl verify-version $${DRONE_TAG}
- ./bin/grabpl verify-version ${DRONE_TAG}
- curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
- tar -C bin -xzvf dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
- rm dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
@@ -738,19 +741,21 @@ steps:
DOCKERIZE_VERSION: 0.6.1
- name: lint-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
- ./scripts/revive-strict
- ./scripts/tidy-check.sh
- ./grafana-mixin/scripts/lint.sh
- ./grafana-mixin/scripts/build.sh
environment:
CGO_ENABLED: 1
depends_on:
- initialize
- name: codespell
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
- codespell -I words_to_ignore.txt docs/
@@ -758,7 +763,7 @@ steps:
- initialize
- name: shellcheck
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
@@ -773,7 +778,7 @@ steps:
- initialize
- name: test-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl test-backend
- ./bin/grabpl integration-tests
@@ -782,7 +787,7 @@ steps:
- lint-backend
- name: test-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- yarn run ci:test-frontend
environment:
@@ -791,10 +796,9 @@ steps:
- initialize
- name: build-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise $${DRONE_TAG}
- ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise ${DRONE_TAG}
environment:
GITHUB_TOKEN:
from_secret: github_token
@@ -804,16 +808,15 @@ steps:
- test-backend
- name: build-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition oss --no-pull-enterprise $${DRONE_TAG}
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition oss --no-pull-enterprise ${DRONE_TAG}
depends_on:
- initialize
- test-frontend
- name: build-plugins
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin
environment:
@@ -824,10 +827,9 @@ steps:
- lint-backend
- name: package
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- ./bin/grabpl package --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign $${DRONE_TAG}
- ./bin/grabpl package --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign ${DRONE_TAG}
environment:
GITHUB_TOKEN:
from_secret: github_token
@@ -849,7 +851,7 @@ steps:
- shellcheck
- name: end-to-end-tests-server
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
detach: true
commands:
- ./e2e/start-server
@@ -867,7 +869,7 @@ steps:
- end-to-end-tests-server
- name: build-storybook
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- yarn storybook:build
depends_on:
@@ -876,11 +878,10 @@ steps:
- name: publish-storybook
image: grafana/grafana-ci-deploy:1.2.6
commands:
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- gcloud auth activate-service-account --key-file=/tmp/gcpkey.json
- gsutil -m rsync -d -r ./packages/grafana-ui/dist/storybook gs://grafana-storybook/latest
- gsutil -m rsync -d -r ./packages/grafana-ui/dist/storybook gs://grafana-storybook/$${DRONE_TAG}
- gsutil -m rsync -d -r ./packages/grafana-ui/dist/storybook gs://grafana-storybook/${DRONE_TAG}
environment:
GCP_KEY:
from_secret: gcp_key
@@ -888,7 +889,7 @@ steps:
- build-storybook
- name: copy-packages-for-docker
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- cp dist/*.tar.gz* packaging/docker/
depends_on:
@@ -918,7 +919,7 @@ steps:
- copy-packages-for-docker
- name: postgres-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq postgresql-client
@@ -935,7 +936,7 @@ steps:
- test-frontend
- name: mysql-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq default-mysql-client
@@ -951,12 +952,11 @@ steps:
- test-frontend
- name: release-npm-packages
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./node_modules/.bin/lerna bootstrap
- echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" >> ~/.npmrc
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- ./scripts/build/release-packages.sh $${DRONE_TAG}
- ./scripts/build/release-packages.sh ${DRONE_TAG}
environment:
NPM_TOKEN:
from_secret: npm_token
@@ -1029,14 +1029,13 @@ steps:
- name: build-windows-installer
image: grafana/ci-wix:0.1.1
commands:
- $$env:DRONE_TAG=$$(.\grabpl.exe parse-tag-ref ${DRONE_TAG})
- $$gcpKey = $$env:GCP_KEY
- "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($$gcpKey)) > gcpkey.json"
- dos2unix gcpkey.json
- gcloud auth activate-service-account --key-file=gcpkey.json
- rm gcpkey.json
- cp C:\App\nssm-2.24.zip .
- .\grabpl.exe windows-installer --edition oss $$env:DRONE_TAG
- .\grabpl.exe windows-installer --edition oss ${DRONE_TAG}
- $$fname = ((Get-Childitem grafana*.msi -name) -split "`n")[0]
- gsutil cp $$fname gs://grafana-downloads/oss/release/
- gsutil cp "$$fname.sha256" gs://grafana-downloads/oss/release/
@@ -1072,29 +1071,28 @@ steps:
- echo $DRONE_RUNNER_NAME
- name: clone
image: alpine/git:v2.26.2
image: grafana/build-container:1.2.28
commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
- chmod +x bin/grabpl
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git"
- cd grafana-enterprise
- git checkout $${DRONE_TAG}
- git checkout ${DRONE_TAG}
environment:
GITHUB_TOKEN:
from_secret: github_token
- name: initialize
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- mv grabpl /tmp
- mv bin/grabpl /tmp/
- rmdir bin
- mv grafana-enterprise /tmp/
- /tmp/grabpl init-enterprise /tmp/grafana-enterprise $${DRONE_TAG}
- /tmp/grabpl init-enterprise /tmp/grafana-enterprise ${DRONE_TAG}
- mkdir bin
- mv /tmp/grabpl bin/
- ./bin/grabpl verify-version $${DRONE_TAG}
- ./bin/grabpl verify-version ${DRONE_TAG}
- curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
- tar -C bin -xzvf dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
- rm dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
@@ -1105,19 +1103,21 @@ steps:
- clone
- name: lint-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
- ./scripts/revive-strict
- ./scripts/tidy-check.sh
- ./grafana-mixin/scripts/lint.sh
- ./grafana-mixin/scripts/build.sh
environment:
CGO_ENABLED: 1
depends_on:
- initialize
- name: codespell
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
- codespell -I words_to_ignore.txt docs/
@@ -1125,7 +1125,7 @@ steps:
- initialize
- name: shellcheck
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
@@ -1140,7 +1140,7 @@ steps:
- initialize
- name: test-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl test-backend
- ./bin/grabpl integration-tests
@@ -1149,7 +1149,7 @@ steps:
- lint-backend
- name: test-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- yarn run ci:test-frontend
environment:
@@ -1158,10 +1158,9 @@ steps:
- initialize
- name: build-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise $${DRONE_TAG}
- ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise ${DRONE_TAG}
environment:
GITHUB_TOKEN:
from_secret: github_token
@@ -1171,16 +1170,15 @@ steps:
- test-backend
- name: build-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition enterprise --no-pull-enterprise $${DRONE_TAG}
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition enterprise --no-pull-enterprise ${DRONE_TAG}
depends_on:
- initialize
- test-frontend
- name: build-plugins
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-plugins --jobs 8 --edition enterprise --no-install-deps --sign --signing-admin
environment:
@@ -1191,10 +1189,9 @@ steps:
- lint-backend
- name: package
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- ./bin/grabpl package --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign $${DRONE_TAG}
- ./bin/grabpl package --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign ${DRONE_TAG}
environment:
GITHUB_TOKEN:
from_secret: github_token
@@ -1216,7 +1213,7 @@ steps:
- shellcheck
- name: end-to-end-tests-server
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
detach: true
commands:
- ./e2e/start-server
@@ -1234,7 +1231,7 @@ steps:
- end-to-end-tests-server
- name: copy-packages-for-docker
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- cp dist/*.tar.gz* packaging/docker/
depends_on:
@@ -1264,7 +1261,7 @@ steps:
- copy-packages-for-docker
- name: postgres-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq postgresql-client
@@ -1281,7 +1278,7 @@ steps:
- test-frontend
- name: mysql-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq default-mysql-client
@@ -1361,10 +1358,9 @@ steps:
commands:
- $$ProgressPreference = "SilentlyContinue"
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/windows/grabpl.exe -OutFile grabpl.exe
- $$env:DRONE_TAG=$$(.\grabpl.exe parse-tag-ref ${DRONE_TAG})
- git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"
- cd grafana-enterprise
- git checkout $$env:DRONE_TAG
- git checkout ${DRONE_TAG}
environment:
GITHUB_TOKEN:
from_secret: github_token
@@ -1384,14 +1380,13 @@ steps:
- name: build-windows-installer
image: grafana/ci-wix:0.1.1
commands:
- $$env:DRONE_TAG=$$(.\grabpl.exe parse-tag-ref ${DRONE_TAG})
- $$gcpKey = $$env:GCP_KEY
- "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($$gcpKey)) > gcpkey.json"
- dos2unix gcpkey.json
- gcloud auth activate-service-account --key-file=gcpkey.json
- rm gcpkey.json
- cp C:\App\nssm-2.24.zip .
- .\grabpl.exe windows-installer --edition enterprise $$env:DRONE_TAG
- .\grabpl.exe windows-installer --edition enterprise ${DRONE_TAG}
- $$fname = ((Get-Childitem grafana*.msi -name) -split "`n")[0]
- gsutil cp $$fname gs://grafana-downloads/enterprise/release/
- gsutil cp "$$fname.sha256" gs://grafana-downloads/enterprise/release/
@@ -1424,22 +1419,20 @@ steps:
- echo $DRONE_RUNNER_NAME
- name: initialize
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
- chmod +x bin/grabpl
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- ./bin/grabpl verify-version $${DRONE_TAG}
- ./bin/grabpl verify-version ${DRONE_TAG}
environment:
DOCKERIZE_VERSION: 0.6.1
- name: publish-packages
image: grafana/grafana-ci-deploy:1.2.6
commands:
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
- ./bin/grabpl publish-packages --edition oss $${DRONE_TAG}
- ./bin/grabpl publish-packages --edition enterprise $${DRONE_TAG}
- ./bin/grabpl publish-packages --edition oss ${DRONE_TAG}
- ./bin/grabpl publish-packages --edition enterprise ${DRONE_TAG}
environment:
GRAFANA_COM_API_KEY:
from_secret: grafana_api_key
@@ -1503,7 +1496,7 @@ steps:
- echo $DRONE_RUNNER_NAME
- name: initialize
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
@@ -1517,19 +1510,21 @@ steps:
DOCKERIZE_VERSION: 0.6.1
- name: lint-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
- ./scripts/revive-strict
- ./scripts/tidy-check.sh
- ./grafana-mixin/scripts/lint.sh
- ./grafana-mixin/scripts/build.sh
environment:
CGO_ENABLED: 1
depends_on:
- initialize
- name: codespell
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
- codespell -I words_to_ignore.txt docs/
@@ -1537,7 +1532,7 @@ steps:
- initialize
- name: shellcheck
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
@@ -1552,7 +1547,7 @@ steps:
- initialize
- name: test-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl test-backend
- ./bin/grabpl integration-tests
@@ -1561,7 +1556,7 @@ steps:
- lint-backend
- name: test-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- yarn run ci:test-frontend
environment:
@@ -1570,7 +1565,7 @@ steps:
- initialize
- name: build-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise v7.3.0-test
environment:
@@ -1582,7 +1577,7 @@ steps:
- test-backend
- name: build-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition oss --no-pull-enterprise v7.3.0-test
depends_on:
@@ -1590,7 +1585,7 @@ steps:
- test-frontend
- name: build-plugins
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin
environment:
@@ -1601,7 +1596,7 @@ steps:
- lint-backend
- name: package
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl package --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign v7.3.0-test
environment:
@@ -1625,7 +1620,7 @@ steps:
- shellcheck
- name: end-to-end-tests-server
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
detach: true
commands:
- ./e2e/start-server
@@ -1643,7 +1638,7 @@ steps:
- end-to-end-tests-server
- name: build-storybook
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- yarn storybook:build
depends_on:
@@ -1660,7 +1655,7 @@ steps:
- build-storybook
- name: copy-packages-for-docker
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- cp dist/*.tar.gz* packaging/docker/
depends_on:
@@ -1684,7 +1679,7 @@ steps:
- copy-packages-for-docker
- name: postgres-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq postgresql-client
@@ -1701,7 +1696,7 @@ steps:
- test-frontend
- name: mysql-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq default-mysql-client
@@ -1717,7 +1712,7 @@ steps:
- test-frontend
- name: release-npm-packages
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./node_modules/.bin/lerna bootstrap
- echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" >> ~/.npmrc
@@ -1835,7 +1830,7 @@ steps:
- echo $DRONE_RUNNER_NAME
- name: clone
image: alpine/git:v2.26.2
image: grafana/build-container:1.2.28
commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
@@ -1848,9 +1843,10 @@ steps:
from_secret: github_token
- name: initialize
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- mv grabpl /tmp
- mv bin/grabpl /tmp/
- rmdir bin
- mv grafana-enterprise /tmp/
- /tmp/grabpl init-enterprise /tmp/grafana-enterprise
- mkdir bin
@@ -1866,19 +1862,21 @@ steps:
- clone
- name: lint-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
- ./scripts/revive-strict
- ./scripts/tidy-check.sh
- ./grafana-mixin/scripts/lint.sh
- ./grafana-mixin/scripts/build.sh
environment:
CGO_ENABLED: 1
depends_on:
- initialize
- name: codespell
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
- codespell -I words_to_ignore.txt docs/
@@ -1886,7 +1884,7 @@ steps:
- initialize
- name: shellcheck
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
@@ -1901,7 +1899,7 @@ steps:
- initialize
- name: test-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl test-backend
- ./bin/grabpl integration-tests
@@ -1910,7 +1908,7 @@ steps:
- lint-backend
- name: test-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- yarn run ci:test-frontend
environment:
@@ -1919,7 +1917,7 @@ steps:
- initialize
- name: build-backend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise v7.3.0-test
environment:
@@ -1931,7 +1929,7 @@ steps:
- test-backend
- name: build-frontend
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition enterprise --no-pull-enterprise v7.3.0-test
depends_on:
@@ -1939,7 +1937,7 @@ steps:
- test-frontend
- name: build-plugins
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl build-plugins --jobs 8 --edition enterprise --no-install-deps --sign --signing-admin
environment:
@@ -1950,7 +1948,7 @@ steps:
- lint-backend
- name: package
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- ./bin/grabpl package --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign v7.3.0-test
environment:
@@ -1974,7 +1972,7 @@ steps:
- shellcheck
- name: end-to-end-tests-server
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
detach: true
commands:
- ./e2e/start-server
@@ -1992,7 +1990,7 @@ steps:
- end-to-end-tests-server
- name: copy-packages-for-docker
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- cp dist/*.tar.gz* packaging/docker/
depends_on:
@@ -2016,7 +2014,7 @@ steps:
- copy-packages-for-docker
- name: postgres-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq postgresql-client
@@ -2033,7 +2031,7 @@ steps:
- test-frontend
- name: mysql-integration-tests
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- apt-get update
- apt-get install -yq default-mysql-client
@@ -2174,7 +2172,7 @@ steps:
- echo $DRONE_RUNNER_NAME
- name: initialize
image: grafana/build-container:1.2.27
image: grafana/build-container:1.2.28
commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl

View File

@@ -1,9 +1,73 @@
# 7.3.0-beta1 (2020-10-14)
# 7.3.0-beta1 (2020-10-15)
### Breaking changes
- **CloudWatch**: The AWS CloudWatch data source's authentication scheme has changed. See the [upgrade notes](https://grafana.com/docs/grafana/latest/installation/upgrading/#upgrading-to-v73) for details and how this may affect you.
### Features / Enhancements
* **Alerting**: Add labels to name when converting data frame to series. [#28085](https://github.com/grafana/grafana/pull/28085), [@kylebrandt](https://github.com/kylebrandt)
* **Alerting**: Ensuring LINE Notify notifications are sent for all alert states. [#27639](https://github.com/grafana/grafana/pull/27639), [@haraldkubota](https://github.com/haraldkubota)
* **Auth**: Add SigV4 auth option to datasources. [#27552](https://github.com/grafana/grafana/pull/27552), [@wbrowne](https://github.com/wbrowne)
* **AzureMonitor**: Pass through null values instead of setting 0. [#28126](https://github.com/grafana/grafana/pull/28126), [@kylebrandt](https://github.com/kylebrandt)
* **Cloud Monitoring**: Out-of-the-box dashboards. [#27864](https://github.com/grafana/grafana/pull/27864), [@papagian](https://github.com/papagian)
* **CloudWatch**: Add support for AWS DirectConnect virtual interface metrics and add missing dimensions. [#28008](https://github.com/grafana/grafana/pull/28008), [@jgulick48](https://github.com/jgulick48)
* **CloudWatch**: Adding support for Amazon ElastiCache Redis metrics. [#28040](https://github.com/grafana/grafana/pull/28040), [@jgulick48](https://github.com/jgulick48)
* **CloudWatch**: Adding support for additional Amazon CloudFront metrics. [#28069](https://github.com/grafana/grafana/pull/28069), [@darrylsepeda](https://github.com/darrylsepeda)
* **CloudWatch**: Re-implement authentication. [#25548](https://github.com/grafana/grafana/pull/25548), [@aknuds1](https://github.com/aknuds1),[@patstrom](https://github.com/patstrom)
* **Dashboard**: Allow shortlink generation. [#27409](https://github.com/grafana/grafana/pull/27409), [@MisterSquishy](https://github.com/MisterSquishy)
* **Docker**: OpenShift compatability. [#27813](https://github.com/grafana/grafana/pull/27813), [@xlson](https://github.com/xlson)
* **Elasticsearch**: Support multiple pipeline aggregations for a query. [#27945](https://github.com/grafana/grafana/pull/27945), [@simianhacker](https://github.com/simianhacker)
* **Explore**: Allow shortlink generation. [#28222](https://github.com/grafana/grafana/pull/28222), [@ivanahuckova](https://github.com/ivanahuckova)
* **Explore**: Remove collapsing of visualisations. [#27026](https://github.com/grafana/grafana/pull/27026), [@ivanahuckova](https://github.com/ivanahuckova)
* **FieldColor**: Adds new standard color option for color. [#28039](https://github.com/grafana/grafana/pull/28039), [@torkelo](https://github.com/torkelo)
* **Gauge**: Improve text sizing and support non threshold color modes. [#28256](https://github.com/grafana/grafana/pull/28256), [@torkelo](https://github.com/torkelo)
* **NamedColors**: Named colors refactors. [#28235](https://github.com/grafana/grafana/pull/28235), [@torkelo](https://github.com/torkelo)
* **Panel Inspect**: Allow CSV download for Excel. [#27284](https://github.com/grafana/grafana/pull/27284), [@tomdaly](https://github.com/tomdaly)
* **Prometheus**: Add time range parameters to labels API. [#27548](https://github.com/grafana/grafana/pull/27548), [@kakkoyun](https://github.com/kakkoyun)
* **Snapshots**: Store dashboard data encrypted in the database. [#28129](https://github.com/grafana/grafana/pull/28129), [@wbrowne](https://github.com/wbrowne)
* **Table**: New cell hover behavior and image cell display mode. [#27669](https://github.com/grafana/grafana/pull/27669), [@torkelo](https://github.com/torkelo)
* **Timezones**: Include IANA timezone canonical name in TimeZoneInfo. [#27591](https://github.com/grafana/grafana/pull/27591), [@dprokop](https://github.com/dprokop)
* **Tracing**: Add Tempo data source. [#28204](https://github.com/grafana/grafana/pull/28204), [@aocenas](https://github.com/aocenas)
* **Transformations**: Add Concatenate fields transformer. [#28237](https://github.com/grafana/grafana/pull/28237), [@ryantxu](https://github.com/ryantxu)
* **Transformations**: improve the reduce transformer. [#27875](https://github.com/grafana/grafana/pull/27875), [@ryantxu](https://github.com/ryantxu)
* **Users**: Expire old user invites. [#27361](https://github.com/grafana/grafana/pull/27361), [@wbrowne](https://github.com/wbrowne)
* **Variables**: Adds loading state and indicators. [#27917](https://github.com/grafana/grafana/pull/27917), [@hugohaggmark](https://github.com/hugohaggmark)
* **Variables**: Adds support for key/value mapping in Custom variable. [#27829](https://github.com/grafana/grafana/pull/27829), [@sartaj10](https://github.com/sartaj10)
* **grafana/toolkit**: expose Jest maxWorkers arg for plugin test & build tasks. [#27724](https://github.com/grafana/grafana/pull/27724), [@domasx2](https://github.com/domasx2)
### Bug Fixes
* **Azure Analytics**: FormatAs Time series groups bool columns wrong. [#27713](https://github.com/grafana/grafana/issues/27713)
* **Azure**: Fixes cancellation of requests with different Azure sources. [#28180](https://github.com/grafana/grafana/pull/28180), [@hugohaggmark](https://github.com/hugohaggmark)
* **BackendSrv**: Reloads page instead of redirect on Unauthorized Error. [#28276](https://github.com/grafana/grafana/pull/28276), [@hugohaggmark](https://github.com/hugohaggmark)
* **Dashboard**: Do not allow users without edit permission to a folder to see new dashboard page. [#28249](https://github.com/grafana/grafana/pull/28249), [@torkelo](https://github.com/torkelo)
* **Dashboard**: Fixed issue accessing horizontal table scrollbar when placed at bottom of dashboard. [#28250](https://github.com/grafana/grafana/pull/28250), [@torkelo](https://github.com/torkelo)
* **DataProxy**: Add additional settings for dataproxy to help with network proxy timeouts. [#27841](https://github.com/grafana/grafana/pull/27841), [@kahinton](https://github.com/kahinton)
* **Database**: Adds new indices to alert_notification_state and alert_rule_tag tables. [#28166](https://github.com/grafana/grafana/pull/28166), [@KarineValenca](https://github.com/KarineValenca)
* **Explore**: Fix showing of Prometheus data in Query inspector. [#28128](https://github.com/grafana/grafana/pull/28128), [@ivanahuckova](https://github.com/ivanahuckova)
* **Explore**: Show results of Prometheus instant queries in formatted table. [#27767](https://github.com/grafana/grafana/pull/27767), [@ivanahuckova](https://github.com/ivanahuckova)
* **Graph**: Prevent legend from overflowing container. [#28254](https://github.com/grafana/grafana/pull/28254), [@jackw](https://github.com/jackw)
* **OAuth**: Fix token refresh failure when custom SSL settings are configured for OAuth provider. [#27523](https://github.com/grafana/grafana/pull/27523), [@billoley](https://github.com/billoley)
* **Plugins**: Let descendant plugins inherit their root's signature. [#27970](https://github.com/grafana/grafana/pull/27970), [@aknuds1](https://github.com/aknuds1)
* **Runtime**: Fix handling of short-lived background services. [#28025](https://github.com/grafana/grafana/pull/28025), [@ahlaw](https://github.com/ahlaw)
* **TemplateSrv**: Fix interpolating strings with object variables. [#28171](https://github.com/grafana/grafana/pull/28171), [@torkelo](https://github.com/torkelo)
* **Variables**: Fixes so constants set from url get completed state. [#28257](https://github.com/grafana/grafana/pull/28257), [@hugohaggmark](https://github.com/hugohaggmark)
* **Variables**: Prevent adhoc filters from crashing when they are not loaded properly. [#28226](https://github.com/grafana/grafana/pull/28226), [@mckn](https://github.com/mckn)
# 7.2.2 (2020-10-21)
### Features / Enhancements
**Caution:** Please do not use/enable the `database_metrics` feature flag. It will corrupt MySQL database tables. See [#28440](https://github.com/grafana/grafana/issues/28440) for more information.
~~**Instrumentation**: Add counters and histograms for database queries. [#28236](https://github.com/grafana/grafana/pull/28236), [@bergquist](https://github.com/bergquist)~~
- **Instrumentation**: Add histogram for request duration. [#28364](https://github.com/grafana/grafana/pull/28364), [@bergquist](https://github.com/bergquist)
- **Instrumentation**: Adds environment_info metric. [#28355](https://github.com/grafana/grafana/pull/28355), [@bergquist](https://github.com/bergquist)
### Bug Fixes
- **CloudWatch**: Fix custom metrics. [#28391](https://github.com/grafana/grafana/pull/28391), [@aknuds1](https://github.com/aknuds1)
# 7.2.1 (2020-10-08)
### Features / Enhancements

View File

@@ -670,6 +670,12 @@ disable_total_stats = false
basic_auth_username =
basic_auth_password =
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
# can expose more information about the Grafana instance.
[metrics.environment_info]
#exampleLabel1 = exampleValue1
#exampleLabel2 = exampleValue2
# Send internal Grafana metrics to graphite
[metrics.graphite]
# Enable by setting the address setting (ex localhost:2003)

View File

@@ -664,6 +664,12 @@
; basic_auth_username =
; basic_auth_password =
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
# can expose more information about the Grafana instance.
[metrics.environment_info]
#exampleLabel1 = exampleValue1
#exampleLabel2 = exampleValue2
# Send internal metrics to Graphite
[metrics.graphite]
# Enable by setting the address setting (ex localhost:2003)

View File

@@ -8,3 +8,6 @@ Learn more about the backend architecture:
- Part 2: [Communication](communication.md)
- Part 3: [Database](database.md)
Learn more about the frontend architecture:
- Part 1: [Data requests](frontend-data-requests.md)

View File

@@ -0,0 +1,41 @@
# Data requests
[BackendSrv](https://grafana.com/docs/grafana/latest/packages_api/runtime/backendsrv) handles all outgoing HTTP requests from Grafana. This document explains the high-level concepts used by `BackendSrv`.
## Canceling requests
This section describes how canceling requests work in Grafana. While data sources can implement their own cancellation concept, we recommend that you use the method we describe here.
A data request can take a long time to finish. During the time between when a request starts and finishes, the user can change context. For example, the user may navigate away or issue the same request again.
If we wait for canceled requests to complete, it might create unnecessary load on data sources.
Grafana uses a concept called _request cancelation_ to cancel any ongoing request that Grafana doesn't need.
#### Before Grafana 7.2
Before Grafana can cancel any data request, it has to identify that request. Grafana identifies a request using the property `requestId` [passed as options](https://github.com/grafana/grafana/blob/master/docs/sources/packages_api/runtime/backendsrvrequest.md) when you use [BackendSrv](https://grafana.com/docs/grafana/latest/packages_api/runtime/backendsrv).
The cancellation logic is as follows:
- When an ongoing request discovers that an additional request with the same `requestId` has started, then Grafana will cancel the ongoing request.
- When an ongoing request discovers that the special "cancel all requests" `requestId` was sent, then Grafana will cancel the ongoing request.
#### After Grafana 7.2
Grafana 7.2 introduced an additional way of canceling requests using [RxJs](https://github.com/ReactiveX/rxjs). To support the new cancellation functionality, the data source needs to use the new `fetch` function in [BackendSrv](https://grafana.com/docs/grafana/latest/packages_api/runtime/backendsrv).
Migrating the core data sources to the new `fetch` function [is an ongoing process that you can read about in this issue.](https://github.com/grafana/grafana/issues/27222)
## Request queue
Depending on how the web browser implements the protocol for HTTP 1.1, it will limit the number of parallel requests, lets call this limit _max_parallel_browser_request_.
Unless you have configured Grafana to use HTTP2, the browser limits parallel data requests according to the browser's implementation. For more information on how to enable HTTP2, refer to [Configuration](https://grafana.com/docs/grafana/latest/administration/configuration/#protocol).
Because there is a _max_parallel_browser_request_ limit, if some of the requests take a long time, they will block later requests and make interacting with Grafana very slow.
#### Before Grafana 7.2
Not supported.
#### After Grafana 7.2
Grafana uses a _request queue_ to process all incoming data requests in order while reserving a free "spot" for any requests to the Grafana API.
Since the first implementation of the request queue doesn't take into account what browser the user uses, the _request queue_ limit for parallel data source requests is hard-coded to 5.
> **Note:** Grafana instances [configured with HTTP2 ](https://grafana.com/docs/grafana/latest/administration/configuration/#protocol) will have a hard coded limit of 1000.

View File

@@ -0,0 +1,338 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"links": [],
"panels": [
{
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-BlYlRd"
},
"custom": {
"align": "center",
"displayMode": "color-background",
"filterable": false
},
"mappings": [],
"thresholds": {
"mode": "percentage",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "blue",
"value": 20
},
{
"color": "orange",
"value": 60
},
{
"color": "red",
"value": 70
}
]
},
"unit": "degree"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Field"
},
"properties": [
{
"id": "custom.displayMode"
}
]
}
]
},
"gridPos": {
"h": 16,
"w": 19,
"x": 0,
"y": 0
},
"id": 4,
"options": {
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Last"
}
]
},
"pluginVersion": "7.4.0-pre",
"targets": [
{
"alias": "",
"csvWave": {
"timeStep": 60,
"valuesCSV": "0,0,2,2,1,1"
},
"lines": 10,
"points": [],
"pulseWave": {
"offCount": 3,
"offValue": 1,
"onCount": 3,
"onValue": 2,
"timeStep": 60
},
"refId": "A",
"scenarioId": "random_walk",
"seriesCount": 15,
"stream": {
"bands": 1,
"noise": 2.2,
"speed": 250,
"spread": 3.5,
"type": "signal"
},
"stringInput": ""
}
],
"timeFrom": null,
"timeShift": null,
"title": "Gradient color schemes",
"transformations": [
{
"id": "reduce",
"options": {
"reducers": ["max", "mean", "last", "min"]
}
},
{
"id": "organize",
"options": {
"excludeByName": {
"Field": false
},
"indexByName": {},
"renameByName": {}
}
}
],
"type": "table"
},
{
"datasource": null,
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-blues"
},
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 20
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 26,
"w": 5,
"x": 19,
"y": 0
},
"id": 2,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": ["mean"],
"fields": "",
"values": false
},
"textMode": "value"
},
"pluginVersion": "7.4.0-pre",
"targets": [
{
"alias": "",
"csvWave": {
"timeStep": 60,
"valuesCSV": "0,0,2,2,1,1"
},
"labels": "",
"lines": 10,
"points": [],
"pulseWave": {
"offCount": 3,
"offValue": 1,
"onCount": 3,
"onValue": 2,
"timeStep": 60
},
"refId": "A",
"scenarioId": "random_walk",
"seriesCount": 30,
"stream": {
"bands": 1,
"noise": 2.2,
"speed": 250,
"spread": 3.5,
"type": "signal"
},
"stringInput": ""
}
],
"timeFrom": null,
"timeShift": null,
"title": "Stats",
"type": "stat"
},
{
"datasource": "gdev-testdata",
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-GrYlRd"
},
"custom": {
"align": "center",
"displayMode": "color-background",
"filterable": false
},
"mappings": [],
"thresholds": {
"mode": "percentage",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "blue",
"value": 20
},
{
"color": "orange",
"value": 60
},
{
"color": "red",
"value": 70
}
]
},
"unit": "degree"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Field"
},
"properties": [
{
"id": "custom.displayMode"
}
]
}
]
},
"gridPos": {
"h": 10,
"w": 19,
"x": 0,
"y": 16
},
"id": 5,
"options": {
"displayMode": "lcd",
"orientation": "auto",
"reduceOptions": {
"calcs": ["mean"],
"fields": "",
"values": false
},
"showUnfilled": true
},
"pluginVersion": "7.4.0-pre",
"targets": [
{
"alias": "",
"csvWave": {
"timeStep": 60,
"valuesCSV": "0,0,2,2,1,1"
},
"lines": 10,
"points": [],
"pulseWave": {
"offCount": 3,
"offValue": 1,
"onCount": 3,
"onValue": 2,
"timeStep": 60
},
"refId": "A",
"scenarioId": "random_walk",
"seriesCount": 15,
"stream": {
"bands": 1,
"noise": 2.2,
"speed": 250,
"spread": 3.5,
"type": "signal"
},
"stringInput": ""
}
],
"timeFrom": null,
"timeShift": null,
"title": "Bar Gauge LCD",
"transformations": [],
"type": "bargauge"
}
],
"schemaVersion": 26,
"style": "dark",
"tags": ["gdev", "demo"],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Gradient Color modes",
"uid": "inxsweKGz",
"version": 17
}

View File

@@ -399,7 +399,7 @@ The length of time that Grafana will wait for a successful TLS handshake with th
### expect_continue_timeout_seconds
The length of time that Grafana will wait for a datasources first response headers after fully writing the request headers, if the request has an “Expect: 100-continue” header. A value of `0` will result in the body being sent immediately. Default is `1` second. For more details check the [Transport.ExpectContinueTimeout](https://golang.org/pkg/net/http/#Transport.ExpectContinueTimeout) documentation.
The length of time that Grafana will wait for a datasources first response headers after fully writing the request headers, if the request has an “Expect: 100-continue” header. A value of `0` will result in the body being sent immediately. Default is `1` second. For more details check the [Transport.ExpectContinueTimeout](https://golang.org/pkg/net/http/#Transport.ExpectContinueTimeout) documentation.
### max_idle_connections
@@ -549,9 +549,11 @@ Number dashboard versions to keep (per dashboard). Default: `20`, Minimum: `1`.
> Only available in Grafana v6.7+.
This prevents users from setting the dashboard refresh interval of a lower than given interval. Per default this is 5 seconds.
This feature prevents users from setting the dashboard refresh interval to a lower value than a given interval value. The default interval value is 5 seconds.
The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. `30s` or `1m`.
As of Grafana v7.3, this also limits the refresh interval options in Explore.
### default_home_dashboard_path
Path to the default home dashboard. If this value is empty, then Grafana uses StaticRootPath + "dashboards/home.json"
@@ -623,7 +625,7 @@ Default is `false`.
### user_invite_max_lifetime_duration
The duration in time a user invitation remains valid before expiring.
The duration in time a user invitation remains valid before expiring.
This setting should be expressed as a duration. Examples: 6h (hours), 2d (days), 1w (week).
Default is `24h` (24 hours). The minimum supported duration is `15m` (15 minutes).
@@ -1073,6 +1075,15 @@ If both are set, then basic authentication is required to access the metrics end
<hr>
## [metrics.environment_info]
Adds dimensions to the `grafana_environment_info` metric, which can expose more information about the Grafana instance.
```
; exampleLabel1 = exampleValue1
; exampleLabel2 = exampleValue2
```
## [metrics.graphite]
Use these options if you want to send internal Grafana metrics to Graphite.

View File

@@ -14,7 +14,7 @@ weight = 500
SAML authentication integration allows your Grafana users to log in by using an external SAML 2.0 Identity Provider (IdP). To enable this, Grafana becomes a Service Provider (SP) in the authentication flow, interacting with the IdP to exchange user information.
The SAML single-sign-on (SSO) standard is varied and flexible. Our implementation contains the subset of features needed to provide a smooth authentication experience into Grafana.
The SAML single sign-on (SSO) standard is varied and flexible. Our implementation contains a subset of features needed to provide a smooth authentication experience into Grafana.
> Only available in Grafana Enterprise v6.3+. If you encounter any problems with our implementation, please don't hesitate to contact us.
@@ -45,12 +45,14 @@ 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_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 | |
| `signature_algorithm` | No | Signature algorithm used for signing requests to the IdP. Supported values are rsa-sha1, rsa-sha256, rsa-sha512. | |
| `idp_metadata`, `idp_metadata_path`, or `idp_metadata_url` | Yes | Base64-encoded string, Path or URL for the IdP SAML metadata XML | |
| `max_issue_delay` | No | Duration, since the IdP issued a response and the SP is allowed to process it | `90s` |
| `metadata_valid_duration` | No | Duration, for how long the SP metadata is valid | `48h` |
| `relay_state` | No | Relay state for IdP-initiated login. Should match relay state configured in IdP | |
| `assertion_attribute_name` | No | Friendly name or name of the attribute within the SAML assertion to use as the user name | `displayName` |
| `assertion_attribute_login` | No | Friendly name or name of the attribute within the SAML assertion to use as the user login handle | `mail` |
| `assertion_attribute_email` | No | Friendly name or name of the attribute within the SAML assertion to use as the user email | `mail` |
@@ -81,7 +83,9 @@ You can only use one form of each configuration option. Using multiple forms, su
### Signature algorithm
The SAML standard recommends using digital signature for some types of messages, like authentication or logout requests. If `signature_algorithm` option configured, Grafana will put digital signature into SAML requests. Supported signature types are `rsa-sha1`, `rsa-sha256`, `rsa-sha512`. This option should match your IdP configuration, otherwise, signature won't be validated by the IdP. Grafana uses key and certificate configured with `private_key` and `certificate` options for signing SAML requests.
> Only available in Grafana v7.3+
The SAML standard recommends using a digital signature for some types of messages, like authentication or logout requests. If the `signature_algorithm` option is configured, Grafana will put a digital signature into SAML requests. Supported signature types are `rsa-sha1`, `rsa-sha256`, `rsa-sha512`. This option should match your IdP configuration, otherwise, signature validation will fail. Grafana uses key and certificate configured with `private_key` and `certificate` options for signing SAML requests.
### IdP metadata
@@ -113,9 +117,19 @@ The integration provides two key endpoints as part of Grafana:
- The `/saml/metadata` endpoint, which contains the SP metadata. You can either download and upload it manually, or youmake the IdP request it directly from the endpoint. Some providers name it Identifier or Entity ID.
- The `/saml/acs` endpoint, which is intended to receive the ACS (Assertion Customer Service) callback. Some providers name it SSO URL or Reply URL.
### Single Logout
### IdP-initiated Single Sign-On (SSO)
Single Logout feature allows user to log out from all applications associated with current IdP session established via SAML SSO. If `single_logout` option set to `true` and user logs out, Grafana requests IdP to terminate user session. Then IdP triggers logout process for all other applications which user logged in with the same IdP session (application should support single logout). And conversely, if another application connected to the same IdP initiates single logout, Grafana gets logout request from IdP and terminates user session.
> Only available in Grafana v7.3+
By default, Grafana allows only service provider (SP) initiated logins (when the user logs in with SAML via Grafanas login page). If you want users to log in into Grafana directly from your identity provider (IdP), set the `allow_idp_initiated` configuration option to `true` and configure `relay_state` with the same value specified in the IdP configuration.
IdP-initiated SSO has some security risks, so make sure you understand the risks before enabling this feature. When using IdP-initiated SSO, Grafana receives unsolicited SAML requests and can't verify that login flow was started by the user. This makes it hard to detect whether SAML message has been stolen or replaced. Because of this, IdP-initiated SSO is vulnerable to login cross-site request forgery (CSRF) and man in the middle (MITM) attacks. We do not recommend using IdP-initiated SSO and keeping it disabled whenever possible.
### Single logout
> Only available in Grafana v7.3+
SAML's single logout feature allows users to log out from all applications associated with the current IdP session established via SAML SSO. If the `single_logout` option is set to `true` and a user logs out, Grafana requests IdP to end the user session which in turn triggers logout from all other applications the user is logged into using the same IdP session (applications should support single logout). Conversely, if another application connected to the same IdP logs out using single logout, Grafana receives a logout request from IdP and ends the user session.
### Assertion mapping

View File

@@ -29,7 +29,7 @@ e2e.scenario({
e2e.components.DashboardLinks.link()
.should('be.visible')
.and(links => {
expect(links).to.have.length(13);
expect(links).to.have.length.greaterThan(13);
for (let index = 0; index < links.length; index++) {
expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${variableValue}`);

1
go.mod
View File

@@ -30,6 +30,7 @@ require (
github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691 // indirect
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
github.com/fatih/color v1.9.0
github.com/gchaincl/sqlhooks v1.3.0
github.com/go-macaron/binding v0.0.0-20190806013118-0b4f37bab25b
github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07
github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659

2
go.sum
View File

@@ -317,6 +317,8 @@ github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03D
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=
github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y=
github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34=
github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk=
github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=

3
grafana-mixin/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
alerts.yaml
rules.yaml
dashboards_out

13
grafana-mixin/Makefile Normal file
View File

@@ -0,0 +1,13 @@
all: fmt lint build clean
fmt:
./scripts/format.sh
lint:
./scripts/lint.sh
build:
./scripts/build.sh
clean:
rm -rf dashboards_out alerts.yaml rules.yaml

28
grafana-mixin/README.md Normal file
View File

@@ -0,0 +1,28 @@
# Grafana Mixin
_This is a work in progress. We aim for it to become a good role model for alerts
and dashboards eventually, but it is not quite there yet._
The Grafana Mixin is a set of configurable, reusable, and extensible alerts and
dashboards based on the metrics exported by Grafana. The mixin creates
recording and alerting rules for Prometheus and suitable dashboard descriptions
for Grafana.
To use them, you need to have `mixtool` and `jsonnetfmt` installed. If you
have a working Go development environment, it's easiest to run the following:
```bash
$ go get github.com/monitoring-mixins/mixtool/cmd/mixtool
$ go get github.com/google/go-jsonnet/cmd/jsonnetfmt
```
You can then build the Prometheus rules files `alerts.yaml` and
`rules.yaml` and a directory `dashboard_out` with the JSON dashboard files
for Grafana:
```bash
$ make build
```
For more advanced uses of mixins, see
https://github.com/monitoring-mixins/docs.

View File

@@ -0,0 +1,14 @@
groups:
- name: GrafanaAlerts
rules:
- alert: GrafanaRequestsFailing
for: 5m
expr: |
100 * namespace_job_handler_statuscode:http_request_total:rate5m{handler!~"/datasources/proxy/:id.*|/ds/query|/tsdb/query", statuscode=~"5.."}
/
namespace_job_handler_statuscode:http_request_total:rate5m{handler!~"/datasources/proxy/:id.*|/ds/query|/tsdb/query"}
> 0.5
labels:
severity: 'critical'
annotations:
message: "'{{ $labels.namespace }}' / '{{ $labels.job }}' / '{{ $labels.handler }}' is experiencing {{ $value | humanize }}% errors"

View File

@@ -0,0 +1,528 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": 35,
"iteration": 1602761142538,
"links": [],
"panels": [
{
"datasource": "$datasource",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"noValue": "0",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 0,
"y": 0
},
"id": 6,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
}
},
"pluginVersion": "7.0.4",
"targets": [
{
"expr": "grafana_alerting_result_total{job=~\"$job\", instance=~\"$instance\", state=\"alerting\"}",
"instant": true,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Firing Alerts",
"type": "stat"
},
{
"datasource": "$datasource",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 6,
"y": 0
},
"id": 8,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
}
},
"pluginVersion": "7.0.4",
"targets": [
{
"expr": "sum(grafana_stat_totals_dashboard{job=~\"$job\", instance=~\"$instance\"})",
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Dashboards",
"type": "stat"
},
{
"datasource": "$datasource",
"fieldConfig": {
"defaults": {
"custom": {
"align": null
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 12,
"x": 12,
"y": 0
},
"id": 10,
"options": {
"showHeader": true
},
"pluginVersion": "7.0.4",
"targets": [
{
"expr": "grafana_build_info{job=~\"$job\", instance=~\"$instance\"}",
"instant": true,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Build Info",
"transformations": [
{
"id": "labelsToFields",
"options": {}
},
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"Value": true,
"branch": true,
"container": true,
"goversion": true,
"namespace": true,
"pod": true,
"revision": true
},
"indexByName": {
"Time": 7,
"Value": 11,
"branch": 4,
"container": 8,
"edition": 2,
"goversion": 6,
"instance": 1,
"job": 0,
"namespace": 9,
"pod": 10,
"revision": 5,
"version": 3
},
"renameByName": {}
}
}
],
"type": "table"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 5
},
"hiddenSeries": false,
"id": 2,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"dataLinks": []
},
"percentage": false,
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum by (statuscode) (irate(http_request_total{job=~\"$job\", instance=~\"$instance\"}[1m])) ",
"interval": "",
"legendFormat": "{{statuscode}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "RPS",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:157",
"format": "reqps",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:158",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 5
},
"hiddenSeries": false,
"id": 4,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"dataLinks": []
},
"percentage": false,
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "max(http_request_duration_milliseconds{job=~\"$job\", instance=~\"$instance\", quantile=\"0.99\"})",
"interval": "",
"legendFormat": "max-99th",
"refId": "A"
},
{
"expr": "max(http_request_duration_milliseconds{job=~\"$job\", instance=~\"$instance\", quantile=\"0.9\"})",
"interval": "",
"legendFormat": "max-90th",
"refId": "B"
},
{
"expr": "sum(irate(http_request_duration_milliseconds_sum{job=~\"$job\", instance=~\"$instance\"}[$__interval])) / sum(irate(http_request_duration_milliseconds_count{job=~\"$job\", instance=~\"$instance\"}[$__interval])) ",
"interval": "",
"legendFormat": "avg",
"refId": "C"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Request Latency",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:210",
"format": "ms",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"$$hashKey": "object:211",
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"schemaVersion": 25,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "prometheus",
"value": "prometheus"
},
"hide": 0,
"includeAll": false,
"label": null,
"multi": false,
"name": "datasource",
"options": [],
"query": "prometheus",
"queryValue": "",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"allValue": ".*",
"current": {
"selected": true,
"tags": [],
"text": "All",
"value": [
"$__all"
]
},
"datasource": "$datasource",
"definition": "label_values(grafana_build_info, job)",
"hide": 0,
"includeAll": true,
"label": null,
"multi": true,
"name": "job",
"options": [],
"query": "label_values(grafana_build_info, job)",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": false,
"text": "All",
"value": "$__all"
},
"datasource": "$datasource",
"definition": "label_values(grafana_build_info, instance)",
"hide": 0,
"includeAll": true,
"label": null,
"multi": true,
"name": "instance",
"options": [],
"query": "label_values(grafana_build_info, instance)",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "Grafana Overview",
"uid": "6be0s85Mk",
"version": 4
}

View File

@@ -0,0 +1,15 @@
{
grafanaDashboards: {
'grafana-overview.json': (import 'dashboards/grafana-overview.json'),
},
// Helper function to ensure that we don't override other rules, by forcing
// the patching of the groups list, and not the overall rules object.
local importRules(rules) = {
groups+: std.native('parseYaml')(rules)[0].groups,
},
prometheusRules+: importRules(importstr 'rules/rules.yaml'),
prometheusAlerts+: importRules(importstr 'alerts/alerts.yaml'),
}

View File

@@ -0,0 +1,7 @@
groups:
- name: grafana_rules
rules:
# Record error rate of http requests excluding dataproxy, /ds/query and /tsdb/query requests
- record: namespace_job_handler_statuscode:http_request_total:rate5m
expr: |
sum by (namespace, job, handler, statuscode) (rate(http_request_total[5m]))

6
grafana-mixin/scripts/build.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
set -eo pipefail
cd "$(dirname "$0")"/..
mixtool generate all mixin.libsonnet

View File

@@ -0,0 +1 @@
JSONNET_FMT="jsonnetfmt -n 2 --max-blank-lines 2 --string-style s --comment-style s"

View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -eo pipefail
cd "$(dirname "$0")"/..
. scripts/common.sh
find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \
xargs -n 1 -- ${JSONNET_FMT} -i

13
grafana-mixin/scripts/lint.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
set -eo pipefail
cd "$(dirname "$0")"/..
. scripts/common.sh
find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \
while read f; do \
${JSONNET_FMT} "$f" | diff -u "$f" -; \
done
mixtool lint mixin.libsonnet

View File

@@ -2,5 +2,5 @@
"npmClient": "yarn",
"useWorkspaces": true,
"packages": ["packages/*"],
"version": "7.3.0-pre.0"
"version": "7.3.0-beta.2"
}

View File

@@ -3,7 +3,7 @@
"license": "Apache-2.0",
"private": true,
"name": "grafana",
"version": "7.3.0-pre",
"version": "7.3.0-beta2",
"repository": "github:grafana/grafana",
"scripts": {
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
@@ -45,8 +45,8 @@
"ci:test-frontend": "yarn run prettier:check && yarn run packages:typecheck && yarn run typecheck && yarn run test"
},
"grafana": {
"whatsNewUrl": "https://grafana.com/docs/grafana/latest/guides/whats-new-in-v7-2/",
"releaseNotesUrl": "https://community.grafana.com/t/release-notes-v7-2-x/36321"
"whatsNewUrl": "https://grafana.com/docs/grafana/latest/guides/whats-new-in-v7-3/",
"releaseNotesUrl": "https://community.grafana.com/t/release-notes-v7-3-x/37993"
},
"husky": {
"hooks": {
@@ -269,6 +269,7 @@
"react-loadable": "5.5.0",
"react-popper": "1.3.3",
"react-redux": "7.2.0",
"react-reverse-portal": "^2.0.1",
"react-sizeme": "2.6.12",
"react-split-pane": "0.1.89",
"react-transition-group": "4.3.0",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/data",
"version": "7.3.0-pre.0",
"version": "7.3.0-beta.2",
"description": "Grafana Data Library",
"keywords": [
"typescript"

View File

@@ -3,13 +3,14 @@ import _ from 'lodash';
// Types
import { Field, FieldType } from '../types/dataFrame';
import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
import { GrafanaTheme } from '../types/theme';
import { DecimalCount, DecimalInfo, DisplayProcessor, DisplayValue } from '../types/displayValue';
import { getValueFormat } from '../valueFormats/valueFormats';
import { getMappedValue } from '../utils/valueMappings';
import { dateTime } from '../datetime';
import { KeyValue, TimeZone } from '../types';
import { getScaleCalculator } from './scale';
import { getTestTheme } from '../utils/testdata/testTheme';
interface DisplayProcessorOptions {
field: Partial<Field>;
@@ -41,7 +42,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
const config = field.config ?? {};
// Theme should be required or we need access to default theme instance from here
const theme = options.theme ?? ({ type: GrafanaThemeType.Dark } as GrafanaTheme);
const theme = options.theme ?? getTestTheme();
let unit = config.unit;
let hasDateUnit = unit && (timeFormats[unit] || unit.startsWith('time:'));

View File

@@ -1,4 +1,5 @@
import { Field, GrafanaThemeType, GrafanaTheme, FieldColorModeId } from '../types';
import { Field, FieldColorModeId } from '../types';
import { getTestTheme } from '../utils/testdata/testTheme';
import { fieldColorModeRegistry, FieldValueColorCalculator } from './fieldColor';
describe('fieldColorModeRegistry', () => {
@@ -9,10 +10,7 @@ describe('fieldColorModeRegistry', () => {
function getCalculator(options: GetCalcOptions): FieldValueColorCalculator {
const mode = fieldColorModeRegistry.get(options.mode);
return mode.getCalculator(
{ state: { seriesIndex: options.seriesIndex } } as Field,
{ type: GrafanaThemeType.Dark } as GrafanaTheme
);
return mode.getCalculator({ state: { seriesIndex: options.seriesIndex } } as Field, getTestTheme());
}
it('Schemes should interpolate', () => {

View File

@@ -54,20 +54,74 @@ export const fieldColorModeRegistry = new Registry<FieldColorMode>(() => {
// }),
new FieldColorSchemeMode({
id: FieldColorModeId.PaletteClassic,
name: 'By series / Classic palette',
//description: 'Assigns color based on series or field index',
name: 'Classic palette',
isContinuous: false,
isByValue: false,
colors: classicColors,
}),
new FieldColorSchemeMode({
id: 'continuous-GrYlRd',
name: 'By value / Green Yellow Red (gradient)',
//description: 'Interpolated colors based value, min and max',
name: 'Green-Yellow-Red',
isContinuous: true,
isByValue: true,
colors: ['green', 'yellow', 'red'],
}),
new FieldColorSchemeMode({
id: 'continuous-BlYlRd',
name: 'Blue-Yellow-Red',
isContinuous: true,
isByValue: true,
colors: ['dark-blue', 'super-light-yellow', 'dark-red'],
}),
new FieldColorSchemeMode({
id: 'continuous-YlRd',
name: 'Yellow-Red',
isContinuous: true,
isByValue: true,
colors: ['super-light-yellow', 'dark-red'],
}),
new FieldColorSchemeMode({
id: 'continuous-BlPu',
name: 'Blue-Purple',
isContinuous: true,
isByValue: true,
colors: ['blue', 'purple'],
}),
new FieldColorSchemeMode({
id: 'continuous-YlBl',
name: 'Yellow-Blue',
isContinuous: true,
isByValue: true,
colors: ['super-light-yellow', 'dark-blue'],
}),
new FieldColorSchemeMode({
id: 'continuous-blues',
name: 'Blues',
isContinuous: true,
isByValue: true,
colors: ['panel-bg', 'dark-blue'],
}),
new FieldColorSchemeMode({
id: 'continuous-reds',
name: 'Reds',
isContinuous: true,
isByValue: true,
colors: ['panel-bg', 'dark-red'],
}),
new FieldColorSchemeMode({
id: 'continuous-greens',
name: 'Greens',
isContinuous: true,
isByValue: true,
colors: ['panel-bg', 'dark-green'],
}),
new FieldColorSchemeMode({
id: 'continuous-purples',
name: 'Purples',
isContinuous: true,
isByValue: true,
colors: ['panel-bg', 'dark-purple'],
}),
];
});

View File

@@ -2,9 +2,9 @@ import merge from 'lodash/merge';
import { getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay';
import { toDataFrame } from '../dataframe/processDataFrame';
import { ReducerID } from '../transformations/fieldReducer';
import { GrafanaTheme } from '../types/theme';
import { MappingType } from '../types';
import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry';
import { getTestTheme } from '../utils/testdata/testTheme';
describe('FieldDisplay', () => {
beforeAll(() => {
@@ -241,7 +241,7 @@ function createDisplayOptions(extend: Partial<GetFieldDisplayValuesOptions> = {}
overrides: [],
defaults: {},
},
theme: {} as GrafanaTheme,
theme: getTestTheme(),
};
return merge<GetFieldDisplayValuesOptions, any>(options, extend);

View File

@@ -15,7 +15,6 @@ import {
FieldConfigPropertyItem,
FieldConfigSource,
FieldType,
GrafanaTheme,
InterpolateFunction,
ThresholdsMode,
FieldColorModeId,
@@ -28,6 +27,7 @@ import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
import { getFieldDisplayName } from './fieldState';
import { ArrayVector } from '../vector';
import { getDisplayProcessor } from './displayProcessor';
import { getTestTheme } from '../utils/testdata/testTheme';
const property1: any = {
id: 'custom.property1', // Match field properties
@@ -136,7 +136,7 @@ describe('applyFieldOverrides', () => {
},
replaceVariables: (value: any) => value,
getDataSourceSettingsByUid: undefined as any,
theme: {} as GrafanaTheme,
theme: getTestTheme(),
fieldConfigRegistry: new FieldConfigOptionsRegistry(),
});
@@ -199,7 +199,7 @@ describe('applyFieldOverrides', () => {
fieldConfigRegistry: customFieldRegistry,
getDataSourceSettingsByUid: undefined as any,
replaceVariables: v => v,
theme: {} as GrafanaTheme,
theme: getTestTheme(),
})[0];
const outField = processed.fields[0];
@@ -216,7 +216,7 @@ describe('applyFieldOverrides', () => {
fieldConfig: src as FieldConfigSource, // defaults + overrides
replaceVariables: (undefined as any) as InterpolateFunction,
getDataSourceSettingsByUid: undefined as any,
theme: (undefined as any) as GrafanaTheme,
theme: getTestTheme(),
fieldConfigRegistry: customFieldRegistry,
})[0];
const valueColumn = data.fields[1];
@@ -244,7 +244,7 @@ describe('applyFieldOverrides', () => {
fieldConfig: src as FieldConfigSource, // defaults + overrides
replaceVariables: (undefined as any) as InterpolateFunction,
getDataSourceSettingsByUid: undefined as any,
theme: (undefined as any) as GrafanaTheme,
theme: getTestTheme(),
autoMinMax: true,
})[0];
const valueColumn = data.fields[1];
@@ -268,7 +268,7 @@ describe('applyFieldOverrides', () => {
return value;
}) as InterpolateFunction,
getDataSourceSettingsByUid: undefined as any,
theme: (undefined as any) as GrafanaTheme,
theme: getTestTheme(),
autoMinMax: true,
fieldConfigRegistry: customFieldRegistry,
})[0];
@@ -521,7 +521,7 @@ describe('getLinksSupplier', () => {
// this is used only for internal links so isn't needed here
() => ({} as any),
{
theme: {} as GrafanaTheme,
theme: getTestTheme(),
}
);
supplier({});
@@ -568,7 +568,7 @@ describe('getLinksSupplier', () => {
// We do not need to interpolate anything for this test
(value, vars, format) => value,
uid => ({ name: 'testDS' } as any),
{ theme: {} as GrafanaTheme }
{ theme: getTestTheme() }
);
const links = supplier({ valueRowIndex: 0 });
expect(links.length).toBe(1);

View File

@@ -2,6 +2,7 @@ import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy';
import { applyFieldOverrides } from './fieldOverrides';
import { toDataFrame } from '../dataframe';
import { GrafanaTheme } from '../types';
import { getTestTheme } from '../utils/testdata/testTheme';
describe('getFieldDisplayValuesProxy', () => {
const data = applyFieldOverrides({
@@ -30,7 +31,7 @@ describe('getFieldDisplayValuesProxy', () => {
replaceVariables: (val: string) => val,
getDataSourceSettingsByUid: (val: string) => ({} as any),
timeZone: 'utc',
theme: {} as GrafanaTheme,
theme: getTestTheme(),
autoMinMax: true,
})[0];

View File

@@ -1,7 +1,8 @@
import { ThresholdsMode, Field, FieldType, GrafanaThemeType, GrafanaTheme } from '../types';
import { ThresholdsMode, Field, FieldType } from '../types';
import { sortThresholds } from './thresholds';
import { ArrayVector } from '../vector/ArrayVector';
import { getScaleCalculator } from './scale';
import { getTestTheme } from '../utils/testdata/testTheme';
describe('getScaleCalculator', () => {
it('should return percent, threshold and color', () => {
@@ -18,7 +19,7 @@ describe('getScaleCalculator', () => {
values: new ArrayVector([0, 50, 100]),
};
const calc = getScaleCalculator(field, { type: GrafanaThemeType.Dark } as GrafanaTheme);
const calc = getScaleCalculator(field, getTestTheme());
expect(calc(70)).toEqual({
percent: 0.7,
threshold: thresholds[1],

View File

@@ -2,7 +2,7 @@ import { map } from 'rxjs/operators';
import { DataTransformerID } from './ids';
import { DataTransformerInfo } from '../../types/transformations';
import { DataFrame, Field } from '../../types/dataFrame';
import { DataFrame, Field, TIME_SERIES_VALUE_FIELD_NAME } from '../../types/dataFrame';
import { ArrayVector } from '../../vector';
export enum ConcatenateFrameNameMode {
@@ -73,7 +73,7 @@ export function concatenateFields(data: DataFrame[], opts: ConcatenateTransforme
} else if (opts.frameNameMode === ConcatenateFrameNameMode.Label) {
copy.labels = { ...f.labels };
copy.labels[frameNameLabel] = frame.name;
} else if (!copy.name || copy.name === 'Value') {
} else if (!copy.name || copy.name === TIME_SERIES_VALUE_FIELD_NAME) {
copy.name = frame.name;
} else {
copy.name = `${frame.name} · ${f.name}`;

View File

@@ -1,4 +1,3 @@
import { SelectableValue } from './select';
import { Observable } from 'rxjs';
/**
@@ -17,7 +16,7 @@ export enum LiveChannelScope {
/**
* @alpha -- experimental
*/
export interface LiveChannelConfig<TMessage = any> {
export interface LiveChannelConfig<TMessage = any, TController = any> {
/**
* The path definition. either static, or it may contain variables identifed with {varname}
*/
@@ -28,11 +27,6 @@ export interface LiveChannelConfig<TMessage = any> {
*/
description?: string;
/**
* When variables exist, this list will identify each one
*/
variables?: Array<SelectableValue<string>>;
/**
* The channel keeps track of who else is connected to the same channel
*/
@@ -46,6 +40,9 @@ export interface LiveChannelConfig<TMessage = any> {
/** convert the raw stream message into a message that should be broadcast */
processMessage?: (msg: any) => TMessage;
/** some channels are managed by an explicit interface */
getController?: () => TController;
}
export enum LiveChannelConnectionState {

View File

@@ -27,10 +27,12 @@ export enum LogLevel {
unknown = 'unknown',
}
// Used for meta information such as common labels or returned log rows in logs view in Explore
export enum LogsMetaKind {
Number,
String,
LabelsMap,
Error,
}
export enum LogsSortOrder {

View File

@@ -9,6 +9,7 @@ import {
calculateStats,
getLogLevelFromKey,
sortLogsResult,
checkLogsError,
} from './logs';
describe('getLoglevel()', () => {
@@ -352,3 +353,15 @@ describe('sortLogsResult', () => {
});
});
});
describe('checkLogsError()', () => {
const log = ({
labels: {
__error__: 'Error Message',
foo: 'boo',
},
} as any) as LogRowModel;
test('should return correct error if error is present', () => {
expect(checkLogsError(log)).toStrictEqual({ hasError: true, errorMessage: 'Error Message' });
});
});

View File

@@ -210,3 +210,16 @@ export const sortLogsResult = (logsResult: LogsModel | null, sortOrder: LogsSort
export const sortLogRows = (logRows: LogRowModel[], sortOrder: LogsSortOrder) =>
sortOrder === LogsSortOrder.Ascending ? logRows.sort(sortInAscendingOrder) : logRows.sort(sortInDescendingOrder);
// Currently supports only error condition in Loki logs
export const checkLogsError = (logRow: LogRowModel): { hasError: boolean; errorMessage?: string } => {
if (logRow.labels.__error__) {
return {
hasError: true,
errorMessage: logRow.labels.__error__,
};
}
return {
hasError: false,
};
};

View File

@@ -34,7 +34,8 @@ export type Color =
| 'dark-purple'
| 'semi-dark-purple'
| 'light-purple'
| 'super-light-purple';
| 'super-light-purple'
| 'panel-bg';
type ThemeVariants = {
dark: string;
@@ -82,6 +83,8 @@ export function buildColorsMapForTheme(theme: GrafanaTheme): Record<Color, strin
}
}
colorsMap['panel-bg'] = theme.colors.panelBg;
return colorsMap;
}
@@ -118,7 +121,25 @@ export function getColorForTheme(color: string, theme: GrafanaTheme): string {
export function getColorFromHexRgbOrName(color: string, type?: GrafanaThemeType): string {
const themeType = type ?? GrafanaThemeType.Dark;
return getColorForTheme(color, ({ type: themeType } as unknown) as GrafanaTheme);
if (themeType === GrafanaThemeType.Dark) {
const darkTheme = ({
type: themeType,
colors: {
panelBg: '#141619',
},
} as unknown) as GrafanaTheme;
return getColorForTheme(color, darkTheme);
}
const lightTheme = ({
type: themeType,
colors: {
panelBg: '#000000',
},
} as unknown) as GrafanaTheme;
return getColorForTheme(color, lightTheme);
}
const buildNamedColorsPalette = () => {

View File

@@ -0,0 +1,12 @@
import { GrafanaTheme, GrafanaThemeType } from '../../types/theme';
export function getTestTheme(type: GrafanaThemeType = GrafanaThemeType.Dark): GrafanaTheme {
return ({
type,
isDark: type === GrafanaThemeType.Dark,
isLight: type === GrafanaThemeType.Light,
colors: {
panelBg: 'white',
},
} as unknown) as GrafanaTheme;
}

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e-selectors",
"version": "7.3.0-pre.0",
"version": "7.3.0-beta.2",
"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": "7.3.0-pre.0",
"version": "7.3.0-beta.2",
"description": "Grafana End-to-End Test Library",
"keywords": [
"cli",
@@ -44,7 +44,7 @@
"types": "src/index.ts",
"dependencies": {
"@cypress/webpack-preprocessor": "4.1.3",
"@grafana/e2e-selectors": "7.3.0-pre.0",
"@grafana/e2e-selectors": "7.3.0-beta.2",
"@grafana/tsconfig": "^1.0.0-rc1",
"@mochajs/json-file-reporter": "^1.2.0",
"blink-diff": "1.0.13",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/runtime",
"version": "7.3.0-pre.0",
"version": "7.3.0-beta.2",
"description": "Grafana Runtime Library",
"keywords": [
"grafana",
@@ -22,8 +22,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@grafana/data": "7.3.0-pre.0",
"@grafana/ui": "7.3.0-pre.0",
"@grafana/data": "7.3.0-beta.2",
"@grafana/ui": "7.3.0-beta.2",
"systemjs": "0.20.19",
"systemjs-plugin-css": "0.1.37"
},

View File

@@ -6,6 +6,7 @@
export * from './services';
export * from './config';
export * from './types';
export * from './measurement';
export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin';
export { reportMetaAnalytics } from './utils/analytics';
export { DataSourceWithBackend, HealthCheckResult, HealthStatus } from './utils/DataSourceWithBackend';

View File

@@ -0,0 +1,250 @@
import { MeasurementCollector } from './collector';
import { MeasurementAction } from './types';
describe('MeasurementCollector', () => {
it('should collect values', () => {
const collector = new MeasurementCollector();
collector.addBatch({
measurements: [
{
name: 'test',
labels: { host: 'a' },
time: 100,
values: {
f0: 0,
f1: 1,
f2: 'hello',
},
},
{
name: 'test',
labels: { host: 'b' },
time: 101,
values: {
f0: 0,
f1: 1,
f2: 'hello',
},
config: {
f2: {
unit: 'mph',
},
},
},
{
name: 'test',
time: 102,
labels: { host: 'a' }, // should append to first value
values: {
// note the missing values for f0/1
f2: 'world',
},
},
],
});
const frames = collector.getData();
expect(frames.length).toEqual(2);
expect(frames[0]).toMatchInlineSnapshot(`
Object {
"fields": Array [
Object {
"config": Object {},
"labels": undefined,
"name": "time",
"type": "time",
"values": Array [
100,
102,
],
},
Object {
"config": Object {},
"labels": Object {
"host": "a",
},
"name": "f0",
"type": "number",
"values": Array [
0,
null,
],
},
Object {
"config": Object {},
"labels": Object {
"host": "a",
},
"name": "f1",
"type": "number",
"values": Array [
1,
null,
],
},
Object {
"config": Object {},
"labels": Object {
"host": "a",
},
"name": "f2",
"type": "string",
"values": Array [
"hello",
"world",
],
},
],
"meta": Object {
"custom": Object {
"labels": Object {
"host": "a",
},
},
},
"name": "test",
"refId": undefined,
}
`);
expect(frames[1]).toMatchInlineSnapshot(`
Object {
"fields": Array [
Object {
"config": Object {},
"labels": undefined,
"name": "time",
"type": "time",
"values": Array [
101,
],
},
Object {
"config": Object {},
"labels": Object {
"host": "b",
},
"name": "f0",
"type": "number",
"values": Array [
0,
],
},
Object {
"config": Object {},
"labels": Object {
"host": "b",
},
"name": "f1",
"type": "number",
"values": Array [
1,
],
},
Object {
"config": Object {
"unit": "mph",
},
"labels": Object {
"host": "b",
},
"name": "f2",
"type": "string",
"values": Array [
"hello",
],
},
],
"meta": Object {
"custom": Object {
"labels": Object {
"host": "b",
},
},
},
"name": "test",
"refId": undefined,
}
`);
collector.addBatch({
action: MeasurementAction.Replace,
measurements: [
{
name: 'test',
time: 105,
labels: { host: 'a' },
values: {
f1: 10,
},
},
],
});
const frames2 = collector.getData();
expect(frames2.length).toEqual(2);
expect(frames2[0].length).toEqual(1); // not three!
expect(frames2[0]).toMatchInlineSnapshot(`
Object {
"fields": Array [
Object {
"config": Object {},
"labels": undefined,
"name": "time",
"type": "time",
"values": Array [
105,
],
},
Object {
"config": Object {},
"labels": Object {
"host": "a",
},
"name": "f0",
"type": "number",
"values": Array [
null,
],
},
Object {
"config": Object {},
"labels": Object {
"host": "a",
},
"name": "f1",
"type": "number",
"values": Array [
10,
],
},
Object {
"config": Object {},
"labels": Object {
"host": "a",
},
"name": "f2",
"type": "string",
"values": Array [
null,
],
},
],
"meta": Object {
"custom": Object {
"labels": Object {
"host": "a",
},
},
},
"name": "test",
"refId": undefined,
}
`);
collector.addBatch({
action: MeasurementAction.Clear,
measurements: [],
});
expect(collector.getData().length).toEqual(0);
});
});

View File

@@ -0,0 +1,209 @@
import {
CircularDataFrame,
Labels,
formatLabels,
FieldType,
DataFrame,
matchAllLabels,
parseLabels,
CircularVector,
ArrayVector,
} from '@grafana/data';
import { Measurement, MeasurementBatch, LiveMeasurements, MeasurementsQuery, MeasurementAction } from './types';
interface MeasurementCacheConfig {
append?: 'head' | 'tail';
capacity?: number;
}
/** This is a cache scoped to a the measurement name
*
* @alpha -- experimental
*/
export class MeasurementCache {
readonly frames: Record<string, CircularDataFrame> = {}; // key is the labels
constructor(public name: string, private config: MeasurementCacheConfig) {
if (!this.config) {
this.config = {
append: 'tail',
capacity: 600, // Default capacity 10min @ 1hz
};
}
}
getFrames(match?: Labels): DataFrame[] {
const frames = Object.values(this.frames);
if (!match) {
return frames;
}
return frames.filter(f => {
return matchAllLabels(match, f.meta?.custom?.labels);
});
}
addMeasurement(m: Measurement, action: MeasurementAction): DataFrame {
const key = m.labels ? formatLabels(m.labels) : '';
let frame = this.frames[key];
if (!frame) {
frame = new CircularDataFrame(this.config);
frame.name = this.name;
frame.addField({
name: 'time',
type: FieldType.time,
});
for (const [key, value] of Object.entries(m.values)) {
frame.addFieldFor(value, key).labels = m.labels;
}
frame.meta = {
custom: {
labels: m.labels,
},
};
this.frames[key] = frame;
}
// Clear existing values
if (action === MeasurementAction.Replace) {
for (const field of frame.fields) {
(field.values as ArrayVector).buffer.length = 0; // same buffer, but reset to empty length
}
}
// Add the timestamp
frame.values['time'].add(m.time || Date.now());
// Attach field config to the current fields
if (m.config) {
for (const [key, value] of Object.entries(m.config)) {
const f = frame.fields.find(f => f.name === key);
if (f) {
f.config = value;
}
}
}
// Append all values (a row)
for (const [key, value] of Object.entries(m.values)) {
let v = frame.values[key];
if (!v) {
const f = frame.addFieldFor(value, key);
f.labels = m.labels;
v = f.values;
}
v.add(value);
}
// Make sure all fields have the same length
frame.validate();
return frame;
}
}
/**
* @alpha -- experimental
*/
export class MeasurementCollector implements LiveMeasurements {
measurements = new Map<string, MeasurementCache>();
config: MeasurementCacheConfig = {
append: 'tail',
capacity: 600, // Default capacity 10min @ 1hz
};
//------------------------------------------------------
// Public
//------------------------------------------------------
getData(query?: MeasurementsQuery): DataFrame[] {
const { name, labels, fields } = query || {};
let data: DataFrame[] = [];
if (name) {
// for now we only match exact names
const m = this.measurements.get(name);
if (m) {
data = m.getFrames(labels);
}
} else {
for (const f of this.measurements.values()) {
data.push.apply(data, f.getFrames(labels));
}
}
if (fields && fields.length) {
let filtered: DataFrame[] = [];
for (const frame of data) {
const match = frame.fields.filter(f => fields.includes(f.name));
if (match.length > 0) {
filtered.push({ ...frame, fields: match }); // Copy the frame with fewer fields
}
}
}
return data;
}
getDistinctNames(): string[] {
return Object.keys(this.measurements);
}
getDistinctLabels(name: string): Labels[] {
const m = this.measurements.get(name);
if (m) {
return Object.keys(m.frames).map(k => parseLabels(k));
}
return [];
}
setCapacity(size: number) {
this.config.capacity = size;
// Now update all the circular buffers
for (const wrap of this.measurements.values()) {
for (const frame of Object.values(wrap.frames)) {
for (const field of frame.fields) {
(field.values as CircularVector).setCapacity(size);
}
}
}
}
getCapacity() {
return this.config.capacity!;
}
clear() {
this.measurements.clear();
}
//------------------------------------------------------
// Collector
//------------------------------------------------------
addBatch = (batch: MeasurementBatch) => {
let action = batch.action ?? MeasurementAction.Append;
if (action === MeasurementAction.Clear) {
this.measurements.clear();
action = MeasurementAction.Append;
}
// Change the local buffer size
if (batch.capacity && batch.capacity !== this.config.capacity) {
this.setCapacity(batch.capacity);
}
for (const measure of batch.measurements) {
const name = measure.name || '';
let m = this.measurements.get(name);
if (!m) {
m = new MeasurementCache(name, this.config);
this.measurements.set(name, m);
}
if (measure.values) {
m.addMeasurement(measure, action);
} else {
console.log('invalid measurement', measure);
}
}
return this;
};
}

View File

@@ -0,0 +1,3 @@
export * from './types';
export * from './collector';
export * from './query';

View File

@@ -0,0 +1,77 @@
import {
DataQueryResponse,
isLiveChannelMessageEvent,
isLiveChannelStatusEvent,
isValidLiveChannelAddress,
LiveChannelAddress,
LoadingState,
} from '@grafana/data';
import { LiveMeasurements, MeasurementsQuery } from './types';
import { getGrafanaLiveSrv } from '../services/live';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
/**
* @alpha -- experimental
*/
export function getLiveMeasurements(addr: LiveChannelAddress): LiveMeasurements | undefined {
if (!isValidLiveChannelAddress(addr)) {
return undefined;
}
const live = getGrafanaLiveSrv();
if (!live) {
return undefined;
}
const channel = live.getChannel<LiveMeasurements>(addr);
const getController = channel?.config?.getController;
return getController ? getController() : undefined;
}
/**
* When you know the stream will be managed measurements
*
* @alpha -- experimental
*/
export function getLiveMeasurementsObserver(
addr: LiveChannelAddress,
requestId: string,
query?: MeasurementsQuery
): Observable<DataQueryResponse> {
const rsp: DataQueryResponse = { data: [] };
if (!addr || !addr.path) {
return of(rsp); // Address not configured yet
}
const live = getGrafanaLiveSrv();
if (!live) {
// This will only happen with the feature flag is not enabled
rsp.error = { message: 'Grafana live is not initalized' };
return of(rsp);
}
rsp.key = requestId;
return live
.getChannel<LiveMeasurements>(addr)
.getStream()
.pipe(
map(evt => {
if (isLiveChannelMessageEvent(evt)) {
rsp.data = evt.message.getData(query);
if (!rsp.data.length) {
// ?? skip when data is empty ???
}
delete rsp.error;
rsp.state = LoadingState.Streaming;
} else if (isLiveChannelStatusEvent(evt)) {
if (evt.error != null) {
rsp.error = rsp.error;
rsp.state = LoadingState.Error;
}
}
return { ...rsp }; // send event on all status messages
})
);
}

View File

@@ -0,0 +1,73 @@
import { DataFrame, Labels, FieldConfig } from '@grafana/data';
/**
* the raw channel events are batches of Measurements
*
* @alpha -- experimental
*/
export interface Measurement {
name: string;
time?: number; // Missing will use the browser time
values: Record<string, any>;
config?: Record<string, FieldConfig>;
labels?: Labels;
}
/**
* @alpha -- experimental
*/
export enum MeasurementAction {
/** The measurements will be added to the client buffer */
Append = 'append',
/** The measurements will replace the client buffer */
Replace = 'replace',
/** All measurements will be removed from the client buffer before processing */
Clear = 'clear',
}
/**
* List of Measurements sent in a batch
*
* @alpha -- experimental
*/
export interface MeasurementBatch {
/**
* The default action is to append values to the client buffer
*/
action?: MeasurementAction;
/**
* List of measurements to process
*/
measurements: Measurement[];
/**
* This will set the capacity on the client buffer for everything
* in the measurement channel
*/
capacity?: number;
}
/**
* @alpha -- experimental
*/
export interface MeasurementsQuery {
name?: string;
labels?: Labels;
fields?: string[]; // only include the fields with these names
}
/**
* Channels that receive Measurements can collect them into frames
*
* @alpha -- experimental
*/
export interface LiveMeasurements {
getData(query?: MeasurementsQuery): DataFrame[];
getDistinctNames(): string[];
getDistinctLabels(name: string): Labels[];
setCapacity(size: number): void;
getCapacity(): number;
}

View File

@@ -6,6 +6,9 @@
*/
import { UrlQueryMap } from '@grafana/data';
/**
* @public
*/
export interface LocationUpdate {
/**
* Target path where you automatically wants to navigate the user.

View File

@@ -39,7 +39,7 @@ export const setGrafanaLiveSrv = (instance: GrafanaLiveSrv) => {
};
/**
* Used to retrieve the {@link GrafanaLiveSrv} that allows you to subscribe to
* Used to retrieve the GrafanaLiveSrv that allows you to subscribe to
* server side events and streams
*
* @alpha -- experimental

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/toolkit",
"version": "7.3.0-pre.0",
"version": "7.3.0-beta.2",
"description": "Grafana Toolkit",
"keywords": [
"grafana",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/ui",
"version": "7.3.0-pre.0",
"version": "7.3.0-beta.2",
"description": "Grafana Components Library",
"keywords": [
"grafana",
@@ -27,15 +27,15 @@
},
"dependencies": {
"@emotion/core": "^10.0.27",
"@grafana/data": "7.3.0-pre.0",
"@grafana/e2e-selectors": "7.3.0-pre.0",
"@grafana/data": "7.3.0-beta.2",
"@grafana/e2e-selectors": "7.3.0-beta.2",
"@grafana/slate-react": "0.22.9-grafana",
"@grafana/tsconfig": "^1.0.0-rc1",
"@iconscout/react-unicons": "1.1.4",
"@torkelo/react-select": "3.0.8",
"@types/hoist-non-react-statics": "3.3.1",
"@types/react-beautiful-dnd": "12.1.2",
"@types/react-color": "3.0.1",
"@types/hoist-non-react-statics": "3.3.1",
"@types/react-select": "3.0.8",
"@types/react-table": "7.0.12",
"@types/slate": "0.47.1",
@@ -43,9 +43,8 @@
"bizcharts": "^3.5.8",
"classnames": "2.2.6",
"d3": "5.15.0",
"hoist-non-react-statics": "3.3.2",
"immutable": "3.8.2",
"emotion": "10.0.27",
"hoist-non-react-statics": "3.3.2",
"immutable": "3.8.2",
"jquery": "3.5.1",
"lodash": "4.17.19",

View File

@@ -9,6 +9,7 @@ import { calculateFontSize } from '../../utils/measureText';
// Types
import { BigValueColorMode, Props, BigValueJustifyMode, BigValueTextMode } from './BigValue';
import { getTextColorForBackground } from '../../utils';
const LINE_HEIGHT = 1.2;
const MAX_TITLE_SIZE = 30;
@@ -51,7 +52,7 @@ export abstract class BigValueLayout {
};
if (this.props.colorMode === BigValueColorMode.Background) {
styles.color = 'white';
styles.color = getTextColorForBackground(this.valueColor);
}
return styles;
@@ -69,7 +70,7 @@ export abstract class BigValueLayout {
styles.color = this.valueColor;
break;
case BigValueColorMode.Background:
styles.color = 'white';
styles.color = getTextColorForBackground(this.valueColor);
}
return styles;

View File

@@ -30,7 +30,7 @@ exports[`BigValue Render with basic options should render 1`] = `
<FormattedDisplayValue
style={
Object {
"color": "white",
"color": "#202226",
"fontSize": 230,
"fontWeight": 500,
"lineHeight": 1.2,

View File

@@ -45,6 +45,12 @@ describe('LogDetails', () => {
expect(wrapper.text().includes('key1label1key2label2')).toBe(true);
});
});
describe('when log row has error', () => {
it('should not render log level border', () => {
const wrapper = setup({ hasError: true }, undefined);
expect(wrapper.find({ 'aria-label': 'Log level' }).html()).not.toContain('logs-row__level');
});
});
describe('when row entry has parsable fields', () => {
it('should render heading ', () => {
const wrapper = setup(undefined, { entry: 'test=successful' });

View File

@@ -28,6 +28,7 @@ export interface Props extends Themeable {
showDuplicates: boolean;
getRows: () => LogRowModel[];
className?: string;
hasError?: boolean;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
onClickFilterLabel?: (key: string, value: string) => void;
@@ -70,6 +71,7 @@ class UnThemedLogDetails extends PureComponent<Props> {
const {
row,
theme,
hasError,
onClickFilterOutLabel,
onClickFilterLabel,
getRows,
@@ -88,6 +90,8 @@ class UnThemedLogDetails extends PureComponent<Props> {
const labelsAvailable = Object.keys(labels).length > 0;
const fields = getAllFields(row, getFieldLinks);
const parsedFieldsAvailable = fields && fields.length > 0;
// If logs with error, we are not showing the level color
const levelClassName = cx(!hasError && [style.logsRowLevel, styles.logsRowLevelDetails]);
return (
<tr
@@ -96,7 +100,7 @@ class UnThemedLogDetails extends PureComponent<Props> {
onMouseLeave={onMouseLeave}
>
{showDuplicates && <td />}
<td className={cx(style.logsRowLevel, styles.logsRowLevelDetails)} />
<td className={levelClassName} aria-label="Log level" />
<td colSpan={4}>
<div className={style.logDetailsContainer}>
<table className={style.logDetailsTable}>

View File

@@ -8,8 +8,10 @@ import {
DataQueryResponse,
GrafanaTheme,
dateTimeFormat,
checkLogsError,
} from '@grafana/data';
import { Icon } from '../Icon/Icon';
import { Tooltip } from '../Tooltip/Tooltip';
import { cx, css } from 'emotion';
import {
@@ -72,6 +74,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
label: hoverBackground;
background-color: ${bgColor};
`,
errorLogRow: css`
label: erroredLogRow;
color: ${theme.colors.textWeak};
`,
};
});
/**
@@ -160,22 +166,27 @@ class UnThemedLogRow extends PureComponent<Props, State> {
const { showDetails, showContext, hasHoverBackground } = this.state;
const style = getLogRowStyles(theme, row.logLevel);
const styles = getStyles(theme);
const hoverBackground = cx(style.logsRow, { [styles.hoverBackground]: hasHoverBackground });
const { errorMessage, hasError } = checkLogsError(row);
const logRowBackground = cx(style.logsRow, {
[styles.hoverBackground]: hasHoverBackground,
[styles.errorLogRow]: hasError,
});
return (
<>
<tr
className={hoverBackground}
onMouseEnter={this.addHoverBackground}
onMouseLeave={this.clearHoverBackground}
onClick={this.toggleDetails}
>
<tr className={logRowBackground} onClick={this.toggleDetails}>
{showDuplicates && (
<td className={style.logsRowDuplicates}>
{row.duplicates && row.duplicates > 0 ? `${row.duplicates + 1}x` : null}
</td>
)}
<td className={style.logsRowLevel} />
<td className={cx({ [style.logsRowLevel]: !hasError })}>
{hasError && (
<Tooltip content={`Error: ${errorMessage}`} placement="right" theme="error">
<Icon className={style.logIconError} name="exclamation-triangle" size="sm" />
</Tooltip>
)}
</td>
{!allowDetails && (
<td title={showDetails ? 'Hide log details' : 'See log details'} className={style.logsRowToggleDetails}>
<Icon className={styles.topVerticalAlign} name={showDetails ? 'angle-down' : 'angle-right'} />
@@ -207,7 +218,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
</tr>
{this.state.showDetails && (
<LogDetails
className={hoverBackground}
className={logRowBackground}
onMouseEnter={this.addHoverBackground}
onMouseLeave={this.clearHoverBackground}
showDuplicates={showDuplicates}
@@ -218,6 +229,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
onClickHideParsedField={onClickHideParsedField}
getRows={getRows}
row={row}
hasError={hasError}
showParsedFields={showParsedFields}
/>
)}

View File

@@ -9,6 +9,7 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
let logColor = selectThemeVariant({ light: theme.palette.gray5, dark: theme.palette.gray2 }, theme.type);
const borderColor = selectThemeVariant({ light: theme.palette.gray5, dark: theme.palette.gray2 }, theme.type);
const bgColor = selectThemeVariant({ light: theme.palette.gray5, dark: theme.palette.dark4 }, theme.type);
const hoverBgColor = selectThemeVariant({ light: theme.palette.gray7, dark: theme.palette.dark2 }, theme.type);
const context = css`
label: context;
visibility: hidden;
@@ -92,7 +93,7 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
}
&:hover {
background: ${theme.colors.bodyBg};
background: ${hoverBgColor};
}
`,
logsRowDuplicates: css`
@@ -116,6 +117,10 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
background-color: ${logColor};
}
`,
logIconError: css`
color: ${theme.palette.red};
margin-left: -5px;
`,
logsRowToggleDetails: css`
label: logs-row-toggle-details__level;
position: relative;

View File

@@ -23,9 +23,11 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
const styles = useStyles(getStyles);
const options = fieldColorModeRegistry.list().map(mode => {
let suffix = mode.isByValue ? ' (by value)' : '';
return {
value: mode.id,
label: mode.name,
label: `${mode.name}${suffix}`,
description: mode.description,
isContinuous: mode.isContinuous,
isByValue: mode.isByValue,

View File

@@ -9,6 +9,7 @@ import memoizeOne from 'memoize-one';
import { GrafanaTheme } from '@grafana/data';
import { withTheme } from '../../themes';
// Default intervals used in the refresh picker component
export const defaultIntervals = ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'];
const getStyles = memoizeOne((theme: GrafanaTheme) => {

View File

@@ -131,3 +131,21 @@ export const CustomLabelField = () => {
</SegmentFrame>
);
};
export const HtmlAttributes = () => {
const [value, setValue] = useState<any>(options[0]);
return (
<SegmentFrame options={options}>
<Segment
data-testid="segment-test"
id="segment-id"
value={value}
options={groupedOptions}
onChange={({ value }) => {
setValue(value);
action('Segment value changed')(value);
}}
/>
</SegmentFrame>
);
};

View File

@@ -1,10 +1,10 @@
import React from 'react';
import React, { HTMLProps } from 'react';
import { cx } from 'emotion';
import _ from 'lodash';
import { SelectableValue } from '@grafana/data';
import { SegmentSelect, useExpandableLabel, SegmentProps } from './';
export interface SegmentSyncProps<T> extends SegmentProps<T> {
export interface SegmentSyncProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
value?: T | SelectableValue<T>;
onChange: (item: SelectableValue<T>) => void;
options: Array<SelectableValue<T>>;
@@ -18,6 +18,7 @@ export function Segment<T>({
className,
allowCustomValue,
placeholder,
...rest
}: React.PropsWithChildren<SegmentSyncProps<T>>) {
const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
@@ -38,6 +39,7 @@ export function Segment<T>({
return (
<SegmentSelect
{...rest}
value={value && !_.isObject(value) ? { value } : value}
options={options}
width={width}

View File

@@ -123,3 +123,21 @@ export const CustomLabel = () => {
</SegmentFrame>
);
};
export const HtmlAttributes = () => {
const [value, setValue] = useState<any>(options[0]);
return (
<SegmentFrame loadOptions={() => loadOptions(options)}>
<SegmentAsync
data-testid="segment-async-test"
id="segment-async"
value={value}
loadOptions={() => loadOptions(options)}
onChange={item => {
setValue(item);
action('Segment value changed')(item.value);
}}
/>
</SegmentFrame>
);
};

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { HTMLProps } from 'react';
import { cx } from 'emotion';
import _ from 'lodash';
import { SegmentSelect } from './SegmentSelect';
@@ -7,7 +7,7 @@ import { useExpandableLabel, SegmentProps } from '.';
import { useAsyncFn } from 'react-use';
import { AsyncState } from 'react-use/lib/useAsync';
export interface SegmentAsyncProps<T> extends SegmentProps<T> {
export interface SegmentAsyncProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
value?: T | SelectableValue<T>;
loadOptions: (query?: string) => Promise<Array<SelectableValue<T>>>;
onChange: (item: SelectableValue<T>) => void;
@@ -21,6 +21,7 @@ export function SegmentAsync<T>({
className,
allowCustomValue,
placeholder,
...rest
}: React.PropsWithChildren<SegmentAsyncProps<T>>) {
const [state, fetchOptions] = useAsyncFn(loadOptions, [loadOptions]);
const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
@@ -43,6 +44,7 @@ export function SegmentAsync<T>({
return (
<SegmentSelect
{...rest}
value={value && !_.isObject(value) ? { value } : value}
options={state.value ?? []}
width={width}

View File

@@ -49,6 +49,23 @@ export const BasicInputWithPlaceholder = () => {
);
};
export const BasicInputWithHtmlAttributes = () => {
const [value, setValue] = useState('some text');
return (
<SegmentFrame>
<SegmentInput
data-testid="segment-input-test"
id="segment-input"
value={value}
onChange={text => {
setValue(text as string);
action('Segment value changed')(text);
}}
/>
</SegmentFrame>
);
};
const InputComponent = ({ initialValue }: any) => {
const [value, setValue] = useState(initialValue);
return (

View File

@@ -1,10 +1,10 @@
import React, { useRef, useState } from 'react';
import React, { HTMLProps, useRef, useState } from 'react';
import { cx, css } from 'emotion';
import useClickAway from 'react-use/lib/useClickAway';
import { measureText } from '../../utils/measureText';
import { useExpandableLabel, SegmentProps } from '.';
export interface SegmentInputProps<T> extends SegmentProps<T> {
export interface SegmentInputProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLInputElement>, 'value' | 'onChange'> {
value: string | number;
onChange: (text: string | number) => void;
autofocus?: boolean;
@@ -19,6 +19,7 @@ export function SegmentInput<T>({
className,
placeholder,
autofocus = false,
...rest
}: React.PropsWithChildren<SegmentInputProps<T>>) {
const ref = useRef<HTMLInputElement>(null);
const [value, setValue] = useState<number | string>(initialValue);
@@ -50,6 +51,7 @@ export function SegmentInput<T>({
return (
<input
{...rest}
ref={ref}
autoFocus
className={cx(`gf-form gf-form-input`, inputWidthStyle)}

View File

@@ -1,10 +1,10 @@
import React, { useRef } from 'react';
import React, { HTMLProps, useRef } from 'react';
import { css, cx } from 'emotion';
import useClickAway from 'react-use/lib/useClickAway';
import { SelectableValue } from '@grafana/data';
import { Select } from '../Forms/Legacy/Select/Select';
export interface Props<T> {
export interface Props<T> extends Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
value?: SelectableValue<T>;
options: Array<SelectableValue<T>>;
onChange: (item: SelectableValue<T>) => void;
@@ -22,6 +22,7 @@ export function SegmentSelect<T>({
width,
noOptionsMessage = '',
allowCustomValue = false,
...rest
}: React.PropsWithChildren<Props<T>>) {
const ref = useRef<HTMLDivElement>(null);
@@ -39,7 +40,7 @@ export function SegmentSelect<T>({
});
return (
<div ref={ref}>
<div {...rest} ref={ref}>
<Select
className={cx(
css`

View File

@@ -5,6 +5,7 @@ import { TableCellDisplayMode, TableCellProps } from './types';
import tinycolor from 'tinycolor2';
import { TableStyles } from './styles';
import { FilterActions } from './FilterActions';
import { getTextColorForBackground } from '../../utils';
export const DefaultCell: FC<TableCellProps> = props => {
const { field, cell, tableStyles, row, cellProps } = props;
@@ -65,7 +66,12 @@ function getCellStyle(tableStyles: TableStyles, field: Field, displayValue: Disp
.spin(5)
.toRgbString();
return tableStyles.buildCellContainerStyle('white', `linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`);
const textColor = getTextColorForBackground(displayValue.color!);
return tableStyles.buildCellContainerStyle(
textColor,
`linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`
);
}
return tableStyles.cellContainer;

View File

@@ -22,7 +22,7 @@ export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult';
export { PieChart, PieChartType } from './PieChart/PieChart';
export { UnitPicker } from './UnitPicker/UnitPicker';
export { StatsPicker } from './StatsPicker/StatsPicker';
export { RefreshPicker } from './RefreshPicker/RefreshPicker';
export { RefreshPicker, defaultIntervals } from './RefreshPicker/RefreshPicker';
export { TimeRangePicker } from './TimePicker/TimeRangePicker';
export { TimeOfDayPicker } from './TimePicker/TimeOfDayPicker';
export { TimeZonePicker } from './TimePicker/TimeZonePicker';

View File

@@ -12,6 +12,8 @@ export type IconName =
| 'filter'
| 'angle-left'
| 'angle-right'
| 'angle-double-right'
| 'angle-double-down'
| 'pen'
| 'envelope'
| 'percentage'
@@ -128,6 +130,8 @@ export const getAvailableIcons = (): IconName[] => [
'filter',
'angle-left',
'angle-right',
'angle-double-right',
'angle-double-down',
'pen',
'envelope',
'percentage',

View File

@@ -4,6 +4,8 @@ import flattenDeep from 'lodash/flattenDeep';
import chunk from 'lodash/chunk';
import zip from 'lodash/zip';
import tinycolor from 'tinycolor2';
import lightTheme from '../themes/light';
import darkTheme from '../themes/dark';
export const PALETTE_ROWS = 4;
export const PALETTE_COLUMNS = 14;
@@ -93,4 +95,9 @@ function hslToHex(color: any) {
return tinycolor(color).toHexString();
}
export function getTextColorForBackground(color: string) {
const b = tinycolor(color).getBrightness();
return b > 150 ? lightTheme.colors.textStrong : darkTheme.colors.textStrong;
}
export let sortedColors = sortColorsByHue(colors);

View File

@@ -1,6 +1,9 @@
let canvas: HTMLCanvasElement | null = null;
const cache: Record<string, TextMetrics> = {};
/**
* @beta
*/
export function measureText(text: string, fontSize: number): TextMetrics {
const fontStyle = `${fontSize}px 'Roboto'`;
const cacheKey = text + fontStyle;
@@ -26,6 +29,9 @@ export function measureText(text: string, fontSize: number): TextMetrics {
return metrics;
}
/**
* @beta
*/
export function calculateFontSize(text: string, width: number, height: number, lineHeight: number, maxSize?: number) {
// calculate width in 14px
const textSize = measureText(text, 14);

View File

@@ -1,6 +1,6 @@
{
"name": "@jaegertracing/jaeger-ui-components",
"version": "7.3.0-pre.0",
"version": "7.3.0-beta.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -14,8 +14,8 @@
"typescript": "4.0.2"
},
"dependencies": {
"@grafana/data": "7.3.0-pre.0",
"@grafana/ui": "7.3.0-pre.0",
"@grafana/data": "7.3.0-beta.2",
"@grafana/ui": "7.3.0-beta.2",
"@types/classnames": "^2.2.7",
"@types/deep-freeze": "^0.1.1",
"@types/hoist-non-react-statics": "^3.3.1",

View File

@@ -15,7 +15,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import TimelineCollapser from './TimelineCollapser';
import { TimelineCollapser } from './TimelineCollapser';
describe('<TimelineCollapser>', () => {
it('renders without exploding', () => {

View File

@@ -12,18 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import { IconButton } from '@grafana/ui';
import { css } from 'emotion';
import cx from 'classnames';
import { UITooltip, UIIcon } from '../../uiElementsContext';
import React from 'react';
import { createStyle } from '../../Theme';
const getStyles = createStyle(() => {
return {
TraceTimelineViewer: css`
border-bottom: 1px solid #bbb;
`,
TimelineCollapser: css`
align-items: center;
display: flex;
@@ -31,21 +26,6 @@ const getStyles = createStyle(() => {
justify-content: center;
margin-right: 0.5rem;
`,
tooltipTitle: css`
white-space: pre;
`,
btn: css`
color: rgba(0, 0, 0, 0.5);
cursor: pointer;
margin-right: 0.3rem;
padding: 0.1rem;
&:hover {
color: rgba(0, 0, 0, 0.85);
}
`,
btnExpanded: css`
transform: rotate(90deg);
`,
};
});
@@ -56,40 +36,27 @@ type CollapserProps = {
onExpandAll: () => void;
};
function getTitle(value: string) {
export function TimelineCollapser(props: CollapserProps) {
const { onExpandAll, onExpandOne, onCollapseAll, onCollapseOne } = props;
const styles = getStyles();
return <span className={styles.tooltipTitle}>{value}</span>;
}
export default class TimelineCollapser extends React.PureComponent<CollapserProps> {
containerRef: React.RefObject<HTMLDivElement>;
constructor(props: CollapserProps) {
super(props);
this.containerRef = React.createRef();
}
// TODO: Something less hacky than createElement to help TypeScript / AntD
getContainer = () => this.containerRef.current || document.createElement('div');
render() {
const { onExpandAll, onExpandOne, onCollapseAll, onCollapseOne } = this.props;
const styles = getStyles();
return (
<div className={styles.TimelineCollapser} ref={this.containerRef} data-test-id="TimelineCollapser">
<UITooltip title={getTitle('Expand +1')} getPopupContainer={this.getContainer}>
<UIIcon type="right" onClick={onExpandOne} className={cx(styles.btn, styles.btnExpanded)} />
</UITooltip>
<UITooltip title={getTitle('Collapse +1')} getPopupContainer={this.getContainer}>
<UIIcon type="right" onClick={onCollapseOne} className={styles.btn} />
</UITooltip>
<UITooltip title={getTitle('Expand All')} getPopupContainer={this.getContainer}>
<UIIcon type="double-right" onClick={onExpandAll} className={cx(styles.btn, styles.btnExpanded)} />
</UITooltip>
<UITooltip title={getTitle('Collapse All')} getPopupContainer={this.getContainer}>
<UIIcon type="double-right" onClick={onCollapseAll} className={styles.btn} />
</UITooltip>
</div>
);
}
return (
<div className={styles.TimelineCollapser} data-test-id="TimelineCollapser">
<IconButton tooltip="Expand +1" size="xl" tooltipPlacement="top" name="angle-down" onClick={onExpandOne} />
<IconButton tooltip="Collapse +1" size="xl" tooltipPlacement="top" name="angle-right" onClick={onCollapseOne} />
<IconButton
tooltip="Expand All"
size="xl"
tooltipPlacement="top"
name="angle-double-down"
onClick={onExpandAll}
/>
<IconButton
tooltip="Collapse All"
size="xl"
tooltipPlacement="top"
name="angle-double-right"
onClick={onCollapseAll}
/>
</div>
);
}

View File

@@ -19,7 +19,7 @@ import TimelineHeaderRow from './TimelineHeaderRow';
import TimelineColumnResizer from './TimelineColumnResizer';
import TimelineViewingLayer from './TimelineViewingLayer';
import Ticks from '../Ticks';
import TimelineCollapser from './TimelineCollapser';
import { TimelineCollapser } from './TimelineCollapser';
describe('<TimelineHeaderRow>', () => {
let wrapper;

View File

@@ -16,7 +16,7 @@ import * as React from 'react';
import { css } from 'emotion';
import cx from 'classnames';
import TimelineCollapser from './TimelineCollapser';
import { TimelineCollapser } from './TimelineCollapser';
import TimelineColumnResizer from './TimelineColumnResizer';
import TimelineViewingLayer from './TimelineViewingLayer';
import Ticks from '../Ticks';

View File

@@ -21,7 +21,7 @@ type URLValidationError struct {
// Error returns the error message.
func (e URLValidationError) Error() string {
return fmt.Sprintf("Validation of data source URL %q failed: %s", e.URL, e.Err.Error())
return fmt.Sprintf("validation of data source URL %q failed: %s", e.URL, e.Err.Error())
}
// Unwrap returns the wrapped error.

View File

@@ -585,7 +585,7 @@ func TestNewDataSourceProxy_InvalidURL(t *testing.T) {
plugin := plugins.DataSourcePlugin{}
_, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg)
require.Error(t, err)
assert.True(t, strings.HasPrefix(err.Error(), `Validation of data source URL "://host/root" failed`))
assert.True(t, strings.HasPrefix(err.Error(), `validation of data source URL "://host/root" failed`))
}
func TestNewDataSourceProxy_ProtocolLessURL(t *testing.T) {

View File

@@ -2,6 +2,7 @@ package api
import (
"errors"
"fmt"
"path"
"strings"
@@ -27,7 +28,7 @@ func (hs *HTTPServer) createShortURL(c *models.ReqContext, cmd dtos.CreateShortU
return Error(500, "Failed to create short URL", err)
}
url := path.Join(setting.AppUrl, "goto", shortURL.Uid)
url := fmt.Sprintf("%s/goto/%s", strings.TrimSuffix(setting.AppUrl, "/"), shortURL.Uid)
c.Logger.Debug("Created short URL", "url", url)
dto := dtos.ShortURL{

View File

@@ -513,6 +513,26 @@ func SetBuildInformation(version, revision, branch string) {
grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version(), edition).Set(1)
}
// SetEnvironmentInformation exposes environment values provided by the operators as an `_info` metric.
// If there are no environment metrics labels configured, this metric will not be exposed.
func SetEnvironmentInformation(labels map[string]string) error {
if len(labels) == 0 {
return nil
}
grafanaEnvironmentInfo := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "environment_info",
Help: "A metric with a constant '1' value labeled by environment information about the running instance.",
Namespace: ExporterName,
ConstLabels: labels,
})
prometheus.MustRegister(grafanaEnvironmentInfo)
grafanaEnvironmentInfo.Set(1)
return nil
}
func SetPluginBuildInformation(pluginID, pluginType, version string) {
grafanaPluginBuildInfoDesc.WithLabelValues(pluginID, pluginType, version).Set(1)
}

View File

@@ -7,12 +7,14 @@ import (
"time"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
"gopkg.in/macaron.v1"
)
var (
httpRequestsInFlight prometheus.Gauge
httpRequestsInFlight prometheus.Gauge
httpRequestDurationHistogram *prometheus.HistogramVec
)
func init() {
@@ -23,33 +25,52 @@ func init() {
},
)
prometheus.MustRegister(httpRequestsInFlight)
httpRequestDurationHistogram = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: "grafana",
Name: "http_request_duration_seconds",
Help: "Histogram of latencies for HTTP requests.",
Buckets: []float64{.1, .2, .4, 1, 3, 8, 20, 60, 120},
},
[]string{"handler"},
)
prometheus.MustRegister(httpRequestsInFlight, httpRequestDurationHistogram)
}
// RequestMetrics is a middleware handler that instruments the request
func RequestMetrics(handler string) macaron.Handler {
return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) {
rw := res.(macaron.ResponseWriter)
now := time.Now()
httpRequestsInFlight.Inc()
defer httpRequestsInFlight.Dec()
c.Next()
func RequestMetrics(cfg *setting.Cfg) func(handler string) macaron.Handler {
return func(handler string) macaron.Handler {
return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) {
rw := res.(macaron.ResponseWriter)
now := time.Now()
httpRequestsInFlight.Inc()
defer httpRequestsInFlight.Dec()
c.Next()
status := rw.Status()
status := rw.Status()
code := sanitizeCode(status)
method := sanitizeMethod(req.Method)
metrics.MHttpRequestTotal.WithLabelValues(handler, code, method).Inc()
duration := time.Since(now).Nanoseconds() / int64(time.Millisecond)
metrics.MHttpRequestSummary.WithLabelValues(handler, code, method).Observe(float64(duration))
code := sanitizeCode(status)
method := sanitizeMethod(req.Method)
metrics.MHttpRequestTotal.WithLabelValues(handler, code, method).Inc()
switch {
case strings.HasPrefix(req.RequestURI, "/api/datasources/proxy"):
countProxyRequests(status)
case strings.HasPrefix(req.RequestURI, "/api/"):
countApiRequests(status)
default:
countPageRequests(status)
duration := time.Since(now).Nanoseconds() / int64(time.Millisecond)
// enable histogram and disable summaries for http requests.
if cfg.IsHTTPRequestHistogramEnabled() {
httpRequestDurationHistogram.WithLabelValues(handler).Observe(float64(duration))
} else {
metrics.MHttpRequestSummary.WithLabelValues(handler, code, method).Observe(float64(duration))
}
switch {
case strings.HasPrefix(req.RequestURI, "/api/datasources/proxy"):
countProxyRequests(status)
case strings.HasPrefix(req.RequestURI, "/api/"):
countApiRequests(status)
default:
countPageRequests(status)
}
}
}
}

View File

@@ -139,7 +139,7 @@ type UpdatePluginDashboardError struct {
}
func (d UpdatePluginDashboardError) Error() string {
return "Dashboard belong to plugin"
return "Dashboard belongs to plugin"
}
const (

View File

@@ -2,7 +2,7 @@ package models
import "github.com/centrifugal/centrifuge"
// ChannelPublisher writes data into a channel
// ChannelPublisher writes data into a channel. Note that pemissions are not checked.
type ChannelPublisher func(channel string, data []byte) error
// ChannelHandler defines the core channel behavior
@@ -17,9 +17,10 @@ type ChannelHandler interface {
OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error)
}
// ChannelHandlerProvider -- this should be implemented by any core feature
type ChannelHandlerProvider interface {
// This is called fast and often -- it must be synchrnozed
// ChannelHandlerFactory should be implemented by all core features.
type ChannelHandlerFactory interface {
// GetHandlerForPath gets a ChannelHandler for a path.
// This is called fast and often -- it must be synchronized
GetHandlerForPath(path string) (ChannelHandler, error)
}

50
pkg/models/measurement.go Normal file
View File

@@ -0,0 +1,50 @@
package models
import "github.com/grafana/grafana-plugin-sdk-go/data"
// NOTE:
// this likely should go in the Plugin SDK since it will be useful from plugins
// Measurement is a single measurement value.
type Measurement struct {
// Name of the measurement.
Name string `json:"name,omitempty"`
// Time is the measurement time. Units are usually ms, but depends on the channel
Time int64 `json:"time,omitempty"`
// Values is the measurement's values. The value type is typically number or string.
Values map[string]interface{} `json:"values,omitempty"`
// Config is an optional list of field configs.
Config map[string]data.FieldConfig `json:"config,omitempty"`
// Labels are applied to all values.
Labels map[string]string `json:"labels,omitempty"`
}
// MeasurementAction defines what should happen when you send a list of measurements.
type MeasurementAction string
const (
// MeasurementActionAppend means new values should be added to a client buffer. This is the default action
MeasurementActionAppend MeasurementAction = "append"
// MeasurementActionReplace means new values should replace any existing values.
MeasurementActionReplace MeasurementAction = "replace"
// MeasurementActionClear means all existing values should be remoed before adding.
MeasurementActionClear MeasurementAction = "clear"
)
// MeasurementBatch is a collection of measurements all sent at once.
type MeasurementBatch struct {
// Action is the action in question, the default is append.
Action MeasurementAction `json:"action,omitempty"`
// Measurements is the array of measurements.
Measurements []Measurement `json:"measurements,omitempty"`
// Capacity is the suggested size of the client buffer
Capacity int64 `json:"capacity,omitempty"`
}

View File

@@ -2,12 +2,15 @@ package plugins
import (
"encoding/json"
"path/filepath"
"strings"
"github.com/gosimple/slug"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/grpcplugin"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
)
type AppPluginCss struct {
@@ -21,6 +24,8 @@ type AppPlugin struct {
FoundChildPlugins []*PluginInclude `json:"-"`
Pinned bool `json:"-"`
Executable string `json:"executable,omitempty"`
}
// AppPluginRoute describes a plugin route that is defined in
@@ -67,6 +72,15 @@ func (app *AppPlugin) Load(decoder *json.Decoder, base *PluginBase, backendPlugi
return err
}
if app.Backend {
cmd := ComposePluginStartCommand(app.Executable)
fullpath := filepath.Join(app.PluginDir, cmd)
factory := grpcplugin.NewBackendPlugin(app.Id, fullpath, grpcplugin.PluginStartFuncs{})
if err := backendPluginManager.Register(app.Id, factory); err != nil {
return errutil.Wrapf(err, "failed to register backend plugin")
}
}
Apps[app.Id] = app
return nil
}

View File

@@ -35,11 +35,25 @@ const (
)
type PluginNotFoundError struct {
PluginId string
PluginID string
}
func (e PluginNotFoundError) Error() string {
return fmt.Sprintf("Plugin with id %s not found", e.PluginId)
return fmt.Sprintf("plugin with ID %q not found", e.PluginID)
}
type duplicatePluginError struct {
Plugin *PluginBase
ExistingPlugin *PluginBase
}
func (e duplicatePluginError) Error() string {
return fmt.Sprintf("plugin with ID %q already loaded from %q", e.Plugin.Id, e.ExistingPlugin.PluginDir)
}
func (e duplicatePluginError) Is(err error) bool {
_, ok := err.(duplicatePluginError)
return ok
}
// PluginLoader can load a plugin.
@@ -77,8 +91,8 @@ type PluginBase struct {
}
func (pb *PluginBase) registerPlugin(base *PluginBase) error {
if _, exists := Plugins[pb.Id]; exists {
return fmt.Errorf("Plugin with ID %q already exists", pb.Id)
if p, exists := Plugins[pb.Id]; exists {
return duplicatePluginError{Plugin: pb, ExistingPlugin: p}
}
if !strings.HasPrefix(base.PluginDir, setting.StaticRootPath) {

View File

@@ -271,6 +271,12 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
// Load the full plugin, and add it to manager
if err := loader.Load(jsonParser, plugin, scanner.backendPluginManager); err != nil {
if errors.Is(err, duplicatePluginError{}) {
pm.log.Warn("Plugin is duplicate", "error", err)
scanner.errors = append(scanner.errors, err)
continue
}
return err
}

View File

@@ -2,6 +2,7 @@ package plugins
import (
"context"
"errors"
"fmt"
"path/filepath"
"testing"
@@ -163,6 +164,23 @@ func TestPluginManager_Init(t *testing.T) {
require.Empty(t, pm.scanningErrors)
assert.Equal(t, []string{"gel"}, fm.registeredPlugins)
})
t.Run("With nested plugin duplicating parent", func(t *testing.T) {
origPluginsPath := setting.PluginsPath
t.Cleanup(func() {
setting.PluginsPath = origPluginsPath
})
setting.PluginsPath = "testdata/duplicate-plugins"
pm := &PluginManager{
Cfg: &setting.Cfg{},
}
err := pm.Init()
require.NoError(t, err)
assert.Len(t, pm.scanningErrors, 1)
assert.True(t, errors.Is(pm.scanningErrors[0], duplicatePluginError{}))
})
}
func TestPluginManager_IsBackendOnlyPlugin(t *testing.T) {

View File

@@ -0,0 +1,14 @@
{
"type": "datasource",
"name": "Child",
"id": "test-app",
"info": {
"description": "Child plugin",
"author": {
"name": "Grafana Labs",
"url": "http://grafana.com"
},
"version": "1.0.0",
"updated": "2020-10-20"
}
}

View File

@@ -0,0 +1,14 @@
{
"type": "datasource",
"name": "Parent",
"id": "test-app",
"info": {
"description": "Parent plugin",
"author": {
"name": "Grafana Labs",
"url": "http://grafana.com"
},
"version": "1.0.0",
"updated": "2020-10-20"
}
}

View File

@@ -22,6 +22,7 @@ import (
_ "github.com/grafana/grafana/pkg/extensions"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
_ "github.com/grafana/grafana/pkg/infra/metrics"
_ "github.com/grafana/grafana/pkg/infra/remotecache"
_ "github.com/grafana/grafana/pkg/infra/serverlock"
@@ -117,6 +118,9 @@ func (s *Server) init(cfg *Config) error {
s.loadConfiguration()
s.writePIDFile()
if err := metrics.SetEnvironmentInformation(s.cfg.MetricsGrafanaEnvironmentInfo); err != nil {
return err
}
login.Init()
social.NewOAuthService()
@@ -269,7 +273,7 @@ func (s *Server) buildServiceGraph(services []*registry.Descriptor) error {
objs := []interface{}{
bus.GetBus(),
s.cfg,
routing.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing),
routing.NewRouteRegister(middleware.RequestMetrics(s.cfg), middleware.RequestTracing),
localcache.New(5*time.Minute, 10*time.Minute),
s,
}

View File

@@ -1,27 +1,48 @@
package live
import (
"fmt"
"strings"
)
// ChannelIdentifier is the channel id split by parts
type ChannelIdentifier struct {
Scope string // grafana, ds, or plugin
Namespace string // feature, id, or name
Path string // path within the channel handler
// ChannelAddress is the channel ID split by parts.
type ChannelAddress struct {
// Scope is "grafana", "ds", or "plugin".
Scope string `json:"scope,omitempty"`
// Namespace meaning depends on the scope.
// * when grafana, namespace is a "feature"
// * when ds, namespace is the datasource id
// * when plugin, namespace is the plugin name
Namespace string `json:"namespace,omitempty"`
// Within each namespace, the handler can process the path as needed.
Path string `json:"path,omitempty"`
}
// ParseChannelIdentifier parses the parts from a channel id:
// ${scope} / ${namespace} / ${path}
func ParseChannelIdentifier(id string) (ChannelIdentifier, error) {
// ParseChannelAddress parses the parts from a channel ID:
// ${scope} / ${namespace} / ${path}.
func ParseChannelAddress(id string) ChannelAddress {
addr := ChannelAddress{}
parts := strings.SplitN(id, "/", 3)
if len(parts) == 3 {
return ChannelIdentifier{
Scope: parts[0],
Namespace: parts[1],
Path: parts[2],
}, nil
length := len(parts)
if length > 0 {
addr.Scope = parts[0]
}
return ChannelIdentifier{}, fmt.Errorf("Invalid channel id: %s", id)
if length > 1 {
addr.Namespace = parts[1]
}
if length > 2 {
addr.Path = parts[2]
}
return addr
}
// IsValid checks if all parts of the address are valid.
func (ca *ChannelAddress) IsValid() bool {
return ca.Scope != "" && ca.Namespace != "" && ca.Path != ""
}
// ToChannelID converts this to a single string.
func (ca *ChannelAddress) ToChannelID() string {
return ca.Scope + "/" + ca.Namespace + "/" + ca.Path
}

View File

@@ -4,27 +4,25 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
)
func TestParseChannelIdentifier(t *testing.T) {
ident, err := ParseChannelIdentifier("aaa/bbb/ccc/ddd")
if err != nil {
t.FailNow()
}
func TestParseChannelAddress_Valid(t *testing.T) {
addr := ParseChannelAddress("aaa/bbb/ccc/ddd")
require.True(t, addr.IsValid())
ex := ChannelIdentifier{
ex := ChannelAddress{
Scope: "aaa",
Namespace: "bbb",
Path: "ccc/ddd",
}
if diff := cmp.Diff(ident, ex); diff != "" {
if diff := cmp.Diff(addr, ex); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
// Check an invalid identifier
_, err = ParseChannelIdentifier("aaa/bbb")
if err == nil {
t.FailNow()
}
}
func TestParseChannelAddress_Invalid(t *testing.T) {
addr := ParseChannelAddress("aaa/bbb")
require.False(t, addr.IsValid())
}

View File

@@ -6,27 +6,28 @@ import (
)
// BroadcastRunner will simply broadcast all events to `grafana/broadcast/*` channels
// This makes no assumptions about the shape of the data and will broadcast it to anyone listening
type BroadcastRunner struct{}
// This assumes that data is a JSON object
type BroadcastRunner struct {
}
// GetHandlerForPath called on init
func (g *BroadcastRunner) GetHandlerForPath(path string) (models.ChannelHandler, error) {
return g, nil // for now all channels share config
func (b *BroadcastRunner) GetHandlerForPath(path string) (models.ChannelHandler, error) {
return b, nil // for now all channels share config
}
// GetChannelOptions called fast and often
func (g *BroadcastRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
func (b *BroadcastRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
return centrifuge.ChannelOptions{}
}
// OnSubscribe for now allows anyone to subscribe to any dashboard
func (g *BroadcastRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
func (b *BroadcastRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
// anyone can subscribe
return nil
}
// OnPublish called when an event is received from the websocket
func (g *BroadcastRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
func (b *BroadcastRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
// expect the data to be the right shape?
return e.Data, nil
}

View File

@@ -17,14 +17,7 @@ type dashboardEvent struct {
// DashboardHandler manages all the `grafana/dashboard/*` channels
type DashboardHandler struct {
publisher models.ChannelPublisher
}
// CreateDashboardHandler Initialize a dashboard handler
func CreateDashboardHandler(p models.ChannelPublisher) DashboardHandler {
return DashboardHandler{
publisher: p,
}
Publisher models.ChannelPublisher
}
// GetHandlerForPath called on init
@@ -58,7 +51,7 @@ func (g *DashboardHandler) publish(event dashboardEvent) error {
if err != nil {
return err
}
return g.publisher("grafana/dashboard/"+event.UID, msg)
return g.Publisher("grafana/dashboard/"+event.UID, msg)
}
// DashboardSaved will broadcast to all connected dashboards

View File

@@ -0,0 +1,41 @@
package features
import (
"github.com/centrifugal/centrifuge"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
)
var (
logger = log.New("live.features") // scoped to all features?
)
// MeasurementsRunner will simply broadcast all events to `grafana/broadcast/*` channels.
// This makes no assumptions about the shape of the data and will broadcast it to anyone listening
type MeasurementsRunner struct {
}
// GetHandlerForPath gets the handler for a path.
// It's called on init.
func (m *MeasurementsRunner) GetHandlerForPath(path string) (models.ChannelHandler, error) {
return m, nil // for now all channels share config
}
// GetChannelOptions gets channel options.
// It gets called fast and often.
func (m *MeasurementsRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
return centrifuge.ChannelOptions{}
}
// OnSubscribe for now allows anyone to subscribe to any dashboard.
func (m *MeasurementsRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
// anyone can subscribe
return nil
}
// OnPublish is called when an event is received from the websocket.
func (m *MeasurementsRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
// currently generic... but should be stricter
// logger.Debug("Measurements runner got event on channel", "channel", e.Channel)
return e.Data, nil
}

View File

@@ -7,48 +7,43 @@ import (
"time"
"github.com/centrifugal/centrifuge"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
"github.com/grafana/grafana/pkg/models"
)
// TestdataRunner manages all the `grafana/dashboard/*` channels
type testdataRunner struct {
// testDataRunner manages all the `grafana/dashboard/*` channels.
type testDataRunner struct {
publisher models.ChannelPublisher
running bool
speedMillis int
dropPercent float64
channel string
name string
}
// TestdataSupplier manages all the `grafana/testdata/*` channels
type TestdataSupplier struct {
publisher models.ChannelPublisher
// TestDataSupplier manages all the `grafana/testdata/*` channels.
type TestDataSupplier struct {
Publisher models.ChannelPublisher
}
// CreateTestdataSupplier Initialize a dashboard handler
func CreateTestdataSupplier(p models.ChannelPublisher) TestdataSupplier {
return TestdataSupplier{
publisher: p,
}
}
// GetHandlerForPath called on init
func (g *TestdataSupplier) GetHandlerForPath(path string) (models.ChannelHandler, error) {
// GetHandlerForPath gets the channel handler for a path.
// Called on init.
func (g *TestDataSupplier) GetHandlerForPath(path string) (models.ChannelHandler, error) {
channel := "grafana/testdata/" + path
if path == "random-2s-stream" {
return &testdataRunner{
publisher: g.publisher,
return &testDataRunner{
publisher: g.Publisher,
running: false,
speedMillis: 2000,
dropPercent: 0,
channel: channel,
name: path,
}, nil
}
if path == "random-flakey-stream" {
return &testdataRunner{
publisher: g.publisher,
return &testDataRunner{
publisher: g.Publisher,
running: false,
speedMillis: 400,
dropPercent: .6,
@@ -59,13 +54,14 @@ func (g *TestdataSupplier) GetHandlerForPath(path string) (models.ChannelHandler
return nil, fmt.Errorf("unknown channel")
}
// GetChannelOptions called fast and often
func (g *testdataRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
// GetChannelOptions gets channel options.
// Called fast and often.
func (g *testDataRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
return centrifuge.ChannelOptions{}
}
// OnSubscribe for now allows anyone to subscribe to any dashboard
func (g *testdataRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
// OnSubscribe for now allows anyone to subscribe to any dashboard.
func (g *testDataRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
if !g.running {
g.running = true
@@ -77,26 +73,26 @@ func (g *testdataRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.Subscrib
return nil
}
// OnPublish called when an event is received from the websocket
func (g *testdataRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
// OnPublish is called when an event is received from the websocket.
func (g *testDataRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
return nil, fmt.Errorf("can not publish to testdata")
}
type randomWalkMessage struct {
Time int64
Value float64
Min float64
Max float64
}
// RunRandomCSV just for an example
func (g *testdataRunner) runRandomCSV() {
// runRandomCSV is just for an example.
func (g *testDataRunner) runRandomCSV() {
spread := 50.0
walker := rand.Float64() * 100
ticker := time.NewTicker(time.Duration(g.speedMillis) * time.Millisecond)
line := randomWalkMessage{}
measurement := models.Measurement{
Name: g.name,
Time: 0,
Values: make(map[string]interface{}, 5),
}
msg := models.MeasurementBatch{
Measurements: []models.Measurement{measurement}, // always a single measurement
}
for t := range ticker.C {
if rand.Float64() <= g.dropPercent {
@@ -105,12 +101,12 @@ func (g *testdataRunner) runRandomCSV() {
delta := rand.Float64() - 0.5
walker += delta
line.Time = t.UnixNano() / int64(time.Millisecond)
line.Value = walker
line.Min = walker - ((rand.Float64() * spread) + 0.01)
line.Max = walker + ((rand.Float64() * spread) + 0.01)
measurement.Time = t.UnixNano() / int64(time.Millisecond)
measurement.Values["value"] = walker
measurement.Values["min"] = walker - ((rand.Float64() * spread) + 0.01)
measurement.Values["max"] = walker + ((rand.Float64() * spread) + 0.01)
bytes, err := json.Marshal(&line)
bytes, err := json.Marshal(&msg)
if err != nil {
logger.Warn("unable to marshal line", "error", err)
continue
@@ -118,7 +114,7 @@ func (g *testdataRunner) runRandomCSV() {
err = g.publisher(g.channel, bytes)
if err != nil {
logger.Warn("write", "channel", g.channel, "line", line)
logger.Warn("write", "channel", g.channel, "measurement", measurement)
}
}
}

View File

@@ -20,7 +20,7 @@ var (
// CoreGrafanaScope list of core features
type CoreGrafanaScope struct {
Features map[string]models.ChannelHandlerProvider
Features map[string]models.ChannelHandlerFactory
// The generic service to advertise dashboard changes
Dashboards models.DashboardActivityChannel
@@ -47,7 +47,7 @@ func InitializeBroker() (*GrafanaLive, error) {
channels: make(map[string]models.ChannelHandler),
channelsMu: sync.RWMutex{},
GrafanaScope: CoreGrafanaScope{
Features: make(map[string]models.ChannelHandlerProvider),
Features: make(map[string]models.ChannelHandlerFactory),
},
}
@@ -83,13 +83,17 @@ func InitializeBroker() (*GrafanaLive, error) {
glive.node = node
// Initialize the main features
dash := features.CreateDashboardHandler(glive.Publish)
tds := features.CreateTestdataSupplier(glive.Publish)
dash := &features.DashboardHandler{
Publisher: glive.Publish,
}
glive.GrafanaScope.Dashboards = &dash
glive.GrafanaScope.Features["dashboard"] = &dash
glive.GrafanaScope.Features["testdata"] = &tds
glive.GrafanaScope.Dashboards = dash
glive.GrafanaScope.Features["dashboard"] = dash
glive.GrafanaScope.Features["testdata"] = &features.TestDataSupplier{
Publisher: glive.Publish,
}
glive.GrafanaScope.Features["broadcast"] = &features.BroadcastRunner{}
glive.GrafanaScope.Features["measurements"] = &features.MeasurementsRunner{}
// Set ConnectHandler called when client successfully connected to Node. Your code
// inside handler must be synchronized since it will be called concurrently from
@@ -232,11 +236,11 @@ func (g *GrafanaLive) GetChannelHandler(channel string) (models.ChannelHandler,
}
// Parse the identifier ${scope}/${namespace}/${path}
id, err := ParseChannelIdentifier(channel)
if err != nil {
return nil, err
addr := ParseChannelAddress(channel)
if !addr.IsValid() {
return nil, fmt.Errorf("invalid channel: %q", channel)
}
logger.Info("initChannel", "channel", channel, "id", id)
logger.Info("initChannel", "channel", channel, "address", addr)
g.channelsMu.Lock()
defer g.channelsMu.Unlock()
@@ -245,39 +249,48 @@ func (g *GrafanaLive) GetChannelHandler(channel string) (models.ChannelHandler,
return c, nil
}
c, err = g.initChannel(id)
getter, err := g.GetChannelHandlerFactory(addr.Scope, addr.Namespace)
if err != nil {
return nil, err
}
// First access will initialize
c, err = getter.GetHandlerForPath(addr.Path)
if err != nil {
return nil, err
}
g.channels[channel] = c
return c, nil
}
func (g *GrafanaLive) initChannel(id ChannelIdentifier) (models.ChannelHandler, error) {
if id.Scope == "grafana" {
p, ok := g.GrafanaScope.Features[id.Namespace]
// GetChannelHandlerFactory gets a ChannelHandlerFactory for a namespace.
// It gives threadsafe access to the channel.
func (g *GrafanaLive) GetChannelHandlerFactory(scope string, name string) (models.ChannelHandlerFactory, error) {
if scope == "grafana" {
p, ok := g.GrafanaScope.Features[name]
if ok {
return p.GetHandlerForPath(id.Path)
return p, nil
}
return nil, fmt.Errorf("Unknown feature: %s", id.Namespace)
return nil, fmt.Errorf("unknown feature: %q", name)
}
if id.Scope == "ds" {
return nil, fmt.Errorf("todo... look up datasource: %s", id.Namespace)
if scope == "ds" {
return nil, fmt.Errorf("todo... look up datasource: %q", name)
}
if id.Scope == "plugin" {
p, ok := plugins.Plugins[id.Namespace]
if scope == "plugin" {
p, ok := plugins.Plugins[name]
if ok {
h := &PluginHandler{
Plugin: p,
}
return h.GetHandlerForPath(id.Path)
return h, nil
}
return nil, fmt.Errorf("unknown plugin: %s", id.Namespace)
return nil, fmt.Errorf("unknown plugin: %q", name)
}
return nil, fmt.Errorf("invalid scope: %s", id.Scope)
return nil, fmt.Errorf("invalid scope: %q", scope)
}
// Publish sends the data to the channel without checking permissions etc

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