Compare commits

...

92 Commits

Author SHA1 Message Date
Marcus Efraimsson
ad9d408ac2 E2E: Update e2e docker image in 7.2.x branch (#28424) 2020-10-21 09:26:59 +02:00
Torkel Ödegaard
9f7f13e02a E2E: Update e2e docker image in 7.2.x branch (#28407) 2020-10-20 17:34:53 +02:00
Marcus Efraimsson
d9540bc368 Release v7.2.2 2020-10-20 16:15:17 +02:00
Marcus Efraimsson
b1543b5676 Go dependency update 2020-10-20 16:15:17 +02:00
Arve Knudsen
0b4aa8b909 CloudWatch: Fix custom metrics (#28391)
* 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)
2020-10-20 16:15:17 +02:00
Carl Bergquist
4d21ade9cd Instrumentation: Add histogram for request duration (#28364)
Signed-off-by: bergquist <carl.bergquist@gmail.com>
(cherry picked from commit edbaa9d681)
2020-10-20 16:15:17 +02:00
Carl Bergquist
36c8a3bd93 remove status label from histogram (#28387)
Signed-off-by: bergquist <carl.bergquist@gmail.com>
(cherry picked from commit b036112444)
2020-10-20 16:15:17 +02:00
Carl Bergquist
589be2872b 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-20 16:15:17 +02:00
Carl Bergquist
f60edaf029 Instrumentation: Adds environment_info metric (#28355)
Signed-off-by: bergquist <carl.bergquist@gmail.com>

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
(cherry picked from commit 89ebb97fca)
2020-10-20 16:15:17 +02:00
Sofia Papagiannaki
72a6c64532 Release v7.2.1 2020-10-08 12:00:32 +03:00
Marcus Andersson
cc745b75ac Bugfix: improved the way of checking if browser supports Intl.DateTimeFormat (#28086)
(cherry picked from commit ab33e46789)
2020-10-08 12:00:32 +03:00
Giordano Ricci
6210531200 Elasticsearch: Fix ad-hoc filter support for Raw Data query and new table panel (#28064)
(cherry picked from commit 09574547b8)
2020-10-08 12:00:32 +03:00
David C
3147dadfdb Docs: Added $__rate_interval to Prometheus help (#28030)
* Docs: Added $__rate_interval to Prometheus help

The variable '$__rate_interval' was added in Grafana 7.2.0. This commit
adds the missing help information to Grafana's Prometheus PromQueryEditor.

Signed-off-by: David Calvert <davidcalvertfr@gmail.com>

* Docs: Fixed line length in PromQueryEditor.tsx

This commit will fix line length of my previous commit to make CI green
again.

Signed-off-by: David Calvert <davidcalvertfr@gmail.com>

* Docs: Fixed ci error

Fixed CI error due to my previous commits

Signed-off-by: David Calvert <davidcalvertfr@gmail.com>
(cherry picked from commit 1f389d72b2)
2020-10-08 12:00:32 +03:00
Ivana Huckova
a3398cafd6 Loki: Run instant query only in Explore (#27974)
* Run instant query only in Explore

* Replace forEach with for loop

(cherry picked from commit 0ffd9a9a3c)
2020-10-08 12:00:32 +03:00
Zoltán Bedi
b699682ee4 Stackdriver: fix project name change regression (#27909)
* Update stackdriver query editor not to overwrite project name

* Fix legacy select form no options didn't show

* Update config link to docs

(cherry picked from commit b1186d693a)
2020-10-08 12:00:32 +03:00
Alex Khomenko
6cd10128e6 UserPicker: Use clearable AsyncSelect for the UserPicker (#27994)
(cherry picked from commit 6173aa70b7)
2020-10-08 12:00:32 +03:00
Carl Bergquist
1cd4391b9b healthchecks should work regardless domain (#27981)
(cherry picked from commit a28a2fba51)
2020-10-08 12:00:32 +03:00
Ivana Huckova
6ff9d3ffad Revert "Update refId for instant queries (#27954)" (#27960)
This reverts commit 9546da3189.

(cherry picked from commit 43c389d119)
2020-10-08 12:00:32 +03:00
Ivana Huckova
ef98b310f1 Update refId for instant queries (#27954)
(cherry picked from commit 9546da3189)
2020-10-08 12:00:32 +03:00
Marcus Efraimsson
6c4d5466e6 Plugins: Fix loading of backend plugins (#27951)
Ref #27921 

(cherry picked from commit 747513d444)
2020-10-08 12:00:32 +03:00
Hugo Häggmark
616588adee DashboardLinks: values in links are updated when variables change (#27926)
* DashboardLinks: values in links are updated when variable changes

* Tests: adds e2e test that verifies links

(cherry picked from commit fa2781391a)
2020-10-08 12:00:32 +03:00
Carl Bergquist
76887a76b9 Instrumentation: Removes invalid chars from label names (#27921)
(cherry picked from commit 7b891d10ee)
2020-10-08 12:00:32 +03:00
Torkel Ödegaard
2b51f520a4 Graph: Fixed histogram bucket calculations to avoid missing buckets (#27883)
* Graph: Fixed histogram bucket calculations to avoid missing buckets

* Removed testdata

* Updated tests

(cherry picked from commit d105db3e5b)
2020-10-08 12:00:32 +03:00
Berbe
0582b21c42 Fix: bps & Bps default scale remains decimal (backwards-compatibility) (#27838)
(cherry picked from commit 7c0bd29fcc)
2020-10-08 12:00:32 +03:00
Will Browne
4e1ff750e2 remove org deprecation logs (#27788)
(cherry picked from commit c764e3c37b)
2020-10-08 12:00:32 +03:00
Hugo Häggmark
eb9057fc09 Variables: Prevents unnecessary calls to update options (#27790)
* Variables: Prevents unnecessary calls to update options

* Tests: adds tests for QueryVariableEditor

(cherry picked from commit 204641a202)
2020-10-08 12:00:32 +03:00
Torkel Ödegaard
9930d2bfce BarGauge: Fixed scrollbar showing for bar gague in Firefox (#27784)
(cherry picked from commit 193565ca26)
2020-10-08 12:00:32 +03:00
William Assis
db754555e0 Toolkit: Add --coverage flag to plugin build command (#27743)
* Add --coverage option to build script

* Update README

(cherry picked from commit 8a22111a8e)
2020-10-08 12:00:32 +03:00
Giordano Ricci
01841d6ded Elasticsearch: Add query's refId to each series returned by a query (#27614)
(cherry picked from commit d4f785d20a)
2020-10-08 12:00:32 +03:00
Tiago Mota Santos
f96e49ae0d Dashboard: Honour root_url for Explore link (#27654)
* Dashboard: Add subUrl to explore url

Honour subUrl when opening explore in new window

* Dashboard: Add tests to onNavigateToExplore

* Dashboard: Remove condition from tests

(cherry picked from commit 17a1e78754)
2020-10-08 12:00:32 +03:00
Torkel Ödegaard
498bfb0ed9 ValueMappings: Fix issue with value mappings in override applying to all columns (#27718)
* ValueMappings: Fix issue with value mappings override creating default value mapping for all fields

* Fixed unit tests

(cherry picked from commit f285569316)
2020-10-08 12:00:32 +03:00
Carl Bergquist
1e1535552f add /healthz endpoint (#27536)
kuberentes (and Im sure other orchastrators does as well) support two
kind of checks. readiness checks and liveness checks. Grafanas current
`/api/health` endpoint requires database access which might not
always be required for the instance to be considered active.

(cherry picked from commit 6dc73a6712)
2020-10-08 12:00:32 +03:00
Leonard Gram
efe4941ee3 Release 7.2.0 2020-09-23 13:23:49 +02:00
kennytm
9c1403cd13 grafana-toolkit: avoid path.resolve with globby in moveStaticFiles (#27670)
(cherry picked from commit ed054c205d)
2020-09-23 13:23:49 +02:00
Torkel Ödegaard
e35c7583b9 DateFormatting: Use system date by default in display processor (#27699)
(cherry picked from commit 52a501c205)
2020-09-23 13:23:49 +02:00
Hugo Häggmark
d383ac9ffb PanelEditor: Prevents adding transformations in panels with alerts (#27706)
(cherry picked from commit a58a9e8e6d)
2020-09-23 13:23:49 +02:00
Andrej Ocenas
8797d753d4 Show full traceID and better discern multiple stackTraces (#27710)
(cherry picked from commit 0fe3b78a50)
2020-09-23 13:23:49 +02:00
Ryan McKinley
c0320fbe40 Annotations: check for null or undefined fields before conversion (#27712)
(cherry picked from commit d46f33c34c)
2020-09-23 13:23:49 +02:00
kennytm
f2f4f22eef grafana/ui: Do not bundle jQuery (#27667)
(cherry picked from commit 7a77eb7635)
2020-09-23 13:23:49 +02:00
Hugo Häggmark
a7e713fb31 Select: Adds labels to select styles (#27698)
(cherry picked from commit 5f2deb2497)
2020-09-23 13:23:49 +02:00
Peter Holmberg
b825c824c5 Fix: Show an ellipsis if Query row title is too long (#27648)
* add overflow hidden to titleWrapper

* show ellipsis and css labels for components

* readd drag handle after bad merge

* rewrite userpicker test with rtl

* update test after adding css label to icon component

* fix more tests..

(cherry picked from commit 6a14f830ba)
2020-09-23 13:23:49 +02:00
Torkel Ödegaard
36c3a139e8 DashboardRow: Update to use the new replaceWithText method (#27671)
* DashboardRow: Update to use the new replaceWithText method

* Fixed panel header as well

(cherry picked from commit 1e5309a788)
2020-09-23 13:23:49 +02:00
Dominik Prokop
da2622d870 Fix mismatch in field config editor types (#27657)
(cherry picked from commit e8a6b9db10)
2020-09-23 13:23:49 +02:00
Spencer McMaster
b25b66e2f6 PanelEditor: fix button position when dragging row (#27666)
(cherry picked from commit 1269fa5cf6)
2020-09-23 13:23:49 +02:00
Peter Holmberg
a3fc96196a FieldConfig: Apply Thresholds for string values (#27656)
* apply thresholds for strings

* apply thresholds for all FieldTypes

(cherry picked from commit 232ad5c42e)
2020-09-23 13:23:49 +02:00
Dominik Prokop
a9a053591b Field config: Respect config paths when rendering default value of field config property (#27652)
(cherry picked from commit e5b16952c7)
2020-09-23 13:23:49 +02:00
Domas
2870eab4d7 DataProxy: Ignore empty URL's in plugin routes (#27653)
This adds a check to see if plugin route URL is empty, and in such case
does not modify request schema and host of the request to be proxied.
This behavior is now the same as in the plugin proxy.

(cherry picked from commit 564d7ecea7)
2020-09-23 13:23:49 +02:00
Alvaro Olmedo Rodriguez
bdeb380c56 Alerting: Ensuring notifications displayed correctly in mobile device with Google Chat (#27578)
* Added previewText to Google Chat notifier

(cherry picked from commit 20292bdb0e)
2020-09-23 13:23:49 +02:00
kay delaney
0bac0044a9 Explore/Actions: Stop loadExploreDatasourcesAndSetDatasource from running queries twice (#27577)
(cherry picked from commit ca7263d898)
2020-09-23 13:23:49 +02:00
Torkel Ödegaard
0d8f2fbda8 DataLinks: Fixes issue with data links not interpolating values with correct field config (#27622)
* DataLinks: Fixes issue with data links not having access to other fields field config

* Fixed test

(cherry picked from commit fcfd5cf0bc)
2020-09-23 13:23:49 +02:00
Emil Tullstedt
88905ca158 Chore(crewjam/saml): go get -u (#27598)
(cherry picked from commit 2e4191afca)
2020-09-23 13:23:49 +02:00
Hugo Häggmark
8406a5d319 Release v7.2.0-beta2 2020-09-17 10:44:02 +02:00
Giordano Ricci
8d71561898 Elasticsearch: Add support for date_nanos type (#27538)
(cherry picked from commit 0e34474099)
2020-09-17 10:44:02 +02:00
Hugo Häggmark
8269ed2407 DataLinks: Respects display name and adds field quoting (#27616)
* DataLinks: Adds field quoting and respects DisplayName

* Update public/app/features/panel/panellinks/link_srv.ts

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>

Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com>
(cherry picked from commit e86ff52d44)
2020-09-17 10:44:02 +02:00
Nick Svanidze
4676ee9dcf ApiKeys: Fixes close('X') button layout issue (#27625)
* ApiKeys: Fixes add API key layout

* ApiKeys: snapshot tests updated

(cherry picked from commit 5a06ed431c)
2020-09-17 10:44:02 +02:00
Torkel Ödegaard
35bdf11c0c SharedQuery: Error when switching to -- Dashboard -- data source caused "no data" no matter what source panel was selected (#27627)
* SharedQuery: Error when switching to -- Dashboard -- data source made observable subscription error and subsequent data results not update visualization

* added null check just in case

(cherry picked from commit 31e2b7e7c8)
2020-09-17 10:44:02 +02:00
Torkel Ödegaard
46bb0e3754 ImageRendering: Fix rendering panel using shared query in png, PDF reports and embedded scenarios (#27628)
* ImageRendering: Fixed issue rendering panel using shared query

* Fixed spelling

(cherry picked from commit c450ffd711)
2020-09-17 10:44:02 +02:00
Kyle Brandt
521f31a702 BackendPlugins: Point to request Headers in the wrapper (#27599)
In particular, so plugins can see the FromAlert header

(cherry picked from commit 50c680f3d7)
2020-09-17 10:44:02 +02:00
Torkel Ödegaard
460ccc326f InputControl: Fixed using InputControl in unit tests from plugins (#27615)
(cherry picked from commit 2ed9124736)
2020-09-17 10:44:02 +02:00
Torkel Ödegaard
eca1c505c7 NewsPanel: Fixed XSS issue when rendering rss links (#27612)
(cherry picked from commit b58864792d)
2020-09-17 10:44:02 +02:00
Ryan McKinley
b112b13c2d Graph: show range warning when all data is outside time range (#27603)
(cherry picked from commit 54b677bda4)
2020-09-17 10:44:02 +02:00
Wouter Smeenk
a4689e646a Dashboard: Support configuring default timezone via config file (#27404)
Add a default timezone that the administrator can set in the settings.
This setting is be used as default for the users timezone preference.
Can be used when creating Grafana instances without administrator
intervention, in order to give user the correct default timezone.

Fixes #25654

(cherry picked from commit 39eba5065b)
2020-09-17 10:44:02 +02:00
Torkel Ödegaard
cfc13f7e30 Docs: Field config docs update, Table docs update, Override matcher naming issue (#27558)
* Docs: Field Config Docs Update, Table Docs update, Override matcher naming sync

* removed sentance that feels duplicated

* Update docs/sources/panels/field-configuration-options.md

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>

* Update docs/sources/panels/field-configuration-options.md

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>

* Update docs/sources/panels/field-configuration-options.md

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>

* Update docs/sources/panels/field-configuration-options.md

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>

* removed bad newlines and minor tweaks, still need to figure out naming for matchers in UI and docs

* Updated matcher names and descriptions, and UX

* Updated field override docs

* Fixed plural

* Updated wording

* removed plurals for the selectors heading

* Updated names

* Updated docs

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>
(cherry picked from commit cade6dd010)
2020-09-17 10:44:02 +02:00
Ryan McKinley
7054834af0 Annotation: use DataFrame[] rather than a single DataFrame (#27587)
(cherry picked from commit 1e4846d443)
2020-09-17 10:44:02 +02:00
Dominik Prokop
48bae0232c Field config: Add support for paths in default field config setup (#27570)
* Add support for paths in default field config setup

* Typecheck fix

(cherry picked from commit e04e3e7d46)
2020-09-17 10:44:02 +02:00
Giordano Ricci
3a32c8f329 Elasticsearch: Increase maximum geohash aggregation precision to 12 (#27539)
(cherry picked from commit e350e1fff6)
2020-09-17 10:44:02 +02:00
Giordano Ricci
b9d4653737 Elasticsearch: Allow fields starting with underscore (#27520)
(cherry picked from commit a0beaa3bbe)
2020-09-17 10:44:02 +02:00
jonny
7220fb5ab8 Variables: Limit rendering of options in dropdown to improve search performance (#27525)
* fixed: bigdata dropdown first limit 1000

* fixed: change to use function of  applylimit

* fixed: remove both applyLimit callers

* feat: test for new logic with applimit

* feat: test showOptions  with the applyLimit logic

* fixed: test equal fixed

(cherry picked from commit 0d2fbd2acd)
2020-09-17 10:44:02 +02:00
Hugo Häggmark
f00222ad03 Postgres: Support request cancellation properly (Uses new backendSrv.fetch Observable request API) (#27478)
* Postgres: Replaces dataSourceRequest with fetch

* Postgres: Replaces dataSourceRequest with fetch

* Tests: removes unnecessary import

(cherry picked from commit a587bf4f56)
2020-09-17 10:44:02 +02:00
Ryan McKinley
6033499576 Chore: use visualization name in field config header (#27579)
(cherry picked from commit c180facba5)
2020-09-17 10:44:02 +02:00
Hansuuuuuuuuuu
1a598a8a41 Auth: Replace maximum inactive/lifetime settings of days to duration (#27150)
Allows login_maximum_inactive_lifetime_duration and
login_maximum_lifetime_duration to be configured using
time.Duration-compatible values while retaining backward compatibility.

Fixes #17554

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
(cherry picked from commit 8d971ab2f2)
2020-09-17 10:44:02 +02:00
Sofia Papagiannaki
4ca888c798 Fix instrumentation panic if there is no response (#27567)
(cherry picked from commit f529223455)
2020-09-17 10:44:02 +02:00
Torkel Ödegaard
569e2a2c74 Annotations: Fixes issue with showing error notice for cancelled annotation queries (#27557)
(cherry picked from commit b37e132cec)
2020-09-17 10:44:02 +02:00
Agnès Toulet
27bfb61d2b Org API: enrich add user to org endpoints with user ID in the response (#27551)
(cherry picked from commit eb970a4985)
2020-09-17 10:44:02 +02:00
Torkel Ödegaard
ac7112876b PanelEdit: Drag and drop query order & UI tweaks (#27502)
* PanelEdit: Drag and drop query order & UI tweaks

* Fixed width of title issue

* added correct color and hover

* Updated e2e tests

(cherry picked from commit 8759a91222)
2020-09-17 10:44:02 +02:00
Marcus Andersson
711a051725 Templating: global/system variables should be properly replaced in templated values. (#27394)
* Fixed so we try to use the variables in the redux store to replace values in template variables.

* First draft of working version.

* Including fieldPath when adding :text format.

* cleaned up code by introducing helper function.

* some minor refactoring.

* Added tests and support for multi variables.

* added test and code to handle the All scenario of a multivariable.

* fixed according to feedback.

* added docs.

* added text format to gdev dashboard.

* updated e2e tests.

* make sure we use the same function for formatting och variable lable.

* increased the number to 22.

* changed label for tests to be All.

* existing format should be respected.

(cherry picked from commit 0c8390cea2)
2020-09-17 10:44:02 +02:00
Ryan McKinley
c3afe89ea2 Toolkit: clean node_modules/@grafana/data/node_modules in prepare (#27554)
(cherry picked from commit b3b72b8ae6)
2020-09-17 10:44:02 +02:00
Agnès Toulet
dc4b97966f Alerting API: send 404 not found error and enrich delete with UID endpoint response with alert notification ID (#27550)
* Alerting API: Send back 404 not found error for update and delete endpoints

* Alerting API: send back alert notification id for delete with uid endpoint

(cherry picked from commit 0c4b7d3f5d)
2020-09-17 10:44:02 +02:00
Ryan McKinley
4d9f298098 Annotations: add standard annotations support (and use it for flux queries) (#27375)
(cherry picked from commit 5d11d8faa3)
2020-09-17 10:44:02 +02:00
Alex Khomenko
95a688a469 Grafana-UI: Expand ConfirmModal docs (#27541)
(cherry picked from commit 126683929c)
2020-09-17 10:44:02 +02:00
Marcus Efraimsson
1f6d68b38e BootData: Fix nav tree sort regression (#27533)
#26395 introduced a regression regarding sort order of nav tree
items set in Grafana boot data and used for rendering the sidemenu.
This fixes so that sort happens after RunIndexDataHooks is called
in case the hook make changes to the nav tree.

(cherry picked from commit 1983de962c)
2020-09-17 10:44:02 +02:00
Torkel Ödegaard
d842db21d2 Transform: Fixed issue in labels to fields and update docs (#27501)
(cherry picked from commit 61463aa123)
2020-09-17 10:44:02 +02:00
Sofia Papagiannaki
adb6d93442 Revert "Alerting: New feature toggle for enabling standalone alerts (#25984)" (#27531)
This reverts commit 20b603ee1a.

(cherry picked from commit 924224eefb)
2020-09-17 10:44:02 +02:00
Berbe
80a19f014c Binary-prefixed data rates (#27022)
* Dashboard: Merge Data units categories

Prefixes already allow to distinguish IEC units from SI ones
+ Prefer using binary function over decimal one when equal

* Dashboard: Clarify SI & binary prefixes

* Dashboard: Homogeneise rate units

* Dashboard: Add Binary (IEC) prefix for data rates

(cherry picked from commit 794333de3d)
2020-09-17 10:44:02 +02:00
Maksim Nabokikh
25c7090cda Provisioning: Remove provisioned dashboards without parental reader (#26143)
(cherry picked from commit 6e3e0dead8)
2020-09-17 10:44:02 +02:00
Torkel Ödegaard
89dbb0f074 Notifications: UX tweak to redesign of form and sections to make it left aligned and cleaner (#27479)
* UX: Redesign of form and sections to make it left aligned and cleaner

* reduced padding

* design tweaks

(cherry picked from commit 0132bca93a)
2020-09-17 10:44:02 +02:00
Zoltán Bedi
a2b97958bd Prometheus: Fix min step variable interpolation (#27505)
* Add missing dependency to lockfile

* Prometheus: Fix min step variable interpolation

(cherry picked from commit a7ac3f1419)
2020-09-17 10:44:02 +02:00
Marcus Efraimsson
619d985214 Azure/Insights: Fix handling of none dimension values (#27513)
Properly handle legacy dimension values in the backend.

Fixes #27512

(cherry picked from commit e85b266f2e)
2020-09-17 10:44:02 +02:00
Marcus Efraimsson
ee59974edb Alerting: Fix integration key so it's stored encrypted for Pagerduty notifier (#27484)
Fixes an issue introduced by migration in #25980 which
removed storing the Pagerduty integrating key encrypted.

(cherry picked from commit 91a8937e6c)
2020-09-17 10:44:02 +02:00
Oana Mangiurea
109754eeb4 Update Input.mdx (#26226)
* Update Input.mdx

clarify form validation text

* Update packages/grafana-ui/src/components/Input/Input.mdx

Align with textArea

Co-authored-by: Peter Holmberg <peterholmberg@users.noreply.github.com>

Co-authored-by: Peter Holmberg <peterholmberg@users.noreply.github.com>
Co-authored-by: Clarity-89 <homes89@ukr.net>
(cherry picked from commit b01a64e146)
2020-09-17 10:44:02 +02:00
Alex Khomenko
2c6020a57a Grafana-UI: Add docs for ConfirmButton (#27477)
* Grafana-UI: Add docs for ConfirmButton

* Grafana-UI: Add comment

* Update packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.mdx

Co-authored-by: Peter Holmberg <peterholmberg@users.noreply.github.com>

Co-authored-by: Peter Holmberg <peterholmberg@users.noreply.github.com>
(cherry picked from commit 1a5c049883)
2020-09-17 10:44:02 +02:00
Marcus Andersson
dfa808ea25 release 7.0.2-beta1 2020-09-09 14:58:44 +02:00
231 changed files with 4888 additions and 1852 deletions

View File

@@ -30,7 +30,7 @@ executors:
- image: cimg/go:1.14
e2e:
docker:
- image: srclosson/grafana-plugin-ci-e2e:latest
- image: grafana/ci-e2e:12.19.0-1
grafana-build:
docker:
- image: grafana/build-container:1.2.26

View File

@@ -279,11 +279,11 @@ editors_can_admin = false
# Login cookie name
login_cookie_name = grafana_session
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
login_maximum_inactive_lifetime_days = 7
# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation (token_rotation_interval_minutes).
login_maximum_inactive_lifetime_duration =
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
login_maximum_lifetime_days = 30
# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month).
login_maximum_lifetime_duration =
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
token_rotation_interval_minutes = 10
@@ -646,6 +646,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)
@@ -823,4 +829,5 @@ interval_year = YYYY
# Experimental feature
use_browser_locale = false
# Default timezone for user preferences. Options are 'browser' for the browser local timezone or a timezone name from IANA Time Zone database, e.g. 'UTC' or 'Europe/Amsterdam' etc.
default_timezone = browser

View File

@@ -278,11 +278,11 @@
# Login cookie name
;login_cookie_name = grafana_session
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days,
;login_maximum_inactive_lifetime_days = 7
# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation
;login_maximum_inactive_lifetime_duration =
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
;login_maximum_lifetime_days = 30
# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month).
;login_maximum_lifetime_duration =
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
;token_rotation_interval_minutes = 10
@@ -640,6 +640,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)
@@ -811,3 +817,6 @@
# Experimental feature
;use_browser_locale = false
# Default timezone for user preferences. Options are 'browser' for the browser local timezone or a timezone name from IANA Time Zone database, e.g. 'UTC' or 'Europe/Amsterdam' etc.
;default_timezone = browser

View File

@@ -34,7 +34,7 @@
},
"id": 11,
"options": {
"content": "## Global variables\n\n* `__dashboard` = `${__dashboard}`\n* `__dashboard.name` = `${__dashboard.name}`\n* `__dashboard.uid` = `${__dashboard.uid}`\n* `__org.name` = `${__org.name}`\n* `__org.id` = `${__org.id}`\n* `__user.id` = `${__user.id}`\n* `__user.login` = `${__user.login}`\n \n## Formats\n\n* `Server:raw` = `${Server:raw}`\n* `Server:regex` = `${Server:regex}`\n* `Server:lucene` = `${Server:lucene}`\n* `Server:glob` = `${Server:glob}`\n* `Server:pipe` = `${Server:pipe}`\n* `Server:distributed` = `${Server:distributed}`\n* `Server:csv` = `${Server:csv}`\n* `Server:html` = `${Server:html}`\n* `Server:json` = `${Server:json}`\n* `Server:percentencode` = `${Server:percentencode}`\n* `Server:singlequote` = `${Server:singlequote}`\n* `Server:doublequote` = `${Server:doublequote}`\n* `Server:sqlstring` = `${Server:sqlstring}`\n* `Server:date` = `${Server:date}`\n\n",
"content": "## Global variables\n\n* `__dashboard` = `${__dashboard}`\n* `__dashboard.name` = `${__dashboard.name}`\n* `__dashboard.uid` = `${__dashboard.uid}`\n* `__org.name` = `${__org.name}`\n* `__org.id` = `${__org.id}`\n* `__user.id` = `${__user.id}`\n* `__user.login` = `${__user.login}`\n \n## Formats\n\n* `Server:raw` = `${Server:raw}`\n* `Server:regex` = `${Server:regex}`\n* `Server:lucene` = `${Server:lucene}`\n* `Server:glob` = `${Server:glob}`\n* `Server:pipe` = `${Server:pipe}`\n* `Server:distributed` = `${Server:distributed}`\n* `Server:csv` = `${Server:csv}`\n* `Server:html` = `${Server:html}`\n* `Server:json` = `${Server:json}`\n* `Server:percentencode` = `${Server:percentencode}`\n* `Server:singlequote` = `${Server:singlequote}`\n* `Server:doublequote` = `${Server:doublequote}`\n* `Server:sqlstring` = `${Server:sqlstring}`\n* `Server:date` = `${Server:date}`\n* `Server:text` = `${Server:text}`\n\n",
"mode": "markdown"
},
"pluginVersion": "7.1.0",

View File

@@ -0,0 +1,132 @@
{
"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": null,
"iteration": 1601526910610,
"links": [
{
"icon": "external link",
"includeVars": true,
"tags": [],
"title": "Grafana",
"tooltip": "",
"type": "link",
"url": "http://www.grafana.com"
},
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"tags": ["templating"],
"title": "Link as DropDown",
"type": "dashboards"
},
{
"icon": "external link",
"includeVars": true,
"tags": ["demo"],
"type": "dashboards"
}
],
"panels": [
{
"description": "",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 0
},
"id": 2,
"options": {
"content": "# ${custom.text}\n ",
"mode": "markdown"
},
"pluginVersion": "7.3.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "${custom.text}",
"type": "text"
}
],
"schemaVersion": 26,
"style": "dark",
"tags": ["gdev", "templating"],
"templating": {
"list": [
{
"allValue": null,
"current": {
"selected": false,
"text": "All",
"value": "$__all"
},
"hide": 0,
"includeAll": true,
"label": null,
"multi": true,
"name": "custom",
"options": [
{
"selected": true,
"text": "All",
"value": "$__all"
},
{
"selected": false,
"text": "p1",
"value": "p1"
},
{
"selected": false,
"text": "p2",
"value": "p2"
},
{
"selected": false,
"text": "p3",
"value": "p3"
}
],
"query": "p1,p2,p3",
"skipUrlSync": false,
"type": "custom"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Templating - Dashboard Links and Variables",
"uid": "yBCC3aKGk",
"version": 7
}

View File

@@ -1041,6 +1041,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.
@@ -1391,7 +1400,7 @@ For more information about Grafana Enterprise, refer to [Grafana Enterprise]({{<
### enable
Keys of alpha features to enable, separated by space. Available alpha features are: `transformations`, `standaloneAlerts`
Keys of alpha features to enable, separated by space. Available alpha features are: `transformations`
## [date_formats]
@@ -1426,3 +1435,6 @@ interval_year = YYYY
Set this to `true` to have date formats be automatically be derived from browser locale. Defaults to `false`. This
is an experimental feature right now with a few problems that remain unsolved.
### default_timezone
Used as the default timezone for user preferences. Can be either `browser` for the browser local timezone or a timezone name from IANA Time Zone database, e.g. `UTC` or `Europe/Amsterdam` etc.

View File

@@ -32,7 +32,7 @@ Your Grafana preferences include whether uses the dark or light theme, your home
1. In the Preferences section, you can edit any of the following:
- **UI Theme -** Click to set the **Dark** or **Light** to select a theme. **Default** is either the dark theme or the theme selected by your Grafana administrator.
- **Home Dashboard -** Refer to [Set your personal home dashboard]({{< relref "change-home-dashboard.md#set-your-personal-home-dashboard" >}}) for more information.
- **Timezone -** Click to select an option in the **Timezone** list. Refer to [Time range controls]({{< relref "../dashboards/time-range-controls.md" >}}) for more information about Grafana time settings.
- **Timezone -** Click to select an option in the **Timezone** list. **Default** is either the browser local timezone or the timezone selected by your Grafana administrator. Refer to [Time range controls]({{< relref "../dashboards/time-range-controls.md" >}}) for more information about Grafana time settings.
1. Click **Save**.
## View your assigned organizations

View File

@@ -59,11 +59,13 @@ Example:
# Login cookie name
login_cookie_name = grafana_session
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
login_maximum_inactive_lifetime_days = 7
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
login_maximum_lifetime_days = 30
# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation (token_rotation_interval_minutes).
login_maximum_inactive_lifetime_duration =
# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month).
login_maximum_lifetime_duration =
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
token_rotation_interval_minutes = 10

View File

@@ -42,7 +42,7 @@ Open a graph in edit mode by clicking the title > Edit (or by pressing `e` key w
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| _Query expression_ | Prometheus query expression, check out the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). |
| _Legend format_ | Controls the name of the time series, using name or pattern. For example `{{hostname}}` is replaced with the label value for the label `hostname`. |
| _Min step_ | An additional lower limit for the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) and for the `$__interval` variable. The limit is absolute and not modified by the _Resolution_ setting. |
| _Min step_ | An additional lower limit for the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) and for the `$__interval` and `$__rate_interval` variables. The limit is absolute and not modified by the _Resolution_ setting. |
| _Resolution_ | `1/1` sets both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) such that each pixel corresponds to one data point. For better performance, lower resolutions can be picked. `1/2` only retrieves a data point for every other pixel, and `1/10` retrieves one data point per 10 pixels. Note that both _Min time interval_ and _Min step_ limit the final value of `$__interval` and `step`. |
| _Metric lookup_ | Search for metric names in this input field. |
| _Format as_ | Switch between `Table`, `Time series` or `Heatmap`. `Table` will only work in the Table panel. `Heatmap` is suitable for displaying metrics of the Histogram type on a Heatmap panel. Under the hood, it converts cumulative histograms to regular ones and sorts series by the bucket bound. |

View File

@@ -212,7 +212,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
`GET /api/orgs/name/:orgName`
Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
**Example Request**:
```http
@@ -463,7 +463,7 @@ Content-Type: application/json
HTTP/1.1 200
Content-Type: application/json
{"message":"User added to organization"}
{"message":"User added to organization", "userId": 1}
```
### Update Users in Organization

View File

@@ -8,23 +8,19 @@ weight = 300
# Field configuration options
This page explains what field configurations and field overrides in Grafana are and how to use them. It also includes [examples](#examples) if you need an idea of how this feature might be useful in the real world.
This page explains what field options and field overrides in Grafana are and how to use them. It also includes
[examples](#examples) if you need an idea of how this feature might be useful in the real world.
> **Note:** This documentation refers to a Grafana 7.0 beta feature. This documentation will be frequently updated to reflect updates to the feature, and it will probably be broken into smaller sections when the feature moves out of beta.
The data model behind Grafana, the [data frame]({{< relref "../developers/plugins/data-frames.md" >}}), is a columnar-oriented table structure.
Each column within this structure is called a _field_. Grafana allows to customize how a particular field is displayed in the visualization.
The data model used in Grafana, the [data frame]({{< relref "../developers/plugins/data-frames.md" >}}),
is a columnar-oriented table structure that unifies both time series and table query results. Each column within this structure is called a _field_. A field can represent a single time series or table column.
## Field configuration options and overrides
Field configuration options allow you to change how the data is displayed in your visualizations. Options and overrides that you apply do not change the data, they change how Grafana displays the data.
> **Note:** The time fields are not affected by field configuration options or overrides.
_Field configuration options_, both standard and custom, can be found in the **Field** tab in the panel editor. Changes on this tab apply to all fields (i.e. series/columns). For example, if you change the unit to percentage, then all fields with numeric values are displayed in percentages. [Apply a field option](#configure-all-fields).
_Field configuration options_, both standard and custom, are applied in the **Field** tab in the panel editor. Changes on this panel apply to all fields in the visualization. For example, if you change the unit to percentage, then all fields with numeric values will be displayed in percentages. [Apply a field option](#configure-all-fields).
_Field overrides_ are applied in the _Overrides_ tab in the panel editor. They are exactly the same as field configuration options except that they only change fields you select. The current feature only allows you to change one field at a time, but future improvements will offer more flexibility. [Apply an override](#override-a-field).
_Field overrides_ can be added in the **Overrides** tab in the panel editor. There you can add the same options as you find in the **Field** tab, but they are only applied to specific fields. [Apply an override](#override-a-field).
All [field options](#field-options) are defined below.
@@ -42,26 +38,16 @@ Standard field options are:
- [Value mappings](#value-mapping)
- [Data links](#data-links)
You can apply standard field options to the following panel visualizations:
- [Bar gauge]({{< relref "visualizations/bar-gauge-panel.md" >}})
- [Gauge]({{< relref "visualizations/gauge-panel.md" >}})
- [Stat]({{< relref "visualizations/stat-panel.md" >}})
- [Table]({{< relref "visualizations/table-panel.md" >}})
You can apply standard field options to most of the built-in Grafana panels. Some older panels and community panels that have yet to update to the new panel and data model will be missing either all or some of these field options.
### Custom field options
You can only apply custom field options to table visualizations. Plugin authors can add their own custom field options as well, and they might differ across visualizations.
Custom field options are:
- [Column width](#column-width)
- [Column alignment](#column-alignment)
- [Cell display mode](#cell-display-mode)
Some visualizations have custom field options. For example the [Table]({{< relref "visualizations/table-panel.md" >}}) visualization has many custom field options. Community panels can add their own custom field options as well, and they might differ across visualizations.
## Configure all fields
To change how all fields display data, you apply a [field option](#field-options). Usually you apply changes that you want to most of or all of the fields here, rather than applying field overrides to exceptions.
To change how all fields display data, you change an option in the **Field** tab. In the **Overrides** tab
you then override that for specific fields.
1. Navigate to the panel you want to edit, click the panel title, and then click **Edit**.
1. Click the **Field** tab.
@@ -69,9 +55,9 @@ To change how all fields display data, you apply a [field option](#field-options
1. Enter options by adding values in the fields. To return options to default values, delete the white text in the fields.
1. When finished, click **Save** to save all panel edits to the dashboard.
## Override a field
## Configure specific fields with overrides
Field overrides allow you to change the settings for one field (column in tables) to be different than the others. Field options for overrides are exactly the same as the field options available in a particular visualization. The only difference is that you choose which field to apply them to.
Overrides allow you to change the settings for one or more fields (i.e. series or column). What fields are targeted by the override depends on the matcher. Field options for overrides are exactly the same as the field options available in a particular visualization. The only difference is that you choose which fields to apply them to.
1. Navigate to the panel you want to edit, click the panel title, and then click **Edit**.
1. Click the **Overrides** tab.
@@ -83,21 +69,21 @@ Field overrides allow you to change the settings for one field (column in tables
1. Continue to add overrides to this field by clicking **Add override property**, or you can click **Add override** and select a different field to add overrides to.
1. When finished, click **Save** to save all panel edits to the dashboard.
## Filter options
## Select fields
This section explains all available filter options for field overrides. They are listed in alphabetical order.
This section explains the different ways you can select which fields an override rule will be applied to.
### Filter field by name
### Fields with name
Allows you to select a field from the list of all available fields that the override will be applied to.
Allows you to select a field from the list of all available fields. Properties you add to a rule with this selector will only be applied to this single field.
### Filter field by name using regex
### Fields with name matching regex
Allows you to type in a regular expression against which fields to be overridden will be matched.
Allows you to specify a regular expression. Properties you add to a rule with this selector will be applied to all fields where the field name match the regex.
### Filter field by type
### Fields with type
Allows you to select fields by their type (string, numeric, etc).
Allows you to select fields by their type (string, numeric, etc). Properties you add to a rule with this selector will be applied to all fields of matching type.
## Field options
@@ -105,37 +91,6 @@ This section explains all available field options. They are listed in alphabetic
Most field options will not affect the visualization until you click outside of the field option box you are editing or press Enter.
### Cell display mode
This custom field option applies only to table visualizations.
By default, Grafana automatically chooses display settings. You can override the settings by choosing one of the following options to change all fields.
- **Color text -** If thresholds are set, then the field text is displayed in the appropriate threshold color.
- **Color background -** If thresholds are set, then the field background is displayed in the appropriate threshold color.
- **Gradient gauge -** The threshold levels define a gradient.
- **LCD gauge -** The gauge is split up in small cells that are lit or unlit.
- **JSON view -** Shows value formatted as code. If a value is an object the JSON view allowing browsing the JSON object will appear on hover
### Column alignment
This custom field option applies only to table visualizations.
Choose how Grafana should align cell contents:
- Auto (default)
- Left
- Center
- Right
### Column width
This custom field option applies only to table visualizations.
By default, Grafana automatically calculates the column width based on the cell contents. In this field option, can override the setting and define the width for all columns in pixels.
For example, if you enter `100` in the field, then when you click outside the field, all the columns will be set to 100 pixels wide.
### Decimals
Number of decimals to render value with. Leave empty for Grafana to use the number of decimals provided by the data source.

View File

@@ -45,8 +45,8 @@ Transformations are available from the Transform tab in the bottom pane of the p
1. Navigate to the panel that you want to add transformations, click the panel title and then click **Edit**.
1. Click the **Transform** tab.
1. Click a transformation to select it.
1. Click a transformation to select it.
A transformation row appears that allows you to configure the transformation options.
Click **Add transformation** to apply another transformation. Keep in mind that the next transformation acts on the result set returned by the previous transformation.
@@ -107,25 +107,24 @@ In the example below, we have two queries returning table data. It is visualized
Query A:
| Time | Job | Uptime |
|---------------------|---------|-----------|
| ------------------- | ------- | --------- |
| 2020-07-07 11:34:20 | node | 25260122 |
| 2020-07-07 11:24:20 | postgre | 123001233 |
Query B:
| Time | Job | Errors |
|---------------------|---------|--------|
| ------------------- | ------- | ------ |
| 2020-07-07 11:34:20 | node | 15 |
| 2020-07-07 11:24:20 | postgre | 5 |
Here is the result after applying the `Merge` transformation.
| Time | Job | Errors | Uptime |
|---------------------|---------|--------|-----------|
| ------------------- | ------- | ------ | --------- |
| 2020-07-07 11:34:20 | node | 15 | 25260122 |
| 2020-07-07 11:24:20 | postgre | 5 | 123001233 |
### Filter by name
Use this transformation to remove portions of the query results.
@@ -170,7 +169,7 @@ Use this transformation to rename, reorder, or hide fields returned by the query
Grafana displays a list of fields returned by the query. You can:
- Change field order by hovering your cursor over a field. The cursor turns into a hand and then you can drag the field to its new place.
- Hide or show a field by clicking the eye icon next to the field name.
- Hide or show a field by clicking the eye icon next to the field name.
- Rename fields by typing a new name in the **Rename <field>** box.
In the example below, I hid the value field and renamed Max and Min.
@@ -209,12 +208,30 @@ In the example below, I added two fields together and named them Sum.
### Labels to fields
Use this transformation to group series by time and return labels or tags as fields.
> **Note:** In order to apply this transformation, you must have a query to a data source that returns labeled fields.
> **Note:** In order to apply this transformation, your query needs to returns labeled fields.
When you select this transformation, Grafana automatically transforms all labeled data into fields.
Example: Given a query result of two time series
1: labels Server=Server A, Datacenter=EU
2: labels Server=Server B, Datacenter=EU
This would result in a table like this
| Time | Server | Datacenter | Value |
| ------------------- | -------- | ---------- | ----- |
| 2020-07-07 11:34:20 | Server A | EU | 1 |
| 2020-07-07 11:34:20 | Server B | EU | 2 |
**Value field name**
If you where to select `Server` as in the **Value field name** you would get one field for every value of the `Server`
label.
| Time | Datacenter | Server A | Server B |
| ------------------- | ---------- | -------- | -------- |
| 2020-07-07 11:34:20 | EU | 1 | 2 |
For this example, I manually defined labels in the Random Walk visualization of TestData DB.
{{< docs-imagebox img="/img/docs/transformations/labels-to-fields-before-7-0.png" class="docs-image--no-shadow" max-width= "1100px" >}}
@@ -223,7 +240,6 @@ After I apply the transformation, my labels appear in the table as fields.
{{< docs-imagebox img="/img/docs/transformations/labels-to-fields-after-7-0.png" class="docs-image--no-shadow" max-width= "1100px" >}}
### Group By
> **Note:** This transformation is only available in Grafana 7.2+.
@@ -232,57 +248,58 @@ This transformation groups the data by a specified field (column) value and proc
Here's an example of original data.
| Time | Server ID | CPU Temperature | Server Status
|---------------------|-------------|-----------------|----------
| 2020-07-07 11:34:20 | server 1 | 80 | Shutdown
| 2020-07-07 11:34:20 | server 3 | 62 | OK
| 2020-07-07 10:32:20 | server 2 | 90 | Overload
| 2020-07-07 10:31:22 | server 3 | 55 | OK
| 2020-07-07 09:30:57 | server 3 | 62 | Rebooting
| 2020-07-07 09:30:05 | server 2 | 88 | OK
| 2020-07-07 09:28:06 | server 1 | 80 | OK
| 2020-07-07 09:25:05 | server 2 | 88 | OK
| 2020-07-07 09:23:07 | server 1 | 86 | OK
| Time | Server ID | CPU Temperature | Server Status |
| ------------------- | --------- | --------------- | ------------- |
| 2020-07-07 11:34:20 | server 1 | 80 | Shutdown |
| 2020-07-07 11:34:20 | server 3 | 62 | OK |
| 2020-07-07 10:32:20 | server 2 | 90 | Overload |
| 2020-07-07 10:31:22 | server 3 | 55 | OK |
| 2020-07-07 09:30:57 | server 3 | 62 | Rebooting |
| 2020-07-07 09:30:05 | server 2 | 88 | OK |
| 2020-07-07 09:28:06 | server 1 | 80 | OK |
| 2020-07-07 09:25:05 | server 2 | 88 | OK |
| 2020-07-07 09:23:07 | server 1 | 86 | OK |
This transformation goes in two steps. First you specify one or multiple fields to group the data by. This will group all the same values of those fields together, as if you sorted them. For instance if we `Group By` the `Server ID` field, it would group the data this way:
| Time | Server ID | CPU Temperature | Server Status
|---------------------|-------------|-----------------|----------
| 2020-07-07 11:34:20 | **server 1** | 80 | Shutdown
| 2020-07-07 09:28:06 | **server 1** | 80 | OK
| 2020-07-07 09:23:07 | **server 1** | 86 | OK
| Time | Server ID | CPU Temperature | Server Status |
| ------------------- | ------------ | --------------- | ------------- |
| 2020-07-07 11:34:20 | **server 1** | 80 | Shutdown |
| 2020-07-07 09:28:06 | **server 1** | 80 | OK |
| 2020-07-07 09:23:07 | **server 1** | 86 | OK |
|
| 2020-07-07 10:32:20 | server 2 | 90 | Overload
| 2020-07-07 09:30:05 | server 2 | 88 | OK
| 2020-07-07 09:25:05 | server 2 | 88 | OK
| 2020-07-07 10:32:20 | server 2 | 90 | Overload
| 2020-07-07 09:30:05 | server 2 | 88 | OK
| 2020-07-07 09:25:05 | server 2 | 88 | OK
|
| 2020-07-07 11:34:20 | ***server 3*** | 62 | OK
| 2020-07-07 10:31:22 | ***server 3*** | 55 | OK
| 2020-07-07 09:30:57 | ***server 3*** | 62 | Rebooting
| 2020-07-07 11:34:20 | **_server 3_** | 62 | OK
| 2020-07-07 10:31:22 | **_server 3_** | 55 | OK
| 2020-07-07 09:30:57 | **_server 3_** | 62 | Rebooting
All rows with the same value of `Server ID` are grouped together.
After choosing which field you want to group your data by, you can add various calculations on the other fields, and the calculation will be applied on each group of rows. For instance, we could want to calculate the average `CPU temperature` for each of those servers. So we can add the _mean_ calculation applied on the `CPU Temperature` field to get the following:
| Server ID | CPU Temperature (mean)
|-----------|--------------------------
| server 1 | 82
| server 2 | 88.6
| server 3 | 59.6
| Server ID | CPU Temperature (mean) |
| --------- | ---------------------- |
| server 1 | 82 |
| server 2 | 88.6 |
| server 3 | 59.6 |
And we can add more than one of those calculation. For instance :
- For field `Time`, we can calculate the *Last* value, to know when the last data point was received for each server
- For field `Server Status`, we can calculate the *Last* value to know what is the last state value for each server
- For field `Temperature`, we can also calculate the *Last* value to know what is the latest monitored temperature for each server
- For field `Time`, we can calculate the _Last_ value, to know when the last data point was received for each server
- For field `Server Status`, we can calculate the _Last_ value to know what is the last state value for each server
- For field `Temperature`, we can also calculate the _Last_ value to know what is the latest monitored temperature for each server
We would then get :
| Server ID | CPU Temperature (mean) | CPU Temperature (last) | Time (last) | Server Status (last)
|-----------|-------------------------- |------------------------|------------------|----------------------
| server 1 | 82 | 80 | 2020-07-07 11:34:20 | Shutdown
| server 2 | 88.6 | 90 | 2020-07-07 10:32:20 | Overload
| server 3 | 59.6 | 62 | 2020-07-07 11:34:20 | OK
| Server ID | CPU Temperature (mean) | CPU Temperature (last) | Time (last) | Server Status (last) |
| --------- | ---------------------- | ---------------------- | ------------------- | -------------------- |
| server 1 | 82 | 80 | 2020-07-07 11:34:20 | Shutdown |
| server 2 | 88.6 | 90 | 2020-07-07 10:32:20 | Overload |
| server 3 | 59.6 | 62 | 2020-07-07 11:34:20 | OK |
This transformation allows you to extract some key information out of your time series and display them in a convenient way.
@@ -290,7 +307,7 @@ This transformation allows you to extract some key information out of your time
> **Note:** This transformation is only available in Grafana 7.1+.
Use this transformation to combine the result from multiple time series data queries into one single result. This is helpful when using the table panel visualization.
Use this transformation to combine the result from multiple time series data queries into one single result. This is helpful when using the table panel visualization.
The result from this transformation will contain three columns: `Time`, `Metric`, and `Value`. The `Metric` column is added so you easily can see from which query the metric originates from. Customize this value by defining `Label` on the source query.
@@ -299,7 +316,7 @@ In the example below, we have two queries returning time series data. It is visu
Query A:
| Time | Temperature |
|---------------------|-------------|
| ------------------- | ----------- |
| 2020-07-07 11:34:20 | 25 |
| 2020-07-07 10:31:22 | 22 |
| 2020-07-07 09:30:05 | 19 |
@@ -307,7 +324,7 @@ Query A:
Query B:
| Time | Humidity |
|---------------------|----------|
| ------------------- | -------- |
| 2020-07-07 11:34:20 | 24 |
| 2020-07-07 10:32:20 | 29 |
| 2020-07-07 09:30:57 | 33 |
@@ -315,7 +332,7 @@ Query B:
Here is the result after applying the `Series to rows` transformation.
| Time | Metric | Value |
|---------------------|-------------|-------|
| ------------------- | ----------- | ----- |
| 2020-07-07 11:34:20 | Temperature | 25 |
| 2020-07-07 11:34:20 | Humidity | 22 |
| 2020-07-07 10:32:20 | Humidity | 29 |

View File

@@ -27,6 +27,43 @@ Table visualizations allow you to apply:
- **Show header -** Show or hide column names imported from your data source..
### Custom field options
- [Column width](#column-width)
- [Column alignment](#column-alignment)
- [Cell display mode](#cell-display-mode)
### Column alignment
This custom field option applies only to table visualizations.
Choose how Grafana should align cell contents:
- Auto (default)
- Left
- Center
- Right
### Column width
This custom field option applies only to table visualizations.
By default, Grafana automatically calculates the column width based on the cell contents. In this field option, can override the setting and define the width for all columns in pixels.
For example, if you enter `100` in the field, then when you click outside the field, all the columns will be set to 100 pixels wide.
#### Cell display mode
This custom field option applies only to table visualizations.
By default, Grafana automatically chooses display settings. You can override the settings by choosing one of the following options to change all fields.
- **Color text -** If thresholds are set, then the field text is displayed in the appropriate threshold color.
- **Color background -** If thresholds are set, then the field background is displayed in the appropriate threshold color.
- **Gradient gauge -** The threshold levels define a gradient.
- **LCD gauge -** The gauge is split up in small cells that are lit or unlit.
- **JSON view -** Shows value formatted as code. If a value is an object the JSON view allowing browsing the JSON object will appear on hover
## Tips
### Display original string value

View File

@@ -143,3 +143,13 @@ servers = ["test'1", "test2"]
String to interpolate: '${servers:sqlstring}'
Interpolation result: "'test''1','test2'"
```
## Text
Formats single- and multi-valued variables into their text representation. For a single variable it will just return the text representation. For multi-valued variables it will return the text representation combined with `+`.
```bash
servers = ["test1", "test2"]
String to interpolate: '${servers:text}'
Interpolation result: "test1 + test2"
```

View File

@@ -33,11 +33,12 @@ e2e.scenario({
`Server:doublequote = "A'A\\"A","BB\\B","CCC"`,
`Server:sqlstring = 'A''A"A','BB\\\B','CCC'`,
`Server:date = null`,
`Server:text = All`,
];
e2e()
.get('.markdown-html li')
.should('have.length', 21)
.should('have.length', 22)
.each(element => {
items.push(element.text());
})

View File

@@ -75,44 +75,6 @@ e2e.scenario({
e2e().wait('@apiPostQuery');
// Change order or query rows
// Check the order of the rows before
e2e.components.QueryEditorRows.rows()
.eq(0)
.within(() => {
e2e.components.QueryEditorRow.title('B').should('be.visible');
});
e2e.components.QueryEditorRows.rows()
.eq(1)
.within(() => {
e2e.components.QueryEditorRow.title('A').should('be.visible');
});
// Change so A is first
e2e.components.QueryEditorRow.actionButton('Move query up')
.eq(1)
.click();
e2e().wait('@apiPostQuery');
// Avoid flaky tests
// Maybe the virtual dom performs optimzations such as node position swapping, meaning 1 becomes 0 and it gets that element before the change because and never finds title 'A'
e2e().wait(250);
// Check the order of the rows after change
e2e.components.QueryEditorRows.rows()
.eq(0)
.within(() => {
e2e.components.QueryEditorRow.title('A').should('be.visible');
});
e2e.components.QueryEditorRows.rows()
.eq(1)
.within(() => {
e2e.components.QueryEditorRow.title('B').should('be.visible');
});
// Disable / enable row
expectInspectorResultAndClose(keys => {
const length = keys.length;
@@ -120,7 +82,7 @@ e2e.scenario({
expect(keys[length - 1].innerText).equals('B:');
});
// Disable row with refId B
// Disable row with refId A
e2e.components.QueryEditorRow.actionButton('Disable/enable query')
.eq(1)
.should('be.visible')
@@ -130,7 +92,7 @@ e2e.scenario({
expectInspectorResultAndClose(keys => {
const length = keys.length;
expect(keys[length - 1].innerText).equals('A:');
expect(keys[length - 1].innerText).equals('B:');
});
// Enable row with refId B

View File

@@ -0,0 +1,17 @@
import { e2e } from '@grafana/e2e';
e2e.scenario({
describeName: 'Solo Route',
itName: 'Can view panels with shared queries in fullsceen',
addScenarioDataSource: false,
addScenarioDashBoard: false,
skipScenario: false,
scenario: () => {
// open Panel Tests - Bar Gauge
e2e.pages.SoloPanel.visit('ZqZnVvFZz/datasource-tests-shared-queries?orgId=1&panelId=4');
e2e()
.get('canvas')
.should('have.length', 6);
},
});

View File

@@ -0,0 +1,68 @@
import { e2e } from '@grafana/e2e';
e2e.scenario({
describeName: 'Templating',
itName: 'Tests dashboard links and variables in links',
addScenarioDataSource: false,
addScenarioDashBoard: false,
skipScenario: false,
scenario: () => {
e2e.flows.openDashboard({ uid: 'yBCC3aKGk' });
e2e().server();
e2e()
.route({
method: 'GET',
url: '/api/search?tag=templating&limit=100',
})
.as('tagsTemplatingSearch');
e2e()
.route({
method: 'GET',
url: '/api/search?tag=demo&limit=100',
})
.as('tagsDemoSearch');
// waiting for links to render, couldn't find a better way using routes for instance
e2e().wait(1000);
const verifyLinks = (variableValue: string) => {
e2e.components.DashboardLinks.link()
.should('be.visible')
.and(links => {
expect(links).to.have.length(13);
for (let index = 0; index < links.length; index++) {
expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${variableValue}`);
}
});
};
e2e.components.DashboardLinks.dropDown()
.should('be.visible')
.click()
.wait('@tagsTemplatingSearch')
.wait('@tagsDemoSearch');
// verify all links, should have All value
verifyLinks('All');
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
.should('be.visible')
.click();
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('p2')
.should('be.visible')
.click();
e2e.pages.Dashboard.Toolbar.navBar().click();
e2e.components.DashboardLinks.dropDown()
.should('be.visible')
.click()
.wait('@tagsTemplatingSearch')
.wait('@tagsDemoSearch');
// verify all links, should have p2 value
verifyLinks('p2');
},
});

12
go.mod
View File

@@ -16,11 +16,10 @@ require (
github.com/BurntSushi/toml v0.3.1
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
github.com/aws/aws-sdk-go v1.33.12
github.com/beevik/etree v1.1.0 // indirect
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
github.com/centrifugal/centrifuge v0.10.0
github.com/crewjam/saml v0.0.0-20191031171751-c42136edf9b1
github.com/centrifugal/centrifuge v0.11.0
github.com/crewjam/saml v0.4.1
github.com/davecgh/go-spew v1.1.1
github.com/deepmap/oapi-codegen v1.3.11 // indirect
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec
@@ -30,6 +29,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
@@ -50,6 +50,7 @@ require (
github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec
github.com/influxdata/influxdb-client-go/v2 v2.0.1
github.com/jmespath/go-jmespath v0.3.0
github.com/jonboulle/clockwork v0.2.1 // indirect
github.com/jung-kurt/gofpdf v1.10.1
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
github.com/lib/pq v1.3.0
@@ -67,8 +68,9 @@ require (
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
github.com/robfig/cron/v3 v3.0.0
github.com/russellhaering/goxmldsig v0.0.0-20200902171629-2e1fbc2c5593 // indirect
github.com/smartystreets/goconvey v1.6.4
github.com/stretchr/testify v1.5.1
github.com/stretchr/testify v1.6.1
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
github.com/timberio/go-datemath v0.1.1-0.20200323150745-74ddef604fff
github.com/ua-parser/uap-go v0.0.0-20190826212731-daf92ba38329
@@ -79,7 +81,7 @@ require (
github.com/yudai/gojsondiff v1.0.0
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yudai/pp v2.0.1+incompatible // indirect
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208

33
go.sum
View File

@@ -147,7 +147,6 @@ github.com/aws/aws-sdk-go v1.33.12 h1:eydMoSwfrSTD9PWKUJOiDL7+/UwDW8AjInUGVE5Llh
github.com/aws/aws-sdk-go v1.33.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beevik/etree v1.0.1/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
@@ -177,10 +176,10 @@ github.com/cenkalti/backoff v1.0.0/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQ
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/centrifugal/centrifuge v0.10.0 h1:Eften1Akke0mgHJwHPqhG8oR/6q/SO4y/+KKdz+Qr14=
github.com/centrifugal/centrifuge v0.10.0/go.mod h1:mkuUdBuYFqz2GincQhkwWmHuVs9VWEHhMjzMcdUk6oA=
github.com/centrifugal/protocol v0.3.3 h1:GCNee3RFsjQu6SyKBX0Ir7ByUrp+Gw0MU/PsIc2CM2s=
github.com/centrifugal/protocol v0.3.3/go.mod h1:2YbBCaDwQHl37ErRdMrKSj18X2yVvpkQYtSX6aVbe5A=
github.com/centrifugal/centrifuge v0.11.0 h1:aaG74PBO+rBmz3hE9W8bCGz8kofiam1LNE4EAVtiMaA=
github.com/centrifugal/centrifuge v0.11.0/go.mod h1:jdFw/2dBFpME3OTisc5FVAC1ilqodnyMnQFjem0k0yg=
github.com/centrifugal/protocol v0.3.4 h1:9q22iSp4CQOdQahfopvfmWWxDbj1Lo7ERG2j56mAxkE=
github.com/centrifugal/protocol v0.3.4/go.mod h1:2YbBCaDwQHl37ErRdMrKSj18X2yVvpkQYtSX6aVbe5A=
github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@@ -224,8 +223,9 @@ github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/crewjam/saml v0.0.0-20191031171751-c42136edf9b1 h1:PKeiHI5SxrkdEtI8FVdk1ubBl2wjnOmHQf5D4ZJOKFE=
github.com/crewjam/saml v0.0.0-20191031171751-c42136edf9b1/go.mod h1:pzACCdpqjQKTvpPZs5P3FzFNQ+RSOJX5StwHwh7ZUgw=
github.com/crewjam/httperr v0.0.0-20190612203328-a946449404da/go.mod h1:+rmNIXRvYMqLQeR4DHyTvs6y0MEMymTz4vyFpFkKTPs=
github.com/crewjam/saml v0.4.1 h1:ZNSRJvdbypQDY2uApMngeIHNcxS6UCRAgiw3S+pmgRU=
github.com/crewjam/saml v0.4.1/go.mod h1:vHcshzXm2WkPOV1dcToZa99cCB1h3nPiKLtLYK+erBE=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
@@ -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=
@@ -529,6 +531,7 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -694,6 +697,9 @@ github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/jonboulle/clockwork v0.2.1 h1:S/EaQvW6FpWMYAvYvY+OBDvpaM+izu0oiwo5y0MH7U0=
github.com/jonboulle/clockwork v0.2.1/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/joncrlsn/dque v2.2.1-0.20200515025108-956d14155fa2+incompatible/go.mod h1:hDZb8oMj3Kp8MxtbNLg9vrtAUDHjgI1yZvqivT4O8Iw=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -709,6 +715,7 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@@ -742,6 +749,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.0.0/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
@@ -1021,6 +1030,8 @@ github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 h1:J4AOUcOh/t1XbQcJfkEqhzgvMJ2tDxdCVvmHxW5QXao=
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7/go.mod h1:Oz4y6ImuOQZxynhbSXk7btjEfNBtGlj2dcaOvXl2FSM=
github.com/russellhaering/goxmldsig v0.0.0-20200902171629-2e1fbc2c5593 h1:wkyiSzH81tsd3tSoznvnXMIJo0cpHjbFuJhs/E9t/B8=
github.com/russellhaering/goxmldsig v0.0.0-20200902171629-2e1fbc2c5593/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
@@ -1097,6 +1108,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA=
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0=
@@ -1228,8 +1241,8 @@ golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1658,7 +1671,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

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

View File

@@ -3,7 +3,7 @@
"license": "Apache-2.0",
"private": true,
"name": "grafana",
"version": "7.2.0-beta1",
"version": "7.2.2",
"repository": "github:grafana/grafana",
"scripts": {
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",

View File

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

View File

@@ -88,7 +88,7 @@ export function localTimeFormat(
locale?: string | string[] | null,
fallback?: string
): string {
if (!window.Intl) {
if (missingIntlDateTimeFormatSupport()) {
return fallback ?? DEFAULT_SYSTEM_DATE_FORMAT;
}
@@ -115,3 +115,7 @@ export function localTimeFormat(
}
export const systemDateFormats = new SystemDateFormatsState();
const missingIntlDateTimeFormatSupport = (): boolean => {
return !('DateTimeFormat' in Intl) || !('formatToParts' in Intl.DateTimeFormat.prototype);
};

View File

@@ -5,6 +5,7 @@ import { Field, FieldConfig, FieldType, GrafanaTheme, Threshold, ThresholdsMode
import { getScaleCalculator, sortThresholds } from './scale';
import { ArrayVector } from '../vector';
import { validateFieldConfig } from './fieldOverrides';
import { systemDateFormats } from '../datetime';
function getDisplayProcessorFromConfig(config: FieldConfig) {
return getDisplayProcessor({
@@ -293,6 +294,23 @@ describe('Date display options', () => {
expect(processor(0).text).toEqual('1970');
});
it('Should use system date format by default', () => {
const currentFormat = systemDateFormats.fullDate;
systemDateFormats.fullDate = 'YYYY-MM';
const processor = getDisplayProcessor({
timeZone: 'utc',
field: {
type: FieldType.time,
config: {},
},
});
expect(processor(0).text).toEqual('1970-01');
systemDateFormats.fullDate = currentFormat;
});
it('should handle ISO string dates', () => {
const processor = getDisplayProcessor({
timeZone: 'utc',

View File

@@ -39,7 +39,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
let hasDateUnit = unit && (timeFormats[unit] || unit.startsWith('time:'));
if (field.type === FieldType.time && !hasDateUnit) {
unit = `dateTimeAsIso`;
unit = `dateTimeAsSystem`;
hasDateUnit = true;
}

View File

@@ -19,6 +19,7 @@ import {
GrafanaTheme,
InterpolateFunction,
ThresholdsMode,
ScopedVars,
} from '../types';
import { locationUtil, Registry } from '../utils';
import { mockStandardProperties } from '../utils/tests/mockStandardProperties';
@@ -64,6 +65,16 @@ export const customFieldRegistry: FieldConfigOptionsRegistry = new Registry<Fiel
return [property1, property2, property3, shouldApplyFalse, ...mockStandardProperties()];
});
locationUtil.initialize({
getConfig: () => {
return { appSubUrl: '/subUrl' } as any;
},
// @ts-ignore
buildParamsFromVariables: () => {},
// @ts-ignore
getTimeRangeForUrl: () => {},
});
describe('Global MinMax', () => {
it('find global min max', () => {
const f0 = new MutableDataFrame();
@@ -93,6 +104,7 @@ describe('applyFieldOverrides', () => {
defaults: {
unit: 'xyz',
decimals: 2,
links: [{ title: 'link', url: '${__value.text}' }],
},
overrides: [
{
@@ -244,6 +256,28 @@ describe('applyFieldOverrides', () => {
// Don't Automatically pick the min value
expect(config.min).toEqual(-20);
});
it('getLinks should use applied field config', () => {
const replaceVariablesCalls: any[] = [];
const data = applyFieldOverrides({
data: [f0], // the frame
fieldConfig: src as FieldConfigSource, // defaults + overrides
replaceVariables: ((value: string, variables: ScopedVars) => {
replaceVariablesCalls.push(variables);
return value;
}) as InterpolateFunction,
getDataSourceSettingsByUid: undefined as any,
theme: (undefined as any) as GrafanaTheme,
autoMinMax: true,
fieldConfigRegistry: customFieldRegistry,
})[0];
data.fields[1].getLinks!({ valueRowIndex: 0 });
expect(data.fields[1].config.decimals).toEqual(1);
expect(replaceVariablesCalls[0].__value.value.text).toEqual('100.0');
});
});
describe('setFieldConfigDefaults', () => {

View File

@@ -101,6 +101,9 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
}
return options.data.map((frame, index) => {
// Need to define this new frame here as it's passed to the getLinkSupplier function inside the fields loop
const newFrame: DataFrame = { ...frame };
const scopedVars: ScopedVars = {
__series: { text: 'Series', value: { name: getFrameDisplayName(frame, index) } }, // might be missing
};
@@ -206,7 +209,7 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
// Attach data links supplier
f.getLinks = getLinksSupplier(
frame,
newFrame,
f,
fieldScopedVars,
context.replaceVariables,
@@ -220,10 +223,8 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
return f;
});
return {
...frame,
fields,
};
newFrame.fields = fields;
return newFrame;
});
}

View File

@@ -317,7 +317,7 @@ export class PanelPlugin<TOptions = any, TFieldConfigOptions extends object = an
for (const customProp of builder.getRegistry().list()) {
customProp.isCustom = true;
customProp.category = ['Custom options'].concat(customProp.category || []);
customProp.category = [`${this.meta.name} options`].concat(customProp.category || []);
// need to do something to make the custom items not conflict with standard ones
// problem is id (registry index) is used as property path
// so sort of need a property path on the FieldPropertyEditorItem

View File

@@ -50,7 +50,16 @@ describe('Labels as Columns', () => {
name: 'A',
fields: [
{ name: 'time', type: FieldType.time, values: [1000, 2000] },
{ name: 'Value', type: FieldType.number, values: [1, 2], labels: { location: 'inside', name: 'Request' } },
{
name: 'Value',
type: FieldType.number,
values: [1, 2],
labels: { location: 'inside', name: 'Request' },
config: {
displayName: 'Custom1',
displayNameFromDS: 'Custom2',
},
},
],
});

View File

@@ -51,7 +51,9 @@ export const labelsToFieldsTransformer: DataTransformerInfo<LabelsToFieldsOption
name,
config: {
...field.config,
// we need to clear thes for this transform as these can contain label names that we no longer want
displayName: undefined,
displayNameFromDS: undefined,
},
labels: undefined,
});

View File

@@ -0,0 +1,87 @@
import { DataQuery, QueryEditorProps } from './datasource';
import { DataFrame } from './dataFrame';
import { ComponentType } from 'react';
/**
* This JSON object is stored in the dashboard json model.
*/
export interface AnnotationQuery<TQuery extends DataQuery = DataQuery> {
datasource: string;
enable: boolean;
name: string;
iconColor: string;
// Standard datasource query
target?: TQuery;
// Convert a dataframe to an AnnotationEvent
mappings?: AnnotationEventMappings;
}
export interface AnnotationEvent {
id?: string;
annotation?: any;
dashboardId?: number;
panelId?: number;
userId?: number;
login?: string;
email?: string;
avatarUrl?: string;
time?: number;
timeEnd?: number;
isRegion?: boolean;
title?: string;
text?: string;
type?: string;
tags?: string[];
// Currently used to merge annotations from alerts and dashboard
source?: any; // source.type === 'dashboard'
}
/**
* @alpha -- any value other than `field` is experimental
*/
export enum AnnotationEventFieldSource {
Field = 'field', // Default -- find the value with a matching key
Text = 'text', // Write a constant string into the value
Skip = 'skip', // Do not include the field
}
export interface AnnotationEventFieldMapping {
source?: AnnotationEventFieldSource; // defautls to 'field'
value?: string;
regex?: string;
}
export type AnnotationEventMappings = Partial<Record<keyof AnnotationEvent, AnnotationEventFieldMapping>>;
/**
* Since Grafana 7.2
*
* This offers a generic approach to annotation processing
*/
export interface AnnotationSupport<TQuery extends DataQuery = DataQuery, TAnno = AnnotationQuery<TQuery>> {
/**
* This hook lets you manipulate any existing stored values before running them though the processor.
* This is particularly helpful when dealing with migrating old formats. ie query as a string vs object
*/
prepareAnnotation?(json: any): TAnno;
/**
* Convert the stored JSON model to a standard datasource query object.
* This query will be executed in the datasource and the results converted into events.
* Returning an undefined result will quietly skip query execution
*/
prepareQuery?(anno: TAnno): TQuery | undefined;
/**
* When the standard frame > event processing is insufficient, this allows explicit control of the mappings
*/
processEvents?(anno: TAnno, data: DataFrame[]): AnnotationEvent[] | undefined;
/**
* Specify a custom QueryEditor for the annotation page. If not specified, the standard one will be used
*/
QueryEditor?: ComponentType<QueryEditorProps<any, TQuery>>;
}

View File

@@ -42,7 +42,6 @@ export interface FeatureToggles {
meta: boolean;
datasourceInsights: boolean;
reportGrid: boolean;
standaloneAlerts: boolean;
}
/**

View File

@@ -132,27 +132,6 @@ export enum NullValueMode {
AsZero = 'null as zero',
}
export interface AnnotationEvent {
id?: string;
annotation?: any;
dashboardId?: number;
panelId?: number;
userId?: number;
login?: string;
email?: string;
avatarUrl?: string;
time?: number;
timeEnd?: number;
isRegion?: boolean;
title?: string;
text?: string;
type?: string;
tags?: string[];
// Currently used to merge annotations from alerts and dashboard
source?: any; // source.type === 'dashboard'
}
/**
* Describes and API for exposing panel specific data configurations.
*/

View File

@@ -3,7 +3,8 @@ import { ComponentType } from 'react';
import { GrafanaPlugin, PluginMeta } from './plugin';
import { PanelData } from './panel';
import { LogRowModel } from './logs';
import { AnnotationEvent, KeyValue, LoadingState, TableData, TimeSeries } from './data';
import { AnnotationEvent, AnnotationSupport } from './annotations';
import { KeyValue, LoadingState, TableData, TimeSeries } from './data';
import { DataFrame, DataFrameDTO } from './dataFrame';
import { RawTimeRange, TimeRange } from './time';
import { ScopedVars } from './ScopedVars';
@@ -155,8 +156,7 @@ export interface DataSourceConstructor<
*/
export abstract class DataSourceApi<
TQuery extends DataQuery = DataQuery,
TOptions extends DataSourceJsonData = DataSourceJsonData,
TAnno = TQuery // defatult to direct query
TOptions extends DataSourceJsonData = DataSourceJsonData
> {
/**
* Set in constructor
@@ -267,13 +267,23 @@ export abstract class DataSourceApi<
showContextToggle?(row?: LogRowModel): boolean;
/**
* Can be optionally implemented to allow datasource to be a source of annotations for dashboard. To be visible
* in the annotation editor `annotations` capability also needs to be enabled in plugin.json.
*/
annotationQuery?(options: AnnotationQueryRequest<TAnno>): Promise<AnnotationEvent[]>;
interpolateVariablesInQueries?(queries: TQuery[], scopedVars: ScopedVars | {}): TQuery[];
/**
* An annotation processor allows explict control for how annotations are managed.
*
* It is only necessary to configure an annotation processor if the default behavior is not desirable
*/
annotations?: AnnotationSupport<TQuery>;
/**
* Can be optionally implemented to allow datasource to be a source of annotations for dashboard.
* This function will only be called if an angular {@link AnnotationsQueryCtrl} is configured and
* the {@link annotations} is undefined
*
* @deprecated -- prefer using {@link AnnotationSupport}
*/
annotationQuery?(options: AnnotationQueryRequest<TQuery>): Promise<AnnotationEvent[]>;
}
export interface MetadataInspectorProps<
@@ -473,12 +483,6 @@ export interface MetricFindValue {
expandable?: boolean;
}
export interface BaseAnnotationQuery {
datasource: string;
enable: boolean;
name: string;
}
export interface DataSourceJsonData {
authType?: string;
defaultRegion?: string;
@@ -547,20 +551,19 @@ export interface DataSourceSelectItem {
/**
* Options passed to the datasource.annotationQuery method. See docs/plugins/developing/datasource.md
*
* @deprecated -- use {@link AnnotationSupport}
*/
export interface AnnotationQueryRequest<TAnno = {}> {
export interface AnnotationQueryRequest<MoreOptions = {}> {
range: TimeRange;
rangeRaw: RawTimeRange;
interval: string;
intervalMs: number;
maxDataPoints?: number;
app: CoreApp | string;
// Should be DataModel but cannot import that here from the main app. Needs to be moved to package first.
dashboard: any;
// The annotation query and common properties
annotation: BaseAnnotationQuery & TAnno;
annotation: {
datasource: string;
enable: boolean;
name: string;
} & MoreOptions;
}
export interface HistoryItem<TQuery extends DataQuery = DataQuery> {

View File

@@ -37,7 +37,7 @@ export interface FieldOverrideContext extends StandardEditorContext<any> {
}
export interface FieldConfigEditorProps<TValue, TSettings>
extends Omit<StandardEditorProps<TValue, TSettings>, 'item'> {
item: FieldConfigPropertyItem<TValue, TSettings>; // The property info
item: FieldConfigPropertyItem<any, TValue, TSettings>; // The property info
value: TValue;
context: FieldOverrideContext;
onChange: (value?: TValue) => void;

View File

@@ -1,6 +1,7 @@
export * from './data';
export * from './dataFrame';
export * from './dataLink';
export * from './annotations';
export * from './logs';
export * from './navModel';
export * from './select';

View File

@@ -20,9 +20,10 @@ import {
toNanoSeconds,
toSeconds,
toTimeTicks,
dateTimeSystemFormatter,
} from './dateTimeFormatters';
import { toHex, sci, toHex0x, toPercent, toPercentUnit } from './arithmeticFormatters';
import { binarySIPrefix, currency, decimalSIPrefix } from './symbolFormatters';
import { binaryPrefix, currency, SIPrefix } from './symbolFormatters';
export const getCategories = (): ValueFormatCategory[] => [
{
@@ -75,14 +76,14 @@ export const getCategories = (): ValueFormatCategory[] => [
{
name: 'Computation',
formats: [
{ name: 'FLOP/s', id: 'flops', fn: decimalSIPrefix('FLOP/s') },
{ name: 'MFLOP/s', id: 'mflops', fn: decimalSIPrefix('FLOP/s', 2) },
{ name: 'GFLOP/s', id: 'gflops', fn: decimalSIPrefix('FLOP/s', 3) },
{ name: 'TFLOP/s', id: 'tflops', fn: decimalSIPrefix('FLOP/s', 4) },
{ name: 'PFLOP/s', id: 'pflops', fn: decimalSIPrefix('FLOP/s', 5) },
{ name: 'EFLOP/s', id: 'eflops', fn: decimalSIPrefix('FLOP/s', 6) },
{ name: 'ZFLOP/s', id: 'zflops', fn: decimalSIPrefix('FLOP/s', 7) },
{ name: 'YFLOP/s', id: 'yflops', fn: decimalSIPrefix('FLOP/s', 8) },
{ name: 'FLOP/s', id: 'flops', fn: SIPrefix('FLOP/s') },
{ name: 'MFLOP/s', id: 'mflops', fn: SIPrefix('FLOP/s', 2) },
{ name: 'GFLOP/s', id: 'gflops', fn: SIPrefix('FLOP/s', 3) },
{ name: 'TFLOP/s', id: 'tflops', fn: SIPrefix('FLOP/s', 4) },
{ name: 'PFLOP/s', id: 'pflops', fn: SIPrefix('FLOP/s', 5) },
{ name: 'EFLOP/s', id: 'eflops', fn: SIPrefix('FLOP/s', 6) },
{ name: 'ZFLOP/s', id: 'zflops', fn: SIPrefix('FLOP/s', 7) },
{ name: 'YFLOP/s', id: 'yflops', fn: SIPrefix('FLOP/s', 8) },
],
},
{
@@ -128,45 +129,52 @@ export const getCategories = (): ValueFormatCategory[] => [
],
},
{
name: 'Data (IEC)',
name: 'Data',
formats: [
{ name: 'bits(IEC)', id: 'bits', fn: binarySIPrefix('b') },
{ name: 'bytes(IEC)', id: 'bytes', fn: binarySIPrefix('B') },
{ name: 'kibibytes', id: 'kbytes', fn: binarySIPrefix('B', 1) },
{ name: 'mebibytes', id: 'mbytes', fn: binarySIPrefix('B', 2) },
{ name: 'gibibytes', id: 'gbytes', fn: binarySIPrefix('B', 3) },
{ name: 'tebibytes', id: 'tbytes', fn: binarySIPrefix('B', 4) },
{ name: 'pebibytes', id: 'pbytes', fn: binarySIPrefix('B', 5) },
],
},
{
name: 'Data (metric)',
formats: [
{ name: 'bits(Metric)', id: 'decbits', fn: decimalSIPrefix('b') },
{ name: 'bytes(Metric)', id: 'decbytes', fn: decimalSIPrefix('B') },
{ name: 'kilobytes', id: 'deckbytes', fn: decimalSIPrefix('B', 1) },
{ name: 'megabytes', id: 'decmbytes', fn: decimalSIPrefix('B', 2) },
{ name: 'gigabytes', id: 'decgbytes', fn: decimalSIPrefix('B', 3) },
{ name: 'terabytes', id: 'dectbytes', fn: decimalSIPrefix('B', 4) },
{ name: 'petabytes', id: 'decpbytes', fn: decimalSIPrefix('B', 5) },
{ name: 'bytes(IEC)', id: 'bytes', fn: binaryPrefix('B') },
{ name: 'bytes(SI)', id: 'decbytes', fn: SIPrefix('B') },
{ name: 'bits(IEC)', id: 'bits', fn: binaryPrefix('b') },
{ name: 'bits(SI)', id: 'decbits', fn: SIPrefix('b') },
{ name: 'kibibytes', id: 'kbytes', fn: binaryPrefix('B', 1) },
{ name: 'kilobytes', id: 'deckbytes', fn: SIPrefix('B', 1) },
{ name: 'mebibytes', id: 'mbytes', fn: binaryPrefix('B', 2) },
{ name: 'megabytes', id: 'decmbytes', fn: SIPrefix('B', 2) },
{ name: 'gibibytes', id: 'gbytes', fn: binaryPrefix('B', 3) },
{ name: 'gigabytes', id: 'decgbytes', fn: SIPrefix('B', 3) },
{ name: 'tebibytes', id: 'tbytes', fn: binaryPrefix('B', 4) },
{ name: 'terabytes', id: 'dectbytes', fn: SIPrefix('B', 4) },
{ name: 'pebibytes', id: 'pbytes', fn: binaryPrefix('B', 5) },
{ name: 'petabytes', id: 'decpbytes', fn: SIPrefix('B', 5) },
],
},
{
name: 'Data rate',
formats: [
{ name: 'packets/sec', id: 'pps', fn: decimalSIPrefix('pps') },
{ name: 'bits/sec', id: 'bps', fn: decimalSIPrefix('bps') },
{ name: 'bytes/sec', id: 'Bps', fn: decimalSIPrefix('B/s') },
{ name: 'kilobytes/sec', id: 'KBs', fn: decimalSIPrefix('B/s', 1) },
{ name: 'kilobits/sec', id: 'Kbits', fn: decimalSIPrefix('bps', 1) },
{ name: 'megabytes/sec', id: 'MBs', fn: decimalSIPrefix('B/s', 2) },
{ name: 'megabits/sec', id: 'Mbits', fn: decimalSIPrefix('bps', 2) },
{ name: 'gigabytes/sec', id: 'GBs', fn: decimalSIPrefix('B/s', 3) },
{ name: 'gigabits/sec', id: 'Gbits', fn: decimalSIPrefix('bps', 3) },
{ name: 'terabytes/sec', id: 'TBs', fn: decimalSIPrefix('B/s', 4) },
{ name: 'terabits/sec', id: 'Tbits', fn: decimalSIPrefix('bps', 4) },
{ name: 'petabytes/sec', id: 'PBs', fn: decimalSIPrefix('B/s', 5) },
{ name: 'petabits/sec', id: 'Pbits', fn: decimalSIPrefix('bps', 5) },
{ name: 'packets/sec', id: 'pps', fn: SIPrefix('p/s') },
{ name: 'bytes/sec(IEC)', id: 'binBps', fn: binaryPrefix('B/s') },
{ name: 'bytes/sec(SI)', id: 'Bps', fn: SIPrefix('B/s') },
{ name: 'bits/sec(IEC)', id: 'binbps', fn: binaryPrefix('b/s') },
{ name: 'bits/sec(SI)', id: 'bps', fn: SIPrefix('b/s') },
{ name: 'kibibytes/sec', id: 'KiBs', fn: binaryPrefix('B/s', 1) },
{ name: 'kibibits/sec', id: 'Kibits', fn: binaryPrefix('b/s', 1) },
{ name: 'kilobytes/sec', id: 'KBs', fn: SIPrefix('B/s', 1) },
{ name: 'kilobits/sec', id: 'Kbits', fn: SIPrefix('b/s', 1) },
{ name: 'mibibytes/sec', id: 'MiBs', fn: binaryPrefix('B/s', 2) },
{ name: 'mibibits/sec', id: 'Mibits', fn: binaryPrefix('b/s', 2) },
{ name: 'megabytes/sec', id: 'MBs', fn: SIPrefix('B/s', 2) },
{ name: 'megabits/sec', id: 'Mbits', fn: SIPrefix('b/s', 2) },
{ name: 'gibibytes/sec', id: 'GiBs', fn: binaryPrefix('B/s', 3) },
{ name: 'gibibits/sec', id: 'Gibits', fn: binaryPrefix('b/s', 3) },
{ name: 'gigabytes/sec', id: 'GBs', fn: SIPrefix('B/s', 3) },
{ name: 'gigabits/sec', id: 'Gbits', fn: SIPrefix('b/s', 3) },
{ name: 'tebibytes/sec', id: 'TiBs', fn: binaryPrefix('B/s', 4) },
{ name: 'tebibits/sec', id: 'Tibits', fn: binaryPrefix('b/s', 4) },
{ name: 'terabytes/sec', id: 'TBs', fn: SIPrefix('B/s', 4) },
{ name: 'terabits/sec', id: 'Tbits', fn: SIPrefix('b/s', 4) },
{ name: 'petibytes/sec', id: 'PiBs', fn: binaryPrefix('B/s', 5) },
{ name: 'petibits/sec', id: 'Pibits', fn: binaryPrefix('b/s', 5) },
{ name: 'petabytes/sec', id: 'PBs', fn: SIPrefix('B/s', 5) },
{ name: 'petabits/sec', id: 'Pbits', fn: SIPrefix('b/s', 5) },
],
},
{
@@ -177,50 +185,51 @@ export const getCategories = (): ValueFormatCategory[] => [
{ name: 'Datetime US', id: 'dateTimeAsUS', fn: dateTimeAsUS },
{ name: 'Datetime US (No date if today)', id: 'dateTimeAsUSNoDateIfToday', fn: dateTimeAsUSNoDateIfToday },
{ name: 'Datetime local', id: 'dateTimeAsLocal', fn: getDateTimeAsLocalFormat() },
{ name: 'Datetime default', id: 'dateTimeAsSystem', fn: dateTimeSystemFormatter },
{ name: 'From Now', id: 'dateTimeFromNow', fn: dateTimeFromNow },
],
},
{
name: 'Energy',
formats: [
{ name: 'Watt (W)', id: 'watt', fn: decimalSIPrefix('W') },
{ name: 'Kilowatt (kW)', id: 'kwatt', fn: decimalSIPrefix('W', 1) },
{ name: 'Megawatt (MW)', id: 'megwatt', fn: decimalSIPrefix('W', 2) },
{ name: 'Gigawatt (GW)', id: 'gwatt', fn: decimalSIPrefix('W', 3) },
{ name: 'Milliwatt (mW)', id: 'mwatt', fn: decimalSIPrefix('W', -1) },
{ name: 'Watt (W)', id: 'watt', fn: SIPrefix('W') },
{ name: 'Kilowatt (kW)', id: 'kwatt', fn: SIPrefix('W', 1) },
{ name: 'Megawatt (MW)', id: 'megwatt', fn: SIPrefix('W', 2) },
{ name: 'Gigawatt (GW)', id: 'gwatt', fn: SIPrefix('W', 3) },
{ name: 'Milliwatt (mW)', id: 'mwatt', fn: SIPrefix('W', -1) },
{ name: 'Watt per square meter (W/m²)', id: 'Wm2', fn: toFixedUnit('W/m²') },
{ name: 'Volt-ampere (VA)', id: 'voltamp', fn: decimalSIPrefix('VA') },
{ name: 'Kilovolt-ampere (kVA)', id: 'kvoltamp', fn: decimalSIPrefix('VA', 1) },
{ name: 'Volt-ampere reactive (var)', id: 'voltampreact', fn: decimalSIPrefix('var') },
{ name: 'Kilovolt-ampere reactive (kvar)', id: 'kvoltampreact', fn: decimalSIPrefix('var', 1) },
{ name: 'Watt-hour (Wh)', id: 'watth', fn: decimalSIPrefix('Wh') },
{ name: 'Watt-hour per Kilogram (Wh/kg)', id: 'watthperkg', fn: decimalSIPrefix('Wh/kg') },
{ name: 'Kilowatt-hour (kWh)', id: 'kwatth', fn: decimalSIPrefix('Wh', 1) },
{ name: 'Kilowatt-min (kWm)', id: 'kwattm', fn: decimalSIPrefix('W-Min', 1) },
{ name: 'Ampere-hour (Ah)', id: 'amph', fn: decimalSIPrefix('Ah') },
{ name: 'Kiloampere-hour (kAh)', id: 'kamph', fn: decimalSIPrefix('Ah', 1) },
{ name: 'Milliampere-hour (mAh)', id: 'mamph', fn: decimalSIPrefix('Ah', -1) },
{ name: 'Joule (J)', id: 'joule', fn: decimalSIPrefix('J') },
{ name: 'Electron volt (eV)', id: 'ev', fn: decimalSIPrefix('eV') },
{ name: 'Ampere (A)', id: 'amp', fn: decimalSIPrefix('A') },
{ name: 'Kiloampere (kA)', id: 'kamp', fn: decimalSIPrefix('A', 1) },
{ name: 'Milliampere (mA)', id: 'mamp', fn: decimalSIPrefix('A', -1) },
{ name: 'Volt (V)', id: 'volt', fn: decimalSIPrefix('V') },
{ name: 'Kilovolt (kV)', id: 'kvolt', fn: decimalSIPrefix('V', 1) },
{ name: 'Millivolt (mV)', id: 'mvolt', fn: decimalSIPrefix('V', -1) },
{ name: 'Decibel-milliwatt (dBm)', id: 'dBm', fn: decimalSIPrefix('dBm') },
{ name: 'Ohm (Ω)', id: 'ohm', fn: decimalSIPrefix('Ω') },
{ name: 'Kiloohm (kΩ)', id: 'kohm', fn: decimalSIPrefix('Ω', 1) },
{ name: 'Megaohm (MΩ)', id: 'Mohm', fn: decimalSIPrefix('Ω', 2) },
{ name: 'Farad (F)', id: 'farad', fn: decimalSIPrefix('F') },
{ name: 'Microfarad (µF)', id: 'µfarad', fn: decimalSIPrefix('F', -2) },
{ name: 'Nanofarad (nF)', id: 'nfarad', fn: decimalSIPrefix('F', -3) },
{ name: 'Picofarad (pF)', id: 'pfarad', fn: decimalSIPrefix('F', -4) },
{ name: 'Femtofarad (fF)', id: 'ffarad', fn: decimalSIPrefix('F', -5) },
{ name: 'Henry (H)', id: 'henry', fn: decimalSIPrefix('H') },
{ name: 'Millihenry (mH)', id: 'mhenry', fn: decimalSIPrefix('H', -1) },
{ name: 'Microhenry (µH)', id: 'µhenry', fn: decimalSIPrefix('H', -2) },
{ name: 'Lumens (Lm)', id: 'lumens', fn: decimalSIPrefix('Lm') },
{ name: 'Volt-ampere (VA)', id: 'voltamp', fn: SIPrefix('VA') },
{ name: 'Kilovolt-ampere (kVA)', id: 'kvoltamp', fn: SIPrefix('VA', 1) },
{ name: 'Volt-ampere reactive (var)', id: 'voltampreact', fn: SIPrefix('var') },
{ name: 'Kilovolt-ampere reactive (kvar)', id: 'kvoltampreact', fn: SIPrefix('var', 1) },
{ name: 'Watt-hour (Wh)', id: 'watth', fn: SIPrefix('Wh') },
{ name: 'Watt-hour per Kilogram (Wh/kg)', id: 'watthperkg', fn: SIPrefix('Wh/kg') },
{ name: 'Kilowatt-hour (kWh)', id: 'kwatth', fn: SIPrefix('Wh', 1) },
{ name: 'Kilowatt-min (kWm)', id: 'kwattm', fn: SIPrefix('W-Min', 1) },
{ name: 'Ampere-hour (Ah)', id: 'amph', fn: SIPrefix('Ah') },
{ name: 'Kiloampere-hour (kAh)', id: 'kamph', fn: SIPrefix('Ah', 1) },
{ name: 'Milliampere-hour (mAh)', id: 'mamph', fn: SIPrefix('Ah', -1) },
{ name: 'Joule (J)', id: 'joule', fn: SIPrefix('J') },
{ name: 'Electron volt (eV)', id: 'ev', fn: SIPrefix('eV') },
{ name: 'Ampere (A)', id: 'amp', fn: SIPrefix('A') },
{ name: 'Kiloampere (kA)', id: 'kamp', fn: SIPrefix('A', 1) },
{ name: 'Milliampere (mA)', id: 'mamp', fn: SIPrefix('A', -1) },
{ name: 'Volt (V)', id: 'volt', fn: SIPrefix('V') },
{ name: 'Kilovolt (kV)', id: 'kvolt', fn: SIPrefix('V', 1) },
{ name: 'Millivolt (mV)', id: 'mvolt', fn: SIPrefix('V', -1) },
{ name: 'Decibel-milliwatt (dBm)', id: 'dBm', fn: SIPrefix('dBm') },
{ name: 'Ohm (Ω)', id: 'ohm', fn: SIPrefix('Ω') },
{ name: 'Kiloohm (kΩ)', id: 'kohm', fn: SIPrefix('Ω', 1) },
{ name: 'Megaohm (MΩ)', id: 'Mohm', fn: SIPrefix('Ω', 2) },
{ name: 'Farad (F)', id: 'farad', fn: SIPrefix('F') },
{ name: 'Microfarad (µF)', id: 'µfarad', fn: SIPrefix('F', -2) },
{ name: 'Nanofarad (nF)', id: 'nfarad', fn: SIPrefix('F', -3) },
{ name: 'Picofarad (pF)', id: 'pfarad', fn: SIPrefix('F', -4) },
{ name: 'Femtofarad (fF)', id: 'ffarad', fn: SIPrefix('F', -5) },
{ name: 'Henry (H)', id: 'henry', fn: SIPrefix('H') },
{ name: 'Millihenry (mH)', id: 'mhenry', fn: SIPrefix('H', -1) },
{ name: 'Microhenry (µH)', id: 'µhenry', fn: SIPrefix('H', -2) },
{ name: 'Lumens (Lm)', id: 'lumens', fn: SIPrefix('Lm') },
],
},
{
@@ -239,50 +248,50 @@ export const getCategories = (): ValueFormatCategory[] => [
{
name: 'Force',
formats: [
{ name: 'Newton-meters (Nm)', id: 'forceNm', fn: decimalSIPrefix('Nm') },
{ name: 'Kilonewton-meters (kNm)', id: 'forcekNm', fn: decimalSIPrefix('Nm', 1) },
{ name: 'Newtons (N)', id: 'forceN', fn: decimalSIPrefix('N') },
{ name: 'Kilonewtons (kN)', id: 'forcekN', fn: decimalSIPrefix('N', 1) },
{ name: 'Newton-meters (Nm)', id: 'forceNm', fn: SIPrefix('Nm') },
{ name: 'Kilonewton-meters (kNm)', id: 'forcekNm', fn: SIPrefix('Nm', 1) },
{ name: 'Newtons (N)', id: 'forceN', fn: SIPrefix('N') },
{ name: 'Kilonewtons (kN)', id: 'forcekN', fn: SIPrefix('N', 1) },
],
},
{
name: 'Hash rate',
formats: [
{ name: 'hashes/sec', id: 'Hs', fn: decimalSIPrefix('H/s') },
{ name: 'kilohashes/sec', id: 'KHs', fn: decimalSIPrefix('H/s', 1) },
{ name: 'megahashes/sec', id: 'MHs', fn: decimalSIPrefix('H/s', 2) },
{ name: 'gigahashes/sec', id: 'GHs', fn: decimalSIPrefix('H/s', 3) },
{ name: 'terahashes/sec', id: 'THs', fn: decimalSIPrefix('H/s', 4) },
{ name: 'petahashes/sec', id: 'PHs', fn: decimalSIPrefix('H/s', 5) },
{ name: 'exahashes/sec', id: 'EHs', fn: decimalSIPrefix('H/s', 6) },
{ name: 'hashes/sec', id: 'Hs', fn: SIPrefix('H/s') },
{ name: 'kilohashes/sec', id: 'KHs', fn: SIPrefix('H/s', 1) },
{ name: 'megahashes/sec', id: 'MHs', fn: SIPrefix('H/s', 2) },
{ name: 'gigahashes/sec', id: 'GHs', fn: SIPrefix('H/s', 3) },
{ name: 'terahashes/sec', id: 'THs', fn: SIPrefix('H/s', 4) },
{ name: 'petahashes/sec', id: 'PHs', fn: SIPrefix('H/s', 5) },
{ name: 'exahashes/sec', id: 'EHs', fn: SIPrefix('H/s', 6) },
],
},
{
name: 'Mass',
formats: [
{ name: 'milligram (mg)', id: 'massmg', fn: decimalSIPrefix('g', -1) },
{ name: 'gram (g)', id: 'massg', fn: decimalSIPrefix('g') },
{ name: 'kilogram (kg)', id: 'masskg', fn: decimalSIPrefix('g', 1) },
{ name: 'milligram (mg)', id: 'massmg', fn: SIPrefix('g', -1) },
{ name: 'gram (g)', id: 'massg', fn: SIPrefix('g') },
{ name: 'kilogram (kg)', id: 'masskg', fn: SIPrefix('g', 1) },
{ name: 'metric ton (t)', id: 'masst', fn: toFixedUnit('t') },
],
},
{
name: 'Length',
formats: [
{ name: 'millimeter (mm)', id: 'lengthmm', fn: decimalSIPrefix('m', -1) },
{ name: 'millimeter (mm)', id: 'lengthmm', fn: SIPrefix('m', -1) },
{ name: 'feet (ft)', id: 'lengthft', fn: toFixedUnit('ft') },
{ name: 'meter (m)', id: 'lengthm', fn: decimalSIPrefix('m') },
{ name: 'kilometer (km)', id: 'lengthkm', fn: decimalSIPrefix('m', 1) },
{ name: 'meter (m)', id: 'lengthm', fn: SIPrefix('m') },
{ name: 'kilometer (km)', id: 'lengthkm', fn: SIPrefix('m', 1) },
{ name: 'mile (mi)', id: 'lengthmi', fn: toFixedUnit('mi') },
],
},
{
name: 'Pressure',
formats: [
{ name: 'Millibars', id: 'pressurembar', fn: decimalSIPrefix('bar', -1) },
{ name: 'Bars', id: 'pressurebar', fn: decimalSIPrefix('bar') },
{ name: 'Kilobars', id: 'pressurekbar', fn: decimalSIPrefix('bar', 1) },
{ name: 'Pascals', id: 'pressurepa', fn: decimalSIPrefix('Pa') },
{ name: 'Millibars', id: 'pressurembar', fn: SIPrefix('bar', -1) },
{ name: 'Bars', id: 'pressurebar', fn: SIPrefix('bar') },
{ name: 'Kilobars', id: 'pressurekbar', fn: SIPrefix('bar', 1) },
{ name: 'Pascals', id: 'pressurepa', fn: SIPrefix('Pa') },
{ name: 'Hectopascals', id: 'pressurehpa', fn: toFixedUnit('hPa') },
{ name: 'Kilopascals', id: 'pressurekpa', fn: toFixedUnit('kPa') },
{ name: 'Inches of mercury', id: 'pressurehg', fn: toFixedUnit('"Hg') },
@@ -292,26 +301,26 @@ export const getCategories = (): ValueFormatCategory[] => [
{
name: 'Radiation',
formats: [
{ name: 'Becquerel (Bq)', id: 'radbq', fn: decimalSIPrefix('Bq') },
{ name: 'curie (Ci)', id: 'radci', fn: decimalSIPrefix('Ci') },
{ name: 'Gray (Gy)', id: 'radgy', fn: decimalSIPrefix('Gy') },
{ name: 'rad', id: 'radrad', fn: decimalSIPrefix('rad') },
{ name: 'Sievert (Sv)', id: 'radsv', fn: decimalSIPrefix('Sv') },
{ name: 'milliSievert (mSv)', id: 'radmsv', fn: decimalSIPrefix('Sv', -1) },
{ name: 'microSievert (µSv)', id: 'radusv', fn: decimalSIPrefix('Sv', -2) },
{ name: 'rem', id: 'radrem', fn: decimalSIPrefix('rem') },
{ name: 'Exposure (C/kg)', id: 'radexpckg', fn: decimalSIPrefix('C/kg') },
{ name: 'roentgen (R)', id: 'radr', fn: decimalSIPrefix('R') },
{ name: 'Sievert/hour (Sv/h)', id: 'radsvh', fn: decimalSIPrefix('Sv/h') },
{ name: 'milliSievert/hour (mSv/h)', id: 'radmsvh', fn: decimalSIPrefix('Sv/h', -1) },
{ name: 'microSievert/hour (µSv/h)', id: 'radusvh', fn: decimalSIPrefix('Sv/h', -2) },
{ name: 'Becquerel (Bq)', id: 'radbq', fn: SIPrefix('Bq') },
{ name: 'curie (Ci)', id: 'radci', fn: SIPrefix('Ci') },
{ name: 'Gray (Gy)', id: 'radgy', fn: SIPrefix('Gy') },
{ name: 'rad', id: 'radrad', fn: SIPrefix('rad') },
{ name: 'Sievert (Sv)', id: 'radsv', fn: SIPrefix('Sv') },
{ name: 'milliSievert (mSv)', id: 'radmsv', fn: SIPrefix('Sv', -1) },
{ name: 'microSievert (µSv)', id: 'radusv', fn: SIPrefix('Sv', -2) },
{ name: 'rem', id: 'radrem', fn: SIPrefix('rem') },
{ name: 'Exposure (C/kg)', id: 'radexpckg', fn: SIPrefix('C/kg') },
{ name: 'roentgen (R)', id: 'radr', fn: SIPrefix('R') },
{ name: 'Sievert/hour (Sv/h)', id: 'radsvh', fn: SIPrefix('Sv/h') },
{ name: 'milliSievert/hour (mSv/h)', id: 'radmsvh', fn: SIPrefix('Sv/h', -1) },
{ name: 'microSievert/hour (µSv/h)', id: 'radusvh', fn: SIPrefix('Sv/h', -2) },
],
},
{
name: 'Rotational Speed',
formats: [
{ name: 'Revolutions per minute (rpm)', id: 'rotrpm', fn: toFixedUnit('rpm') },
{ name: 'Hertz (Hz)', id: 'rothz', fn: decimalSIPrefix('Hz') },
{ name: 'Hertz (Hz)', id: 'rothz', fn: SIPrefix('Hz') },
{ name: 'Radians per second (rad/s)', id: 'rotrads', fn: toFixedUnit('rad/s') },
{ name: 'Degrees per second (°/s)', id: 'rotdegs', fn: toFixedUnit('°/s') },
],
@@ -327,7 +336,7 @@ export const getCategories = (): ValueFormatCategory[] => [
{
name: 'Time',
formats: [
{ name: 'Hertz (1/s)', id: 'hertz', fn: decimalSIPrefix('Hz') },
{ name: 'Hertz (1/s)', id: 'hertz', fn: SIPrefix('Hz') },
{ name: 'nanoseconds (ns)', id: 'ns', fn: toNanoSeconds },
{ name: 'microseconds (µs)', id: 'µs', fn: toMicroSeconds },
{ name: 'milliseconds (ms)', id: 'ms', fn: toMilliSeconds },
@@ -371,8 +380,8 @@ export const getCategories = (): ValueFormatCategory[] => [
{
name: 'Volume',
formats: [
{ name: 'millilitre (mL)', id: 'mlitre', fn: decimalSIPrefix('L', -1) },
{ name: 'litre (L)', id: 'litre', fn: decimalSIPrefix('L') },
{ name: 'millilitre (mL)', id: 'mlitre', fn: SIPrefix('L', -1) },
{ name: 'litre (L)', id: 'litre', fn: SIPrefix('L') },
{ name: 'cubic meter', id: 'm3', fn: toFixedUnit('m³') },
{ name: 'Normal cubic meter', id: 'Nm3', fn: toFixedUnit('Nm³') },
{ name: 'cubic decimeter', id: 'dm3', fn: toFixedUnit('dm³') },

View File

@@ -3,7 +3,7 @@ import { toDuration as duration, toUtc, dateTime } from '../datetime/moment_wrap
import { toFixed, toFixedScaled, FormattedValue, ValueFormatter } from './valueFormats';
import { DecimalCount } from '../types/displayValue';
import { TimeZone } from '../types';
import { dateTimeFormat, dateTimeFormatTimeAgo, localTimeFormat } from '../datetime';
import { dateTimeFormat, dateTimeFormatTimeAgo, localTimeFormat, systemDateFormats } from '../datetime';
interface IntervalsInSeconds {
[interval: string]: number;
@@ -383,6 +383,15 @@ export function getDateTimeAsLocalFormat() {
);
}
export function dateTimeSystemFormatter(
value: number,
decimals: DecimalCount,
scaledDecimals: DecimalCount,
timeZone?: TimeZone
): FormattedValue {
return { text: dateTimeFormat(value, { format: systemDateFormats.fullDate, timeZone }) };
}
export function dateTimeFromNow(
value: number,
decimals: DecimalCount,

View File

@@ -53,7 +53,7 @@ export function getOffsetFromSIPrefix(c: string): number {
return 0;
}
export function binarySIPrefix(unit: string, offset = 0): ValueFormatter {
export function binaryPrefix(unit: string, offset = 0): ValueFormatter {
const prefixes = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'].slice(offset);
const units = prefixes.map(p => {
return ' ' + p + unit;
@@ -61,7 +61,7 @@ export function binarySIPrefix(unit: string, offset = 0): ValueFormatter {
return scaledUnits(1024, units);
}
export function decimalSIPrefix(unit: string, offset = 0): ValueFormatter {
export function SIPrefix(unit: string, offset = 0): ValueFormatter {
let prefixes = ['f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
prefixes = prefixes.slice(5 + (offset || 0));
const units = prefixes.map(p => {

View File

@@ -66,6 +66,19 @@ const formatTests: ValueFormatTest[] = [
// Time format
{ id: 'time:YYYY', decimals: 0, value: dateTime(new Date(1999, 6, 2)).valueOf(), result: '1999' },
{ id: 'time:YYYY.MM', decimals: 0, value: dateTime(new Date(2010, 6, 2)).valueOf(), result: '2010.07' },
{ id: 'dateTimeAsIso', decimals: 0, value: dateTime(new Date(2010, 6, 2)).valueOf(), result: '2010-07-02 00:00:00' },
{
id: 'dateTimeAsUS',
decimals: 0,
value: dateTime(new Date(2010, 6, 2)).valueOf(),
result: '07/02/2010 12:00:00 am',
},
{
id: 'dateTimeAsSystem',
decimals: 0,
value: dateTime(new Date(2010, 6, 2)).valueOf(),
result: '2010-07-02 00:00:00',
},
];
describe('valueFormats', () => {

View File

@@ -1,7 +1,7 @@
import { getCategories } from './categories';
import { DecimalCount } from '../types/displayValue';
import { toDateTimeValueFormatter } from './dateTimeFormatters';
import { getOffsetFromSIPrefix, decimalSIPrefix, currency } from './symbolFormatters';
import { getOffsetFromSIPrefix, SIPrefix, currency } from './symbolFormatters';
import { TimeZone } from '../types';
export interface FormattedValue {
@@ -213,7 +213,7 @@ export function getValueFormat(id?: string | null): ValueFormatter {
if (key === 'si') {
const offset = getOffsetFromSIPrefix(sub.charAt(0));
const unit = offset === 0 ? sub : sub.substring(1);
return decimalSIPrefix(unit, offset);
return SIPrefix(unit, offset);
}
if (key === 'count') {

View File

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

View File

@@ -153,4 +153,9 @@ export const Components = {
section: 'Search section',
items: 'Search items',
},
DashboardLinks: {
container: 'Dashboard link container',
dropDown: 'Dashboard link dropdown',
link: 'Dashboard link',
},
};

View File

@@ -133,4 +133,7 @@ export const Pages = {
navBar: () => '.explore-toolbar',
},
},
SoloPanel: {
url: (page: string) => `/d-solo/${page}`,
},
};

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e",
"version": "7.2.0-pre.0",
"version": "7.2.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.2.0-pre.0",
"@grafana/e2e-selectors": "7.2.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.2.0-pre.0",
"version": "7.2.2",
"description": "Grafana Runtime Library",
"keywords": [
"grafana",
@@ -22,8 +22,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@grafana/data": "7.2.0-pre.0",
"@grafana/ui": "7.2.0-pre.0",
"@grafana/data": "7.2.2",
"@grafana/ui": "7.2.2",
"systemjs": "0.20.19",
"systemjs-plugin-css": "0.1.37"
},

View File

@@ -56,7 +56,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
meta: false,
datasourceInsights: false,
reportGrid: false,
standaloneAlerts: false,
};
licenseInfo: LicenseInfo = {} as LicenseInfo;
rendererAvailable = false;

View File

@@ -122,6 +122,8 @@ export class DataSourceWithBackend<
/**
* Override to skip executing a query
*
* @returns false if the query should be skipped
*
* @virtual
*/
filterQuery?(query: TQuery): boolean;

View File

@@ -100,6 +100,10 @@ Available options:
This command creates a production-ready build of your plugin.
Available options:
- `--coverage` - Reports code coverage after the test step of the build.
## FAQ
### Which version of grafana-toolkit should I use?

View File

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

View File

@@ -139,8 +139,9 @@ export const run = (includeInternalScripts = false) => {
program
.command('plugin:build')
.description('Prepares plugin dist package')
.option('--coverage', 'Run code coverage', false)
.action(async cmd => {
await execTask(pluginBuildTask)({ coverage: false, silent: true });
await execTask(pluginBuildTask)({ coverage: cmd.coverage, silent: true });
});
program

View File

@@ -3,7 +3,6 @@ import execa = require('execa');
import * as fs from 'fs';
// @ts-ignore
import * as path from 'path';
import { resolve as resolvePath } from 'path';
import chalk from 'chalk';
import { useSpinner } from '../utils/useSpinner';
import { Task, TaskRunner } from './task';
@@ -89,13 +88,13 @@ const moveFiles = () => {
})();
};
const moveStaticFiles = async (pkg: any, cwd: string) => {
const moveStaticFiles = async (pkg: any) => {
if (pkg.name.endsWith('/ui')) {
const staticFiles = await globby(resolvePath(process.cwd(), 'src/**/*.+(png|svg|gif|jpg)'));
const staticFiles = await globby('src/**/*.{png,svg,gif,jpg}');
return useSpinner<void>(`Moving static files`, async () => {
const promises = staticFiles.map(file => {
return new Promise((resolve, reject) => {
fs.copyFile(file, `${cwd}/compiled/${file.replace(`${cwd}/src`, '')}`, (err: any) => {
fs.copyFile(file, file.replace(/^src/, 'compiled'), (err: any) => {
if (err) {
reject(err);
return;
@@ -130,7 +129,7 @@ const buildTaskRunner: TaskRunner<PackageBuildOptions> = async ({ scope }) => {
await clean();
await compile();
await moveStaticFiles(pkg, cwd);
await moveStaticFiles(pkg);
await rollup();
await preparePackage(pkg);
await moveFiles();

View File

@@ -37,6 +37,10 @@ const copyIfNonExistent = (srcPath: string, destPath: string) =>
export const prepare = useSpinner<void>('Preparing', async () => {
await Promise.all([
// Remove local dependencies for @grafana/data/node_modules
// See: https://github.com/grafana/grafana/issues/26748
rimraf(resolvePath(__dirname, 'node_modules/@grafana/data/node_modules')),
// Copy only if local tsconfig does not exist. Otherwise this will work, but have odd behavior
copyIfNonExistent(
resolvePath(__dirname, '../../config/tsconfig.plugin.local.json'),

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/ui",
"version": "7.2.0-pre.0",
"version": "7.2.2",
"description": "Grafana Components Library",
"keywords": [
"grafana",
@@ -27,8 +27,8 @@
},
"dependencies": {
"@emotion/core": "^10.0.27",
"@grafana/data": "7.2.0-pre.0",
"@grafana/e2e-selectors": "7.2.0-pre.0",
"@grafana/data": "7.2.2",
"@grafana/e2e-selectors": "7.2.2",
"@grafana/slate-react": "0.22.9-grafana",
"@grafana/tsconfig": "^1.0.0-rc1",
"@iconscout/react-unicons": "1.1.4",

View File

@@ -35,6 +35,7 @@ const buildCjsPackage = ({ env }) => {
'monaco-editor', // Monaco should not be used directly
'monaco-editor/esm/vs/editor/editor.api', // Monaco should not be used directly
'react-monaco-editor',
'jquery', // required to use jquery.plot, which is assigned externally
],
plugins: [
commonjs({

View File

@@ -1,11 +1,11 @@
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
import { Meta, Props } from '@storybook/addon-docs/blocks';
import { Cascader } from './Cascader';
<Meta title="MDX|Cascader" component={Cascader} />
# Cascader
The cascader component is a `Select` with a cascading flyout menu. When you have lots of options in your select, they can be hard to navigate from a regular dropdown list. In that case you can use the cascader to organize your options into groups hierarchically. Just like in the `Select` component, the cascader input doubles as a search field to quickly jump to a selection without navigating the list.
The cascader component is a `Select` with a cascading flyout menu. When you have lots of options in your select, they can be hard to navigate from a regular dropdown list. In that case you can use the cascader to organize your options into groups hierarchically. Just like in the `Select` component, the cascader input doubles as a search field to quickly jump to a selection without navigating the list.
You can either use the `Simple` cascader component for an empty input as default state or use the `initialValue` or `allowCustomValue` fields to pre-fill your cascader. Initial value means that one of the options from your cascaded list is pre-selected. Custom value means that apart from existing options from the list, your users can add custom values to the list by typing them in the `Select` input.

View File

@@ -1,5 +1,5 @@
import React, { FC, ReactNode, useState } from 'react';
import { css } from 'emotion';
import { css, cx } from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { useStyles } from '../../themes';
import { Icon } from '..';
@@ -13,14 +13,20 @@ export interface Props {
export const CollapsableSection: FC<Props> = ({ label, isOpen, children }) => {
const [open, toggleOpen] = useState<boolean>(isOpen);
const styles = useStyles(collapsableSectionStyles);
const headerClass = cx({
[styles.header]: true,
[styles.headerCollapsed]: !open,
});
const tooltip = `Click to ${open ? 'collapse' : 'expand'}`;
return (
<div>
<div onClick={() => toggleOpen(!open)} className={styles.header}>
<Icon name={open ? 'angle-down' : 'angle-right'} size="xl" />
<div onClick={() => toggleOpen(!open)} className={headerClass} title={tooltip}>
{label}
<Icon name={open ? 'angle-down' : 'angle-right'} size="xl" className={styles.icon} />
</div>
<div className={styles.content}>{open && children}</div>
{open && <div className={styles.content}>{children}</div>}
</div>
);
};
@@ -28,11 +34,19 @@ export const CollapsableSection: FC<Props> = ({ label, isOpen, children }) => {
const collapsableSectionStyles = (theme: GrafanaTheme) => {
return {
header: css`
display: flex;
justify-content: space-between;
font-size: ${theme.typography.size.lg};
cursor: pointer;
`,
headerCollapsed: css`
border-bottom: 1px solid ${theme.colors.border2};
`,
icon: css`
color: ${theme.colors.textWeak};
`,
content: css`
padding: ${theme.spacing.md} 0 ${theme.spacing.md} ${theme.spacing.md};
padding: ${theme.spacing.md} 0;
`,
};
};

View File

@@ -1,11 +1,31 @@
import { Meta, Props } from '@storybook/addon-docs/blocks';
import { ConfirmButton } from './ConfirmButton';
<Meta title="MDX|ConfirmButton" component={ConfirmButton} />
# ConfirmButton
The ConfirmButton is an interactive component that adds a double-confirm option to a clickable action. When clicked, the action is replaced by an inline confirmation with the option to cancel. In Grafana, this is used for example for editing values in settings tables.
The ConfirmButton is an interactive component that adds a double-confirm option to a clickable action. When clicked, the action is replaced by an inline confirmation with the option to cancel. In Grafana, this is used, for example, for editing values in settings tables.
## Variants
There are four variants of the `ConfirmButton`: primary, secondary, destructive, and link. The primary and secondary variants include a primary or secondary `Button` component. The primary and secondary variant should be used to confirm actions like saving or adding data. The destructive variant includes a destructive `Button` component. The destructive variant should be used to double-confirm a deletion or removal of an element. The link variant doesn't include any button and double-confirms as links instead.
Apart from the button variant, you can also modify the button size and the button text.
## Usage
```jsx
<ConfirmButton
closeOnConfirm
size='md'
confirmText='Are you sure?'
confirmVariant='secondary'
onConfirm={() => {
console.log('Action confirmed!')
}}
>
Click me
</ConfirmButton>
```
<Props of={ConfirmButton} />

View File

@@ -5,6 +5,7 @@ import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { action } from '@storybook/addon-actions';
import { Button } from '../Button';
import { DeleteButton } from './DeleteButton';
import mdx from './ConfirmButton.mdx';
const getKnobs = () => {
return {
@@ -31,6 +32,11 @@ export default {
component: ConfirmButton,
decorators: [withCenteredStory],
subcomponents: { DeleteButton },
parameters: {
docs: {
page: mdx,
},
},
};
export const basic = () => {

View File

@@ -53,15 +53,24 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
});
export interface Props extends Themeable {
/** Confirm action callback */
onConfirm(): void;
/** Custom button styles */
className?: string;
/** Button size */
size?: ComponentSize;
/** Text for the Confirm button */
confirmText?: string;
/** Disable button click action */
disabled?: boolean;
/** Variant of the Confirm button */
confirmVariant?: ButtonVariant;
/** Hide confirm actions when after of them is clicked */
closeOnConfirm?: boolean;
onConfirm(): void;
/** Optional on click handler for the original button */
onClick?(): void;
/** Callback for the cancel action */
onCancel?(): void;
}
@@ -70,13 +79,6 @@ interface State {
}
class UnThemedConfirmButton extends PureComponent<Props, State> {
static defaultProps: Partial<Props> = {
size: 'md',
confirmText: 'Save',
disabled: false,
confirmVariant: 'primary',
};
state: State = {
showConfirm: false,
};
@@ -106,7 +108,7 @@ class UnThemedConfirmButton extends PureComponent<Props, State> {
this.props.onCancel();
}
};
onConfirm = (event: SyntheticEvent) => {
onConfirm = () => {
this.props.onConfirm();
if (this.props.closeOnConfirm) {
this.setState({
@@ -166,4 +168,13 @@ class UnThemedConfirmButton extends PureComponent<Props, State> {
}
export const ConfirmButton = withTheme(UnThemedConfirmButton);
// Declare defaultProps directly on the themed component so they are displayed
// in the props table
ConfirmButton.defaultProps = {
size: 'md',
confirmText: 'Save',
disabled: false,
confirmVariant: 'primary',
};
ConfirmButton.displayName = 'ConfirmButton';

View File

@@ -4,9 +4,12 @@ import { ComponentSize } from '../../types/size';
import { Button } from '../Button';
export interface Props {
size?: ComponentSize;
disabled?: boolean;
/** Confirm action callback */
onConfirm(): void;
/** Button size */
size?: ComponentSize;
/** Disable button click action */
disabled?: boolean;
}
export const DeleteButton: FC<Props> = ({ size, disabled, onConfirm }) => {

View File

@@ -7,4 +7,18 @@ import { ConfirmModal } from './ConfirmModal';
Used to request user for action confirmation, e.g. deleting items. Triggers provided 'onConfirm' callback.
# Usage
```jsx
<ConfirmModal
isOpen={false}
title='Delete user'
body='Are you sure you want to delete this user?'
confirmText='Confirm'
icon='exclamation-triangle'
onConfirm={() => console.log('Confirm action')}
onDismiss={() => console.log('Dismiss action')}
/>
```
<Props of={ConfirmModal} />

View File

@@ -8,13 +8,21 @@ import { GrafanaTheme } from '@grafana/data';
import { HorizontalGroup } from '..';
export interface Props {
/** Toggle modal's open/closed state */
isOpen: boolean;
/** Title for the modal header */
title: string;
/** Modal content */
body: React.ReactNode;
/** Text for confirm button */
confirmText: string;
/** Text for dismiss button */
dismissText?: string;
/** Icon for the modal header */
icon?: IconName;
/** Confirm action callback */
onConfirm(): void;
/** Dismiss action callback */
onDismiss(): void;
}

View File

@@ -154,7 +154,7 @@ export class Select<T> extends PureComponent<LegacySelectProps<T>> {
onBlur={onBlur}
openMenuOnFocus={openMenuOnFocus}
maxMenuHeight={maxMenuHeight}
noOptionsMessage={() => noOptionsMessage}
noOptionsMessage={noOptionsMessage}
isMulti={isMulti}
backspaceRemovesValue={backspaceRemovesValue}
menuIsOpen={isOpen}

View File

@@ -19,6 +19,7 @@ export interface IconProps extends React.HTMLAttributes<HTMLDivElement> {
const getIconStyles = stylesFactory((theme: GrafanaTheme) => {
return {
container: css`
label: Icon;
display: inline-block;
`,
icon: css`

View File

@@ -21,7 +21,7 @@ To add more context to the input you can add either text or an icon before or af
## Usage in forms with Field
`Input` should be used with the `Field` component to get labels and descriptions. It should also be used for validation. See the `Field` component for more information.
`Input` should be used with the `Field` component to get labels and descriptions. It can also be used for validation by using the `required` attribute. See the `Field` component for more information.
```jsx
<Field label="Important information" description="This information is very important, so you really need to fill it in">

View File

@@ -1 +1,5 @@
export { Controller as InputControl } from 'react-hook-form';
/**
* Rollup does not support renamed exports so do not change this to export { Controller as InputControl } ...
*/
import { Controller } from 'react-hook-form';
export const InputControl = Controller;

View File

@@ -119,6 +119,7 @@ const getStyles = stylesFactory(
return {
layout: css`
label: HorizontalGroup;
display: flex;
flex-direction: ${orientation === Orientation.Vertical ? 'column' : 'row'};
flex-wrap: ${wrap ? 'wrap' : 'nowrap'};

View File

@@ -20,7 +20,7 @@ export const fieldNameByRegexMatcherItem: FieldMatcherUIRegistryItem<string> = {
id: FieldMatcherID.byRegexp,
component: FieldNameByRegexMatcherEditor,
matcher: fieldMatchers.get(FieldMatcherID.byRegexp),
name: 'Filter by field using regex',
description: 'Set properties for fields with names matching provided regex',
name: 'Fields with name matching regex',
description: 'Set properties for fields with names matching a regex',
optionsToLabel: options => options,
};

View File

@@ -26,8 +26,8 @@ export const fieldNameMatcherItem: FieldMatcherUIRegistryItem<string> = {
id: FieldMatcherID.byName,
component: FieldNameMatcherEditor,
matcher: fieldMatchers.get(FieldMatcherID.byName),
name: 'Filter by field',
description: 'Set properties for fields matching the name',
name: 'Fields with name',
description: 'Set properties for a specific field',
optionsToLabel: options => options,
};

View File

@@ -79,7 +79,7 @@ export const fieldTypeMatcherItem: FieldMatcherUIRegistryItem<string> = {
id: FieldMatcherID.byType,
component: FieldTypeMatcherEditor,
matcher: fieldMatchers.get(FieldMatcherID.byType),
name: 'Filter by type',
description: 'Set properties for fields matching a type',
name: 'Fields with type',
description: 'Set properties for fields of a specific type (number, string, boolean)',
optionsToLabel: options => options,
};

View File

@@ -23,7 +23,7 @@ export const ColorValueEditor: React.FC<FieldConfigEditorProps<string, ColorFiel
const color = value || (item.defaultValue as string) || theme.colors.panelBg;
return (
<ColorPicker color={color} onChange={onChange} enableNamedColors={!settings.disableNamedColors}>
<ColorPicker color={color} onChange={onChange} enableNamedColors={!settings?.disableNamedColors}>
{({ ref, showColorPicker, hideColorPicker }) => {
return (
<div className={styles.spot} onBlur={hideColorPicker}>
@@ -36,9 +36,9 @@ export const ColorValueEditor: React.FC<FieldConfigEditorProps<string, ColorFiel
/>
</div>
<div className={styles.colorText} onClick={showColorPicker}>
{value ?? settings.textWhenUndefined ?? 'Pick Color'}
{value ?? settings?.textWhenUndefined ?? 'Pick Color'}
</div>
{value && settings.allowUndefined && (
{value && settings?.allowUndefined && (
<Icon className={styles.trashIcon} name="trash-alt" onClick={() => onChange(undefined)} />
)}
</div>

View File

@@ -17,6 +17,6 @@ export class ValueMappingsValueEditor extends React.PureComponent<
value = [];
}
return <ValueMappingsEditor valueMappings={value} onChange={onChange} />;
return <ValueMappingsEditor value={value} onChange={onChange} />;
}
}

View File

@@ -24,7 +24,7 @@ export class SelectValueEditor<T> extends React.PureComponent<Props<T>, State<T>
const now = this.props.item?.settings;
if (old !== now) {
this.updateOptions();
} else if (now.getOptions) {
} else if (now?.getOptions) {
const old = oldProps.context?.data;
const now = this.props.context?.data;
if (old !== now) {
@@ -53,7 +53,6 @@ export class SelectValueEditor<T> extends React.PureComponent<Props<T>, State<T>
const { value, onChange, item } = this.props;
const { settings } = item;
const { allowCustomValue } = settings;
let current = options.find(v => v.value === value);
if (!current && value) {
current = {
@@ -66,7 +65,7 @@ export class SelectValueEditor<T> extends React.PureComponent<Props<T>, State<T>
isLoading={isLoading}
value={current}
defaultValue={value}
allowCustomValue={allowCustomValue}
allowCustomValue={settings?.allowCustomValue}
onChange={e => onChange(e.value)}
options={options}
/>

View File

@@ -31,7 +31,7 @@ export const StringValueEditor: React.FC<FieldConfigEditorProps<string, StringFi
<Component
placeholder={item.settings?.placeholder}
defaultValue={value || ''}
rows={item.settings?.useTextarea && item.settings.rows}
rows={(item.settings?.useTextarea && item.settings.rows) || 5}
onBlur={onValueChange}
onKeyDown={onValueChange}
/>

View File

@@ -11,6 +11,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
return {
menu: css`
label: grafana-select-menu;
background: ${bgColor};
box-shadow: 0px 4px 4px ${menuShadowColor};
position: relative;
@@ -18,6 +19,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
z-index: 1;
`,
option: css`
label: grafana-select-option;
padding: 8px;
display: flex;
align-items: center;
@@ -31,22 +33,26 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
}
`,
optionImage: css`
label: grafana-select-option-image;
width: 16px;
margin-right: 10px;
`,
optionDescription: css`
label: grafana-select-option-description;
font-weight: normal;
font-size: ${theme.typography.size.sm};
color: ${theme.colors.textWeak};
white-space: normal;
`,
optionBody: css`
label: grafana-select-option-body;
display: flex;
font-weight: ${theme.typography.weight.semibold};
flex-direction: column;
flex-grow: 1;
`,
optionFocused: css`
label: grafana-select-option-focused;
background: ${optionBgHover};
border-image: linear-gradient(#f05a28 30%, #fbca0a 99%);
border-image-slice: 1;
@@ -57,6 +63,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
border-left-width: 2px;
`,
singleValue: css`
label: grafana-select-single-value;
color: ${theme.colors.formInputText};
white-space: nowrap;
overflow: hidden;
@@ -65,6 +72,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
max-width: 100%;
`,
valueContainer: css`
label: grafana-select-value-container;
align-items: center;
display: flex;
position: relative;
@@ -74,14 +82,17 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
overflow: hidden;
`,
valueContainerMulti: css`
label: grafana-select-value-container-multi;
flex-wrap: wrap;
`,
loadingMessage: css`
label: grafana-select-loading-message;
padding: ${theme.spacing.sm};
text-align: center;
width: 100%;
`,
multiValueContainer: css`
label: grafana-select-multi-value-container;
display: flex;
align-items: center;
line-height: 1;
@@ -93,6 +104,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
font-size: ${theme.typography.size.sm};
`,
multiValueRemove: css`
label: grafana-select-multi-value-remove;
margin: 0 ${theme.spacing.xs};
cursor: pointer;
`,

View File

@@ -7,8 +7,8 @@ import { MappingType, RangeMap, SelectableValue, ValueMap, ValueMapping } from '
export interface Props {
valueMapping: ValueMapping;
updateValueMapping: (valueMapping: ValueMapping) => void;
removeValueMapping: () => void;
onUpdate: (value: ValueMapping) => void;
onRemove: () => void;
}
const MAPPING_OPTIONS: Array<SelectableValue<MappingType>> = [
@@ -16,27 +16,27 @@ const MAPPING_OPTIONS: Array<SelectableValue<MappingType>> = [
{ value: MappingType.RangeToText, label: 'Range' },
];
export const MappingRow: React.FC<Props> = ({ valueMapping, updateValueMapping, removeValueMapping }) => {
export const MappingRow: React.FC<Props> = ({ valueMapping, onUpdate, onRemove }) => {
const { type } = valueMapping;
const onMappingValueChange = (value: string) => {
updateValueMapping({ ...valueMapping, value: value });
onUpdate({ ...valueMapping, value: value });
};
const onMappingFromChange = (value: string) => {
updateValueMapping({ ...valueMapping, from: value });
onUpdate({ ...valueMapping, from: value });
};
const onMappingToChange = (value: string) => {
updateValueMapping({ ...valueMapping, to: value });
onUpdate({ ...valueMapping, to: value });
};
const onMappingTextChange = (value: string) => {
updateValueMapping({ ...valueMapping, text: value });
onUpdate({ ...valueMapping, text: value });
};
const onMappingTypeChange = (mappingType: MappingType) => {
updateValueMapping({ ...valueMapping, type: mappingType });
onUpdate({ ...valueMapping, type: mappingType });
};
const onKeyDown = (handler: (value: string) => void) => (e: React.KeyboardEvent<HTMLInputElement>) => {
@@ -103,7 +103,7 @@ export const MappingRow: React.FC<Props> = ({ valueMapping, updateValueMapping,
const label = (
<HorizontalGroup justify="space-between" align="center">
<Label>Mapping type</Label>
<IconButton name="times" onClick={removeValueMapping} aria-label="ValueMappingsEditor remove button" />
<IconButton name="times" onClick={onRemove} aria-label="ValueMappingsEditor remove button" />
</HorizontalGroup>
);
return (

View File

@@ -8,5 +8,5 @@ export default {
};
export const basic = () => {
return <ValueMappingsEditor valueMappings={[]} onChange={action('Mapping changed')} />;
return <ValueMappingsEditor value={[]} onChange={action('Mapping changed')} />;
};

View File

@@ -11,7 +11,7 @@ const setup = (spy?: any, propOverrides?: object) => {
spy(mappings);
}
},
valueMappings: [
value: [
{ id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' },
{ id: 2, type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
],
@@ -64,11 +64,11 @@ describe('Next id to add', () => {
]);
});
it('should default to 0', () => {
it('should default to id 1', () => {
const onChangeSpy = jest.fn();
const wrapper = setup(onChangeSpy, { valueMappings: [] });
const wrapper = setup(onChangeSpy, { value: [] });
const add = wrapper.find('*[aria-label="ValueMappingsEditor add mapping button"]');
add.at(0).simulate('click');
expect(onChangeSpy).toBeCalledWith([{ id: 0, type: MappingType.ValueToText, from: '', to: '', text: '' }]);
expect(onChangeSpy).toBeCalledWith([{ id: 1, type: MappingType.ValueToText, from: '', to: '', text: '' }]);
});
});

View File

@@ -4,65 +4,48 @@ import { Button } from '../Button/Button';
import { MappingRow } from './MappingRow';
export interface Props {
valueMappings?: ValueMapping[];
value: ValueMapping[];
onChange: (valueMappings: ValueMapping[]) => void;
}
export const ValueMappingsEditor: React.FC<Props> = ({ valueMappings, onChange, children }) => {
export const ValueMappingsEditor: React.FC<Props> = ({ value, onChange, children }) => {
const onAdd = () => {
let update = valueMappings;
const defaultMapping = {
type: MappingType.ValueToText,
from: '',
to: '',
text: '',
};
const id = update && update.length > 0 ? Math.max(...update.map(v => v.id)) + 1 : 0;
if (update) {
update.push({
const id = Math.max(...value.map(v => v.id), 0) + 1;
onChange([
...value,
{
id,
...defaultMapping,
});
} else {
update = [
{
id,
...defaultMapping,
},
];
}
onChange(update);
},
]);
};
const onRemove = (index: number) => {
const update = valueMappings;
update!.splice(index, 1);
onChange(update!);
onChange(value.filter((_, i) => i !== index));
};
const onMappingChange = (index: number, value: ValueMapping) => {
const update = valueMappings;
update![index] = value;
onChange(update!);
const onMappingChange = (update: ValueMapping) => {
onChange(value.map(item => (item.id === update.id ? update : item)));
};
return (
<>
{valueMappings && valueMappings.length > 0 && (
<>
{valueMappings.length > 0 &&
valueMappings.map((valueMapping, index) => (
<MappingRow
key={`${valueMapping.text}-${index}`}
valueMapping={valueMapping}
updateValueMapping={value => onMappingChange(index, value)}
removeValueMapping={() => onRemove(index)}
/>
))}
</>
)}
{value.map((valueMapping, index) => (
<MappingRow
key={`${valueMapping.text}-${index}`}
valueMapping={valueMapping}
onUpdate={onMappingChange}
onRemove={() => onRemove(index)}
/>
))}
<Button
size="sm"
icon="plus"

View File

@@ -152,7 +152,7 @@ export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>>
const repeaterStyle: React.CSSProperties = {
display: 'flex',
overflow: minVizHeight ? 'hidden scroll' : 'visible',
overflow: minVizHeight ? 'hidden auto' : 'hidden',
};
let vizHeight = height;

View File

@@ -146,7 +146,7 @@ export const getStandardFieldConfigs = () => {
{ value: 80, color: 'red' },
],
},
shouldApply: field => field.type === FieldType.number,
shouldApply: () => true,
category: ['Thresholds'],
getItemsCount: value => (value ? value.steps.length : 0),
};

View File

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

View File

@@ -130,6 +130,10 @@ const getStyles = createStyle((theme: Theme) => {
font-size: 1.78em;
margin-right: 0.15em;
`,
TracePageHeaderTraceId: css`
label: TracePageHeaderTraceId;
white-space: nowrap;
`,
};
});
@@ -238,7 +242,7 @@ export default function TracePageHeader(props: TracePageHeaderEmbedProps) {
const title = (
<h1 className={cx(styles.TracePageHeaderTitle, canCollapse && styles.TracePageHeaderTitleCollapsible)}>
<TraceName traceName={getTraceName(trace.spans)} />{' '}
<small className={uTxMuted}>{trace.traceID.slice(0, 7)}</small>
<small className={cx(styles.TracePageHeaderTraceId, uTxMuted)}>{trace.traceID}</small>
</h1>
);

View File

@@ -225,16 +225,26 @@ export default function SpanDetail(props: SpanDetailProps) {
label="Stack trace"
data={stackTraces}
isOpen={isStackTracesOpen}
TextComponent={textComponentProps => (
<TextArea
className={styles.Textarea}
style={{ cursor: 'unset' }}
readOnly
cols={10}
rows={10}
value={textComponentProps.data}
/>
)}
TextComponent={textComponentProps => {
let text;
if (textComponentProps.data?.length > 1) {
text = textComponentProps.data
.map((stackTrace, index) => `StackTrace ${index + 1}:\n${stackTrace}`)
.join('\n');
} else {
text = textComponentProps.data?.[0];
}
return (
<TextArea
className={styles.Textarea}
style={{ cursor: 'unset' }}
readOnly
cols={10}
rows={10}
value={text}
/>
);
}}
onToggle={() => stackTracesToggle(spanID)}
/>
)}

View File

@@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/util"
)
func ValidateOrgAlert(c *models.ReqContext) {
@@ -287,13 +288,12 @@ func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotific
}
if err := bus.Dispatch(&cmd); err != nil {
if err == models.ErrAlertNotificationNotFound {
return Error(404, err.Error(), err)
}
return Error(500, "Failed to update alert notification", err)
}
if cmd.Result == nil {
return Error(404, "Alert notification not found", nil)
}
query := models.GetAlertNotificationsQuery{
OrgId: c.OrgId,
Id: cmd.Id,
@@ -316,13 +316,12 @@ func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNo
}
if err := bus.Dispatch(&cmd); err != nil {
if err == models.ErrAlertNotificationNotFound {
return Error(404, err.Error(), nil)
}
return Error(500, "Failed to update alert notification", err)
}
if cmd.Result == nil {
return Error(404, "Alert notification not found", nil)
}
query := models.GetAlertNotificationsWithUidQuery{
OrgId: cmd.OrgId,
Uid: cmd.Uid,
@@ -390,6 +389,9 @@ func DeleteAlertNotification(c *models.ReqContext) Response {
}
if err := bus.Dispatch(&cmd); err != nil {
if err == models.ErrAlertNotificationNotFound {
return Error(404, err.Error(), nil)
}
return Error(500, "Failed to delete alert notification", err)
}
@@ -403,10 +405,16 @@ func DeleteAlertNotificationByUID(c *models.ReqContext) Response {
}
if err := bus.Dispatch(&cmd); err != nil {
if err == models.ErrAlertNotificationNotFound {
return Error(404, err.Error(), nil)
}
return Error(500, "Failed to delete alert notification", err)
}
return Success("Notification deleted")
return JSON(200, util.DynMap{
"message": "Notification deleted",
"id": cmd.DeletedAlertNotificationId,
})
}
//POST /api/alert-notifications/test

View File

@@ -436,8 +436,5 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/api/snapshots-delete/:deleteKey", reqSnapshotPublicModeOrSignedIn, Wrap(DeleteDashboardSnapshotByDeleteKey))
r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot))
// Health check
r.Get("/api/health", hs.healthHandler)
r.Get("/*", reqSignedIn, hs.Index)
}

View File

@@ -336,7 +336,12 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
Delims: macaron.Delims{Left: "[[", Right: "]]"},
}))
// These endpoints are used for monitoring the Grafana instance
// and should not be redirect or rejected.
m.Use(hs.healthzHandler)
m.Use(hs.apiHealthHandler)
m.Use(hs.metricsEndpoint)
m.Use(middleware.GetContextHandler(
hs.AuthTokenService,
hs.RemoteCacheService,
@@ -375,7 +380,24 @@ func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) {
ServeHTTP(ctx.Resp, ctx.Req.Request)
}
func (hs *HTTPServer) healthHandler(ctx *macaron.Context) {
// healthzHandler always return 200 - Ok if Grafana's web server is running
func (hs *HTTPServer) healthzHandler(ctx *macaron.Context) {
notHeadOrGet := ctx.Req.Method != http.MethodGet && ctx.Req.Method != http.MethodHead
if notHeadOrGet || ctx.Req.URL.Path != "/healthz" {
return
}
ctx.WriteHeader(200)
_, err := ctx.Resp.Write([]byte("Ok"))
if err != nil {
hs.log.Error("could not write to response", "err", err)
}
}
// apiHealthHandler will return ok if Grafana's web server is running and it
// can access the database. If the database cannot be access it will return
// http status code 503.
func (hs *HTTPServer) apiHealthHandler(ctx *macaron.Context) {
notHeadOrGet := ctx.Req.Method != http.MethodGet && ctx.Req.Method != http.MethodHead
if notHeadOrGet || ctx.Req.URL.Path != "/api/health" {
return

View File

@@ -327,10 +327,6 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
Children: []*dtos.NavLink{},
})
sort.SliceStable(navTree, func(i, j int) bool {
return navTree[i].SortWeight < navTree[j].SortWeight
})
return navTree, nil
}
@@ -434,6 +430,10 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
hs.HooksService.RunIndexDataHooks(&data, c)
sort.SliceStable(data.NavTree, func(i, j int) bool {
return data.NavTree[i].SortWeight < data.NavTree[j].SortWeight
})
return &data, nil
}

View File

@@ -249,7 +249,7 @@ func (hs *HTTPServer) loginUserWithUser(user *models.User, c *models.ReqContext)
}
hs.log.Info("Successful Login", "User", user.Email)
middleware.WriteSessionCookie(c, userToken.UnhashedToken, hs.Cfg.LoginMaxLifetimeDays)
middleware.WriteSessionCookie(c, userToken.UnhashedToken, hs.Cfg.LoginMaxLifetime)
return nil
}

View File

@@ -123,7 +123,10 @@ func inviteExistingUserToOrg(c *models.ReqContext, user *models.User, inviteDto
}
}
return Success(fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName))
return JSON(200, util.DynMap{
"message": fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName),
"userId": user.Id,
})
}
func RevokeInvite(c *models.ReqContext) Response {

View File

@@ -4,6 +4,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util"
)
// POST /api/org/users
@@ -35,12 +36,18 @@ func addOrgUserHelper(cmd models.AddOrgUserCommand) Response {
if err := bus.Dispatch(&cmd); err != nil {
if err == models.ErrOrgUserAlreadyAdded {
return Error(409, "User is already member of this organization", nil)
return JSON(409, util.DynMap{
"message": "User is already member of this organization",
"userId": cmd.UserId,
})
}
return Error(500, "Could not add user to organization", err)
}
return Success("User added to organization")
return JSON(200, util.DynMap{
"message": "User added to organization",
"userId": cmd.UserId,
})
}
// GET /api/org/users

View File

@@ -22,22 +22,24 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
SecureJsonData: ds.SecureJsonData.Decrypt(),
}
interpolatedURL, err := InterpolateString(route.URL, data)
if err != nil {
logger.Error("Error interpolating proxy url", "error", err)
return
}
if len(route.URL) > 0 {
interpolatedURL, err := InterpolateString(route.URL, data)
if err != nil {
logger.Error("Error interpolating proxy url", "error", err)
return
}
routeURL, err := url.Parse(interpolatedURL)
if err != nil {
logger.Error("Error parsing plugin route url", "error", err)
return
}
routeURL, err := url.Parse(interpolatedURL)
if err != nil {
logger.Error("Error parsing plugin route url", "error", err)
return
}
req.URL.Scheme = routeURL.Scheme
req.URL.Host = routeURL.Host
req.Host = routeURL.Host
req.URL.Path = util.JoinURLFragments(routeURL.Path, proxyPath)
req.URL.Scheme = routeURL.Scheme
req.URL.Host = routeURL.Host
req.Host = routeURL.Host
req.URL.Path = util.JoinURLFragments(routeURL.Path, proxyPath)
}
if err := addQueryString(req, route, data); err != nil {
logger.Error("Failed to render plugin URL query string", "error", err)

View File

@@ -67,6 +67,10 @@ func TestDSRouteRule(t *testing.T) {
{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
},
},
{
Path: "api/restricted",
ReqRole: models.ROLE_ADMIN,
},
},
}
@@ -116,6 +120,17 @@ func TestDSRouteRule(t *testing.T) {
})
})
Convey("When matching route path with no url", func() {
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{})
So(err, ShouldBeNil)
proxy.route = plugin.Routes[4]
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
Convey("Should not replace request url", func() {
So(req.URL.String(), ShouldEqual, "http://localhost/asd")
})
})
Convey("Validating request", func() {
Convey("plugin route with valid role", func() {
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", &setting.Cfg{})

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

@@ -0,0 +1,31 @@
package metricutil
import (
"errors"
"fmt"
"strings"
)
// SanitizeLabelName removes all invalid chars from the label name.
// If the label name is empty or contains only invalid chars, it
// will return an error.
func SanitizeLabelName(name string) (string, error) {
if len(name) == 0 {
return "", errors.New("label name cannot be empty")
}
out := strings.Builder{}
for i, b := range name {
if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0) {
out.WriteRune(b)
} else if b == ' ' {
out.WriteRune('_')
}
}
if out.Len() == 0 {
return "", fmt.Errorf("label name only contains invalid chars: %q", name)
}
return out.String(), nil
}

View File

@@ -0,0 +1,32 @@
package metricutil
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLabelNameSanitization(t *testing.T) {
testcases := []struct {
input string
expected string
err bool
}{
{input: "job", expected: "job"},
{input: "job._loal['", expected: "job_loal"},
{input: "", expected: "", err: true},
{input: ";;;", expected: "", err: true},
{input: "Data source", expected: "Data_source"},
}
for _, tc := range testcases {
got, err := SanitizeLabelName(tc.input)
if tc.err {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tc.expected, got)
}
}
}

View File

@@ -261,22 +261,21 @@ func rotateEndOfRequestFunc(ctx *models.ReqContext, authTokenService models.User
}
if rotated {
WriteSessionCookie(ctx, token.UnhashedToken, setting.LoginMaxLifetimeDays)
WriteSessionCookie(ctx, token.UnhashedToken, setting.LoginMaxLifetime)
}
}
}
func WriteSessionCookie(ctx *models.ReqContext, value string, maxLifetimeDays int) {
func WriteSessionCookie(ctx *models.ReqContext, value string, maxLifetime time.Duration) {
if setting.Env == setting.DEV {
ctx.Logger.Info("New token", "unhashed token", value)
}
var maxAge int
if maxLifetimeDays <= 0 {
if maxLifetime <= 0 {
maxAge = -1
} else {
maxAgeHours := (time.Duration(setting.LoginMaxLifetimeDays) * 24 * time.Hour) + time.Hour
maxAge = int(maxAgeHours.Seconds())
maxAge = int(maxLifetime.Seconds())
}
WriteCookie(ctx.Resp, setting.LoginCookieName, url.QueryEscape(value), maxAge, newCookieOptions)

View File

@@ -17,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/gtime"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/remotecache"
authproxy "github.com/grafana/grafana/pkg/middleware/auth_proxy"
@@ -253,8 +254,7 @@ func TestMiddlewareContext(t *testing.T) {
return true, nil
}
maxAgeHours := (time.Duration(setting.LoginMaxLifetimeDays) * 24 * time.Hour)
maxAge := (maxAgeHours + time.Hour).Seconds()
maxAge := int(setting.LoginMaxLifetime.Seconds())
sameSitePolicies := []http.SameSite{
http.SameSiteNoneMode,
@@ -272,7 +272,7 @@ func TestMiddlewareContext(t *testing.T) {
Value: "rotated",
Path: expectedCookiePath,
HttpOnly: true,
MaxAge: int(maxAge),
MaxAge: maxAge,
Secure: setting.CookieSecure,
SameSite: sameSitePolicy,
}
@@ -303,7 +303,7 @@ func TestMiddlewareContext(t *testing.T) {
Value: "rotated",
Path: expectedCookiePath,
HttpOnly: true,
MaxAge: int(maxAge),
MaxAge: maxAge,
Secure: setting.CookieSecure,
}
@@ -546,7 +546,7 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc) {
defer bus.ClearBusHandlers()
setting.LoginCookieName = "grafana_session"
setting.LoginMaxLifetimeDays = 30
setting.LoginMaxLifetime, _ = gtime.ParseInterval("30d")
sc := &scenarioContext{}
@@ -637,7 +637,7 @@ func TestTokenRotationAtEndOfRequest(t *testing.T) {
func initTokenRotationTest(ctx context.Context) (*models.ReqContext, *httptest.ResponseRecorder, error) {
setting.LoginCookieName = "login_token"
setting.LoginMaxLifetimeDays = 7
setting.LoginMaxLifetime, _ = gtime.ParseInterval("7d")
rr := httptest.NewRecorder()
req, err := http.NewRequestWithContext(ctx, "", "", nil)

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