Compare commits

...

19 Commits

Author SHA1 Message Date
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
59 changed files with 382 additions and 503 deletions

11
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
@@ -50,6 +49,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 +67,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 +80,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

23
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=
@@ -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=
@@ -529,6 +529,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 +695,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 +713,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 +747,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 +1028,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 +1106,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 +1239,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 +1669,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-beta.2"
"version": "7.2.0"
}

View File

@@ -3,7 +3,7 @@
"license": "Apache-2.0",
"private": true,
"name": "grafana",
"version": "7.2.0-beta2",
"version": "7.2.0",
"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-beta.2",
"version": "7.2.0",
"description": "Grafana Data Library",
"keywords": [
"typescript"

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

@@ -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

@@ -20,6 +20,7 @@ import {
toNanoSeconds,
toSeconds,
toTimeTicks,
dateTimeSystemFormatter,
} from './dateTimeFormatters';
import { toHex, sci, toHex0x, toPercent, toPercentUnit } from './arithmeticFormatters';
import { binaryPrefix, currency, SIPrefix } from './symbolFormatters';
@@ -184,6 +185,7 @@ 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 },
],
},

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

@@ -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

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

View File

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

View File

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

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

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/ui",
"version": "7.2.0-beta.2",
"version": "7.2.0",
"description": "Grafana Components Library",
"keywords": [
"grafana",
@@ -27,8 +27,8 @@
},
"dependencies": {
"@emotion/core": "^10.0.27",
"@grafana/data": "7.2.0-beta.2",
"@grafana/e2e-selectors": "7.2.0-beta.2",
"@grafana/data": "7.2.0",
"@grafana/e2e-selectors": "7.2.0",
"@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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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-beta.2",
"version": "7.2.0",
"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-beta.2",
"@grafana/ui": "7.2.0-beta.2",
"@grafana/data": "7.2.0",
"@grafana/ui": "7.2.0",
"@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

@@ -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

@@ -58,6 +58,7 @@ Structs used to build a custom Google Hangouts Chat message card.
See: https://developers.google.com/hangouts/chat/reference/message-formats/cards
*/
type outerStruct struct {
PreviewText string `json:"previewText"`
FallbackText string `json:"fallbackText"`
Cards []card `json:"cards"`
}
@@ -195,6 +196,7 @@ func (gcn *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error {
// nest the required structs
res1D := &outerStruct{
PreviewText: evalContext.GetNotificationTitle(),
FallbackText: evalContext.GetNotificationTitle(),
Cards: []card{
{

View File

@@ -1,6 +1,6 @@
{
"name": "@grafana-plugins/input-datasource",
"version": "7.2.0-beta.2",
"version": "7.2.0",
"description": "Input Datasource",
"private": true,
"repository": {
@@ -16,9 +16,9 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"devDependencies": {
"@grafana/data": "7.2.0-beta.2",
"@grafana/toolkit": "7.2.0-beta.2",
"@grafana/ui": "7.2.0-beta.2"
"@grafana/data": "7.2.0",
"@grafana/toolkit": "7.2.0",
"@grafana/ui": "7.2.0"
},
"volta": {
"node": "12.16.2"

View File

@@ -68,10 +68,10 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({
<div className={styles.header}>
<div className={styles.titleWrapper} onClick={onRowToggle} aria-label="Query operation row title">
<Icon name={isContentVisible ? 'angle-down' : 'angle-right'} className={styles.collapseIcon} />
{title && <span className={styles.title}>{titleElement}</span>}
{title && <div className={styles.title}>{titleElement}</div>}
{headerElement}
</div>
{actions && actionsElement}
{actions && <div>{actionsElement}</div>}
{draggable && (
<Icon title="Drag and drop to reorder" name="draggabledots" size="lg" className={styles.dragIcon} />
)}
@@ -113,7 +113,6 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
border-radius: ${theme.border.radius.sm};
background: ${theme.colors.bg2};
min-height: ${theme.spacing.formInputHeight}px;
line-height: ${theme.spacing.sm}px;
display: flex;
align-items: center;
justify-content: space-between;
@@ -143,6 +142,7 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
font-weight: ${theme.typography.weight.semibold};
color: ${theme.colors.textBlue};
margin-left: ${theme.spacing.sm};
overflow: hidden;
`,
content: css`
margin-top: ${theme.spacing.inlineFormMargin};

View File

@@ -1,6 +1,5 @@
import React from 'react';
// @ts-ignore
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { TeamPicker } from './TeamPicker';
jest.mock('@grafana/runtime', () => ({
@@ -18,7 +17,7 @@ describe('TeamPicker', () => {
const props = {
onSelected: () => {},
};
const tree = renderer.create(<TeamPicker {...props} />).toJSON();
expect(tree).toMatchSnapshot();
render(<TeamPicker {...props} />);
expect(screen.getByTestId('teamPicker')).toBeInTheDocument();
});
});

View File

@@ -64,7 +64,7 @@ export class TeamPicker extends Component<Props, State> {
const { onSelected, className } = this.props;
const { isLoading } = this.state;
return (
<div className="user-picker">
<div className="user-picker" data-testid="teamPicker">
<AsyncSelect
isLoading={isLoading}
defaultOptions={true}

View File

@@ -1,6 +1,5 @@
import React from 'react';
// @ts-ignore
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { UserPicker } from './UserPicker';
jest.mock('@grafana/runtime', () => ({
@@ -9,7 +8,7 @@ jest.mock('@grafana/runtime', () => ({
describe('UserPicker', () => {
it('renders correctly', () => {
const tree = renderer.create(<UserPicker onSelected={() => {}} />).toJSON();
expect(tree).toMatchSnapshot();
render(<UserPicker onSelected={() => {}} />);
expect(screen.getByTestId('userPicker')).toBeInTheDocument();
});
});

View File

@@ -64,7 +64,7 @@ export class UserPicker extends Component<Props, State> {
const { isLoading } = this.state;
return (
<div className="user-picker">
<div className="user-picker" data-testid="userPicker">
<AsyncSelect
className={className}
isLoading={isLoading}

View File

@@ -1,112 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TeamPicker renders correctly 1`] = `
<div
className="user-picker"
>
<div>
<div
className="gf-form-input gf-form-input--form-dropdown css-at6rp9-SelectContainer"
onKeyDown={[Function]}
>
<div
className="gf-form-select-box__control css-ia584n-Control"
onMouseDown={[Function]}
onTouchEnd={[Function]}
>
<div
className="gf-form-select-box__value-container css-1q9zhbr-ValueContainer"
>
<div
className="gf-form-select-box__placeholder css-d8h0m4-Placeholder"
>
Select a team
</div>
<div
className="css-zz0hea-Input"
>
<div
className="gf-form-select-box__input"
style={
Object {
"display": "inline-block",
}
}
>
<input
aria-autocomplete="list"
autoCapitalize="none"
autoComplete="off"
autoCorrect="off"
disabled={false}
id="react-select-2-input"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
spellCheck="false"
style={
Object {
"background": 0,
"border": 0,
"boxSizing": "content-box",
"color": "inherit",
"fontSize": "inherit",
"label": "input",
"opacity": 1,
"outline": 0,
"padding": 0,
"width": "1px",
}
}
tabIndex="0"
type="text"
value=""
/>
<div
style={
Object {
"height": 0,
"left": 0,
"overflow": "scroll",
"position": "absolute",
"top": 0,
"visibility": "hidden",
"whiteSpace": "pre",
}
}
>
</div>
</div>
</div>
</div>
<div
className="gf-form-select-box__indicators css-q46mcr-IndicatorsContainer"
>
<div
className="css-1cvxpvr"
>
<svg
className="css-sr6nr"
fill="currentColor"
height={16}
style={
Object {
"marginTop": "7px",
}
}
viewBox="0 0 24 24"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17,9.17a1,1,0,0,0-1.41,0L12,12.71,8.46,9.17a1,1,0,0,0-1.41,0,1,1,0,0,0,0,1.42l4.24,4.24a1,1,0,0,0,1.42,0L17,10.59A1,1,0,0,0,17,9.17Z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@@ -1,112 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UserPicker renders correctly 1`] = `
<div
className="user-picker"
>
<div>
<div
className="gf-form-input gf-form-input--form-dropdown css-at6rp9-SelectContainer"
onKeyDown={[Function]}
>
<div
className="gf-form-select-box__control css-ia584n-Control"
onMouseDown={[Function]}
onTouchEnd={[Function]}
>
<div
className="gf-form-select-box__value-container css-1q9zhbr-ValueContainer"
>
<div
className="gf-form-select-box__placeholder css-d8h0m4-Placeholder"
>
Select user
</div>
<div
className="css-zz0hea-Input"
>
<div
className="gf-form-select-box__input"
style={
Object {
"display": "inline-block",
}
}
>
<input
aria-autocomplete="list"
autoCapitalize="none"
autoComplete="off"
autoCorrect="off"
disabled={false}
id="react-select-2-input"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
spellCheck="false"
style={
Object {
"background": 0,
"border": 0,
"boxSizing": "content-box",
"color": "inherit",
"fontSize": "inherit",
"label": "input",
"opacity": 1,
"outline": 0,
"padding": 0,
"width": "1px",
}
}
tabIndex="0"
type="text"
value=""
/>
<div
style={
Object {
"height": 0,
"left": 0,
"overflow": "scroll",
"position": "absolute",
"top": 0,
"visibility": "hidden",
"whiteSpace": "pre",
}
}
>
</div>
</div>
</div>
</div>
<div
className="gf-form-select-box__indicators css-q46mcr-IndicatorsContainer"
>
<div
className="css-1cvxpvr"
>
<svg
className="css-sr6nr"
fill="currentColor"
height={16}
style={
Object {
"marginTop": "7px",
}
}
viewBox="0 0 24 24"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17,9.17a1,1,0,0,0-1.41,0L12,12.71,8.46,9.17a1,1,0,0,0-1.41,0,1,1,0,0,0,0,1.42l4.24,4.24a1,1,0,0,0,1.42,0L17,10.59A1,1,0,0,0,17,9.17Z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@@ -1,7 +1,6 @@
import React, { PureComponent } from 'react';
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
import { css } from 'emotion';
import { Alert, Button, IconName, CustomScrollbar, Container, HorizontalGroup, ConfirmModal, Modal } from '@grafana/ui';
import { Alert, Button, ConfirmModal, Container, CustomScrollbar, HorizontalGroup, IconName, Modal } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors';
import { AngularComponent, getAngularLoader, getDataSourceSrv } from '@grafana/runtime';
import { getAlertingValidationMessage } from './getAlertingValidationMessage';
@@ -14,8 +13,7 @@ import { DashboardModel } from '../dashboard/state/DashboardModel';
import { PanelModel } from '../dashboard/state/PanelModel';
import { TestRuleResult } from './TestRuleResult';
import { AppNotificationSeverity, StoreState } from 'app/types';
import { updateLocation } from 'app/core/actions';
import { PanelEditorTabId } from '../dashboard/components/PanelEditor/types';
import { PanelNotSupported } from '../dashboard/components/PanelEditor/PanelNotSupported';
interface OwnProps {
dashboard: DashboardModel;
@@ -26,14 +24,12 @@ interface ConnectedProps {
angularPanelComponent?: AngularComponent | null;
}
interface DispatchProps {
updateLocation: typeof updateLocation;
}
interface DispatchProps {}
export type Props = OwnProps & ConnectedProps & DispatchProps;
interface State {
validatonMessage: string;
validationMessage: string;
showStateHistory: boolean;
showDeleteConfirmation: boolean;
showTestRule: boolean;
@@ -45,7 +41,7 @@ class UnConnectedAlertTab extends PureComponent<Props, State> {
panelCtrl: any;
state: State = {
validatonMessage: '',
validationMessage: '',
showStateHistory: false,
showDeleteConfirmation: false,
showTestRule: false,
@@ -94,15 +90,15 @@ class UnConnectedAlertTab extends PureComponent<Props, State> {
this.component = loader.load(this.element, scopeProps, template);
const validatonMessage = await getAlertingValidationMessage(
const validationMessage = await getAlertingValidationMessage(
panel.transformations,
panel.targets,
getDataSourceSrv(),
panel.datasource
);
if (validatonMessage) {
this.setState({ validatonMessage });
if (validationMessage) {
this.setState({ validationMessage });
}
}
@@ -112,37 +108,11 @@ class UnConnectedAlertTab extends PureComponent<Props, State> {
this.forceUpdate();
};
switchToQueryTab = () => {
const { updateLocation } = this.props;
updateLocation({ query: { tab: PanelEditorTabId.Query }, partial: true });
};
onToggleModal = (prop: keyof Omit<State, 'validatonMessage'>) => {
onToggleModal = (prop: keyof Omit<State, 'validationMessage'>) => {
const value = this.state[prop];
this.setState({ ...this.state, [prop]: !value });
};
renderValidationMessage = () => {
const { validatonMessage } = this.state;
return (
<div
className={css`
width: 508px;
margin: 128px auto;
`}
>
<h2>{validatonMessage}</h2>
<br />
<div className="gf-form-group">
<Button size={'md'} variant={'secondary'} icon="arrow-left" onClick={this.switchToQueryTab}>
Go back to Queries
</Button>
</div>
</div>
);
};
renderTestRule = () => {
if (!this.state.showTestRule) {
return null;
@@ -213,11 +183,11 @@ class UnConnectedAlertTab extends PureComponent<Props, State> {
render() {
const { alert, transformations } = this.props.panel;
const { validatonMessage } = this.state;
const { validationMessage } = this.state;
const hasTransformations = transformations && transformations.length > 0;
if (!alert && validatonMessage) {
return this.renderValidationMessage();
if (!alert && validationMessage) {
return <PanelNotSupported message={validationMessage} />;
}
const model = {
@@ -253,7 +223,7 @@ class UnConnectedAlertTab extends PureComponent<Props, State> {
</Button>
</HorizontalGroup>
)}
{!alert && !validatonMessage && <EmptyListCTA {...model} />}
{!alert && !validationMessage && <EmptyListCTA {...model} />}
</div>
</Container>
</CustomScrollbar>
@@ -272,6 +242,6 @@ const mapStateToProps: MapStateToProps<ConnectedProps, OwnProps, StoreState> = (
};
};
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = { updateLocation };
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {};
export const AlertTab = connect(mapStateToProps, mapDispatchToProps)(UnConnectedAlertTab);

View File

@@ -5,9 +5,9 @@ describe('DataFrame to annotations', () => {
test('simple conversion', () => {
const frame = toDataFrame({
fields: [
{ type: FieldType.time, values: [1, 2, 3] },
{ name: 'first string field', values: ['t1', 't2', 't3'] },
{ name: 'tags', values: ['aaa,bbb', 'bbb,ccc', 'zyz'] },
{ type: FieldType.time, values: [1, 2, 3, 4, 5] },
{ name: 'first string field', values: ['t1', 't2', 't3', null, undefined] },
{ name: 'tags', values: ['aaa,bbb', 'bbb,ccc', 'zyz', null, undefined] },
],
});
@@ -37,6 +37,12 @@ describe('DataFrame to annotations', () => {
"text": "t3",
"time": 3,
},
Object {
"time": 4,
},
Object {
"time": 5,
},
]
`);
});

View File

@@ -175,8 +175,8 @@ export function getAnnotationsFromData(data: DataFrame[], options?: AnnotationEv
}
}
if (v !== undefined) {
if (f.split) {
if (!(v === null || v === undefined)) {
if (v && f.split) {
v = (v as string).split(',');
}
(anno as any)[f.key] = v;

View File

@@ -69,7 +69,7 @@ export class DashboardRow extends React.Component<DashboardRowProps, any> {
'dashboard-row--collapsed': this.state.collapsed,
});
const title = templateSrv.replaceWithText(this.props.panel.title, this.props.panel.scopedVars);
const title = templateSrv.replace(this.props.panel.title, this.props.panel.scopedVars, 'text');
const count = this.props.panel.panels ? this.props.panel.panels.length : 0;
const panels = count === 1 ? 'panel' : 'panels';
const canEdit = this.props.dashboard.meta.canEdit === true;

View File

@@ -1,5 +1,5 @@
import React, { ReactNode, useCallback } from 'react';
import cloneDeep from 'lodash/cloneDeep';
import { get as lodashGet, cloneDeep } from 'lodash';
import {
DataFrame,
DocsId,
@@ -146,9 +146,9 @@ export const DefaultFieldConfigEditor: React.FC<Props> = ({ data, onChange, conf
const defaults = config.defaults;
const value = item.isCustom
? defaults.custom
? defaults.custom[item.path]
? lodashGet(defaults.custom, item.path)
: undefined
: (defaults as any)[item.path];
: lodashGet(defaults, item.path);
let label: ReactNode | undefined = (
<Label description={item.description} category={item.category?.slice(1)}>

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { PanelNotSupported, Props } from './PanelNotSupported';
import { updateLocation } from '../../../../core/actions';
import { PanelEditorTabId } from './types';
const setupTestContext = (options: Partial<Props>) => {
const defaults: Props = {
message: '',
dispatch: jest.fn(),
};
const props = { ...defaults, ...options };
render(<PanelNotSupported {...props} />);
return { props };
};
describe('PanelNotSupported', () => {
describe('when component is mounted', () => {
it('then the supplied message should be shown', () => {
setupTestContext({ message: 'Expected message' });
expect(screen.getByRole('heading', { name: /expected message/i })).toBeInTheDocument();
});
it('then the back to queries button should exist', () => {
setupTestContext({ message: 'Expected message' });
expect(screen.getByRole('button', { name: /go back to queries/i })).toBeInTheDocument();
});
});
describe('when the back to queries button is clicked', () => {
it('then correct action should be dispatched', () => {
const {
props: { dispatch },
} = setupTestContext({});
userEvent.click(screen.getByRole('button', { name: /go back to queries/i }));
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith(updateLocation({ query: { tab: PanelEditorTabId.Query }, partial: true }));
});
});
});

View File

@@ -0,0 +1,33 @@
import React, { FC, useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import { Button, VerticalGroup } from '@grafana/ui';
import { Layout } from '@grafana/ui/src/components/Layout/Layout';
import { PanelEditorTabId } from './types';
import { updateLocation } from '../../../../core/actions';
export interface Props {
message: string;
dispatch?: Dispatch;
}
export const PanelNotSupported: FC<Props> = ({ message, dispatch: propsDispatch }) => {
const dispatch = propsDispatch ? propsDispatch : useDispatch();
const onBackToQueries = useCallback(() => {
dispatch(updateLocation({ query: { tab: PanelEditorTabId.Query }, partial: true }));
}, [dispatch]);
return (
<Layout justify="center" style={{ marginTop: '100px' }}>
<VerticalGroup spacing="md">
<h2>{message}</h2>
<div>
<Button size="md" variant="secondary" icon="arrow-left" onClick={onBackToQueries}>
Go back to Queries
</Button>
</div>
</VerticalGroup>
</Layout>
);
};

View File

@@ -1,5 +1,6 @@
import React from 'react';
import {
Alert,
Button,
Container,
CustomScrollbar,
@@ -10,14 +11,14 @@ import {
VerticalGroup,
} from '@grafana/ui';
import {
DataFrame,
DataTransformerConfig,
DocsId,
GrafanaTheme,
PanelData,
SelectableValue,
standardTransformersRegistry,
transformDataFrame,
DataFrame,
PanelData,
DocsId,
} from '@grafana/data';
import { TransformationOperationRow } from './TransformationOperationRow';
import { Card, CardProps } from '../../../../core/components/Card/Card';
@@ -27,6 +28,8 @@ import { Unsubscribable } from 'rxjs';
import { PanelModel } from '../../state';
import { getDocsLink } from 'app/core/utils/docsLinks';
import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
import { PanelNotSupported } from '../PanelEditor/PanelNotSupported';
import { AppNotificationSeverity } from '../../../../types';
interface TransformationsEditorProps {
panel: PanelModel;
@@ -285,14 +288,27 @@ export class TransformationsEditor extends React.PureComponent<TransformationsEd
}
render() {
const {
panel: { alert },
} = this.props;
const { transformations } = this.state;
const hasTransforms = transformations.length > 0;
if (!hasTransforms && alert) {
return <PanelNotSupported message="Transformations can't be used on a panel with existing alerts" />;
}
return (
<CustomScrollbar autoHeightMin="100%">
<Container padding="md">
<div aria-label={selectors.components.TransformTab.content}>
{hasTransforms && alert ? (
<Alert
severity={AppNotificationSeverity.Error}
title="Transformations can't be used on a panel with alerts"
/>
) : null}
{!hasTransforms && this.renderNoAddedTransformsState()}
{hasTransforms && this.renderTransformationEditors()}
{hasTransforms && this.renderTransformationSelector()}

View File

@@ -131,7 +131,7 @@ export class PanelHeader extends Component<Props, State> {
render() {
const { panel, scopedVars, error, isViewing, isEditing, data, alertState } = this.props;
const { menuItems } = this.state;
const title = templateSrv.replaceWithText(panel.title, scopedVars);
const title = templateSrv.replace(panel.title, scopedVars, 'text');
const panelHeaderClass = classNames({
'panel-header': true,

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { css } from 'emotion';
import { DataQuery, DataSourceApi, GrafanaTheme } from '@grafana/data';
import { HorizontalGroup, stylesFactory, useTheme } from '@grafana/ui';
import { stylesFactory, useTheme } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors';
interface QueryEditorRowTitleProps {
@@ -25,7 +25,7 @@ export const QueryEditorRowTitle: React.FC<QueryEditorRowTitleProps> = ({
const styles = getQueryEditorRowTitleStyles(theme);
return (
<HorizontalGroup align="center">
<div className={styles.wrapper}>
<div className={styles.refId} aria-label={selectors.components.QueryEditorRow.title(query.refId)}>
<span>{query.refId}</span>
{inMixedMode && <em className={styles.contextInfo}> ({datasource.name})</em>}
@@ -36,12 +36,17 @@ export const QueryEditorRowTitle: React.FC<QueryEditorRowTitleProps> = ({
{collapsedText}
</div>
)}
</HorizontalGroup>
</div>
);
};
const getQueryEditorRowTitleStyles = stylesFactory((theme: GrafanaTheme) => {
return {
wrapper: css`
display: flex;
align-items: center;
`,
refId: css`
font-weight: ${theme.typography.weight.semibold};
color: ${theme.colors.textBlue};
@@ -53,10 +58,8 @@ const getQueryEditorRowTitleStyles = stylesFactory((theme: GrafanaTheme) => {
font-weight: ${theme.typography.weight.regular};
font-size: ${theme.typography.size.sm};
color: ${theme.colors.textWeak};
padding: 0 10px;
display: flex;
padding-left: ${theme.spacing.sm};
align-items: center;
flex-grow: 1;
overflow: hidden;
font-style: italic;
overflow: hidden;

View File

@@ -108,6 +108,7 @@ export class QueryEditorRows extends PureComponent<Props> {
inMixedMode={props.datasource.meta.mixed}
/>
))}
{provided.placeholder}
</div>
);
}}

View File

@@ -263,7 +263,6 @@ export function loadExploreDatasourcesAndSetDatasource(
if (exploreDatasources.length >= 1) {
await dispatch(changeDatasource(exploreId, datasourceName, { importQueries: true }));
dispatch(runQueries(exploreId));
} else {
dispatch(loadDatasourceMissingAction({ exploreId }));
}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { shallow } from 'enzyme';
import { Segment } from '@grafana/ui';
import { Aggregations, Props } from './Aggregations';
@@ -22,8 +22,8 @@ const props: Props = {
describe('Aggregations', () => {
it('renders correctly', () => {
const tree = renderer.create(<Aggregations {...props} />).toJSON();
expect(tree).toMatchSnapshot();
render(<Aggregations {...props} />);
expect(screen.getByTestId('aggregations')).toBeInTheDocument();
});
describe('options', () => {

View File

@@ -22,7 +22,7 @@ export const Aggregations: FC<Props> = props => {
const selected = useSelectedFromOptions(aggOptions, props);
return (
<>
<div data-testid="aggregations">
<div className="gf-form-inline">
<label className="gf-form-label query-keyword width-9">Aggregation</label>
<Segment
@@ -40,7 +40,7 @@ export const Aggregations: FC<Props> = props => {
},
]}
placeholder="Select Reducer"
></Segment>
/>
<div className="gf-form gf-form--grow">
<label className="gf-form-label gf-form-label--grow">
<a onClick={() => setDisplayAdvancedOptions(!displayAdvancedOptions)}>
@@ -52,7 +52,7 @@ export const Aggregations: FC<Props> = props => {
</div>
</div>
{props.children(displayAdvancedOptions)}
</>
</div>
);
};

View File

@@ -1,55 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Aggregations renders correctly 1`] = `
Array [
<div
className="gf-form-inline"
>
<label
className="gf-form-label query-keyword width-9"
>
Aggregation
</label>
<div
className="gf-form"
onClick={[Function]}
>
<a
className="gf-form-label query-part query-placeholder"
>
Select Reducer
</a>
</div>
<div
className="gf-form gf-form--grow"
>
<label
className="gf-form-label gf-form-label--grow"
>
<a
onClick={[Function]}
>
<div
className="css-1cvxpvr"
>
<svg
className="css-sr6nr"
fill="currentColor"
height={16}
viewBox="0 0 24 24"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.83,11.29,10.59,7.05a1,1,0,0,0-1.42,0,1,1,0,0,0,0,1.41L12.71,12,9.17,15.54a1,1,0,0,0,0,1.41,1,1,0,0,0,.71.29,1,1,0,0,0,.71-.29l4.24-4.24A1,1,0,0,0,14.83,11.29Z"
/>
</svg>
</div>
Advanced Options
</a>
</label>
</div>
</div>,
<div />,
]
`;

View File

@@ -1,21 +1,19 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { Stats } from './Stats';
const toOption = (value: any) => ({ label: value, value });
describe('Stats', () => {
it('should render component', () => {
const tree = renderer
.create(
<Stats
values={['Average', 'Minimum']}
variableOptionGroup={{ label: 'templateVar', value: 'templateVar' }}
onChange={() => {}}
stats={['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount'].map(toOption)}
/>
)
.toJSON();
expect(tree).toMatchSnapshot();
render(
<Stats
values={['Average', 'Minimum']}
variableOptionGroup={{ label: 'templateVar', value: 'templateVar' }}
onChange={() => {}}
stats={['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount'].map(toOption)}
/>
);
expect(screen.getByTestId('stats')).toBeInTheDocument();
});
});

View File

@@ -14,7 +14,7 @@ const removeText = '-- remove stat --';
const removeOption: SelectableValue<string> = { label: removeText, value: removeText };
export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, variableOptionGroup }) => (
<>
<div data-testid="stats">
{values &&
values.map((value, index) => (
<Segment
@@ -41,5 +41,5 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia
onChange={({ value }) => onChange([...values, value!])}
options={[...stats.filter(({ value }) => !values.includes(value!)), variableOptionGroup]}
/>
</>
</div>
);

View File

@@ -1,51 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Stats should render component 1`] = `
Array [
<div
className="gf-form"
onClick={[Function]}
>
<a
className="gf-form-label query-part"
>
Average
</a>
</div>,
<div
className="gf-form"
onClick={[Function]}
>
<a
className="gf-form-label query-part"
>
Minimum
</a>
</div>,
<div
className="gf-form"
onClick={[Function]}
>
<a
className="gf-form-label query-part"
>
<div
className="css-1cvxpvr"
>
<svg
className="css-sr6nr"
fill="currentColor"
height={16}
viewBox="0 0 24 24"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19,11H13V5a1,1,0,0,0-2,0v6H5a1,1,0,0,0,0,2h6v6a1,1,0,0,0,2,0V13h6a1,1,0,0,0,0-2Z"
/>
</svg>
</div>
</a>
</div>,
]
`;