mirror of
https://github.com/grafana/grafana.git
synced 2026-01-09 21:47:46 +08:00
Compare commits
19 Commits
v7.2.0-bet
...
v7.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efe4941ee3 | ||
|
|
9c1403cd13 | ||
|
|
e35c7583b9 | ||
|
|
d383ac9ffb | ||
|
|
8797d753d4 | ||
|
|
c0320fbe40 | ||
|
|
f2f4f22eef | ||
|
|
a7e713fb31 | ||
|
|
b825c824c5 | ||
|
|
36c3a139e8 | ||
|
|
da2622d870 | ||
|
|
b25b66e2f6 | ||
|
|
a3fc96196a | ||
|
|
a9a053591b | ||
|
|
2870eab4d7 | ||
|
|
bdeb380c56 | ||
|
|
0bac0044a9 | ||
|
|
0d8f2fbda8 | ||
|
|
88905ca158 |
11
go.mod
11
go.mod
@@ -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
23
go.sum
@@ -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=
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"packages": ["packages/*"],
|
||||
"version": "7.2.0-beta.2"
|
||||
"version": "7.2.0"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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'};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
`,
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{})
|
||||
|
||||
@@ -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{
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)}>
|
||||
|
||||
@@ -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 }));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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()}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -108,6 +108,7 @@ export class QueryEditorRows extends PureComponent<Props> {
|
||||
inMixedMode={props.datasource.meta.mixed}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -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 }));
|
||||
}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 />,
|
||||
]
|
||||
`;
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>,
|
||||
]
|
||||
`;
|
||||
Reference in New Issue
Block a user