mirror of
https://github.com/grafana/grafana.git
synced 2025-12-21 03:54:29 +08:00
Compare commits
145 Commits
docs/add-d
...
v7.3.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14c494085e | ||
|
|
3a50fc8db0 | ||
|
|
7472e37cf9 | ||
|
|
be6425d461 | ||
|
|
f34fecbca0 | ||
|
|
509174df31 | ||
|
|
6ada37b445 | ||
|
|
6d79790397 | ||
|
|
089c636acf | ||
|
|
fcd66eef29 | ||
|
|
cffb1cd98d | ||
|
|
881a595651 | ||
|
|
004104717c | ||
|
|
b345e28a2d | ||
|
|
fa1db4dc12 | ||
|
|
9a3fbb8782 | ||
|
|
7292e1508e | ||
|
|
2489dc4d3a | ||
|
|
ca8de25f0c | ||
|
|
ff7c462600 | ||
|
|
41bbafe979 | ||
|
|
37e4a19ea8 | ||
|
|
f04131c9c6 | ||
|
|
7e6c34fc20 | ||
|
|
0e5da44bc1 | ||
|
|
fc44872ca2 | ||
|
|
c88fc42fc6 | ||
|
|
d05c3462da | ||
|
|
8db22cef96 | ||
|
|
edfe914cb0 | ||
|
|
4cf6c916c7 | ||
|
|
697e4f7037 | ||
|
|
ca4fdc46fd | ||
|
|
e086a96161 | ||
|
|
945573eb93 | ||
|
|
b8ad4eaab3 | ||
|
|
f034cbef50 | ||
|
|
23be6e3898 | ||
|
|
7798f01cc9 | ||
|
|
38b96278c8 | ||
|
|
f142752ad6 | ||
|
|
1cfe644d51 | ||
|
|
d8ac457ebc | ||
|
|
3d4ff87721 | ||
|
|
0c5524786b | ||
|
|
c0adf1022b | ||
|
|
706cc59d1d | ||
|
|
f9f3b9d953 | ||
|
|
1b14c6b8db | ||
|
|
d8674013cb | ||
|
|
fc86862533 | ||
|
|
3636749a43 | ||
|
|
f30f6d8a35 | ||
|
|
202b9e5ca9 | ||
|
|
c9a6e09235 | ||
|
|
c03fbac5db | ||
|
|
de19ba97cb | ||
|
|
e5be54b0f0 | ||
|
|
70bf01f4f9 | ||
|
|
0ee8427386 | ||
|
|
7995ab999f | ||
|
|
b51303b83d | ||
|
|
c14ee2d245 | ||
|
|
d394be2555 | ||
|
|
f523500df2 | ||
|
|
d55ca82f0b | ||
|
|
7f9990c889 | ||
|
|
6a2d9224f1 | ||
|
|
88ae4c0e64 | ||
|
|
f11bfe95da | ||
|
|
49dd35f8d9 | ||
|
|
557639a458 | ||
|
|
6668161a88 | ||
|
|
f81257c2f2 | ||
|
|
e8d399ef94 | ||
|
|
03563abba0 | ||
|
|
43928c4c88 | ||
|
|
3069d764f8 | ||
|
|
de9ac280b2 | ||
|
|
4ff80d541b | ||
|
|
4f4fcf6331 | ||
|
|
bf8837f6f8 | ||
|
|
95a3c6a024 | ||
|
|
1ff5718af9 | ||
|
|
e123e50d27 | ||
|
|
44f691ff91 | ||
|
|
12a69876c7 | ||
|
|
5bb203df99 | ||
|
|
23ae63913f | ||
|
|
9b3981fb3c | ||
|
|
c933d58893 | ||
|
|
1c61f3fd45 | ||
|
|
be4b32270a | ||
|
|
1e027f5289 | ||
|
|
99613f86f8 | ||
|
|
43a1cb25b9 | ||
|
|
648a149813 | ||
|
|
d8ed14f5a5 | ||
|
|
8f5c9b192f | ||
|
|
0a184cb84f | ||
|
|
be4ef1dcd8 | ||
|
|
de86b51fb5 | ||
|
|
25653e15a6 | ||
|
|
fce221a7d3 | ||
|
|
6c5e139d8f | ||
|
|
194362c302 | ||
|
|
11eb920e87 | ||
|
|
bb555684b1 | ||
|
|
f2fac78c0b | ||
|
|
08be99fa45 | ||
|
|
df2fda3f88 | ||
|
|
983d6c8ef8 | ||
|
|
12dc0ea111 | ||
|
|
103765c349 | ||
|
|
6b4fd2d33c | ||
|
|
1a0500bbeb | ||
|
|
f1b9c6cde1 | ||
|
|
fd851af389 | ||
|
|
8f7eb69db6 | ||
|
|
bf74c1fe3f | ||
|
|
c60ea5f25f | ||
|
|
12dc9ea49f | ||
|
|
e5f12afda2 | ||
|
|
77ab9f4331 | ||
|
|
0f0d860ed3 | ||
|
|
ec0383c9ff | ||
|
|
c510f30eb3 | ||
|
|
f0dafad5cb | ||
|
|
6b056c527f | ||
|
|
c9513c5e81 | ||
|
|
9153d2146b | ||
|
|
f3fa16706c | ||
|
|
9d7eaedb2b | ||
|
|
2221c2bbad | ||
|
|
363d0a9588 | ||
|
|
d18ac27126 | ||
|
|
37327d74f5 | ||
|
|
5757bd80d3 | ||
|
|
f071817b95 | ||
|
|
2110d4e9b4 | ||
|
|
5ac4ae37a2 | ||
|
|
c11c8b0b4a | ||
|
|
0df7b25a49 | ||
|
|
68a3631ed0 | ||
|
|
2e9a0d4755 |
1155
.circleci/config.yml
1155
.circleci/config.yml
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,9 @@
|
||||
load('scripts/pr.star', 'pr_pipelines')
|
||||
load('scripts/master.star', 'master_pipelines')
|
||||
load('scripts/release.star', 'release_pipelines', 'test_release_pipelines')
|
||||
load('scripts/version.star', 'version_branch_pipelines')
|
||||
|
||||
def main(ctx):
|
||||
edition = 'oss'
|
||||
return pr_pipelines(edition=edition) + master_pipelines(edition=edition) + release_pipelines() + \
|
||||
test_release_pipelines()
|
||||
test_release_pipelines() + version_branch_pipelines()
|
||||
|
||||
1197
.drone.yml
1197
.drone.yml
File diff suppressed because it is too large
Load Diff
27
.github/workflows/bump-version.yml
vendored
Normal file
27
.github/workflows/bump-version.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Bump version
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
required: true
|
||||
default: '7.x.x'
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: "grafana/grafana-github-actions"
|
||||
path: ./actions
|
||||
ref: main
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12'
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
- name: Run bump version
|
||||
uses: ./actions/bump-version
|
||||
with:
|
||||
token: ${{secrets.GH_BOT_ACCESS_TOKEN}}
|
||||
metricsWriteAPIKey: ${{secrets.GRAFANA_MISC_STATS_API_KEY}}
|
||||
66
CHANGELOG.md
66
CHANGELOG.md
@@ -1,9 +1,73 @@
|
||||
# 7.3.0-beta1 (2020-10-14)
|
||||
# 7.3.0-beta1 (2020-10-15)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- **CloudWatch**: The AWS CloudWatch data source's authentication scheme has changed. See the [upgrade notes](https://grafana.com/docs/grafana/latest/installation/upgrading/#upgrading-to-v73) for details and how this may affect you.
|
||||
|
||||
### Features / Enhancements
|
||||
* **Alerting**: Add labels to name when converting data frame to series. [#28085](https://github.com/grafana/grafana/pull/28085), [@kylebrandt](https://github.com/kylebrandt)
|
||||
* **Alerting**: Ensuring LINE Notify notifications are sent for all alert states. [#27639](https://github.com/grafana/grafana/pull/27639), [@haraldkubota](https://github.com/haraldkubota)
|
||||
* **Auth**: Add SigV4 auth option to datasources. [#27552](https://github.com/grafana/grafana/pull/27552), [@wbrowne](https://github.com/wbrowne)
|
||||
* **AzureMonitor**: Pass through null values instead of setting 0. [#28126](https://github.com/grafana/grafana/pull/28126), [@kylebrandt](https://github.com/kylebrandt)
|
||||
* **Cloud Monitoring**: Out-of-the-box dashboards. [#27864](https://github.com/grafana/grafana/pull/27864), [@papagian](https://github.com/papagian)
|
||||
* **CloudWatch**: Add support for AWS DirectConnect virtual interface metrics and add missing dimensions. [#28008](https://github.com/grafana/grafana/pull/28008), [@jgulick48](https://github.com/jgulick48)
|
||||
* **CloudWatch**: Adding support for Amazon ElastiCache Redis metrics. [#28040](https://github.com/grafana/grafana/pull/28040), [@jgulick48](https://github.com/jgulick48)
|
||||
* **CloudWatch**: Adding support for additional Amazon CloudFront metrics. [#28069](https://github.com/grafana/grafana/pull/28069), [@darrylsepeda](https://github.com/darrylsepeda)
|
||||
* **CloudWatch**: Re-implement authentication. [#25548](https://github.com/grafana/grafana/pull/25548), [@aknuds1](https://github.com/aknuds1),[@patstrom](https://github.com/patstrom)
|
||||
* **Dashboard**: Allow shortlink generation. [#27409](https://github.com/grafana/grafana/pull/27409), [@MisterSquishy](https://github.com/MisterSquishy)
|
||||
* **Docker**: OpenShift compatability. [#27813](https://github.com/grafana/grafana/pull/27813), [@xlson](https://github.com/xlson)
|
||||
* **Elasticsearch**: Support multiple pipeline aggregations for a query. [#27945](https://github.com/grafana/grafana/pull/27945), [@simianhacker](https://github.com/simianhacker)
|
||||
* **Explore**: Allow shortlink generation. [#28222](https://github.com/grafana/grafana/pull/28222), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Explore**: Remove collapsing of visualisations. [#27026](https://github.com/grafana/grafana/pull/27026), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **FieldColor**: Adds new standard color option for color. [#28039](https://github.com/grafana/grafana/pull/28039), [@torkelo](https://github.com/torkelo)
|
||||
* **Gauge**: Improve text sizing and support non threshold color modes. [#28256](https://github.com/grafana/grafana/pull/28256), [@torkelo](https://github.com/torkelo)
|
||||
* **NamedColors**: Named colors refactors. [#28235](https://github.com/grafana/grafana/pull/28235), [@torkelo](https://github.com/torkelo)
|
||||
* **Panel Inspect**: Allow CSV download for Excel. [#27284](https://github.com/grafana/grafana/pull/27284), [@tomdaly](https://github.com/tomdaly)
|
||||
* **Prometheus**: Add time range parameters to labels API. [#27548](https://github.com/grafana/grafana/pull/27548), [@kakkoyun](https://github.com/kakkoyun)
|
||||
* **Snapshots**: Store dashboard data encrypted in the database. [#28129](https://github.com/grafana/grafana/pull/28129), [@wbrowne](https://github.com/wbrowne)
|
||||
* **Table**: New cell hover behavior and image cell display mode. [#27669](https://github.com/grafana/grafana/pull/27669), [@torkelo](https://github.com/torkelo)
|
||||
* **Timezones**: Include IANA timezone canonical name in TimeZoneInfo. [#27591](https://github.com/grafana/grafana/pull/27591), [@dprokop](https://github.com/dprokop)
|
||||
* **Tracing**: Add Tempo data source. [#28204](https://github.com/grafana/grafana/pull/28204), [@aocenas](https://github.com/aocenas)
|
||||
* **Transformations**: Add Concatenate fields transformer. [#28237](https://github.com/grafana/grafana/pull/28237), [@ryantxu](https://github.com/ryantxu)
|
||||
* **Transformations**: improve the reduce transformer. [#27875](https://github.com/grafana/grafana/pull/27875), [@ryantxu](https://github.com/ryantxu)
|
||||
* **Users**: Expire old user invites. [#27361](https://github.com/grafana/grafana/pull/27361), [@wbrowne](https://github.com/wbrowne)
|
||||
* **Variables**: Adds loading state and indicators. [#27917](https://github.com/grafana/grafana/pull/27917), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
* **Variables**: Adds support for key/value mapping in Custom variable. [#27829](https://github.com/grafana/grafana/pull/27829), [@sartaj10](https://github.com/sartaj10)
|
||||
* **grafana/toolkit**: expose Jest maxWorkers arg for plugin test & build tasks. [#27724](https://github.com/grafana/grafana/pull/27724), [@domasx2](https://github.com/domasx2)
|
||||
|
||||
### Bug Fixes
|
||||
* **Azure Analytics**: FormatAs Time series groups bool columns wrong. [#27713](https://github.com/grafana/grafana/issues/27713)
|
||||
* **Azure**: Fixes cancellation of requests with different Azure sources. [#28180](https://github.com/grafana/grafana/pull/28180), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
* **BackendSrv**: Reloads page instead of redirect on Unauthorized Error. [#28276](https://github.com/grafana/grafana/pull/28276), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
* **Dashboard**: Do not allow users without edit permission to a folder to see new dashboard page. [#28249](https://github.com/grafana/grafana/pull/28249), [@torkelo](https://github.com/torkelo)
|
||||
* **Dashboard**: Fixed issue accessing horizontal table scrollbar when placed at bottom of dashboard. [#28250](https://github.com/grafana/grafana/pull/28250), [@torkelo](https://github.com/torkelo)
|
||||
* **DataProxy**: Add additional settings for dataproxy to help with network proxy timeouts. [#27841](https://github.com/grafana/grafana/pull/27841), [@kahinton](https://github.com/kahinton)
|
||||
* **Database**: Adds new indices to alert_notification_state and alert_rule_tag tables. [#28166](https://github.com/grafana/grafana/pull/28166), [@KarineValenca](https://github.com/KarineValenca)
|
||||
* **Explore**: Fix showing of Prometheus data in Query inspector. [#28128](https://github.com/grafana/grafana/pull/28128), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Explore**: Show results of Prometheus instant queries in formatted table. [#27767](https://github.com/grafana/grafana/pull/27767), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Graph**: Prevent legend from overflowing container. [#28254](https://github.com/grafana/grafana/pull/28254), [@jackw](https://github.com/jackw)
|
||||
* **OAuth**: Fix token refresh failure when custom SSL settings are configured for OAuth provider. [#27523](https://github.com/grafana/grafana/pull/27523), [@billoley](https://github.com/billoley)
|
||||
* **Plugins**: Let descendant plugins inherit their root's signature. [#27970](https://github.com/grafana/grafana/pull/27970), [@aknuds1](https://github.com/aknuds1)
|
||||
* **Runtime**: Fix handling of short-lived background services. [#28025](https://github.com/grafana/grafana/pull/28025), [@ahlaw](https://github.com/ahlaw)
|
||||
* **TemplateSrv**: Fix interpolating strings with object variables. [#28171](https://github.com/grafana/grafana/pull/28171), [@torkelo](https://github.com/torkelo)
|
||||
* **Variables**: Fixes so constants set from url get completed state. [#28257](https://github.com/grafana/grafana/pull/28257), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
* **Variables**: Prevent adhoc filters from crashing when they are not loaded properly. [#28226](https://github.com/grafana/grafana/pull/28226), [@mckn](https://github.com/mckn)
|
||||
|
||||
# 7.2.2 (2020-10-21)
|
||||
|
||||
### Features / Enhancements
|
||||
|
||||
**Caution:** Please do not use/enable the `database_metrics` feature flag. It will corrupt MySQL database tables. See [#28440](https://github.com/grafana/grafana/issues/28440) for more information.
|
||||
|
||||
~~**Instrumentation**: Add counters and histograms for database queries. [#28236](https://github.com/grafana/grafana/pull/28236), [@bergquist](https://github.com/bergquist)~~
|
||||
|
||||
- **Instrumentation**: Add histogram for request duration. [#28364](https://github.com/grafana/grafana/pull/28364), [@bergquist](https://github.com/bergquist)
|
||||
- **Instrumentation**: Adds environment_info metric. [#28355](https://github.com/grafana/grafana/pull/28355), [@bergquist](https://github.com/bergquist)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **CloudWatch**: Fix custom metrics. [#28391](https://github.com/grafana/grafana/pull/28391), [@aknuds1](https://github.com/aknuds1)
|
||||
|
||||
# 7.2.1 (2020-10-08)
|
||||
|
||||
### Features / Enhancements
|
||||
|
||||
13
build.go
13
build.go
@@ -37,6 +37,7 @@ var (
|
||||
libc string
|
||||
pkgArch string
|
||||
version string = "v1"
|
||||
buildTags []string
|
||||
// deb & rpm does not support semver so have to handle their version a little differently
|
||||
linuxPackageVersion string = "v1"
|
||||
linuxPackageIteration string = ""
|
||||
@@ -59,11 +60,13 @@ func main() {
|
||||
log.SetFlags(0)
|
||||
|
||||
var buildIdRaw string
|
||||
var buildTagsRaw string
|
||||
|
||||
flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
|
||||
flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
|
||||
flag.StringVar(&gocc, "cc", "", "CC")
|
||||
flag.StringVar(&libc, "libc", "", "LIBC")
|
||||
flag.StringVar(&buildTagsRaw, "build-tags", "", "Sets custom build tags")
|
||||
flag.BoolVar(&cgo, "cgo-enabled", cgo, "Enable cgo")
|
||||
flag.StringVar(&pkgArch, "pkg-arch", "", "PKG ARCH")
|
||||
flag.BoolVar(&race, "race", race, "Use race detector")
|
||||
@@ -89,6 +92,10 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if len(buildTagsRaw) > 0 {
|
||||
buildTags = strings.Split(buildTagsRaw, ",")
|
||||
}
|
||||
|
||||
log.Printf("Version: %s, Linux Version: %s, Package Iteration: %s\n", version, linuxPackageVersion, linuxPackageIteration)
|
||||
|
||||
if flag.NArg() == 0 {
|
||||
@@ -105,16 +112,16 @@ func main() {
|
||||
|
||||
case "build-srv", "build-server":
|
||||
clean()
|
||||
doBuild("grafana-server", "./pkg/cmd/grafana-server", []string{})
|
||||
doBuild("grafana-server", "./pkg/cmd/grafana-server", buildTags)
|
||||
|
||||
case "build-cli":
|
||||
clean()
|
||||
doBuild("grafana-cli", "./pkg/cmd/grafana-cli", []string{})
|
||||
doBuild("grafana-cli", "./pkg/cmd/grafana-cli", buildTags)
|
||||
|
||||
case "build":
|
||||
//clean()
|
||||
for _, binary := range binaries {
|
||||
doBuild(binary, "./pkg/cmd/"+binary, []string{})
|
||||
doBuild(binary, "./pkg/cmd/"+binary, buildTags)
|
||||
}
|
||||
|
||||
case "build-frontend":
|
||||
|
||||
@@ -670,6 +670,12 @@ disable_total_stats = false
|
||||
basic_auth_username =
|
||||
basic_auth_password =
|
||||
|
||||
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
|
||||
# can expose more information about the Grafana instance.
|
||||
[metrics.environment_info]
|
||||
#exampleLabel1 = exampleValue1
|
||||
#exampleLabel2 = exampleValue2
|
||||
|
||||
# Send internal Grafana metrics to graphite
|
||||
[metrics.graphite]
|
||||
# Enable by setting the address setting (ex localhost:2003)
|
||||
@@ -699,6 +705,8 @@ sampler_type = const
|
||||
# and indicates the initial sampling rate before the actual one
|
||||
# is received from the mothership
|
||||
sampler_param = 1
|
||||
# sampling_server_url is the URL of a sampling manager providing a sampling strategy.
|
||||
sampling_server_url =
|
||||
# Whether or not to use Zipkin span propagation (x-b3- HTTP headers).
|
||||
zipkin_propagation = false
|
||||
# Setting this to true disables shared RPC spans.
|
||||
@@ -762,6 +770,7 @@ enable_alpha = false
|
||||
app_tls_skip_verify_insecure = false
|
||||
# Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature.
|
||||
allow_loading_unsigned_plugins =
|
||||
marketplace_url = https://grafana.com/grafana/plugins/
|
||||
|
||||
#################################### Grafana Image Renderer Plugin ##########################
|
||||
[plugin.grafana-image-renderer]
|
||||
|
||||
@@ -664,6 +664,12 @@
|
||||
; basic_auth_username =
|
||||
; basic_auth_password =
|
||||
|
||||
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
|
||||
# can expose more information about the Grafana instance.
|
||||
[metrics.environment_info]
|
||||
#exampleLabel1 = exampleValue1
|
||||
#exampleLabel2 = exampleValue2
|
||||
|
||||
# Send internal metrics to Graphite
|
||||
[metrics.graphite]
|
||||
# Enable by setting the address setting (ex localhost:2003)
|
||||
@@ -691,6 +697,8 @@
|
||||
# and indicates the initial sampling rate before the actual one
|
||||
# is received from the mothership
|
||||
;sampler_param = 1
|
||||
# sampling_server_url is the URL of a sampling manager providing a sampling strategy.
|
||||
;sampling_server_url =
|
||||
# Whether or not to use Zipkin propagation (x-b3- HTTP headers).
|
||||
;zipkin_propagation = false
|
||||
# Setting this to true disables shared RPC spans.
|
||||
@@ -750,6 +758,7 @@
|
||||
;app_tls_skip_verify_insecure = false
|
||||
# Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature.
|
||||
;allow_loading_unsigned_plugins =
|
||||
;marketplace_url = https://grafana.com/grafana/plugins/
|
||||
|
||||
#################################### Grafana Image Renderer Plugin ##########################
|
||||
[plugin.grafana-image-renderer]
|
||||
|
||||
@@ -8,3 +8,6 @@ Learn more about the backend architecture:
|
||||
- Part 2: [Communication](communication.md)
|
||||
- Part 3: [Database](database.md)
|
||||
|
||||
Learn more about the frontend architecture:
|
||||
- Part 1: [Data requests](frontend-data-requests.md)
|
||||
|
||||
|
||||
41
contribute/architecture/frontend-data-requests.md
Normal file
41
contribute/architecture/frontend-data-requests.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Data requests
|
||||
|
||||
[BackendSrv](https://grafana.com/docs/grafana/latest/packages_api/runtime/backendsrv) handles all outgoing HTTP requests from Grafana. This document explains the high-level concepts used by `BackendSrv`.
|
||||
|
||||
## Canceling requests
|
||||
This section describes how canceling requests work in Grafana. While data sources can implement their own cancellation concept, we recommend that you use the method we describe here.
|
||||
|
||||
A data request can take a long time to finish. During the time between when a request starts and finishes, the user can change context. For example, the user may navigate away or issue the same request again.
|
||||
|
||||
If we wait for canceled requests to complete, it might create unnecessary load on data sources.
|
||||
|
||||
Grafana uses a concept called _request cancelation_ to cancel any ongoing request that Grafana doesn't need.
|
||||
|
||||
#### Before Grafana 7.2
|
||||
Before Grafana can cancel any data request, it has to identify that request. Grafana identifies a request using the property `requestId` [passed as options](https://github.com/grafana/grafana/blob/master/docs/sources/packages_api/runtime/backendsrvrequest.md) when you use [BackendSrv](https://grafana.com/docs/grafana/latest/packages_api/runtime/backendsrv).
|
||||
|
||||
The cancellation logic is as follows:
|
||||
- When an ongoing request discovers that an additional request with the same `requestId` has started, then Grafana will cancel the ongoing request.
|
||||
- When an ongoing request discovers that the special "cancel all requests" `requestId` was sent, then Grafana will cancel the ongoing request.
|
||||
|
||||
#### After Grafana 7.2
|
||||
Grafana 7.2 introduced an additional way of canceling requests using [RxJs](https://github.com/ReactiveX/rxjs). To support the new cancellation functionality, the data source needs to use the new `fetch` function in [BackendSrv](https://grafana.com/docs/grafana/latest/packages_api/runtime/backendsrv).
|
||||
|
||||
Migrating the core data sources to the new `fetch` function [is an ongoing process that you can read about in this issue.](https://github.com/grafana/grafana/issues/27222)
|
||||
|
||||
## Request queue
|
||||
Depending on how the web browser implements the protocol for HTTP 1.1, it will limit the number of parallel requests, lets call this limit _max_parallel_browser_request_.
|
||||
|
||||
Unless you have configured Grafana to use HTTP2, the browser limits parallel data requests according to the browser's implementation. For more information on how to enable HTTP2, refer to [Configuration](https://grafana.com/docs/grafana/latest/administration/configuration/#protocol).
|
||||
|
||||
Because there is a _max_parallel_browser_request_ limit, if some of the requests take a long time, they will block later requests and make interacting with Grafana very slow.
|
||||
|
||||
#### Before Grafana 7.2
|
||||
Not supported.
|
||||
|
||||
#### After Grafana 7.2
|
||||
Grafana uses a _request queue_ to process all incoming data requests in order while reserving a free "spot" for any requests to the Grafana API.
|
||||
|
||||
Since the first implementation of the request queue doesn't take into account what browser the user uses, the _request queue_ limit for parallel data source requests is hard-coded to 5.
|
||||
|
||||
> **Note:** Grafana instances [configured with HTTP2 ](https://grafana.com/docs/grafana/latest/administration/configuration/#protocol) will have a hard coded limit of 1000.
|
||||
338
devenv/dev-dashboards/panel-common/color_modes.json
Normal file
338
devenv/dev-dashboards/panel-common/color_modes.json
Normal file
@@ -0,0 +1,338 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": "gdev-testdata",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-BlYlRd"
|
||||
},
|
||||
"custom": {
|
||||
"align": "center",
|
||||
"displayMode": "color-background",
|
||||
"filterable": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "percentage",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "blue",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 70
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "degree"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Field"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.displayMode"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 16,
|
||||
"w": 19,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"showHeader": true,
|
||||
"sortBy": [
|
||||
{
|
||||
"desc": true,
|
||||
"displayName": "Last"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"csvWave": {
|
||||
"timeStep": 60,
|
||||
"valuesCSV": "0,0,2,2,1,1"
|
||||
},
|
||||
"lines": 10,
|
||||
"points": [],
|
||||
"pulseWave": {
|
||||
"offCount": 3,
|
||||
"offValue": 1,
|
||||
"onCount": 3,
|
||||
"onValue": 2,
|
||||
"timeStep": 60
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 15,
|
||||
"stream": {
|
||||
"bands": 1,
|
||||
"noise": 2.2,
|
||||
"speed": 250,
|
||||
"spread": 3.5,
|
||||
"type": "signal"
|
||||
},
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Gradient color schemes",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "reduce",
|
||||
"options": {
|
||||
"reducers": ["max", "mean", "last", "min"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"Field": false
|
||||
},
|
||||
"indexByName": {},
|
||||
"renameByName": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-blues"
|
||||
},
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 20
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 26,
|
||||
"w": 5,
|
||||
"x": 19,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": ["mean"],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "value"
|
||||
},
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"csvWave": {
|
||||
"timeStep": 60,
|
||||
"valuesCSV": "0,0,2,2,1,1"
|
||||
},
|
||||
"labels": "",
|
||||
"lines": 10,
|
||||
"points": [],
|
||||
"pulseWave": {
|
||||
"offCount": 3,
|
||||
"offValue": 1,
|
||||
"onCount": 3,
|
||||
"onValue": 2,
|
||||
"timeStep": 60
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 30,
|
||||
"stream": {
|
||||
"bands": 1,
|
||||
"noise": 2.2,
|
||||
"speed": 250,
|
||||
"spread": 3.5,
|
||||
"type": "signal"
|
||||
},
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Stats",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "gdev-testdata",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-GrYlRd"
|
||||
},
|
||||
"custom": {
|
||||
"align": "center",
|
||||
"displayMode": "color-background",
|
||||
"filterable": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "percentage",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "blue",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 70
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "degree"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Field"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.displayMode"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 19,
|
||||
"x": 0,
|
||||
"y": 16
|
||||
},
|
||||
"id": 5,
|
||||
"options": {
|
||||
"displayMode": "lcd",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": ["mean"],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showUnfilled": true
|
||||
},
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"csvWave": {
|
||||
"timeStep": 60,
|
||||
"valuesCSV": "0,0,2,2,1,1"
|
||||
},
|
||||
"lines": 10,
|
||||
"points": [],
|
||||
"pulseWave": {
|
||||
"offCount": 3,
|
||||
"offValue": 1,
|
||||
"onCount": 3,
|
||||
"onValue": 2,
|
||||
"timeStep": 60
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 15,
|
||||
"stream": {
|
||||
"bands": 1,
|
||||
"noise": 2.2,
|
||||
"speed": 250,
|
||||
"spread": 3.5,
|
||||
"type": "signal"
|
||||
},
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Bar Gauge LCD",
|
||||
"transformations": [],
|
||||
"type": "bargauge"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 26,
|
||||
"style": "dark",
|
||||
"tags": ["gdev", "demo"],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Gradient Color modes",
|
||||
"uid": "inxsweKGz",
|
||||
"version": 17
|
||||
}
|
||||
@@ -399,7 +399,7 @@ The length of time that Grafana will wait for a successful TLS handshake with th
|
||||
|
||||
### expect_continue_timeout_seconds
|
||||
|
||||
The length of time that Grafana will wait for a datasource’s first response headers after fully writing the request headers, if the request has an “Expect: 100-continue” header. A value of `0` will result in the body being sent immediately. Default is `1` second. For more details check the [Transport.ExpectContinueTimeout](https://golang.org/pkg/net/http/#Transport.ExpectContinueTimeout) documentation.
|
||||
The length of time that Grafana will wait for a datasource’s first response headers after fully writing the request headers, if the request has an “Expect: 100-continue” header. A value of `0` will result in the body being sent immediately. Default is `1` second. For more details check the [Transport.ExpectContinueTimeout](https://golang.org/pkg/net/http/#Transport.ExpectContinueTimeout) documentation.
|
||||
|
||||
### max_idle_connections
|
||||
|
||||
@@ -549,9 +549,11 @@ Number dashboard versions to keep (per dashboard). Default: `20`, Minimum: `1`.
|
||||
|
||||
> Only available in Grafana v6.7+.
|
||||
|
||||
This prevents users from setting the dashboard refresh interval of a lower than given interval. Per default this is 5 seconds.
|
||||
This feature prevents users from setting the dashboard refresh interval to a lower value than a given interval value. The default interval value is 5 seconds.
|
||||
The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. `30s` or `1m`.
|
||||
|
||||
As of Grafana v7.3, this also limits the refresh interval options in Explore.
|
||||
|
||||
### default_home_dashboard_path
|
||||
|
||||
Path to the default home dashboard. If this value is empty, then Grafana uses StaticRootPath + "dashboards/home.json"
|
||||
@@ -623,7 +625,7 @@ Default is `false`.
|
||||
|
||||
### user_invite_max_lifetime_duration
|
||||
|
||||
The duration in time a user invitation remains valid before expiring.
|
||||
The duration in time a user invitation remains valid before expiring.
|
||||
This setting should be expressed as a duration. Examples: 6h (hours), 2d (days), 1w (week).
|
||||
Default is `24h` (24 hours). The minimum supported duration is `15m` (15 minutes).
|
||||
|
||||
@@ -1073,6 +1075,15 @@ If both are set, then basic authentication is required to access the metrics end
|
||||
|
||||
<hr>
|
||||
|
||||
## [metrics.environment_info]
|
||||
|
||||
Adds dimensions to the `grafana_environment_info` metric, which can expose more information about the Grafana instance.
|
||||
|
||||
```
|
||||
; exampleLabel1 = exampleValue1
|
||||
; exampleLabel2 = exampleValue2
|
||||
```
|
||||
|
||||
## [metrics.graphite]
|
||||
|
||||
Use these options if you want to send internal Grafana metrics to Graphite.
|
||||
@@ -1148,6 +1159,10 @@ This is the sampler configuration parameter. Depending on the value of `sampler_
|
||||
|
||||
May be set with the environment variable `JAEGER_SAMPLER_PARAM`.
|
||||
|
||||
### sampling_server_url
|
||||
|
||||
sampling_server_url is the URL of a sampling manager providing a sampling strategy.
|
||||
|
||||
### zipkin_propagation
|
||||
|
||||
Default value is `false`.
|
||||
@@ -1325,6 +1340,10 @@ Set to `true` if you want to test alpha plugins that are not yet ready for gener
|
||||
|
||||
Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature.
|
||||
|
||||
### marketplace_url
|
||||
|
||||
Custom install/learn more url for enterprise plugins. Defaults to https://grafana.com/grafana/plugins/.
|
||||
|
||||
<hr>
|
||||
|
||||
## [plugin.grafana-image-renderer]
|
||||
|
||||
@@ -248,3 +248,19 @@ datasources:
|
||||
logMessageField: message
|
||||
logLevelField: fields.level
|
||||
```
|
||||
|
||||
## Amazon Elasticsearch Service
|
||||
|
||||
AWS users using Amazon's Elasticsearch Service can use Grafana's Elasticsearch data source to visualize Elasticsearch data.
|
||||
If you are using an AWS Identity and Access Management (IAM) policy to control access to your Amazon Elasticsearch Service domain, then you must use AWS Signature Version 4 (AWS SigV4) to sign all requests to that domain.
|
||||
For more details on AWS SigV4, refer to the [AWS documentation](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html).
|
||||
|
||||
### AWS Signature Version 4 authentication
|
||||
|
||||
> **Note:** Only available in Grafana v7.3+.
|
||||
|
||||
In order to sign requests to your Amazon Elasticsearch Service domain, SigV4 can be enabled in the Grafana [configuration]({{< relref "../administration/configuration.md#sigv4_auth_enabled" >}}).
|
||||
|
||||
Once AWS SigV4 is enabled, it can be configured on the Elasticsearch data source configuration page. Refer to [Cloudwatch authentication]({{<relref "./cloudwatch.md#authentication" >}}) for more information about authentication options.
|
||||
|
||||
{{< docs-imagebox img="/img/docs/v73/elasticsearch-sigv4-config-editor.png" max-width="500px" class="docs-image--no-shadow" caption="SigV4 configuration for AWS Elasticsearch Service" >}}
|
||||
|
||||
@@ -14,7 +14,7 @@ weight = 500
|
||||
|
||||
SAML authentication integration allows your Grafana users to log in by using an external SAML 2.0 Identity Provider (IdP). To enable this, Grafana becomes a Service Provider (SP) in the authentication flow, interacting with the IdP to exchange user information.
|
||||
|
||||
The SAML single-sign-on (SSO) standard is varied and flexible. Our implementation contains the subset of features needed to provide a smooth authentication experience into Grafana.
|
||||
The SAML single sign-on (SSO) standard is varied and flexible. Our implementation contains a subset of features needed to provide a smooth authentication experience into Grafana.
|
||||
|
||||
> Only available in Grafana Enterprise v6.3+. If you encounter any problems with our implementation, please don't hesitate to contact us.
|
||||
|
||||
@@ -45,12 +45,14 @@ The table below describes all SAML configuration options. Continue reading below
|
||||
| ----------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------- | ------------- |
|
||||
| `enabled` | No | Whether SAML authentication is allowed | `false` |
|
||||
| `single_logout` | No | Whether SAML Single Logout enabled | `false` |
|
||||
| `allow_idp_initiated` | No | Whether SAML IdP-initiated login is allowed | `false` |
|
||||
| `certificate` or `certificate_path` | Yes | Base64-encoded string or Path for the SP X.509 certificate | |
|
||||
| `private_key` or `private_key_path` | Yes | Base64-encoded string or Path for the SP private key | |
|
||||
| `signature_algorithm` | No | Signature algorithm used for signing requests to the IdP. Supported values are rsa-sha1, rsa-sha256, rsa-sha512. | |
|
||||
| `idp_metadata`, `idp_metadata_path`, or `idp_metadata_url` | Yes | Base64-encoded string, Path or URL for the IdP SAML metadata XML | |
|
||||
| `max_issue_delay` | No | Duration, since the IdP issued a response and the SP is allowed to process it | `90s` |
|
||||
| `metadata_valid_duration` | No | Duration, for how long the SP metadata is valid | `48h` |
|
||||
| `relay_state` | No | Relay state for IdP-initiated login. Should match relay state configured in IdP | |
|
||||
| `assertion_attribute_name` | No | Friendly name or name of the attribute within the SAML assertion to use as the user name | `displayName` |
|
||||
| `assertion_attribute_login` | No | Friendly name or name of the attribute within the SAML assertion to use as the user login handle | `mail` |
|
||||
| `assertion_attribute_email` | No | Friendly name or name of the attribute within the SAML assertion to use as the user email | `mail` |
|
||||
@@ -81,7 +83,9 @@ You can only use one form of each configuration option. Using multiple forms, su
|
||||
|
||||
### Signature algorithm
|
||||
|
||||
The SAML standard recommends using digital signature for some types of messages, like authentication or logout requests. If `signature_algorithm` option configured, Grafana will put digital signature into SAML requests. Supported signature types are `rsa-sha1`, `rsa-sha256`, `rsa-sha512`. This option should match your IdP configuration, otherwise, signature won't be validated by the IdP. Grafana uses key and certificate configured with `private_key` and `certificate` options for signing SAML requests.
|
||||
> Only available in Grafana v7.3+
|
||||
|
||||
The SAML standard recommends using a digital signature for some types of messages, like authentication or logout requests. If the `signature_algorithm` option is configured, Grafana will put a digital signature into SAML requests. Supported signature types are `rsa-sha1`, `rsa-sha256`, `rsa-sha512`. This option should match your IdP configuration, otherwise, signature validation will fail. Grafana uses key and certificate configured with `private_key` and `certificate` options for signing SAML requests.
|
||||
|
||||
### IdP metadata
|
||||
|
||||
@@ -113,9 +117,19 @@ The integration provides two key endpoints as part of Grafana:
|
||||
- The `/saml/metadata` endpoint, which contains the SP metadata. You can either download and upload it manually, or youmake the IdP request it directly from the endpoint. Some providers name it Identifier or Entity ID.
|
||||
- The `/saml/acs` endpoint, which is intended to receive the ACS (Assertion Customer Service) callback. Some providers name it SSO URL or Reply URL.
|
||||
|
||||
### Single Logout
|
||||
### IdP-initiated Single Sign-On (SSO)
|
||||
|
||||
Single Logout feature allows user to log out from all applications associated with current IdP session established via SAML SSO. If `single_logout` option set to `true` and user logs out, Grafana requests IdP to terminate user session. Then IdP triggers logout process for all other applications which user logged in with the same IdP session (application should support single logout). And conversely, if another application connected to the same IdP initiates single logout, Grafana gets logout request from IdP and terminates user session.
|
||||
> Only available in Grafana v7.3+
|
||||
|
||||
By default, Grafana allows only service provider (SP) initiated logins (when the user logs in with SAML via Grafana’s login page). If you want users to log in into Grafana directly from your identity provider (IdP), set the `allow_idp_initiated` configuration option to `true` and configure `relay_state` with the same value specified in the IdP configuration.
|
||||
|
||||
IdP-initiated SSO has some security risks, so make sure you understand the risks before enabling this feature. When using IdP-initiated SSO, Grafana receives unsolicited SAML requests and can't verify that login flow was started by the user. This makes it hard to detect whether SAML message has been stolen or replaced. Because of this, IdP-initiated SSO is vulnerable to login cross-site request forgery (CSRF) and man in the middle (MITM) attacks. We do not recommend using IdP-initiated SSO and keeping it disabled whenever possible.
|
||||
|
||||
### Single logout
|
||||
|
||||
> Only available in Grafana v7.3+
|
||||
|
||||
SAML's single logout feature allows users to log out from all applications associated with the current IdP session established via SAML SSO. If the `single_logout` option is set to `true` and a user logs out, Grafana requests IdP to end the user session which in turn triggers logout from all other applications the user is logged into using the same IdP session (applications should support single logout). Conversely, if another application connected to the same IdP logs out using single logout, Grafana receives a logout request from IdP and ends the user session.
|
||||
|
||||
### Assertion mapping
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ You can close the newly created query by clicking on the Close Split button.
|
||||
|
||||
> Share shortened link is only available in Grafana 7.3 and above.
|
||||
|
||||
The Share shortened link capability allows you to create smaller and simpler URLs of the format /goto/:uid instead of using longer URLs containing complex query parameters. You can create a shortened link by clicking on the **Share** option in Explore toolbar.
|
||||
The Share shortened link capability allows you to create smaller and simpler URLs of the format /goto/:uid instead of using longer URLs containing complex query parameters. You can create a shortened link by clicking on the **Share** option in Explore toolbar. Please note that any shortened links that are never used will be automatically deleted after 7 days.
|
||||
|
||||
## Query history
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Overrides allow you to change the settings for one or more fields. Field options
|
||||
|
||||
For example, you could change the number of decimal places shown in all numeric fields or columns by changing the **Decimals** option for **Fields with type** that matches **Numeric**. For more information about options, refer to:
|
||||
- [Standard field options]({{< relref "standard-field-options.md" >}}), which apply to all panel visualizations that allow transformations.
|
||||
- [Table field options]({{< relref "table-field-options.md" >}}), which only apply to table panel visualizations.
|
||||
- There can also be visualization specific field display options
|
||||
|
||||
## Add a field override
|
||||
|
||||
@@ -26,8 +26,6 @@ You can override as many field options as you want to.
|
||||
- **Fields with type -** Allows you to select fields by type, such as string, numeric, and so on. Properties you add to a rule with this selector are applied to all fields that match the selected type.
|
||||
1. Click **Add override property**.
|
||||
1. Select the field option that you want to apply.
|
||||
- [Standard field options]({{< relref "standard-field-options.md" >}}), which apply to all panel visualizations that allow transformations.
|
||||
- [Table field options]({{< relref "table-field-options.md" >}}), which only apply to table panel visualizations.
|
||||
1. Enter options by adding values in the fields. To return options to default values, delete the white text in the fields.
|
||||
1. Continue to add overrides to this field by clicking **Add override property**, or you can click **Add override** and select a different field to add overrides to.
|
||||
1. When finished, click **Save** to save all panel edits to the dashboard.
|
||||
|
||||
@@ -37,11 +37,11 @@ Can do everything scoped to the organization. For example:
|
||||
- Can view, add, and edit dashboards, panels, and alert rules in dashboards they have access to. This can be disabled on specific folders and dashboards.
|
||||
- Can create, update, or delete playlists.
|
||||
- Can access Explore.
|
||||
- Can add, edit, or delete alert notification channels.
|
||||
- Cannot add, edit, or delete data sources.
|
||||
- Cannot add, edit, or delete alert notification channels.
|
||||
- Cannot manage other organizations, users, and teams.
|
||||
|
||||
This role can be changed with the Grafana server setting [editors_can_admin]({{< relref "../administration/configuration.md#editors_can_admin" >}}). If you set this to `true`, then users with the Editor role can also administrate dashboards, folders and teams they create. This is especially useful for enabling self-organizing teams to administer their own dashboards.
|
||||
This role can be changed with the Grafana server setting [editors_can_admin]({{< relref "../administration/configuration.md#editors_can_admin" >}}). If you set this to `true`, then users with the Editor role can also administrate dashboards, folders, and teams they create. This is especially useful for enabling self-organizing teams to administer their own dashboards.
|
||||
|
||||
## Viewer role
|
||||
|
||||
|
||||
@@ -15,16 +15,53 @@ weight = -17
|
||||
|
||||
This topic includes the release notes for the Grafana v7.3. For all details, read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md).
|
||||
|
||||
## Highlights
|
||||
|
||||
Grafana 7.3 comes with a number of features and enhancements:
|
||||
The main highlights are:
|
||||
|
||||
- [**Google Cloud Monitoring:** Out of the box dashboards]({{< relref "#cloud-monitoring-out-of-the-box-dashboards" >}})
|
||||
- [**Shorten URL for dashboards and Explore**]({{< relref "#shorten-url-for-dashboards-and-explore" >}})
|
||||
- [**Table improvements and new image cell mode**]({{< relref "#table-improvements-and-new-image-cell-mode" >}})
|
||||
- [**New color scheme option**]({{< relref "#new-color-scheme-option" >}})
|
||||
- [**SigV4 Authentication for Amazon Elasticsearch Service**]({{< relref "#sigv4-authentication-for-aws-users" >}})
|
||||
|
||||
#### Cloud monitoring out-of-the-box dashboards
|
||||
## Table improvements and new image cell mode
|
||||
|
||||
The updated Cloud monitoring data source is shipped with pre-configured dashboards for five of the most popular GCP services:
|
||||
The table has been updated with improved hover behavior for cells that have longer content than what fits the current column width. As you can see
|
||||
in the animated gif below the cell will automatically expand to show you full content of the cell.
|
||||
|
||||
{{< figure src="/img/docs/v73/table_hover.gif" max-width="900px" caption="Table hover" >}}
|
||||
|
||||
Another new feature that can be seen in the image above is the new image cell display mode. If you have a field value that is an image URL or a base64 encoded image you can configure the table to display it as an image.
|
||||
|
||||
## New color scheme option
|
||||
|
||||
{{< figure src="/img/docs/v73/color_scheme_dropdown.png" max-width="450px" caption="Color scheme" class="pull-right" >}}
|
||||
|
||||
A new standard field [color scheme]({{< relref "../panels/field-options/standard-field-options.md#color-scheme" >}}) option has been added. This new option will provide a unified way for all new panels to specify how colors should be assigned.
|
||||
|
||||
* **Single color**: Specify a single color, useful in an override rule.
|
||||
* **From thresholds**: Informs Grafana to take the color from the matching threshold.
|
||||
* **Classic palette**: Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations.
|
||||
* **Green-Yellow-Red (by value)**: This is a continuous color scheme where Grafana will interpolate a color based on the value being displayed and the field min & max values.
|
||||
* **Blue-Yellow-Red (by value)**: Same as above but different colors.
|
||||
* **Blues (by value)**: Same as above but color scheme go from panel background to blue.
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
As you can see this adds new continuous color schemes where Grafana will interpolate colors. A great use of these new color schemes is the table panel where you can color the background and get a heatmap like effect.
|
||||
|
||||
{{< figure src="/img/docs/v73/table_color_scheme.png" max-width="900px" caption="table color scheme" >}}
|
||||
|
||||
Another thing to highlight is that all these new color schemes are theme aware and adapt to the current theme. For example here is how the new monochrome color scheme look like in the light theme:
|
||||
|
||||
{{< figure src="/img/docs/v73/table_color_scheme_mono_light.png" max-width="900px" caption="table color monochrome scheme" >}}
|
||||
|
||||
As this new option is a standard field option it works in every panel. Here is another example from the [Bar Gauge]({{< relref "../panels/visualizations/bar-gauge-panel.md" >}}) panel.
|
||||
|
||||
{{< figure src="/img/docs/v73/bar_gauge_gradient_color_scheme.png" max-width="900px" caption="bar gauge color scheme" >}}
|
||||
|
||||
## Google Cloud monitoring out-of-the-box dashboards
|
||||
|
||||
The updated Google Cloud monitoring data source is shipped with pre-configured dashboards for five of the most popular Google Cloud Platform (GCP) services:
|
||||
|
||||
- BigQuery
|
||||
- Cloud Load Balancing
|
||||
@@ -38,7 +75,13 @@ For more details, see the [Google Cloud Monitoring docs]({{<relref "../datasourc
|
||||
|
||||
## Shorten URL for dashboards and Explore
|
||||
|
||||
This is an amazing new feature that was created in cooperation with one of our community members. The new **share shortened link** capability allows you to create smaller and simpler URLs of the format `/goto/:uid` instead of using longer URLs that can contain complex query parameters. In Explore, you can create a shortened link by clicking on the share button in Explore toolbar. In the dashboards, a shortened url option is available through the share panel or dashboard button.
|
||||
This is an amazing new feature that was created in cooperation with one of our community members. The new share shortened link capability allows you to create smaller and simpler URLs of the format `/goto/:uid` instead of using longer URLs that can contain complex query parameters. In Explore, you can create a shortened link by clicking on the share button in Explore toolbar. In the dashboards, a shortened url option is available through the share panel or dashboard button.
|
||||
|
||||
## SigV4 authentication for AWS users
|
||||
|
||||
You can now configure your Elasticsearch data source to access your Amazon Elasticsearch Service domain directly from Grafana.
|
||||
|
||||
For more details, refer to the [Elasticsearch docs]({{<relref "../datasources/elasticsearch/#aws-signature-version-4-authentication">}}).
|
||||
|
||||
## Grafana Enterprise features
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ e2e.scenario({
|
||||
e2e.components.DashboardLinks.link()
|
||||
.should('be.visible')
|
||||
.and(links => {
|
||||
expect(links).to.have.length(13);
|
||||
expect(links).to.have.length.greaterThan(13);
|
||||
|
||||
for (let index = 0; index < links.length; index++) {
|
||||
expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${variableValue}`);
|
||||
@@ -59,8 +59,7 @@ e2e.scenario({
|
||||
e2e.components.DashboardLinks.dropDown()
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.wait('@tagsTemplatingSearch')
|
||||
.wait('@tagsDemoSearch');
|
||||
.wait('@tagsTemplatingSearch');
|
||||
|
||||
// verify all links, should have p2 value
|
||||
verifyLinks('p2');
|
||||
|
||||
6
go.mod
6
go.mod
@@ -22,7 +22,6 @@ require (
|
||||
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
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
||||
github.com/facebookgo/inject v0.0.0-20180706035515-f23751cae28b
|
||||
@@ -30,6 +29,7 @@ require (
|
||||
github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691 // indirect
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/gchaincl/sqlhooks v1.3.0
|
||||
github.com/go-macaron/binding v0.0.0-20190806013118-0b4f37bab25b
|
||||
github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07
|
||||
github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659
|
||||
@@ -40,14 +40,14 @@ require (
|
||||
github.com/google/go-cmp v0.5.0
|
||||
github.com/gosimple/slug v1.4.2
|
||||
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.78.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.79.0
|
||||
github.com/grafana/loki v1.6.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.1
|
||||
github.com/hashicorp/go-hclog v0.12.2
|
||||
github.com/hashicorp/go-plugin v1.2.2
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.0.1
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.2.0
|
||||
github.com/jmespath/go-jmespath v0.3.0
|
||||
github.com/jonboulle/clockwork v0.2.1 // indirect
|
||||
github.com/jung-kurt/gofpdf v1.10.1
|
||||
|
||||
16
go.sum
16
go.sum
@@ -244,9 +244,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
|
||||
github.com/deepmap/oapi-codegen v1.3.6/go.mod h1:aBozjEveG+33xPiP55Iw/XbVkhtZHEGLq3nxlX0+hfU=
|
||||
github.com/deepmap/oapi-codegen v1.3.11 h1:Nd3tDQfqgquLmCzyRONHzs5SJEwPPoQcFZxT8MKt1Hs=
|
||||
github.com/deepmap/oapi-codegen v1.3.11/go.mod h1:suMvK7+rKlx3+tpa8ByptmvoXbAV70wERKTOGH3hLp0=
|
||||
github.com/deepmap/oapi-codegen v1.3.13 h1:9HKGCsdJqE4dnrQ8VerFS0/1ZOJPmAhN+g8xgp8y3K4=
|
||||
github.com/deepmap/oapi-codegen v1.3.13/go.mod h1:WAmG5dWY8/PYHt4vKxlt90NsbHMAOCiteYKZMiIRfOo=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec h1:NfhRXXFDPxcF5Cwo06DzeIaE7uuJtAUhsDwH3LNsjos=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
@@ -317,7 +316,8 @@ github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03D
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=
|
||||
github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk=
|
||||
github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y=
|
||||
github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34=
|
||||
github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@@ -578,8 +578,8 @@ github.com/gosimple/slug v1.4.2 h1:jDmprx3q/9Lfk4FkGZtvzDQ9Cj9eAmsjzeQGp24PeiQ=
|
||||
github.com/gosimple/slug v1.4.2/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0=
|
||||
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4 h1:SPdxCL9BChFTlyi0Khv64vdCW4TMna8+sxL7+Chx+Ag=
|
||||
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4/go.mod h1:nc0XxBzjeGcrMltCDw269LoWF9S8ibhgxolCdA1R8To=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.78.0 h1:w43X+b36goTvis4TAW5PzhkGuSJokgm3KKYPOYiENAc=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.78.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.79.0 h1:7NVEIMlF8G9H7XUdLX9jH/g01FllE1GEBcFvzXZD+Kw=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60=
|
||||
github.com/grafana/loki v1.6.0 h1:vHuFgfhW1iRMCm7/LgnZi02Ifvqj389vWhyfNJlHQTQ=
|
||||
github.com/grafana/loki v1.6.0/go.mod h1:X+GvtCzAf2ok/xRLLvGB8kuWP1R+75nXnvjCEnenP0s=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
@@ -670,8 +670,8 @@ github.com/influxdata/go-syslog/v3 v3.0.1-0.20200510134747-836dce2cf6da/go.mod h
|
||||
github.com/influxdata/influxdb v1.7.7/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
|
||||
github.com/influxdata/influxdb v1.8.0/go.mod h1:SIzcnsjaHRFpmlxpJ4S3NT64qtEKYweNTUMb/vh0OMQ=
|
||||
github.com/influxdata/influxdb v1.8.1/go.mod h1:SIzcnsjaHRFpmlxpJ4S3NT64qtEKYweNTUMb/vh0OMQ=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.0.1 h1:vRla3taM+zkziP1NUGfN6Y6zJ9ZSSMg0fs/JhCGyX1s=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.0.1/go.mod h1:eyFPc0lhFnNSpyCDb0ZkrB3Hbtqvn1K1JZmjo2BXqeo=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.2.0 h1:2R/le0s/MZpHtc+ijuXKe2c4KGN14M85mWtGlmg6vec=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.2.0/go.mod h1:fa/d1lAdUHxuc1jedx30ZfNG573oQTQmUni3N6pcW+0=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/influxdata/influxql v1.1.0/go.mod h1:KpVI7okXjK6PRi3Z5B+mtKZli+R1DnZgb3N+tzevNgo=
|
||||
github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE=
|
||||
|
||||
3
grafana-mixin/.gitignore
vendored
Normal file
3
grafana-mixin/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
alerts.yaml
|
||||
rules.yaml
|
||||
dashboards_out
|
||||
13
grafana-mixin/Makefile
Normal file
13
grafana-mixin/Makefile
Normal file
@@ -0,0 +1,13 @@
|
||||
all: fmt lint build clean
|
||||
|
||||
fmt:
|
||||
./scripts/format.sh
|
||||
|
||||
lint:
|
||||
./scripts/lint.sh
|
||||
|
||||
build:
|
||||
./scripts/build.sh
|
||||
|
||||
clean:
|
||||
rm -rf dashboards_out alerts.yaml rules.yaml
|
||||
28
grafana-mixin/README.md
Normal file
28
grafana-mixin/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Grafana Mixin
|
||||
|
||||
_This is a work in progress. We aim for it to become a good role model for alerts
|
||||
and dashboards eventually, but it is not quite there yet._
|
||||
|
||||
The Grafana Mixin is a set of configurable, reusable, and extensible alerts and
|
||||
dashboards based on the metrics exported by Grafana. The mixin creates
|
||||
recording and alerting rules for Prometheus and suitable dashboard descriptions
|
||||
for Grafana.
|
||||
|
||||
To use them, you need to have `mixtool` and `jsonnetfmt` installed. If you
|
||||
have a working Go development environment, it's easiest to run the following:
|
||||
|
||||
```bash
|
||||
$ go get github.com/monitoring-mixins/mixtool/cmd/mixtool
|
||||
$ go get github.com/google/go-jsonnet/cmd/jsonnetfmt
|
||||
```
|
||||
|
||||
You can then build the Prometheus rules files `alerts.yaml` and
|
||||
`rules.yaml` and a directory `dashboard_out` with the JSON dashboard files
|
||||
for Grafana:
|
||||
|
||||
```bash
|
||||
$ make build
|
||||
```
|
||||
|
||||
For more advanced uses of mixins, see
|
||||
https://github.com/monitoring-mixins/docs.
|
||||
14
grafana-mixin/alerts/alerts.yaml
Normal file
14
grafana-mixin/alerts/alerts.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
groups:
|
||||
- name: GrafanaAlerts
|
||||
rules:
|
||||
- alert: GrafanaRequestsFailing
|
||||
for: 5m
|
||||
expr: |
|
||||
100 * namespace_job_handler_statuscode:http_request_total:rate5m{handler!~"/datasources/proxy/:id.*|/ds/query|/tsdb/query", statuscode=~"5.."}
|
||||
/
|
||||
namespace_job_handler_statuscode:http_request_total:rate5m{handler!~"/datasources/proxy/:id.*|/ds/query|/tsdb/query"}
|
||||
> 0.5
|
||||
labels:
|
||||
severity: 'critical'
|
||||
annotations:
|
||||
message: "'{{ $labels.namespace }}' / '{{ $labels.job }}' / '{{ $labels.handler }}' is experiencing {{ $value | humanize }}% errors"
|
||||
528
grafana-mixin/dashboards/grafana-overview.json
Normal file
528
grafana-mixin/dashboards/grafana-overview.json
Normal file
@@ -0,0 +1,528 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": 35,
|
||||
"iteration": 1602761142538,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"noValue": "0",
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 6,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"mean"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "grafana_alerting_result_total{job=~\"$job\", instance=~\"$instance\", state=\"alerting\"}",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Firing Alerts",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 0
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"mean"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(grafana_stat_totals_dashboard{job=~\"$job\", instance=~\"$instance\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Dashboards",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"align": null
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 10,
|
||||
"options": {
|
||||
"showHeader": true
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "grafana_build_info{job=~\"$job\", instance=~\"$instance\"}",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Build Info",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "labelsToFields",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"Time": true,
|
||||
"Value": true,
|
||||
"branch": true,
|
||||
"container": true,
|
||||
"goversion": true,
|
||||
"namespace": true,
|
||||
"pod": true,
|
||||
"revision": true
|
||||
},
|
||||
"indexByName": {
|
||||
"Time": 7,
|
||||
"Value": 11,
|
||||
"branch": 4,
|
||||
"container": 8,
|
||||
"edition": 2,
|
||||
"goversion": 6,
|
||||
"instance": 1,
|
||||
"job": 0,
|
||||
"namespace": 9,
|
||||
"pod": 10,
|
||||
"revision": 5,
|
||||
"version": 3
|
||||
},
|
||||
"renameByName": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "$datasource",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 5
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 2,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (statuscode) (irate(http_request_total{job=~\"$job\", instance=~\"$instance\"}[1m])) ",
|
||||
"interval": "",
|
||||
"legendFormat": "{{statuscode}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "RPS",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:157",
|
||||
"format": "reqps",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:158",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "$datasource",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 5
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 4,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(http_request_duration_milliseconds{job=~\"$job\", instance=~\"$instance\", quantile=\"0.99\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "max-99th",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "max(http_request_duration_milliseconds{job=~\"$job\", instance=~\"$instance\", quantile=\"0.9\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "max-90th",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "sum(irate(http_request_duration_milliseconds_sum{job=~\"$job\", instance=~\"$instance\"}[$__interval])) / sum(irate(http_request_duration_milliseconds_count{job=~\"$job\", instance=~\"$instance\"}[$__interval])) ",
|
||||
"interval": "",
|
||||
"legendFormat": "avg",
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Request Latency",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:210",
|
||||
"format": "ms",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:211",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemaVersion": 25,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "prometheus",
|
||||
"value": "prometheus"
|
||||
},
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": null,
|
||||
"multi": false,
|
||||
"name": "datasource",
|
||||
"options": [],
|
||||
"query": "prometheus",
|
||||
"queryValue": "",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"type": "datasource"
|
||||
},
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {
|
||||
"selected": true,
|
||||
"tags": [],
|
||||
"text": "All",
|
||||
"value": [
|
||||
"$__all"
|
||||
]
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"definition": "label_values(grafana_build_info, job)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": null,
|
||||
"multi": true,
|
||||
"name": "job",
|
||||
"options": [],
|
||||
"query": "label_values(grafana_build_info, job)",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tags": [],
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "All",
|
||||
"value": "$__all"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"definition": "label_values(grafana_build_info, instance)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": null,
|
||||
"multi": true,
|
||||
"name": "instance",
|
||||
"options": [],
|
||||
"query": "label_values(grafana_build_info, instance)",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tags": [],
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
]
|
||||
},
|
||||
"timezone": "",
|
||||
"title": "Grafana Overview",
|
||||
"uid": "6be0s85Mk",
|
||||
"version": 4
|
||||
}
|
||||
15
grafana-mixin/mixin.libsonnet
Normal file
15
grafana-mixin/mixin.libsonnet
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
grafanaDashboards: {
|
||||
'grafana-overview.json': (import 'dashboards/grafana-overview.json'),
|
||||
},
|
||||
|
||||
// Helper function to ensure that we don't override other rules, by forcing
|
||||
// the patching of the groups list, and not the overall rules object.
|
||||
local importRules(rules) = {
|
||||
groups+: std.native('parseYaml')(rules)[0].groups,
|
||||
},
|
||||
|
||||
prometheusRules+: importRules(importstr 'rules/rules.yaml'),
|
||||
|
||||
prometheusAlerts+: importRules(importstr 'alerts/alerts.yaml'),
|
||||
}
|
||||
7
grafana-mixin/rules/rules.yaml
Normal file
7
grafana-mixin/rules/rules.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
groups:
|
||||
- name: grafana_rules
|
||||
rules:
|
||||
# Record error rate of http requests excluding dataproxy, /ds/query and /tsdb/query requests
|
||||
- record: namespace_job_handler_statuscode:http_request_total:rate5m
|
||||
expr: |
|
||||
sum by (namespace, job, handler, statuscode) (rate(http_request_total[5m]))
|
||||
6
grafana-mixin/scripts/build.sh
Executable file
6
grafana-mixin/scripts/build.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
mixtool generate all mixin.libsonnet
|
||||
1
grafana-mixin/scripts/common.sh
Normal file
1
grafana-mixin/scripts/common.sh
Normal file
@@ -0,0 +1 @@
|
||||
JSONNET_FMT="jsonnetfmt -n 2 --max-blank-lines 2 --string-style s --comment-style s"
|
||||
9
grafana-mixin/scripts/format.sh
Executable file
9
grafana-mixin/scripts/format.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
. scripts/common.sh
|
||||
|
||||
find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \
|
||||
xargs -n 1 -- ${JSONNET_FMT} -i
|
||||
13
grafana-mixin/scripts/lint.sh
Executable file
13
grafana-mixin/scripts/lint.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
. scripts/common.sh
|
||||
|
||||
find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \
|
||||
while read f; do \
|
||||
${JSONNET_FMT} "$f" | diff -u "$f" -; \
|
||||
done
|
||||
|
||||
mixtool lint mixin.libsonnet
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"packages": ["packages/*"],
|
||||
"version": "7.3.0-pre.0"
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "7.3.4"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "7.3.0-pre",
|
||||
"version": "7.3.4",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
|
||||
@@ -45,8 +45,8 @@
|
||||
"ci:test-frontend": "yarn run prettier:check && yarn run packages:typecheck && yarn run typecheck && yarn run test"
|
||||
},
|
||||
"grafana": {
|
||||
"whatsNewUrl": "https://grafana.com/docs/grafana/latest/guides/whats-new-in-v7-2/",
|
||||
"releaseNotesUrl": "https://community.grafana.com/t/release-notes-v7-2-x/36321"
|
||||
"whatsNewUrl": "https://grafana.com/docs/grafana/latest/guides/whats-new-in-v7-3/",
|
||||
"releaseNotesUrl": "https://grafana.com/docs/grafana/latest/release-notes/"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
@@ -269,6 +269,7 @@
|
||||
"react-loadable": "5.5.0",
|
||||
"react-popper": "1.3.3",
|
||||
"react-redux": "7.2.0",
|
||||
"react-reverse-portal": "^2.0.1",
|
||||
"react-sizeme": "2.6.12",
|
||||
"react-split-pane": "0.1.89",
|
||||
"react-transition-group": "4.3.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.4",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getDisplayProcessor, getRawDisplayProcessor } from './displayProcessor';
|
||||
import { getDecimalsForValue, getDisplayProcessor, getRawDisplayProcessor } from './displayProcessor';
|
||||
import { DisplayProcessor, DisplayValue } from '../types/displayValue';
|
||||
import { MappingType, ValueMapping } from '../types/valueMapping';
|
||||
import { FieldConfig, FieldType, ThresholdsMode } from '../types';
|
||||
@@ -16,9 +16,8 @@ function getDisplayProcessorFromConfig(config: FieldConfig) {
|
||||
function assertSame(input: any, processors: DisplayProcessor[], match: DisplayValue) {
|
||||
processors.forEach(processor => {
|
||||
const value = processor(input);
|
||||
expect(value.text).toEqual(match.text);
|
||||
if (match.hasOwnProperty('numeric')) {
|
||||
expect(value.numeric).toEqual(match.numeric);
|
||||
for (const key of Object.keys(match)) {
|
||||
expect((value as any)[key]).toEqual((match as any)[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -89,6 +88,27 @@ describe('Process simple display values', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Process null values', () => {
|
||||
const processors = [
|
||||
getDisplayProcessorFromConfig({
|
||||
min: 0,
|
||||
max: 100,
|
||||
thresholds: {
|
||||
mode: ThresholdsMode.Absolute,
|
||||
steps: [
|
||||
{ value: -Infinity, color: '#000' },
|
||||
{ value: 0, color: '#100' },
|
||||
{ value: 100, color: '#200' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
it('Null should get -Infinity (base) color', () => {
|
||||
assertSame(null, processors, { text: '', numeric: NaN, color: '#000' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Format value', () => {
|
||||
it('should return if value isNaN', () => {
|
||||
const valueMappings: ValueMapping[] = [];
|
||||
@@ -329,3 +349,21 @@ describe('getRawDisplayProcessor', () => {
|
||||
expect(result).toEqual({ text: expected, numeric: null });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDecimalsForValue', () => {
|
||||
it.each`
|
||||
value | expected
|
||||
${0} | ${0}
|
||||
${13.37} | ${0}
|
||||
${-13.37} | ${0}
|
||||
${12679.3712345811212} | ${0}
|
||||
${-12679.3712345811212} | ${0}
|
||||
${0.3712345} | ${2}
|
||||
${-0.37123458} | ${2}
|
||||
${-0.04671994403853774} | ${3}
|
||||
${0.04671994403853774} | ${3}
|
||||
`('should return correct suggested decimal count', ({ value, expected }) => {
|
||||
const result = getDecimalsForValue(value);
|
||||
expect(result.decimals).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,13 +3,14 @@ import _ from 'lodash';
|
||||
|
||||
// Types
|
||||
import { Field, FieldType } from '../types/dataFrame';
|
||||
import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
|
||||
import { GrafanaTheme } from '../types/theme';
|
||||
import { DecimalCount, DecimalInfo, DisplayProcessor, DisplayValue } from '../types/displayValue';
|
||||
import { getValueFormat } from '../valueFormats/valueFormats';
|
||||
import { getMappedValue } from '../utils/valueMappings';
|
||||
import { dateTime } from '../datetime';
|
||||
import { KeyValue, TimeZone } from '../types';
|
||||
import { getScaleCalculator } from './scale';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
interface DisplayProcessorOptions {
|
||||
field: Partial<Field>;
|
||||
@@ -41,7 +42,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
const config = field.config ?? {};
|
||||
|
||||
// Theme should be required or we need access to default theme instance from here
|
||||
const theme = options.theme ?? ({ type: GrafanaThemeType.Dark } as GrafanaTheme);
|
||||
const theme = options.theme ?? getTestTheme();
|
||||
|
||||
let unit = config.unit;
|
||||
let hasDateUnit = unit && (timeFormats[unit] || unit.startsWith('time:'));
|
||||
@@ -114,7 +115,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
}
|
||||
}
|
||||
|
||||
return { text, numeric, prefix, suffix, ...scaleFunc(0) };
|
||||
return { text, numeric, prefix, suffix, ...scaleFunc(-Infinity) };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -142,7 +143,7 @@ export function getDecimalsForValue(value: number, decimalOverride?: DecimalCoun
|
||||
return { decimals: decimalOverride, scaledDecimals: null };
|
||||
}
|
||||
|
||||
let dec = -Math.floor(Math.log(value) / Math.LN10) + 1;
|
||||
let dec = -Math.floor(Math.log(Math.abs(value)) / Math.LN10) + 1;
|
||||
const magn = Math.pow(10, -dec);
|
||||
const norm = value / magn; // norm is between 1.0 and 10.0
|
||||
let size;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Field, GrafanaThemeType, GrafanaTheme, FieldColorModeId } from '../types';
|
||||
import { Field, FieldColorModeId } from '../types';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
import { fieldColorModeRegistry, FieldValueColorCalculator } from './fieldColor';
|
||||
|
||||
describe('fieldColorModeRegistry', () => {
|
||||
@@ -9,10 +10,7 @@ describe('fieldColorModeRegistry', () => {
|
||||
|
||||
function getCalculator(options: GetCalcOptions): FieldValueColorCalculator {
|
||||
const mode = fieldColorModeRegistry.get(options.mode);
|
||||
return mode.getCalculator(
|
||||
{ state: { seriesIndex: options.seriesIndex } } as Field,
|
||||
{ type: GrafanaThemeType.Dark } as GrafanaTheme
|
||||
);
|
||||
return mode.getCalculator({ state: { seriesIndex: options.seriesIndex } } as Field, getTestTheme());
|
||||
}
|
||||
|
||||
it('Schemes should interpolate', () => {
|
||||
|
||||
@@ -54,20 +54,74 @@ export const fieldColorModeRegistry = new Registry<FieldColorMode>(() => {
|
||||
// }),
|
||||
new FieldColorSchemeMode({
|
||||
id: FieldColorModeId.PaletteClassic,
|
||||
name: 'By series / Classic palette',
|
||||
//description: 'Assigns color based on series or field index',
|
||||
name: 'Classic palette',
|
||||
isContinuous: false,
|
||||
isByValue: false,
|
||||
colors: classicColors,
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-GrYlRd',
|
||||
name: 'By value / Green Yellow Red (gradient)',
|
||||
//description: 'Interpolated colors based value, min and max',
|
||||
name: 'Green-Yellow-Red',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['green', 'yellow', 'red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-BlYlRd',
|
||||
name: 'Blue-Yellow-Red',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['dark-blue', 'super-light-yellow', 'dark-red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-YlRd',
|
||||
name: 'Yellow-Red',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['super-light-yellow', 'dark-red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-BlPu',
|
||||
name: 'Blue-Purple',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['blue', 'purple'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-YlBl',
|
||||
name: 'Yellow-Blue',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['super-light-yellow', 'dark-blue'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-blues',
|
||||
name: 'Blues',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-blue'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-reds',
|
||||
name: 'Reds',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-greens',
|
||||
name: 'Greens',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-green'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-purples',
|
||||
name: 'Purples',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-purple'],
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import merge from 'lodash/merge';
|
||||
import { getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay';
|
||||
import { toDataFrame } from '../dataframe/processDataFrame';
|
||||
import { ReducerID } from '../transformations/fieldReducer';
|
||||
import { GrafanaTheme } from '../types/theme';
|
||||
import { MappingType } from '../types';
|
||||
import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
describe('FieldDisplay', () => {
|
||||
beforeAll(() => {
|
||||
@@ -241,7 +241,7 @@ function createDisplayOptions(extend: Partial<GetFieldDisplayValuesOptions> = {}
|
||||
overrides: [],
|
||||
defaults: {},
|
||||
},
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
};
|
||||
|
||||
return merge<GetFieldDisplayValuesOptions, any>(options, extend);
|
||||
|
||||
@@ -7,19 +7,18 @@ import {
|
||||
setDynamicConfigValue,
|
||||
setFieldConfigDefaults,
|
||||
} from './fieldOverrides';
|
||||
import { MutableDataFrame, toDataFrame } from '../dataframe';
|
||||
import { MutableDataFrame, toDataFrame, ArrayDataFrame } from '../dataframe';
|
||||
import {
|
||||
DataFrame,
|
||||
Field,
|
||||
FieldColorModeId,
|
||||
FieldConfig,
|
||||
FieldConfigPropertyItem,
|
||||
FieldConfigSource,
|
||||
FieldType,
|
||||
GrafanaTheme,
|
||||
InterpolateFunction,
|
||||
ThresholdsMode,
|
||||
FieldColorModeId,
|
||||
ScopedVars,
|
||||
ThresholdsMode,
|
||||
} from '../types';
|
||||
import { locationUtil, Registry } from '../utils';
|
||||
import { mockStandardProperties } from '../utils/tests/mockStandardProperties';
|
||||
@@ -28,6 +27,7 @@ import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
|
||||
import { getFieldDisplayName } from './fieldState';
|
||||
import { ArrayVector } from '../vector';
|
||||
import { getDisplayProcessor } from './displayProcessor';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
const property1: any = {
|
||||
id: 'custom.property1', // Match field properties
|
||||
@@ -87,6 +87,54 @@ describe('Global MinMax', () => {
|
||||
expect(minmax.min).toEqual(-20);
|
||||
expect(minmax.max).toEqual(1234);
|
||||
});
|
||||
|
||||
it('find global min max when all values are zero', () => {
|
||||
const f0 = new ArrayDataFrame<{ title: string; value: number; value2: number | null }>([
|
||||
{ title: 'AAA', value: 0, value2: 0 },
|
||||
{ title: 'CCC', value: 0, value2: 0 },
|
||||
]);
|
||||
|
||||
const minmax = findNumericFieldMinMax([f0]);
|
||||
expect(minmax.min).toEqual(0);
|
||||
expect(minmax.max).toEqual(0);
|
||||
});
|
||||
|
||||
describe('when value is null', () => {
|
||||
it('then global min max should be null', () => {
|
||||
const frame = toDataFrame({
|
||||
fields: [
|
||||
{ name: 'Time', type: FieldType.time, values: [1] },
|
||||
{ name: 'Value', type: FieldType.number, values: [null] },
|
||||
],
|
||||
});
|
||||
const { min, max } = findNumericFieldMinMax([frame]);
|
||||
|
||||
expect(min).toBe(null);
|
||||
expect(max).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when value values are zeo', () => {
|
||||
it('then global min max should be correct', () => {
|
||||
const frame = toDataFrame({
|
||||
fields: [
|
||||
{ name: 'Time', type: FieldType.time, values: [1, 2] },
|
||||
{ name: 'Value', type: FieldType.number, values: [1, 2] },
|
||||
],
|
||||
});
|
||||
const frame2 = toDataFrame({
|
||||
fields: [
|
||||
{ name: 'Time', type: FieldType.time, values: [1, 2] },
|
||||
{ name: 'Value', type: FieldType.number, values: [0, 0] },
|
||||
],
|
||||
});
|
||||
|
||||
const { min, max } = findNumericFieldMinMax([frame, frame2]);
|
||||
|
||||
expect(min).toBe(0);
|
||||
expect(max).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyFieldOverrides', () => {
|
||||
@@ -136,7 +184,7 @@ describe('applyFieldOverrides', () => {
|
||||
},
|
||||
replaceVariables: (value: any) => value,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
fieldConfigRegistry: new FieldConfigOptionsRegistry(),
|
||||
});
|
||||
|
||||
@@ -199,7 +247,7 @@ describe('applyFieldOverrides', () => {
|
||||
fieldConfigRegistry: customFieldRegistry,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
replaceVariables: v => v,
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
})[0];
|
||||
|
||||
const outField = processed.fields[0];
|
||||
@@ -216,7 +264,7 @@ describe('applyFieldOverrides', () => {
|
||||
fieldConfig: src as FieldConfigSource, // defaults + overrides
|
||||
replaceVariables: (undefined as any) as InterpolateFunction,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: (undefined as any) as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
fieldConfigRegistry: customFieldRegistry,
|
||||
})[0];
|
||||
const valueColumn = data.fields[1];
|
||||
@@ -244,7 +292,7 @@ describe('applyFieldOverrides', () => {
|
||||
fieldConfig: src as FieldConfigSource, // defaults + overrides
|
||||
replaceVariables: (undefined as any) as InterpolateFunction,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: (undefined as any) as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
autoMinMax: true,
|
||||
})[0];
|
||||
const valueColumn = data.fields[1];
|
||||
@@ -268,7 +316,7 @@ describe('applyFieldOverrides', () => {
|
||||
return value;
|
||||
}) as InterpolateFunction,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: (undefined as any) as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
autoMinMax: true,
|
||||
fieldConfigRegistry: customFieldRegistry,
|
||||
})[0];
|
||||
@@ -521,7 +569,7 @@ describe('getLinksSupplier', () => {
|
||||
// this is used only for internal links so isn't needed here
|
||||
() => ({} as any),
|
||||
{
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
}
|
||||
);
|
||||
supplier({});
|
||||
@@ -568,7 +616,7 @@ describe('getLinksSupplier', () => {
|
||||
// We do not need to interpolate anything for this test
|
||||
(value, vars, format) => value,
|
||||
uid => ({ name: 'testDS' } as any),
|
||||
{ theme: {} as GrafanaTheme }
|
||||
{ theme: getTestTheme() }
|
||||
);
|
||||
const links = supplier({ valueRowIndex: 0 });
|
||||
expect(links.length).toBe(1);
|
||||
|
||||
@@ -41,13 +41,13 @@ interface OverrideProps {
|
||||
}
|
||||
|
||||
interface GlobalMinMax {
|
||||
min: number;
|
||||
max: number;
|
||||
min?: number | null;
|
||||
max?: number | null;
|
||||
}
|
||||
|
||||
export function findNumericFieldMinMax(data: DataFrame[]): GlobalMinMax {
|
||||
let min = Number.MAX_VALUE;
|
||||
let max = Number.MIN_VALUE;
|
||||
let min: number | null = null;
|
||||
let max: number | null = null;
|
||||
|
||||
const reducers = [ReducerID.min, ReducerID.max];
|
||||
|
||||
@@ -55,11 +55,15 @@ export function findNumericFieldMinMax(data: DataFrame[]): GlobalMinMax {
|
||||
for (const field of frame.fields) {
|
||||
if (field.type === FieldType.number) {
|
||||
const stats = reduceField({ field, reducers });
|
||||
if (stats[ReducerID.min] < min) {
|
||||
min = stats[ReducerID.min];
|
||||
const statsMin = stats[ReducerID.min];
|
||||
const statsMax = stats[ReducerID.max];
|
||||
|
||||
if (min === null || statsMin < min) {
|
||||
min = statsMin;
|
||||
}
|
||||
if (stats[ReducerID.max] > max) {
|
||||
max = stats[ReducerID.max];
|
||||
|
||||
if (max === null || statsMax > max) {
|
||||
max = statsMax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy';
|
||||
import { applyFieldOverrides } from './fieldOverrides';
|
||||
import { toDataFrame } from '../dataframe';
|
||||
import { GrafanaTheme } from '../types';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
describe('getFieldDisplayValuesProxy', () => {
|
||||
const data = applyFieldOverrides({
|
||||
@@ -30,7 +31,7 @@ describe('getFieldDisplayValuesProxy', () => {
|
||||
replaceVariables: (val: string) => val,
|
||||
getDataSourceSettingsByUid: (val: string) => ({} as any),
|
||||
timeZone: 'utc',
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
autoMinMax: true,
|
||||
})[0];
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ThresholdsMode, Field, FieldType, GrafanaThemeType, GrafanaTheme } from '../types';
|
||||
import { ThresholdsMode, Field, FieldType } from '../types';
|
||||
import { sortThresholds } from './thresholds';
|
||||
import { ArrayVector } from '../vector/ArrayVector';
|
||||
import { getScaleCalculator } from './scale';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
describe('getScaleCalculator', () => {
|
||||
it('should return percent, threshold and color', () => {
|
||||
@@ -18,7 +19,7 @@ describe('getScaleCalculator', () => {
|
||||
values: new ArrayVector([0, 50, 100]),
|
||||
};
|
||||
|
||||
const calc = getScaleCalculator(field, { type: GrafanaThemeType.Dark } as GrafanaTheme);
|
||||
const calc = getScaleCalculator(field, getTestTheme());
|
||||
expect(calc(70)).toEqual({
|
||||
percent: 0.7,
|
||||
threshold: thresholds[1],
|
||||
|
||||
@@ -18,7 +18,12 @@ export function getScaleCalculator(field: Field, theme: GrafanaTheme): ScaleCalc
|
||||
const info = getMinMaxAndDelta(field);
|
||||
|
||||
return (value: number) => {
|
||||
const percent = (value - info.min!) / info.delta;
|
||||
let percent = 0;
|
||||
|
||||
if (value !== -Infinity) {
|
||||
percent = (value - info.min!) / info.delta;
|
||||
}
|
||||
|
||||
const threshold = getActiveThresholdForValue(field, value, percent);
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { map } from 'rxjs/operators';
|
||||
|
||||
import { DataTransformerID } from './ids';
|
||||
import { DataTransformerInfo } from '../../types/transformations';
|
||||
import { DataFrame, Field } from '../../types/dataFrame';
|
||||
import { DataFrame, Field, TIME_SERIES_VALUE_FIELD_NAME } from '../../types/dataFrame';
|
||||
import { ArrayVector } from '../../vector';
|
||||
|
||||
export enum ConcatenateFrameNameMode {
|
||||
@@ -73,7 +73,7 @@ export function concatenateFields(data: DataFrame[], opts: ConcatenateTransforme
|
||||
} else if (opts.frameNameMode === ConcatenateFrameNameMode.Label) {
|
||||
copy.labels = { ...f.labels };
|
||||
copy.labels[frameNameLabel] = frame.name;
|
||||
} else if (!copy.name || copy.name === 'Value') {
|
||||
} else if (!copy.name || copy.name === TIME_SERIES_VALUE_FIELD_NAME) {
|
||||
copy.name = frame.name;
|
||||
} else {
|
||||
copy.name = `${frame.name} · ${f.name}`;
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface AnnotationQuery<TQuery extends DataQuery = DataQuery> {
|
||||
enable: boolean;
|
||||
name: string;
|
||||
iconColor: string;
|
||||
hide?: boolean;
|
||||
|
||||
// Standard datasource query
|
||||
target?: TQuery;
|
||||
|
||||
@@ -55,6 +55,8 @@ export interface LicenseInfo {
|
||||
expiry: number;
|
||||
licenseUrl: string;
|
||||
stateInfo: string;
|
||||
hasValidLicense: boolean;
|
||||
edition: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -114,6 +114,7 @@ export interface DataSourcePluginMeta<T extends KeyValue = {}> extends PluginMet
|
||||
queryOptions?: PluginMetaQueryOptions;
|
||||
sort?: number;
|
||||
streaming?: boolean;
|
||||
unlicensed?: boolean;
|
||||
}
|
||||
|
||||
interface PluginMetaQueryOptions {
|
||||
@@ -599,6 +600,6 @@ export abstract class LanguageProvider {
|
||||
* Returns startTask that resolves with a task list when main syntax is loaded.
|
||||
* Task list consists of secondary promises that load more detailed language features.
|
||||
*/
|
||||
abstract start: () => Promise<any[]>;
|
||||
abstract start: () => Promise<Array<Promise<any>>>;
|
||||
startTask?: Promise<any[]>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SelectableValue } from './select';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
/**
|
||||
@@ -17,7 +16,7 @@ export enum LiveChannelScope {
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface LiveChannelConfig<TMessage = any> {
|
||||
export interface LiveChannelConfig<TMessage = any, TController = any> {
|
||||
/**
|
||||
* The path definition. either static, or it may contain variables identifed with {varname}
|
||||
*/
|
||||
@@ -28,11 +27,6 @@ export interface LiveChannelConfig<TMessage = any> {
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* When variables exist, this list will identify each one
|
||||
*/
|
||||
variables?: Array<SelectableValue<string>>;
|
||||
|
||||
/**
|
||||
* The channel keeps track of who else is connected to the same channel
|
||||
*/
|
||||
@@ -46,6 +40,9 @@ export interface LiveChannelConfig<TMessage = any> {
|
||||
|
||||
/** convert the raw stream message into a message that should be broadcast */
|
||||
processMessage?: (msg: any) => TMessage;
|
||||
|
||||
/** some channels are managed by an explicit interface */
|
||||
getController?: () => TController;
|
||||
}
|
||||
|
||||
export enum LiveChannelConnectionState {
|
||||
|
||||
@@ -27,10 +27,12 @@ export enum LogLevel {
|
||||
unknown = 'unknown',
|
||||
}
|
||||
|
||||
// Used for meta information such as common labels or returned log rows in logs view in Explore
|
||||
export enum LogsMetaKind {
|
||||
Number,
|
||||
String,
|
||||
LabelsMap,
|
||||
Error,
|
||||
}
|
||||
|
||||
export enum LogsSortOrder {
|
||||
|
||||
@@ -2,12 +2,14 @@ import { ComponentClass } from 'react';
|
||||
import { KeyValue } from './data';
|
||||
import { LiveChannelSupport } from './live';
|
||||
|
||||
/** Describes plugins life cycle status */
|
||||
export enum PluginState {
|
||||
alpha = 'alpha', // Only included it `enable_alpha` is true
|
||||
alpha = 'alpha', // Only included if `enable_alpha` config option is true
|
||||
beta = 'beta', // Will show a warning banner
|
||||
deprecated = 'deprecated', // Will continue to work -- but not show up in the options to add
|
||||
}
|
||||
|
||||
/** Describes {@link https://grafana.com/docs/grafana/latest/plugins | type of plugin} */
|
||||
export enum PluginType {
|
||||
panel = 'panel',
|
||||
datasource = 'datasource',
|
||||
@@ -15,12 +17,26 @@ export enum PluginType {
|
||||
renderer = 'renderer',
|
||||
}
|
||||
|
||||
/** Describes status of {@link https://grafana.com/docs/grafana/latest/plugins/plugin-signatures/ | plugin signature} */
|
||||
export enum PluginSignatureStatus {
|
||||
internal = 'internal', // core plugin, no signature
|
||||
valid = 'valid', // signed and accurate MANIFEST
|
||||
invalid = 'invalid', // invalid signature
|
||||
modified = 'modified', // valid signature, but content mismatch
|
||||
unsigned = 'unsigned', // no MANIFEST file
|
||||
missing = 'missing', // missing signature file
|
||||
}
|
||||
|
||||
/** Describes error code returned from Grafana plugins API call */
|
||||
export enum PluginErrorCode {
|
||||
missingSignature = 'signatureMissing',
|
||||
invalidSignature = 'signatureInvalid',
|
||||
modifiedSignature = 'signatureModified',
|
||||
}
|
||||
|
||||
/** Describes error returned from Grafana plugins API call */
|
||||
export interface PluginError {
|
||||
errorCode: PluginErrorCode;
|
||||
pluginId: string;
|
||||
}
|
||||
|
||||
export interface PluginMeta<T extends KeyValue = {}> {
|
||||
|
||||
@@ -80,6 +80,10 @@ export type TraceData = {
|
||||
warnings?: string[] | null;
|
||||
};
|
||||
|
||||
export type TraceViewData = TraceData & {
|
||||
spans: TraceSpanData[];
|
||||
};
|
||||
|
||||
export type Trace = TraceData & {
|
||||
duration: number;
|
||||
endTime: number;
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
calculateStats,
|
||||
getLogLevelFromKey,
|
||||
sortLogsResult,
|
||||
checkLogsError,
|
||||
} from './logs';
|
||||
|
||||
describe('getLoglevel()', () => {
|
||||
@@ -352,3 +353,15 @@ describe('sortLogsResult', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkLogsError()', () => {
|
||||
const log = ({
|
||||
labels: {
|
||||
__error__: 'Error Message',
|
||||
foo: 'boo',
|
||||
},
|
||||
} as any) as LogRowModel;
|
||||
test('should return correct error if error is present', () => {
|
||||
expect(checkLogsError(log)).toStrictEqual({ hasError: true, errorMessage: 'Error Message' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -210,3 +210,16 @@ export const sortLogsResult = (logsResult: LogsModel | null, sortOrder: LogsSort
|
||||
|
||||
export const sortLogRows = (logRows: LogRowModel[], sortOrder: LogsSortOrder) =>
|
||||
sortOrder === LogsSortOrder.Ascending ? logRows.sort(sortInAscendingOrder) : logRows.sort(sortInDescendingOrder);
|
||||
|
||||
// Currently supports only error condition in Loki logs
|
||||
export const checkLogsError = (logRow: LogRowModel): { hasError: boolean; errorMessage?: string } => {
|
||||
if (logRow.labels.__error__) {
|
||||
return {
|
||||
hasError: true,
|
||||
errorMessage: logRow.labels.__error__,
|
||||
};
|
||||
}
|
||||
return {
|
||||
hasError: false,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -34,7 +34,8 @@ export type Color =
|
||||
| 'dark-purple'
|
||||
| 'semi-dark-purple'
|
||||
| 'light-purple'
|
||||
| 'super-light-purple';
|
||||
| 'super-light-purple'
|
||||
| 'panel-bg';
|
||||
|
||||
type ThemeVariants = {
|
||||
dark: string;
|
||||
@@ -82,6 +83,8 @@ export function buildColorsMapForTheme(theme: GrafanaTheme): Record<Color, strin
|
||||
}
|
||||
}
|
||||
|
||||
colorsMap['panel-bg'] = theme.colors.panelBg;
|
||||
|
||||
return colorsMap;
|
||||
}
|
||||
|
||||
@@ -118,7 +121,25 @@ export function getColorForTheme(color: string, theme: GrafanaTheme): string {
|
||||
export function getColorFromHexRgbOrName(color: string, type?: GrafanaThemeType): string {
|
||||
const themeType = type ?? GrafanaThemeType.Dark;
|
||||
|
||||
return getColorForTheme(color, ({ type: themeType } as unknown) as GrafanaTheme);
|
||||
if (themeType === GrafanaThemeType.Dark) {
|
||||
const darkTheme = ({
|
||||
type: themeType,
|
||||
colors: {
|
||||
panelBg: '#141619',
|
||||
},
|
||||
} as unknown) as GrafanaTheme;
|
||||
|
||||
return getColorForTheme(color, darkTheme);
|
||||
}
|
||||
|
||||
const lightTheme = ({
|
||||
type: themeType,
|
||||
colors: {
|
||||
panelBg: '#000000',
|
||||
},
|
||||
} as unknown) as GrafanaTheme;
|
||||
|
||||
return getColorForTheme(color, lightTheme);
|
||||
}
|
||||
|
||||
const buildNamedColorsPalette = () => {
|
||||
|
||||
12
packages/grafana-data/src/utils/testdata/testTheme.ts
vendored
Normal file
12
packages/grafana-data/src/utils/testdata/testTheme.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { GrafanaTheme, GrafanaThemeType } from '../../types/theme';
|
||||
|
||||
export function getTestTheme(type: GrafanaThemeType = GrafanaThemeType.Dark): GrafanaTheme {
|
||||
return ({
|
||||
type,
|
||||
isDark: type === GrafanaThemeType.Dark,
|
||||
isLight: type === GrafanaThemeType.Light,
|
||||
colors: {
|
||||
panelBg: 'white',
|
||||
},
|
||||
} as unknown) as GrafanaTheme;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e-selectors",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.4",
|
||||
"description": "Grafana End-to-End Test Selectors Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -135,4 +135,14 @@ export const Pages = {
|
||||
SoloPanel: {
|
||||
url: (page: string) => `/d-solo/${page}`,
|
||||
},
|
||||
PluginsList: {
|
||||
page: 'Plugins list page',
|
||||
list: 'Plugins list',
|
||||
listItem: 'Plugins list item',
|
||||
signatureErrorNotice: 'Unsigned plugins notice',
|
||||
},
|
||||
PluginPage: {
|
||||
page: 'Plugin page',
|
||||
signatureInfo: 'Plugin signature info',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.4",
|
||||
"description": "Grafana End-to-End Test Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -44,7 +44,7 @@
|
||||
"types": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@cypress/webpack-preprocessor": "4.1.3",
|
||||
"@grafana/e2e-selectors": "7.3.0-pre.0",
|
||||
"@grafana/e2e-selectors": "7.3.4",
|
||||
"@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.3.0-pre.0",
|
||||
"version": "7.3.4",
|
||||
"description": "Grafana Runtime Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -22,8 +22,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "7.3.0-pre.0",
|
||||
"@grafana/ui": "7.3.0-pre.0",
|
||||
"@grafana/data": "7.3.4",
|
||||
"@grafana/ui": "7.3.4",
|
||||
"systemjs": "0.20.19",
|
||||
"systemjs-plugin-css": "0.1.37"
|
||||
},
|
||||
|
||||
@@ -62,6 +62,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
rendererAvailable = false;
|
||||
http2Enabled = false;
|
||||
dateFormats?: SystemDateFormatSettings;
|
||||
marketplaceUrl?: string;
|
||||
|
||||
constructor(options: GrafanaBootConfig) {
|
||||
this.theme = options.bootData.user.lightTheme ? getTheme(GrafanaThemeType.Light) : getTheme(GrafanaThemeType.Dark);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
export * from './services';
|
||||
export * from './config';
|
||||
export * from './types';
|
||||
export * from './measurement';
|
||||
export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin';
|
||||
export { reportMetaAnalytics } from './utils/analytics';
|
||||
export { DataSourceWithBackend, HealthCheckResult, HealthStatus } from './utils/DataSourceWithBackend';
|
||||
|
||||
250
packages/grafana-runtime/src/measurement/collector.test.ts
Normal file
250
packages/grafana-runtime/src/measurement/collector.test.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { MeasurementCollector } from './collector';
|
||||
import { MeasurementAction } from './types';
|
||||
|
||||
describe('MeasurementCollector', () => {
|
||||
it('should collect values', () => {
|
||||
const collector = new MeasurementCollector();
|
||||
collector.addBatch({
|
||||
measurements: [
|
||||
{
|
||||
name: 'test',
|
||||
labels: { host: 'a' },
|
||||
time: 100,
|
||||
values: {
|
||||
f0: 0,
|
||||
f1: 1,
|
||||
f2: 'hello',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
labels: { host: 'b' },
|
||||
time: 101,
|
||||
values: {
|
||||
f0: 0,
|
||||
f1: 1,
|
||||
f2: 'hello',
|
||||
},
|
||||
config: {
|
||||
f2: {
|
||||
unit: 'mph',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
time: 102,
|
||||
labels: { host: 'a' }, // should append to first value
|
||||
values: {
|
||||
// note the missing values for f0/1
|
||||
f2: 'world',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const frames = collector.getData();
|
||||
expect(frames.length).toEqual(2);
|
||||
expect(frames[0]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": undefined,
|
||||
"name": "time",
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
100,
|
||||
102,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f0",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
0,
|
||||
null,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f1",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
1,
|
||||
null,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f2",
|
||||
"type": "string",
|
||||
"values": Array [
|
||||
"hello",
|
||||
"world",
|
||||
],
|
||||
},
|
||||
],
|
||||
"meta": Object {
|
||||
"custom": Object {
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
},
|
||||
},
|
||||
"name": "test",
|
||||
"refId": undefined,
|
||||
}
|
||||
`);
|
||||
expect(frames[1]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": undefined,
|
||||
"name": "time",
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
101,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "b",
|
||||
},
|
||||
"name": "f0",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
0,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "b",
|
||||
},
|
||||
"name": "f1",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
1,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {
|
||||
"unit": "mph",
|
||||
},
|
||||
"labels": Object {
|
||||
"host": "b",
|
||||
},
|
||||
"name": "f2",
|
||||
"type": "string",
|
||||
"values": Array [
|
||||
"hello",
|
||||
],
|
||||
},
|
||||
],
|
||||
"meta": Object {
|
||||
"custom": Object {
|
||||
"labels": Object {
|
||||
"host": "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
"name": "test",
|
||||
"refId": undefined,
|
||||
}
|
||||
`);
|
||||
|
||||
collector.addBatch({
|
||||
action: MeasurementAction.Replace,
|
||||
measurements: [
|
||||
{
|
||||
name: 'test',
|
||||
time: 105,
|
||||
labels: { host: 'a' },
|
||||
values: {
|
||||
f1: 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const frames2 = collector.getData();
|
||||
expect(frames2.length).toEqual(2);
|
||||
expect(frames2[0].length).toEqual(1); // not three!
|
||||
expect(frames2[0]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": undefined,
|
||||
"name": "time",
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
105,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f0",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
null,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f1",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
10,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f2",
|
||||
"type": "string",
|
||||
"values": Array [
|
||||
null,
|
||||
],
|
||||
},
|
||||
],
|
||||
"meta": Object {
|
||||
"custom": Object {
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
},
|
||||
},
|
||||
"name": "test",
|
||||
"refId": undefined,
|
||||
}
|
||||
`);
|
||||
|
||||
collector.addBatch({
|
||||
action: MeasurementAction.Clear,
|
||||
measurements: [],
|
||||
});
|
||||
expect(collector.getData().length).toEqual(0);
|
||||
});
|
||||
});
|
||||
209
packages/grafana-runtime/src/measurement/collector.ts
Normal file
209
packages/grafana-runtime/src/measurement/collector.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import {
|
||||
CircularDataFrame,
|
||||
Labels,
|
||||
formatLabels,
|
||||
FieldType,
|
||||
DataFrame,
|
||||
matchAllLabels,
|
||||
parseLabels,
|
||||
CircularVector,
|
||||
ArrayVector,
|
||||
} from '@grafana/data';
|
||||
import { Measurement, MeasurementBatch, LiveMeasurements, MeasurementsQuery, MeasurementAction } from './types';
|
||||
|
||||
interface MeasurementCacheConfig {
|
||||
append?: 'head' | 'tail';
|
||||
capacity?: number;
|
||||
}
|
||||
|
||||
/** This is a cache scoped to a the measurement name
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export class MeasurementCache {
|
||||
readonly frames: Record<string, CircularDataFrame> = {}; // key is the labels
|
||||
|
||||
constructor(public name: string, private config: MeasurementCacheConfig) {
|
||||
if (!this.config) {
|
||||
this.config = {
|
||||
append: 'tail',
|
||||
capacity: 600, // Default capacity 10min @ 1hz
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getFrames(match?: Labels): DataFrame[] {
|
||||
const frames = Object.values(this.frames);
|
||||
if (!match) {
|
||||
return frames;
|
||||
}
|
||||
return frames.filter(f => {
|
||||
return matchAllLabels(match, f.meta?.custom?.labels);
|
||||
});
|
||||
}
|
||||
|
||||
addMeasurement(m: Measurement, action: MeasurementAction): DataFrame {
|
||||
const key = m.labels ? formatLabels(m.labels) : '';
|
||||
let frame = this.frames[key];
|
||||
if (!frame) {
|
||||
frame = new CircularDataFrame(this.config);
|
||||
frame.name = this.name;
|
||||
frame.addField({
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
});
|
||||
for (const [key, value] of Object.entries(m.values)) {
|
||||
frame.addFieldFor(value, key).labels = m.labels;
|
||||
}
|
||||
frame.meta = {
|
||||
custom: {
|
||||
labels: m.labels,
|
||||
},
|
||||
};
|
||||
this.frames[key] = frame;
|
||||
}
|
||||
|
||||
// Clear existing values
|
||||
if (action === MeasurementAction.Replace) {
|
||||
for (const field of frame.fields) {
|
||||
(field.values as ArrayVector).buffer.length = 0; // same buffer, but reset to empty length
|
||||
}
|
||||
}
|
||||
|
||||
// Add the timestamp
|
||||
frame.values['time'].add(m.time || Date.now());
|
||||
|
||||
// Attach field config to the current fields
|
||||
if (m.config) {
|
||||
for (const [key, value] of Object.entries(m.config)) {
|
||||
const f = frame.fields.find(f => f.name === key);
|
||||
if (f) {
|
||||
f.config = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append all values (a row)
|
||||
for (const [key, value] of Object.entries(m.values)) {
|
||||
let v = frame.values[key];
|
||||
if (!v) {
|
||||
const f = frame.addFieldFor(value, key);
|
||||
f.labels = m.labels;
|
||||
v = f.values;
|
||||
}
|
||||
v.add(value);
|
||||
}
|
||||
|
||||
// Make sure all fields have the same length
|
||||
frame.validate();
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export class MeasurementCollector implements LiveMeasurements {
|
||||
measurements = new Map<string, MeasurementCache>();
|
||||
config: MeasurementCacheConfig = {
|
||||
append: 'tail',
|
||||
capacity: 600, // Default capacity 10min @ 1hz
|
||||
};
|
||||
|
||||
//------------------------------------------------------
|
||||
// Public
|
||||
//------------------------------------------------------
|
||||
|
||||
getData(query?: MeasurementsQuery): DataFrame[] {
|
||||
const { name, labels, fields } = query || {};
|
||||
|
||||
let data: DataFrame[] = [];
|
||||
if (name) {
|
||||
// for now we only match exact names
|
||||
const m = this.measurements.get(name);
|
||||
if (m) {
|
||||
data = m.getFrames(labels);
|
||||
}
|
||||
} else {
|
||||
for (const f of this.measurements.values()) {
|
||||
data.push.apply(data, f.getFrames(labels));
|
||||
}
|
||||
}
|
||||
|
||||
if (fields && fields.length) {
|
||||
let filtered: DataFrame[] = [];
|
||||
for (const frame of data) {
|
||||
const match = frame.fields.filter(f => fields.includes(f.name));
|
||||
if (match.length > 0) {
|
||||
filtered.push({ ...frame, fields: match }); // Copy the frame with fewer fields
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
getDistinctNames(): string[] {
|
||||
return Object.keys(this.measurements);
|
||||
}
|
||||
|
||||
getDistinctLabels(name: string): Labels[] {
|
||||
const m = this.measurements.get(name);
|
||||
if (m) {
|
||||
return Object.keys(m.frames).map(k => parseLabels(k));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
setCapacity(size: number) {
|
||||
this.config.capacity = size;
|
||||
|
||||
// Now update all the circular buffers
|
||||
for (const wrap of this.measurements.values()) {
|
||||
for (const frame of Object.values(wrap.frames)) {
|
||||
for (const field of frame.fields) {
|
||||
(field.values as CircularVector).setCapacity(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCapacity() {
|
||||
return this.config.capacity!;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.measurements.clear();
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
// Collector
|
||||
//------------------------------------------------------
|
||||
|
||||
addBatch = (batch: MeasurementBatch) => {
|
||||
let action = batch.action ?? MeasurementAction.Append;
|
||||
if (action === MeasurementAction.Clear) {
|
||||
this.measurements.clear();
|
||||
action = MeasurementAction.Append;
|
||||
}
|
||||
|
||||
// Change the local buffer size
|
||||
if (batch.capacity && batch.capacity !== this.config.capacity) {
|
||||
this.setCapacity(batch.capacity);
|
||||
}
|
||||
|
||||
for (const measure of batch.measurements) {
|
||||
const name = measure.name || '';
|
||||
let m = this.measurements.get(name);
|
||||
if (!m) {
|
||||
m = new MeasurementCache(name, this.config);
|
||||
this.measurements.set(name, m);
|
||||
}
|
||||
if (measure.values) {
|
||||
m.addMeasurement(measure, action);
|
||||
} else {
|
||||
console.log('invalid measurement', measure);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
}
|
||||
3
packages/grafana-runtime/src/measurement/index.ts
Normal file
3
packages/grafana-runtime/src/measurement/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './types';
|
||||
export * from './collector';
|
||||
export * from './query';
|
||||
77
packages/grafana-runtime/src/measurement/query.ts
Normal file
77
packages/grafana-runtime/src/measurement/query.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
DataQueryResponse,
|
||||
isLiveChannelMessageEvent,
|
||||
isLiveChannelStatusEvent,
|
||||
isValidLiveChannelAddress,
|
||||
LiveChannelAddress,
|
||||
LoadingState,
|
||||
} from '@grafana/data';
|
||||
import { LiveMeasurements, MeasurementsQuery } from './types';
|
||||
import { getGrafanaLiveSrv } from '../services/live';
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export function getLiveMeasurements(addr: LiveChannelAddress): LiveMeasurements | undefined {
|
||||
if (!isValidLiveChannelAddress(addr)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const live = getGrafanaLiveSrv();
|
||||
if (!live) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const channel = live.getChannel<LiveMeasurements>(addr);
|
||||
const getController = channel?.config?.getController;
|
||||
return getController ? getController() : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* When you know the stream will be managed measurements
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export function getLiveMeasurementsObserver(
|
||||
addr: LiveChannelAddress,
|
||||
requestId: string,
|
||||
query?: MeasurementsQuery
|
||||
): Observable<DataQueryResponse> {
|
||||
const rsp: DataQueryResponse = { data: [] };
|
||||
if (!addr || !addr.path) {
|
||||
return of(rsp); // Address not configured yet
|
||||
}
|
||||
|
||||
const live = getGrafanaLiveSrv();
|
||||
if (!live) {
|
||||
// This will only happen with the feature flag is not enabled
|
||||
rsp.error = { message: 'Grafana live is not initalized' };
|
||||
return of(rsp);
|
||||
}
|
||||
|
||||
rsp.key = requestId;
|
||||
return live
|
||||
.getChannel<LiveMeasurements>(addr)
|
||||
.getStream()
|
||||
.pipe(
|
||||
map(evt => {
|
||||
if (isLiveChannelMessageEvent(evt)) {
|
||||
rsp.data = evt.message.getData(query);
|
||||
if (!rsp.data.length) {
|
||||
// ?? skip when data is empty ???
|
||||
}
|
||||
delete rsp.error;
|
||||
rsp.state = LoadingState.Streaming;
|
||||
} else if (isLiveChannelStatusEvent(evt)) {
|
||||
if (evt.error != null) {
|
||||
rsp.error = rsp.error;
|
||||
rsp.state = LoadingState.Error;
|
||||
}
|
||||
}
|
||||
return { ...rsp }; // send event on all status messages
|
||||
})
|
||||
);
|
||||
}
|
||||
73
packages/grafana-runtime/src/measurement/types.ts
Normal file
73
packages/grafana-runtime/src/measurement/types.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { DataFrame, Labels, FieldConfig } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* the raw channel events are batches of Measurements
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface Measurement {
|
||||
name: string;
|
||||
time?: number; // Missing will use the browser time
|
||||
values: Record<string, any>;
|
||||
config?: Record<string, FieldConfig>;
|
||||
labels?: Labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export enum MeasurementAction {
|
||||
/** The measurements will be added to the client buffer */
|
||||
Append = 'append',
|
||||
|
||||
/** The measurements will replace the client buffer */
|
||||
Replace = 'replace',
|
||||
|
||||
/** All measurements will be removed from the client buffer before processing */
|
||||
Clear = 'clear',
|
||||
}
|
||||
|
||||
/**
|
||||
* List of Measurements sent in a batch
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface MeasurementBatch {
|
||||
/**
|
||||
* The default action is to append values to the client buffer
|
||||
*/
|
||||
action?: MeasurementAction;
|
||||
|
||||
/**
|
||||
* List of measurements to process
|
||||
*/
|
||||
measurements: Measurement[];
|
||||
|
||||
/**
|
||||
* This will set the capacity on the client buffer for everything
|
||||
* in the measurement channel
|
||||
*/
|
||||
capacity?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface MeasurementsQuery {
|
||||
name?: string;
|
||||
labels?: Labels;
|
||||
fields?: string[]; // only include the fields with these names
|
||||
}
|
||||
|
||||
/**
|
||||
* Channels that receive Measurements can collect them into frames
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface LiveMeasurements {
|
||||
getData(query?: MeasurementsQuery): DataFrame[];
|
||||
getDistinctNames(): string[];
|
||||
getDistinctLabels(name: string): Labels[];
|
||||
setCapacity(size: number): void;
|
||||
getCapacity(): number;
|
||||
}
|
||||
@@ -6,6 +6,9 @@
|
||||
*/
|
||||
import { UrlQueryMap } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface LocationUpdate {
|
||||
/**
|
||||
* Target path where you automatically wants to navigate the user.
|
||||
|
||||
@@ -39,7 +39,7 @@ export const setGrafanaLiveSrv = (instance: GrafanaLiveSrv) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to retrieve the {@link GrafanaLiveSrv} that allows you to subscribe to
|
||||
* Used to retrieve the GrafanaLiveSrv that allows you to subscribe to
|
||||
* server side events and streams
|
||||
*
|
||||
* @alpha -- experimental
|
||||
|
||||
@@ -43,7 +43,7 @@ get_file "https://codeclimate.com/downloads/test-reporter/test-reporter-latest-l
|
||||
"b4138199aa755ebfe171b57cc46910b13258ace5fbc4eaa099c42607cd0bff32"
|
||||
chmod +x /usr/local/bin/cc-test-reporter
|
||||
|
||||
curl -fL -o /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.16/grabpl"
|
||||
curl -fL -o /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.20/grabpl"
|
||||
|
||||
apk add --no-cache git
|
||||
# Install Mage
|
||||
|
||||
@@ -44,7 +44,7 @@ get_file "https://codeclimate.com/downloads/test-reporter/test-reporter-latest-l
|
||||
"b4138199aa755ebfe171b57cc46910b13258ace5fbc4eaa099c42607cd0bff32"
|
||||
chmod 755 /usr/local/bin/cc-test-reporter
|
||||
|
||||
wget -O /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.16/grabpl"
|
||||
wget -O /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.20/grabpl"
|
||||
chmod +x /usr/local/bin/grabpl
|
||||
|
||||
# Install Mage
|
||||
|
||||
@@ -27,7 +27,7 @@ get_file "https://codeclimate.com/downloads/test-reporter/test-reporter-latest-l
|
||||
"b4138199aa755ebfe171b57cc46910b13258ace5fbc4eaa099c42607cd0bff32"
|
||||
chmod +x /usr/local/bin/cc-test-reporter
|
||||
|
||||
wget -O /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.16/grabpl"
|
||||
wget -O /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.20/grabpl"
|
||||
chmod +x /usr/local/bin/grabpl
|
||||
|
||||
# Install Mage
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/toolkit",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.4",
|
||||
"description": "Grafana Toolkit",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
|
||||
@@ -97,6 +97,7 @@ const getCommonPlugins = (options: WebpackConfigurationOptions) => {
|
||||
{ from: hasREADME ? 'README.md' : '../README.md', to: '.', force: true },
|
||||
{ from: 'plugin.json', to: '.' },
|
||||
{ from: '../LICENSE', to: '.' },
|
||||
{ from: '../CHANGELOG.md', to: '.', force: true },
|
||||
{ from: '**/*.json', to: '.' },
|
||||
{ from: '**/*.svg', to: '.' },
|
||||
{ from: '**/*.png', to: '.' },
|
||||
|
||||
@@ -103,7 +103,7 @@ module.exports = ({ config, mode }) => {
|
||||
minimize: isProductionBuild,
|
||||
minimizer: isProductionBuild
|
||||
? [
|
||||
new TerserPlugin({ cache: false, parallel: false, sourceMap: false, exclude: /monaco/ }),
|
||||
new TerserPlugin({ cache: false, parallel: false, sourceMap: false, exclude: /monaco|bizcharts/ }),
|
||||
new OptimizeCSSAssetsPlugin({}),
|
||||
]
|
||||
: [],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/ui",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.4",
|
||||
"description": "Grafana Components Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -27,15 +27,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.27",
|
||||
"@grafana/data": "7.3.0-pre.0",
|
||||
"@grafana/e2e-selectors": "7.3.0-pre.0",
|
||||
"@grafana/data": "7.3.4",
|
||||
"@grafana/e2e-selectors": "7.3.4",
|
||||
"@grafana/slate-react": "0.22.9-grafana",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@iconscout/react-unicons": "1.1.4",
|
||||
"@torkelo/react-select": "3.0.8",
|
||||
"@types/hoist-non-react-statics": "3.3.1",
|
||||
"@types/react-beautiful-dnd": "12.1.2",
|
||||
"@types/react-color": "3.0.1",
|
||||
"@types/hoist-non-react-statics": "3.3.1",
|
||||
"@types/react-select": "3.0.8",
|
||||
"@types/react-table": "7.0.12",
|
||||
"@types/slate": "0.47.1",
|
||||
@@ -43,9 +43,8 @@
|
||||
"bizcharts": "^3.5.8",
|
||||
"classnames": "2.2.6",
|
||||
"d3": "5.15.0",
|
||||
"hoist-non-react-statics": "3.3.2",
|
||||
"immutable": "3.8.2",
|
||||
"emotion": "10.0.27",
|
||||
"hoist-non-react-statics": "3.3.2",
|
||||
"immutable": "3.8.2",
|
||||
"jquery": "3.5.1",
|
||||
"lodash": "4.17.19",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { selectors } from '@grafana/e2e-selectors';
|
||||
import { useTheme } from '../../themes';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { IconName } from '../../types/icon';
|
||||
import { getColorsFromSeverity } from '../../utils/colors';
|
||||
|
||||
export type AlertVariant = 'success' | 'warning' | 'error' | 'info';
|
||||
|
||||
@@ -76,21 +77,11 @@ export const Alert: FC<Props> = ({
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme, severity: AlertVariant, outline: boolean) => {
|
||||
const { redBase, redShade, greenBase, greenShade, blue80, blue77, white } = theme.palette;
|
||||
const backgrounds = {
|
||||
error: css`
|
||||
background: linear-gradient(90deg, ${redBase}, ${redShade});
|
||||
`,
|
||||
warning: css`
|
||||
background: linear-gradient(90deg, ${redBase}, ${redShade});
|
||||
`,
|
||||
info: css`
|
||||
background: linear-gradient(100deg, ${blue80}, ${blue77});
|
||||
`,
|
||||
success: css`
|
||||
background: linear-gradient(100deg, ${greenBase}, ${greenShade});
|
||||
`,
|
||||
};
|
||||
const { white } = theme.palette;
|
||||
const severityColors = getColorsFromSeverity(severity, theme);
|
||||
const background = css`
|
||||
background: linear-gradient(90deg, ${severityColors[0]}, ${severityColors[0]});
|
||||
`;
|
||||
|
||||
return {
|
||||
container: css`
|
||||
@@ -106,7 +97,7 @@ const getStyles = (theme: GrafanaTheme, severity: AlertVariant, outline: boolean
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
${backgrounds[severity]}
|
||||
${background}
|
||||
`,
|
||||
icon: css`
|
||||
padding: 0 ${theme.spacing.md} 0 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { HTMLAttributes } from 'react';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { useTheme } from '../../themes/ThemeContext';
|
||||
import { stylesFactory } from '../../themes/stylesFactory';
|
||||
@@ -6,23 +6,23 @@ import { IconName } from '../../types';
|
||||
import { Tooltip } from '../Tooltip/Tooltip';
|
||||
import { getColorForTheme, GrafanaTheme } from '@grafana/data';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { css } from 'emotion';
|
||||
import { HorizontalGroup } from '..';
|
||||
import { css, cx } from 'emotion';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
|
||||
export type BadgeColor = 'blue' | 'red' | 'green' | 'orange' | 'purple';
|
||||
|
||||
export interface BadgeProps {
|
||||
export interface BadgeProps extends HTMLAttributes<HTMLDivElement> {
|
||||
text: string;
|
||||
color: BadgeColor;
|
||||
icon?: IconName;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export const Badge = React.memo<BadgeProps>(({ icon, color, text, tooltip }) => {
|
||||
export const Badge = React.memo<BadgeProps>(({ icon, color, text, tooltip, className, ...otherProps }) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyles(theme, color);
|
||||
const badge = (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={cx(styles.wrapper, className)} {...otherProps}>
|
||||
<HorizontalGroup align="center" spacing="xs">
|
||||
{icon && <Icon name={icon} size="sm" />}
|
||||
<span>{text}</span>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { calculateFontSize } from '../../utils/measureText';
|
||||
|
||||
// Types
|
||||
import { BigValueColorMode, Props, BigValueJustifyMode, BigValueTextMode } from './BigValue';
|
||||
import { getTextColorForBackground } from '../../utils';
|
||||
|
||||
const LINE_HEIGHT = 1.2;
|
||||
const MAX_TITLE_SIZE = 30;
|
||||
@@ -51,7 +52,7 @@ export abstract class BigValueLayout {
|
||||
};
|
||||
|
||||
if (this.props.colorMode === BigValueColorMode.Background) {
|
||||
styles.color = 'white';
|
||||
styles.color = getTextColorForBackground(this.valueColor);
|
||||
}
|
||||
|
||||
return styles;
|
||||
@@ -62,6 +63,8 @@ export abstract class BigValueLayout {
|
||||
fontSize: this.valueFontSize,
|
||||
fontWeight: 500,
|
||||
lineHeight: LINE_HEIGHT,
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
};
|
||||
|
||||
switch (this.props.colorMode) {
|
||||
@@ -69,7 +72,7 @@ export abstract class BigValueLayout {
|
||||
styles.color = this.valueColor;
|
||||
break;
|
||||
case BigValueColorMode.Background:
|
||||
styles.color = 'white';
|
||||
styles.color = getTextColorForBackground(this.valueColor);
|
||||
}
|
||||
|
||||
return styles;
|
||||
|
||||
@@ -30,10 +30,12 @@ exports[`BigValue Render with basic options should render 1`] = `
|
||||
<FormattedDisplayValue
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
"color": "#f7f8fa",
|
||||
"fontSize": 230,
|
||||
"fontWeight": 500,
|
||||
"lineHeight": 1.2,
|
||||
"position": "relative",
|
||||
"zIndex": 1,
|
||||
}
|
||||
}
|
||||
value={
|
||||
|
||||
@@ -13,7 +13,7 @@ It is used in a `ConfigEditor` for data source plugins. You can find more exampl
|
||||
### Example usage
|
||||
```jsx
|
||||
export const ConfigEditor = (props: Props) => {
|
||||
const { options, onOptionsChange, config } = props;
|
||||
const { options, onOptionsChange } = props;
|
||||
return (
|
||||
<>
|
||||
<DataSourceHttpSettings
|
||||
|
||||
@@ -100,7 +100,7 @@ export const SigV4AuthSettings: React.FC<HttpSettingsProps> = props => {
|
||||
authProvider => authProvider.value === dataSourceConfig.jsonData.sigV4AuthType
|
||||
)}
|
||||
options={authProviderOptions}
|
||||
defaultValue={dataSourceConfig.jsonData.sigV4AuthType || 'keys'}
|
||||
defaultValue={dataSourceConfig.jsonData.sigV4AuthType || authProviderOptions[0]}
|
||||
onChange={option => {
|
||||
onJsonDataChange('sigV4AuthType', option.value);
|
||||
}}
|
||||
|
||||
@@ -25,8 +25,6 @@ export interface Props extends Themeable {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const FONT_SCALE = 1;
|
||||
|
||||
export class Gauge extends PureComponent<Props> {
|
||||
canvasElement: any;
|
||||
|
||||
@@ -95,13 +93,6 @@ export class Gauge extends PureComponent<Props> {
|
||||
return formatted;
|
||||
}
|
||||
|
||||
getFontScale(length: number): number {
|
||||
if (length > 12) {
|
||||
return FONT_SCALE - (length * 5) / 110;
|
||||
}
|
||||
return FONT_SCALE - (length * 5) / 101;
|
||||
}
|
||||
|
||||
draw() {
|
||||
const { field, showThresholdLabels, showThresholdMarkers, width, height, theme, value } = this.props;
|
||||
|
||||
@@ -112,7 +103,12 @@ export class Gauge extends PureComponent<Props> {
|
||||
const gaugeWidth = Math.min(dimension / 5.5, 40) / gaugeWidthReduceRatio;
|
||||
const thresholdMarkersWidth = gaugeWidth / 5;
|
||||
const text = formattedValueToString(value);
|
||||
const fontSize = calculateFontSize(text, dimension - gaugeWidth * 3, dimension, 1, 48);
|
||||
// This not 100% accurate as I am unsure of flot's calculations here
|
||||
const valueWidthBase = Math.min(width, dimension * 1.3) * 0.9;
|
||||
// remove gauge & marker width (on left and right side)
|
||||
// and 10px is some padding that flot adds to the outer canvas
|
||||
const valueWidth = valueWidthBase - ((gaugeWidth + (showThresholdMarkers ? thresholdMarkersWidth : 0)) * 2 + 10);
|
||||
const fontSize = calculateFontSize(text, valueWidth, dimension, 1, 48);
|
||||
const thresholdLabelFontSize = fontSize / 2.5;
|
||||
|
||||
let min = field.min!;
|
||||
|
||||
@@ -7,13 +7,22 @@ import { IconButton } from '../IconButton/IconButton';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
import panelArtDark from './panelArt_dark.svg';
|
||||
import panelArtLight from './panelArt_light.svg';
|
||||
import { AlertVariant } from '../Alert/Alert';
|
||||
import { getColorsFromSeverity } from '../../utils/colors';
|
||||
|
||||
export interface InfoBoxProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
|
||||
children: React.ReactNode;
|
||||
/** Title of the box */
|
||||
title?: string | JSX.Element;
|
||||
/** Url of the read more link */
|
||||
url?: string;
|
||||
/** Text of the read more link */
|
||||
urlTitle?: string;
|
||||
/** Indicates whether or not box should be rendered with Grafana branding background */
|
||||
branded?: boolean;
|
||||
/** Color variant of the box */
|
||||
severity?: AlertVariant;
|
||||
/** Call back to be performed when box is dismissed */
|
||||
onDismiss?: () => void;
|
||||
}
|
||||
|
||||
@@ -24,9 +33,9 @@ export interface InfoBoxProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
||||
*/
|
||||
export const InfoBox = React.memo(
|
||||
React.forwardRef<HTMLDivElement, InfoBoxProps>(
|
||||
({ title, className, children, branded, url, urlTitle, onDismiss, ...otherProps }, ref) => {
|
||||
({ title, className, children, branded, url, urlTitle, onDismiss, severity = 'info', ...otherProps }, ref) => {
|
||||
const theme = useTheme();
|
||||
const styles = getInfoBoxStyles(theme);
|
||||
const styles = getInfoBoxStyles(theme, severity);
|
||||
const wrapperClassName = branded ? cx(styles.wrapperBranded, className) : cx(styles.wrapper, className);
|
||||
|
||||
return (
|
||||
@@ -49,18 +58,15 @@ export const InfoBox = React.memo(
|
||||
)
|
||||
);
|
||||
|
||||
const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme, severity: AlertVariant) => ({
|
||||
wrapper: css`
|
||||
position: relative;
|
||||
padding: ${theme.spacing.md};
|
||||
background-color: ${theme.colors.bg2};
|
||||
border-top: 3px solid ${theme.palette.blue80};
|
||||
border-top: 3px solid ${getColorsFromSeverity(severity, theme)[0]};
|
||||
margin-bottom: ${theme.spacing.md};
|
||||
flex-grow: 1;
|
||||
|
||||
ul {
|
||||
padding-left: ${theme.spacing.lg};
|
||||
}
|
||||
color: ${theme.colors.textSemiWeak};
|
||||
|
||||
code {
|
||||
@include font-family-monospace();
|
||||
@@ -109,5 +115,6 @@ const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||
display: inline-block;
|
||||
margin-top: ${theme.spacing.md};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
color: ${theme.colors.textSemiWeak};
|
||||
`,
|
||||
}));
|
||||
|
||||
@@ -45,6 +45,12 @@ describe('LogDetails', () => {
|
||||
expect(wrapper.text().includes('key1label1key2label2')).toBe(true);
|
||||
});
|
||||
});
|
||||
describe('when log row has error', () => {
|
||||
it('should not render log level border', () => {
|
||||
const wrapper = setup({ hasError: true }, undefined);
|
||||
expect(wrapper.find({ 'aria-label': 'Log level' }).html()).not.toContain('logs-row__level');
|
||||
});
|
||||
});
|
||||
describe('when row entry has parsable fields', () => {
|
||||
it('should render heading ', () => {
|
||||
const wrapper = setup(undefined, { entry: 'test=successful' });
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface Props extends Themeable {
|
||||
showDuplicates: boolean;
|
||||
getRows: () => LogRowModel[];
|
||||
className?: string;
|
||||
hasError?: boolean;
|
||||
onMouseEnter?: () => void;
|
||||
onMouseLeave?: () => void;
|
||||
onClickFilterLabel?: (key: string, value: string) => void;
|
||||
@@ -70,6 +71,7 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
||||
const {
|
||||
row,
|
||||
theme,
|
||||
hasError,
|
||||
onClickFilterOutLabel,
|
||||
onClickFilterLabel,
|
||||
getRows,
|
||||
@@ -88,6 +90,8 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
||||
const labelsAvailable = Object.keys(labels).length > 0;
|
||||
const fields = getAllFields(row, getFieldLinks);
|
||||
const parsedFieldsAvailable = fields && fields.length > 0;
|
||||
// If logs with error, we are not showing the level color
|
||||
const levelClassName = cx(!hasError && [style.logsRowLevel, styles.logsRowLevelDetails]);
|
||||
|
||||
return (
|
||||
<tr
|
||||
@@ -96,7 +100,7 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
{showDuplicates && <td />}
|
||||
<td className={cx(style.logsRowLevel, styles.logsRowLevelDetails)} />
|
||||
<td className={levelClassName} aria-label="Log level" />
|
||||
<td colSpan={4}>
|
||||
<div className={style.logDetailsContainer}>
|
||||
<table className={style.logDetailsTable}>
|
||||
|
||||
@@ -8,8 +8,10 @@ import {
|
||||
DataQueryResponse,
|
||||
GrafanaTheme,
|
||||
dateTimeFormat,
|
||||
checkLogsError,
|
||||
} from '@grafana/data';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { Tooltip } from '../Tooltip/Tooltip';
|
||||
import { cx, css } from 'emotion';
|
||||
|
||||
import {
|
||||
@@ -72,6 +74,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
label: hoverBackground;
|
||||
background-color: ${bgColor};
|
||||
`,
|
||||
errorLogRow: css`
|
||||
label: erroredLogRow;
|
||||
color: ${theme.colors.textWeak};
|
||||
`,
|
||||
};
|
||||
});
|
||||
/**
|
||||
@@ -160,22 +166,27 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
const { showDetails, showContext, hasHoverBackground } = this.state;
|
||||
const style = getLogRowStyles(theme, row.logLevel);
|
||||
const styles = getStyles(theme);
|
||||
const hoverBackground = cx(style.logsRow, { [styles.hoverBackground]: hasHoverBackground });
|
||||
const { errorMessage, hasError } = checkLogsError(row);
|
||||
const logRowBackground = cx(style.logsRow, {
|
||||
[styles.hoverBackground]: hasHoverBackground,
|
||||
[styles.errorLogRow]: hasError,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr
|
||||
className={hoverBackground}
|
||||
onMouseEnter={this.addHoverBackground}
|
||||
onMouseLeave={this.clearHoverBackground}
|
||||
onClick={this.toggleDetails}
|
||||
>
|
||||
<tr className={logRowBackground} onClick={this.toggleDetails}>
|
||||
{showDuplicates && (
|
||||
<td className={style.logsRowDuplicates}>
|
||||
{row.duplicates && row.duplicates > 0 ? `${row.duplicates + 1}x` : null}
|
||||
</td>
|
||||
)}
|
||||
<td className={style.logsRowLevel} />
|
||||
<td className={cx({ [style.logsRowLevel]: !hasError })}>
|
||||
{hasError && (
|
||||
<Tooltip content={`Error: ${errorMessage}`} placement="right" theme="error">
|
||||
<Icon className={style.logIconError} name="exclamation-triangle" size="sm" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</td>
|
||||
{!allowDetails && (
|
||||
<td title={showDetails ? 'Hide log details' : 'See log details'} className={style.logsRowToggleDetails}>
|
||||
<Icon className={styles.topVerticalAlign} name={showDetails ? 'angle-down' : 'angle-right'} />
|
||||
@@ -207,7 +218,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
</tr>
|
||||
{this.state.showDetails && (
|
||||
<LogDetails
|
||||
className={hoverBackground}
|
||||
className={logRowBackground}
|
||||
onMouseEnter={this.addHoverBackground}
|
||||
onMouseLeave={this.clearHoverBackground}
|
||||
showDuplicates={showDuplicates}
|
||||
@@ -218,6 +229,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
onClickHideParsedField={onClickHideParsedField}
|
||||
getRows={getRows}
|
||||
row={row}
|
||||
hasError={hasError}
|
||||
showParsedFields={showParsedFields}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -9,6 +9,7 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
||||
let logColor = selectThemeVariant({ light: theme.palette.gray5, dark: theme.palette.gray2 }, theme.type);
|
||||
const borderColor = selectThemeVariant({ light: theme.palette.gray5, dark: theme.palette.gray2 }, theme.type);
|
||||
const bgColor = selectThemeVariant({ light: theme.palette.gray5, dark: theme.palette.dark4 }, theme.type);
|
||||
const hoverBgColor = selectThemeVariant({ light: theme.palette.gray7, dark: theme.palette.dark2 }, theme.type);
|
||||
const context = css`
|
||||
label: context;
|
||||
visibility: hidden;
|
||||
@@ -92,7 +93,7 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: ${theme.colors.bodyBg};
|
||||
background: ${hoverBgColor};
|
||||
}
|
||||
`,
|
||||
logsRowDuplicates: css`
|
||||
@@ -116,6 +117,10 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
||||
background-color: ${logColor};
|
||||
}
|
||||
`,
|
||||
logIconError: css`
|
||||
color: ${theme.palette.red};
|
||||
margin-left: -5px;
|
||||
`,
|
||||
logsRowToggleDetails: css`
|
||||
label: logs-row-toggle-details__level;
|
||||
position: relative;
|
||||
|
||||
@@ -23,9 +23,11 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
const options = fieldColorModeRegistry.list().map(mode => {
|
||||
let suffix = mode.isByValue ? ' (by value)' : '';
|
||||
|
||||
return {
|
||||
value: mode.id,
|
||||
label: mode.name,
|
||||
label: `${mode.name}${suffix}`,
|
||||
description: mode.description,
|
||||
isContinuous: mode.isContinuous,
|
||||
isByValue: mode.isByValue,
|
||||
|
||||
@@ -9,6 +9,7 @@ import memoizeOne from 'memoize-one';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { withTheme } from '../../themes';
|
||||
|
||||
// Default intervals used in the refresh picker component
|
||||
export const defaultIntervals = ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'];
|
||||
|
||||
const getStyles = memoizeOne((theme: GrafanaTheme) => {
|
||||
|
||||
@@ -131,3 +131,21 @@ export const CustomLabelField = () => {
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export const HtmlAttributes = () => {
|
||||
const [value, setValue] = useState<any>(options[0]);
|
||||
return (
|
||||
<SegmentFrame options={options}>
|
||||
<Segment
|
||||
data-testid="segment-test"
|
||||
id="segment-id"
|
||||
value={value}
|
||||
options={groupedOptions}
|
||||
onChange={({ value }) => {
|
||||
setValue(value);
|
||||
action('Segment value changed')(value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { HTMLProps } from 'react';
|
||||
import { cx } from 'emotion';
|
||||
import _ from 'lodash';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { SegmentSelect, useExpandableLabel, SegmentProps } from './';
|
||||
|
||||
export interface SegmentSyncProps<T> extends SegmentProps<T> {
|
||||
export interface SegmentSyncProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
|
||||
value?: T | SelectableValue<T>;
|
||||
onChange: (item: SelectableValue<T>) => void;
|
||||
options: Array<SelectableValue<T>>;
|
||||
@@ -18,6 +18,7 @@ export function Segment<T>({
|
||||
className,
|
||||
allowCustomValue,
|
||||
placeholder,
|
||||
...rest
|
||||
}: React.PropsWithChildren<SegmentSyncProps<T>>) {
|
||||
const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
|
||||
|
||||
@@ -38,6 +39,7 @@ export function Segment<T>({
|
||||
|
||||
return (
|
||||
<SegmentSelect
|
||||
{...rest}
|
||||
value={value && !_.isObject(value) ? { value } : value}
|
||||
options={options}
|
||||
width={width}
|
||||
|
||||
@@ -123,3 +123,21 @@ export const CustomLabel = () => {
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export const HtmlAttributes = () => {
|
||||
const [value, setValue] = useState<any>(options[0]);
|
||||
return (
|
||||
<SegmentFrame loadOptions={() => loadOptions(options)}>
|
||||
<SegmentAsync
|
||||
data-testid="segment-async-test"
|
||||
id="segment-async"
|
||||
value={value}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
onChange={item => {
|
||||
setValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { HTMLProps } from 'react';
|
||||
import { cx } from 'emotion';
|
||||
import _ from 'lodash';
|
||||
import { SegmentSelect } from './SegmentSelect';
|
||||
@@ -7,7 +7,7 @@ import { useExpandableLabel, SegmentProps } from '.';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
import { AsyncState } from 'react-use/lib/useAsync';
|
||||
|
||||
export interface SegmentAsyncProps<T> extends SegmentProps<T> {
|
||||
export interface SegmentAsyncProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
|
||||
value?: T | SelectableValue<T>;
|
||||
loadOptions: (query?: string) => Promise<Array<SelectableValue<T>>>;
|
||||
onChange: (item: SelectableValue<T>) => void;
|
||||
@@ -21,6 +21,7 @@ export function SegmentAsync<T>({
|
||||
className,
|
||||
allowCustomValue,
|
||||
placeholder,
|
||||
...rest
|
||||
}: React.PropsWithChildren<SegmentAsyncProps<T>>) {
|
||||
const [state, fetchOptions] = useAsyncFn(loadOptions, [loadOptions]);
|
||||
const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
|
||||
@@ -43,6 +44,7 @@ export function SegmentAsync<T>({
|
||||
|
||||
return (
|
||||
<SegmentSelect
|
||||
{...rest}
|
||||
value={value && !_.isObject(value) ? { value } : value}
|
||||
options={state.value ?? []}
|
||||
width={width}
|
||||
|
||||
@@ -49,6 +49,23 @@ export const BasicInputWithPlaceholder = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const BasicInputWithHtmlAttributes = () => {
|
||||
const [value, setValue] = useState('some text');
|
||||
return (
|
||||
<SegmentFrame>
|
||||
<SegmentInput
|
||||
data-testid="segment-input-test"
|
||||
id="segment-input"
|
||||
value={value}
|
||||
onChange={text => {
|
||||
setValue(text as string);
|
||||
action('Segment value changed')(text);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
const InputComponent = ({ initialValue }: any) => {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { HTMLProps, useRef, useState } from 'react';
|
||||
import { cx, css } from 'emotion';
|
||||
import useClickAway from 'react-use/lib/useClickAway';
|
||||
import { measureText } from '../../utils/measureText';
|
||||
import { useExpandableLabel, SegmentProps } from '.';
|
||||
|
||||
export interface SegmentInputProps<T> extends SegmentProps<T> {
|
||||
export interface SegmentInputProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLInputElement>, 'value' | 'onChange'> {
|
||||
value: string | number;
|
||||
onChange: (text: string | number) => void;
|
||||
autofocus?: boolean;
|
||||
@@ -19,6 +19,7 @@ export function SegmentInput<T>({
|
||||
className,
|
||||
placeholder,
|
||||
autofocus = false,
|
||||
...rest
|
||||
}: React.PropsWithChildren<SegmentInputProps<T>>) {
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
const [value, setValue] = useState<number | string>(initialValue);
|
||||
@@ -50,6 +51,7 @@ export function SegmentInput<T>({
|
||||
|
||||
return (
|
||||
<input
|
||||
{...rest}
|
||||
ref={ref}
|
||||
autoFocus
|
||||
className={cx(`gf-form gf-form-input`, inputWidthStyle)}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useRef } from 'react';
|
||||
import React, { HTMLProps, useRef } from 'react';
|
||||
import { css, cx } from 'emotion';
|
||||
import useClickAway from 'react-use/lib/useClickAway';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '../Forms/Legacy/Select/Select';
|
||||
|
||||
export interface Props<T> {
|
||||
export interface Props<T> extends Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
|
||||
value?: SelectableValue<T>;
|
||||
options: Array<SelectableValue<T>>;
|
||||
onChange: (item: SelectableValue<T>) => void;
|
||||
@@ -22,6 +22,7 @@ export function SegmentSelect<T>({
|
||||
width,
|
||||
noOptionsMessage = '',
|
||||
allowCustomValue = false,
|
||||
...rest
|
||||
}: React.PropsWithChildren<Props<T>>) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -39,7 +40,7 @@ export function SegmentSelect<T>({
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div {...rest} ref={ref}>
|
||||
<Select
|
||||
className={cx(
|
||||
css`
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface SelectCommonProps<T> {
|
||||
filterOption?: (option: SelectableValue, searchQuery: string) => boolean;
|
||||
/** Function for formatting the text that is displayed when creating a new value*/
|
||||
formatCreateLabel?: (input: string) => string;
|
||||
getOptionLabel?: (item: SelectableValue<T>) => string;
|
||||
getOptionLabel?: (item: SelectableValue<T>) => React.ReactNode;
|
||||
getOptionValue?: (item: SelectableValue<T>) => string;
|
||||
inputValue?: string;
|
||||
invalid?: boolean;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { TableCellDisplayMode, TableCellProps } from './types';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { TableStyles } from './styles';
|
||||
import { FilterActions } from './FilterActions';
|
||||
import { getTextColorForBackground } from '../../utils';
|
||||
|
||||
export const DefaultCell: FC<TableCellProps> = props => {
|
||||
const { field, cell, tableStyles, row, cellProps } = props;
|
||||
@@ -48,7 +49,7 @@ export const DefaultCell: FC<TableCellProps> = props => {
|
||||
{value}
|
||||
</a>
|
||||
)}
|
||||
{showFilters && cell.value && <FilterActions {...props} />}
|
||||
{showFilters && cell.value !== undefined && <FilterActions {...props} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -65,7 +66,12 @@ function getCellStyle(tableStyles: TableStyles, field: Field, displayValue: Disp
|
||||
.spin(5)
|
||||
.toRgbString();
|
||||
|
||||
return tableStyles.buildCellContainerStyle('white', `linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`);
|
||||
const textColor = getTextColorForBackground(displayValue.color!);
|
||||
|
||||
return tableStyles.buildCellContainerStyle(
|
||||
textColor,
|
||||
`linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`
|
||||
);
|
||||
}
|
||||
|
||||
return tableStyles.cellContainer;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user