mirror of
https://github.com/grafana/grafana.git
synced 2025-12-20 19:44:55 +08:00
Compare commits
94 Commits
docs/updat
...
v7.2.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d88c1a264 | ||
|
|
1b3c05077d | ||
|
|
ad9d408ac2 | ||
|
|
9f7f13e02a | ||
|
|
d9540bc368 | ||
|
|
b1543b5676 | ||
|
|
0b4aa8b909 | ||
|
|
4d21ade9cd | ||
|
|
36c8a3bd93 | ||
|
|
589be2872b | ||
|
|
f60edaf029 | ||
|
|
72a6c64532 | ||
|
|
cc745b75ac | ||
|
|
6210531200 | ||
|
|
3147dadfdb | ||
|
|
a3398cafd6 | ||
|
|
b699682ee4 | ||
|
|
6cd10128e6 | ||
|
|
1cd4391b9b | ||
|
|
6ff9d3ffad | ||
|
|
ef98b310f1 | ||
|
|
6c4d5466e6 | ||
|
|
616588adee | ||
|
|
76887a76b9 | ||
|
|
2b51f520a4 | ||
|
|
0582b21c42 | ||
|
|
4e1ff750e2 | ||
|
|
eb9057fc09 | ||
|
|
9930d2bfce | ||
|
|
db754555e0 | ||
|
|
01841d6ded | ||
|
|
f96e49ae0d | ||
|
|
498bfb0ed9 | ||
|
|
1e1535552f | ||
|
|
efe4941ee3 | ||
|
|
9c1403cd13 | ||
|
|
e35c7583b9 | ||
|
|
d383ac9ffb | ||
|
|
8797d753d4 | ||
|
|
c0320fbe40 | ||
|
|
f2f4f22eef | ||
|
|
a7e713fb31 | ||
|
|
b825c824c5 | ||
|
|
36c3a139e8 | ||
|
|
da2622d870 | ||
|
|
b25b66e2f6 | ||
|
|
a3fc96196a | ||
|
|
a9a053591b | ||
|
|
2870eab4d7 | ||
|
|
bdeb380c56 | ||
|
|
0bac0044a9 | ||
|
|
0d8f2fbda8 | ||
|
|
88905ca158 | ||
|
|
8406a5d319 | ||
|
|
8d71561898 | ||
|
|
8269ed2407 | ||
|
|
4676ee9dcf | ||
|
|
35bdf11c0c | ||
|
|
46bb0e3754 | ||
|
|
521f31a702 | ||
|
|
460ccc326f | ||
|
|
eca1c505c7 | ||
|
|
b112b13c2d | ||
|
|
a4689e646a | ||
|
|
cfc13f7e30 | ||
|
|
7054834af0 | ||
|
|
48bae0232c | ||
|
|
3a32c8f329 | ||
|
|
b9d4653737 | ||
|
|
7220fb5ab8 | ||
|
|
f00222ad03 | ||
|
|
6033499576 | ||
|
|
1a598a8a41 | ||
|
|
4ca888c798 | ||
|
|
569e2a2c74 | ||
|
|
27bfb61d2b | ||
|
|
ac7112876b | ||
|
|
711a051725 | ||
|
|
c3afe89ea2 | ||
|
|
dc4b97966f | ||
|
|
4d9f298098 | ||
|
|
95a688a469 | ||
|
|
1f6d68b38e | ||
|
|
d842db21d2 | ||
|
|
adb6d93442 | ||
|
|
80a19f014c | ||
|
|
25c7090cda | ||
|
|
89dbb0f074 | ||
|
|
a2b97958bd | ||
|
|
619d985214 | ||
|
|
ee59974edb | ||
|
|
109754eeb4 | ||
|
|
2c6020a57a | ||
|
|
dfa808ea25 |
@@ -30,7 +30,7 @@ executors:
|
|||||||
- image: cimg/go:1.14
|
- image: cimg/go:1.14
|
||||||
e2e:
|
e2e:
|
||||||
docker:
|
docker:
|
||||||
- image: srclosson/grafana-plugin-ci-e2e:latest
|
- image: grafana/ci-e2e:12.19.0-1
|
||||||
grafana-build:
|
grafana-build:
|
||||||
docker:
|
docker:
|
||||||
- image: grafana/build-container:1.2.26
|
- image: grafana/build-container:1.2.26
|
||||||
|
|||||||
@@ -279,11 +279,11 @@ editors_can_admin = false
|
|||||||
# Login cookie name
|
# Login cookie name
|
||||||
login_cookie_name = grafana_session
|
login_cookie_name = grafana_session
|
||||||
|
|
||||||
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
|
# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation (token_rotation_interval_minutes).
|
||||||
login_maximum_inactive_lifetime_days = 7
|
login_maximum_inactive_lifetime_duration =
|
||||||
|
|
||||||
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
|
# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month).
|
||||||
login_maximum_lifetime_days = 30
|
login_maximum_lifetime_duration =
|
||||||
|
|
||||||
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
|
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
|
||||||
token_rotation_interval_minutes = 10
|
token_rotation_interval_minutes = 10
|
||||||
@@ -646,6 +646,12 @@ disable_total_stats = false
|
|||||||
basic_auth_username =
|
basic_auth_username =
|
||||||
basic_auth_password =
|
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
|
# Send internal Grafana metrics to graphite
|
||||||
[metrics.graphite]
|
[metrics.graphite]
|
||||||
# Enable by setting the address setting (ex localhost:2003)
|
# Enable by setting the address setting (ex localhost:2003)
|
||||||
@@ -823,4 +829,5 @@ interval_year = YYYY
|
|||||||
# Experimental feature
|
# Experimental feature
|
||||||
use_browser_locale = false
|
use_browser_locale = false
|
||||||
|
|
||||||
|
# Default timezone for user preferences. Options are 'browser' for the browser local timezone or a timezone name from IANA Time Zone database, e.g. 'UTC' or 'Europe/Amsterdam' etc.
|
||||||
|
default_timezone = browser
|
||||||
|
|||||||
@@ -278,11 +278,11 @@
|
|||||||
# Login cookie name
|
# Login cookie name
|
||||||
;login_cookie_name = grafana_session
|
;login_cookie_name = grafana_session
|
||||||
|
|
||||||
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days,
|
# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation
|
||||||
;login_maximum_inactive_lifetime_days = 7
|
;login_maximum_inactive_lifetime_duration =
|
||||||
|
|
||||||
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
|
# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month).
|
||||||
;login_maximum_lifetime_days = 30
|
;login_maximum_lifetime_duration =
|
||||||
|
|
||||||
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
|
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
|
||||||
;token_rotation_interval_minutes = 10
|
;token_rotation_interval_minutes = 10
|
||||||
@@ -640,6 +640,12 @@
|
|||||||
; basic_auth_username =
|
; basic_auth_username =
|
||||||
; basic_auth_password =
|
; 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
|
# Send internal metrics to Graphite
|
||||||
[metrics.graphite]
|
[metrics.graphite]
|
||||||
# Enable by setting the address setting (ex localhost:2003)
|
# Enable by setting the address setting (ex localhost:2003)
|
||||||
@@ -811,3 +817,6 @@
|
|||||||
|
|
||||||
# Experimental feature
|
# Experimental feature
|
||||||
;use_browser_locale = false
|
;use_browser_locale = false
|
||||||
|
|
||||||
|
# Default timezone for user preferences. Options are 'browser' for the browser local timezone or a timezone name from IANA Time Zone database, e.g. 'UTC' or 'Europe/Amsterdam' etc.
|
||||||
|
;default_timezone = browser
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
},
|
},
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"options": {
|
"options": {
|
||||||
"content": "## Global variables\n\n* `__dashboard` = `${__dashboard}`\n* `__dashboard.name` = `${__dashboard.name}`\n* `__dashboard.uid` = `${__dashboard.uid}`\n* `__org.name` = `${__org.name}`\n* `__org.id` = `${__org.id}`\n* `__user.id` = `${__user.id}`\n* `__user.login` = `${__user.login}`\n \n## Formats\n\n* `Server:raw` = `${Server:raw}`\n* `Server:regex` = `${Server:regex}`\n* `Server:lucene` = `${Server:lucene}`\n* `Server:glob` = `${Server:glob}`\n* `Server:pipe` = `${Server:pipe}`\n* `Server:distributed` = `${Server:distributed}`\n* `Server:csv` = `${Server:csv}`\n* `Server:html` = `${Server:html}`\n* `Server:json` = `${Server:json}`\n* `Server:percentencode` = `${Server:percentencode}`\n* `Server:singlequote` = `${Server:singlequote}`\n* `Server:doublequote` = `${Server:doublequote}`\n* `Server:sqlstring` = `${Server:sqlstring}`\n* `Server:date` = `${Server:date}`\n\n",
|
"content": "## Global variables\n\n* `__dashboard` = `${__dashboard}`\n* `__dashboard.name` = `${__dashboard.name}`\n* `__dashboard.uid` = `${__dashboard.uid}`\n* `__org.name` = `${__org.name}`\n* `__org.id` = `${__org.id}`\n* `__user.id` = `${__user.id}`\n* `__user.login` = `${__user.login}`\n \n## Formats\n\n* `Server:raw` = `${Server:raw}`\n* `Server:regex` = `${Server:regex}`\n* `Server:lucene` = `${Server:lucene}`\n* `Server:glob` = `${Server:glob}`\n* `Server:pipe` = `${Server:pipe}`\n* `Server:distributed` = `${Server:distributed}`\n* `Server:csv` = `${Server:csv}`\n* `Server:html` = `${Server:html}`\n* `Server:json` = `${Server:json}`\n* `Server:percentencode` = `${Server:percentencode}`\n* `Server:singlequote` = `${Server:singlequote}`\n* `Server:doublequote` = `${Server:doublequote}`\n* `Server:sqlstring` = `${Server:sqlstring}`\n* `Server:date` = `${Server:date}`\n* `Server:text` = `${Server:text}`\n\n",
|
||||||
"mode": "markdown"
|
"mode": "markdown"
|
||||||
},
|
},
|
||||||
"pluginVersion": "7.1.0",
|
"pluginVersion": "7.1.0",
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": "-- Grafana --",
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"gnetId": null,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"id": null,
|
||||||
|
"iteration": 1601526910610,
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"icon": "external link",
|
||||||
|
"includeVars": true,
|
||||||
|
"tags": [],
|
||||||
|
"title": "Grafana",
|
||||||
|
"tooltip": "",
|
||||||
|
"type": "link",
|
||||||
|
"url": "http://www.grafana.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"asDropdown": true,
|
||||||
|
"icon": "external link",
|
||||||
|
"includeVars": true,
|
||||||
|
"tags": ["templating"],
|
||||||
|
"title": "Link as DropDown",
|
||||||
|
"type": "dashboards"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "external link",
|
||||||
|
"includeVars": true,
|
||||||
|
"tags": ["demo"],
|
||||||
|
"type": "dashboards"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"description": "",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"custom": {}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"content": "# ${custom.text}\n ",
|
||||||
|
"mode": "markdown"
|
||||||
|
},
|
||||||
|
"pluginVersion": "7.3.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"scenarioId": "random_walk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "${custom.text}",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"schemaVersion": 26,
|
||||||
|
"style": "dark",
|
||||||
|
"tags": ["gdev", "templating"],
|
||||||
|
"templating": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"allValue": null,
|
||||||
|
"current": {
|
||||||
|
"selected": false,
|
||||||
|
"text": "All",
|
||||||
|
"value": "$__all"
|
||||||
|
},
|
||||||
|
"hide": 0,
|
||||||
|
"includeAll": true,
|
||||||
|
"label": null,
|
||||||
|
"multi": true,
|
||||||
|
"name": "custom",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"selected": true,
|
||||||
|
"text": "All",
|
||||||
|
"value": "$__all"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selected": false,
|
||||||
|
"text": "p1",
|
||||||
|
"value": "p1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selected": false,
|
||||||
|
"text": "p2",
|
||||||
|
"value": "p2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selected": false,
|
||||||
|
"text": "p3",
|
||||||
|
"value": "p3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"query": "p1,p2,p3",
|
||||||
|
"skipUrlSync": false,
|
||||||
|
"type": "custom"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Templating - Dashboard Links and Variables",
|
||||||
|
"uid": "yBCC3aKGk",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -1041,6 +1041,15 @@ If both are set, then basic authentication is required to access the metrics end
|
|||||||
|
|
||||||
<hr>
|
<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]
|
## [metrics.graphite]
|
||||||
|
|
||||||
Use these options if you want to send internal Grafana metrics to Graphite.
|
Use these options if you want to send internal Grafana metrics to Graphite.
|
||||||
@@ -1391,7 +1400,7 @@ For more information about Grafana Enterprise, refer to [Grafana Enterprise]({{<
|
|||||||
|
|
||||||
### enable
|
### enable
|
||||||
|
|
||||||
Keys of alpha features to enable, separated by space. Available alpha features are: `transformations`, `standaloneAlerts`
|
Keys of alpha features to enable, separated by space. Available alpha features are: `transformations`
|
||||||
|
|
||||||
## [date_formats]
|
## [date_formats]
|
||||||
|
|
||||||
@@ -1426,3 +1435,6 @@ interval_year = YYYY
|
|||||||
Set this to `true` to have date formats be automatically be derived from browser locale. Defaults to `false`. This
|
Set this to `true` to have date formats be automatically be derived from browser locale. Defaults to `false`. This
|
||||||
is an experimental feature right now with a few problems that remain unsolved.
|
is an experimental feature right now with a few problems that remain unsolved.
|
||||||
|
|
||||||
|
### default_timezone
|
||||||
|
|
||||||
|
Used as the default timezone for user preferences. Can be either `browser` for the browser local timezone or a timezone name from IANA Time Zone database, e.g. `UTC` or `Europe/Amsterdam` etc.
|
||||||
@@ -32,7 +32,7 @@ Your Grafana preferences include whether uses the dark or light theme, your home
|
|||||||
1. In the Preferences section, you can edit any of the following:
|
1. In the Preferences section, you can edit any of the following:
|
||||||
- **UI Theme -** Click to set the **Dark** or **Light** to select a theme. **Default** is either the dark theme or the theme selected by your Grafana administrator.
|
- **UI Theme -** Click to set the **Dark** or **Light** to select a theme. **Default** is either the dark theme or the theme selected by your Grafana administrator.
|
||||||
- **Home Dashboard -** Refer to [Set your personal home dashboard]({{< relref "change-home-dashboard.md#set-your-personal-home-dashboard" >}}) for more information.
|
- **Home Dashboard -** Refer to [Set your personal home dashboard]({{< relref "change-home-dashboard.md#set-your-personal-home-dashboard" >}}) for more information.
|
||||||
- **Timezone -** Click to select an option in the **Timezone** list. Refer to [Time range controls]({{< relref "../dashboards/time-range-controls.md" >}}) for more information about Grafana time settings.
|
- **Timezone -** Click to select an option in the **Timezone** list. **Default** is either the browser local timezone or the timezone selected by your Grafana administrator. Refer to [Time range controls]({{< relref "../dashboards/time-range-controls.md" >}}) for more information about Grafana time settings.
|
||||||
1. Click **Save**.
|
1. Click **Save**.
|
||||||
|
|
||||||
## View your assigned organizations
|
## View your assigned organizations
|
||||||
|
|||||||
@@ -59,11 +59,13 @@ Example:
|
|||||||
# Login cookie name
|
# Login cookie name
|
||||||
login_cookie_name = grafana_session
|
login_cookie_name = grafana_session
|
||||||
|
|
||||||
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days.
|
|
||||||
login_maximum_inactive_lifetime_days = 7
|
|
||||||
|
|
||||||
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
|
# The maximum lifetime (duration) an authenticated user can be inactive before being required to login at next visit. Default is 7 days (7d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month). The lifetime resets at each successful token rotation (token_rotation_interval_minutes).
|
||||||
login_maximum_lifetime_days = 30
|
login_maximum_inactive_lifetime_duration =
|
||||||
|
|
||||||
|
|
||||||
|
# The maximum lifetime (duration) an authenticated user can be logged in since login time before being required to login. Default is 30 days (30d). This setting should be expressed as a duration, e.g. 5m (minutes), 6h (hours), 10d (days), 2w (weeks), 1M (month).
|
||||||
|
login_maximum_lifetime_duration =
|
||||||
|
|
||||||
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
|
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
|
||||||
token_rotation_interval_minutes = 10
|
token_rotation_interval_minutes = 10
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ Open a graph in edit mode by clicking the title > Edit (or by pressing `e` key w
|
|||||||
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| _Query expression_ | Prometheus query expression, check out the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). |
|
| _Query expression_ | Prometheus query expression, check out the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). |
|
||||||
| _Legend format_ | Controls the name of the time series, using name or pattern. For example `{{hostname}}` is replaced with the label value for the label `hostname`. |
|
| _Legend format_ | Controls the name of the time series, using name or pattern. For example `{{hostname}}` is replaced with the label value for the label `hostname`. |
|
||||||
| _Min step_ | An additional lower limit for the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) and for the `$__interval` variable. The limit is absolute and not modified by the _Resolution_ setting. |
|
| _Min step_ | An additional lower limit for the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) and for the `$__interval` and `$__rate_interval` variables. The limit is absolute and not modified by the _Resolution_ setting. |
|
||||||
| _Resolution_ | `1/1` sets both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) such that each pixel corresponds to one data point. For better performance, lower resolutions can be picked. `1/2` only retrieves a data point for every other pixel, and `1/10` retrieves one data point per 10 pixels. Note that both _Min time interval_ and _Min step_ limit the final value of `$__interval` and `step`. |
|
| _Resolution_ | `1/1` sets both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) such that each pixel corresponds to one data point. For better performance, lower resolutions can be picked. `1/2` only retrieves a data point for every other pixel, and `1/10` retrieves one data point per 10 pixels. Note that both _Min time interval_ and _Min step_ limit the final value of `$__interval` and `step`. |
|
||||||
| _Metric lookup_ | Search for metric names in this input field. |
|
| _Metric lookup_ | Search for metric names in this input field. |
|
||||||
| _Format as_ | Switch between `Table`, `Time series` or `Heatmap`. `Table` will only work in the Table panel. `Heatmap` is suitable for displaying metrics of the Histogram type on a Heatmap panel. Under the hood, it converts cumulative histograms to regular ones and sorts series by the bucket bound. |
|
| _Format as_ | Switch between `Table`, `Time series` or `Heatmap`. `Table` will only work in the Table panel. `Heatmap` is suitable for displaying metrics of the Histogram type on a Heatmap panel. Under the hood, it converts cumulative histograms to regular ones and sorts series by the bucket bound. |
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
|||||||
`GET /api/orgs/name/:orgName`
|
`GET /api/orgs/name/:orgName`
|
||||||
|
|
||||||
Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
|
Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
|
||||||
|
|
||||||
**Example Request**:
|
**Example Request**:
|
||||||
|
|
||||||
```http
|
```http
|
||||||
@@ -463,7 +463,7 @@ Content-Type: application/json
|
|||||||
HTTP/1.1 200
|
HTTP/1.1 200
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{"message":"User added to organization"}
|
{"message":"User added to organization", "userId": 1}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Update Users in Organization
|
### Update Users in Organization
|
||||||
|
|||||||
@@ -8,23 +8,19 @@ weight = 300
|
|||||||
|
|
||||||
# Field configuration options
|
# Field configuration options
|
||||||
|
|
||||||
This page explains what field configurations and field overrides in Grafana are and how to use them. It also includes [examples](#examples) if you need an idea of how this feature might be useful in the real world.
|
This page explains what field options and field overrides in Grafana are and how to use them. It also includes
|
||||||
|
[examples](#examples) if you need an idea of how this feature might be useful in the real world.
|
||||||
|
|
||||||
> **Note:** This documentation refers to a Grafana 7.0 beta feature. This documentation will be frequently updated to reflect updates to the feature, and it will probably be broken into smaller sections when the feature moves out of beta.
|
The data model used in Grafana, the [data frame]({{< relref "../developers/plugins/data-frames.md" >}}),
|
||||||
|
is a columnar-oriented table structure that unifies both time series and table query results. Each column within this structure is called a _field_. A field can represent a single time series or table column.
|
||||||
The data model behind Grafana, the [data frame]({{< relref "../developers/plugins/data-frames.md" >}}), is a columnar-oriented table structure.
|
|
||||||
|
|
||||||
Each column within this structure is called a _field_. Grafana allows to customize how a particular field is displayed in the visualization.
|
|
||||||
|
|
||||||
## Field configuration options and overrides
|
## Field configuration options and overrides
|
||||||
|
|
||||||
Field configuration options allow you to change how the data is displayed in your visualizations. Options and overrides that you apply do not change the data, they change how Grafana displays the data.
|
Field configuration options allow you to change how the data is displayed in your visualizations. Options and overrides that you apply do not change the data, they change how Grafana displays the data.
|
||||||
|
|
||||||
> **Note:** The time fields are not affected by field configuration options or overrides.
|
_Field configuration options_, both standard and custom, can be found in the **Field** tab in the panel editor. Changes on this tab apply to all fields (i.e. series/columns). For example, if you change the unit to percentage, then all fields with numeric values are displayed in percentages. [Apply a field option](#configure-all-fields).
|
||||||
|
|
||||||
_Field configuration options_, both standard and custom, are applied in the **Field** tab in the panel editor. Changes on this panel apply to all fields in the visualization. For example, if you change the unit to percentage, then all fields with numeric values will be displayed in percentages. [Apply a field option](#configure-all-fields).
|
_Field overrides_ can be added in the **Overrides** tab in the panel editor. There you can add the same options as you find in the **Field** tab, but they are only applied to specific fields. [Apply an override](#override-a-field).
|
||||||
|
|
||||||
_Field overrides_ are applied in the _Overrides_ tab in the panel editor. They are exactly the same as field configuration options except that they only change fields you select. The current feature only allows you to change one field at a time, but future improvements will offer more flexibility. [Apply an override](#override-a-field).
|
|
||||||
|
|
||||||
All [field options](#field-options) are defined below.
|
All [field options](#field-options) are defined below.
|
||||||
|
|
||||||
@@ -42,26 +38,16 @@ Standard field options are:
|
|||||||
- [Value mappings](#value-mapping)
|
- [Value mappings](#value-mapping)
|
||||||
- [Data links](#data-links)
|
- [Data links](#data-links)
|
||||||
|
|
||||||
You can apply standard field options to the following panel visualizations:
|
You can apply standard field options to most of the built-in Grafana panels. Some older panels and community panels that have yet to update to the new panel and data model will be missing either all or some of these field options.
|
||||||
|
|
||||||
- [Bar gauge]({{< relref "visualizations/bar-gauge-panel.md" >}})
|
|
||||||
- [Gauge]({{< relref "visualizations/gauge-panel.md" >}})
|
|
||||||
- [Stat]({{< relref "visualizations/stat-panel.md" >}})
|
|
||||||
- [Table]({{< relref "visualizations/table-panel.md" >}})
|
|
||||||
|
|
||||||
### Custom field options
|
### Custom field options
|
||||||
|
|
||||||
You can only apply custom field options to table visualizations. Plugin authors can add their own custom field options as well, and they might differ across visualizations.
|
Some visualizations have custom field options. For example the [Table]({{< relref "visualizations/table-panel.md" >}}) visualization has many custom field options. Community panels can add their own custom field options as well, and they might differ across visualizations.
|
||||||
|
|
||||||
Custom field options are:
|
|
||||||
|
|
||||||
- [Column width](#column-width)
|
|
||||||
- [Column alignment](#column-alignment)
|
|
||||||
- [Cell display mode](#cell-display-mode)
|
|
||||||
|
|
||||||
## Configure all fields
|
## Configure all fields
|
||||||
|
|
||||||
To change how all fields display data, you apply a [field option](#field-options). Usually you apply changes that you want to most of or all of the fields here, rather than applying field overrides to exceptions.
|
To change how all fields display data, you change an option in the **Field** tab. In the **Overrides** tab
|
||||||
|
you then override that for specific fields.
|
||||||
|
|
||||||
1. Navigate to the panel you want to edit, click the panel title, and then click **Edit**.
|
1. Navigate to the panel you want to edit, click the panel title, and then click **Edit**.
|
||||||
1. Click the **Field** tab.
|
1. Click the **Field** tab.
|
||||||
@@ -69,9 +55,9 @@ To change how all fields display data, you apply a [field option](#field-options
|
|||||||
1. Enter options by adding values in the fields. To return options to default values, delete the white text in the fields.
|
1. Enter options by adding values in the fields. To return options to default values, delete the white text in the fields.
|
||||||
1. When finished, click **Save** to save all panel edits to the dashboard.
|
1. When finished, click **Save** to save all panel edits to the dashboard.
|
||||||
|
|
||||||
## Override a field
|
## Configure specific fields with overrides
|
||||||
|
|
||||||
Field overrides allow you to change the settings for one field (column in tables) to be different than the others. Field options for overrides are exactly the same as the field options available in a particular visualization. The only difference is that you choose which field to apply them to.
|
Overrides allow you to change the settings for one or more fields (i.e. series or column). What fields are targeted by the override depends on the matcher. Field options for overrides are exactly the same as the field options available in a particular visualization. The only difference is that you choose which fields to apply them to.
|
||||||
|
|
||||||
1. Navigate to the panel you want to edit, click the panel title, and then click **Edit**.
|
1. Navigate to the panel you want to edit, click the panel title, and then click **Edit**.
|
||||||
1. Click the **Overrides** tab.
|
1. Click the **Overrides** tab.
|
||||||
@@ -83,21 +69,21 @@ Field overrides allow you to change the settings for one field (column in tables
|
|||||||
1. Continue to add overrides to this field by clicking **Add override property**, or you can click **Add override** and select a different field to add overrides to.
|
1. 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.
|
1. When finished, click **Save** to save all panel edits to the dashboard.
|
||||||
|
|
||||||
## Filter options
|
## Select fields
|
||||||
|
|
||||||
This section explains all available filter options for field overrides. They are listed in alphabetical order.
|
This section explains the different ways you can select which fields an override rule will be applied to.
|
||||||
|
|
||||||
### Filter field by name
|
### Fields with name
|
||||||
|
|
||||||
Allows you to select a field from the list of all available fields that the override will be applied to.
|
Allows you to select a field from the list of all available fields. Properties you add to a rule with this selector will only be applied to this single field.
|
||||||
|
|
||||||
### Filter field by name using regex
|
### Fields with name matching regex
|
||||||
|
|
||||||
Allows you to type in a regular expression against which fields to be overridden will be matched.
|
Allows you to specify a regular expression. Properties you add to a rule with this selector will be applied to all fields where the field name match the regex.
|
||||||
|
|
||||||
### Filter field by type
|
### Fields with type
|
||||||
|
|
||||||
Allows you to select fields by their type (string, numeric, etc).
|
Allows you to select fields by their type (string, numeric, etc). Properties you add to a rule with this selector will be applied to all fields of matching type.
|
||||||
|
|
||||||
## Field options
|
## Field options
|
||||||
|
|
||||||
@@ -105,37 +91,6 @@ This section explains all available field options. They are listed in alphabetic
|
|||||||
|
|
||||||
Most field options will not affect the visualization until you click outside of the field option box you are editing or press Enter.
|
Most field options will not affect the visualization until you click outside of the field option box you are editing or press Enter.
|
||||||
|
|
||||||
### Cell display mode
|
|
||||||
|
|
||||||
This custom field option applies only to table visualizations.
|
|
||||||
|
|
||||||
By default, Grafana automatically chooses display settings. You can override the settings by choosing one of the following options to change all fields.
|
|
||||||
|
|
||||||
- **Color text -** If thresholds are set, then the field text is displayed in the appropriate threshold color.
|
|
||||||
- **Color background -** If thresholds are set, then the field background is displayed in the appropriate threshold color.
|
|
||||||
- **Gradient gauge -** The threshold levels define a gradient.
|
|
||||||
- **LCD gauge -** The gauge is split up in small cells that are lit or unlit.
|
|
||||||
- **JSON view -** Shows value formatted as code. If a value is an object the JSON view allowing browsing the JSON object will appear on hover
|
|
||||||
|
|
||||||
### Column alignment
|
|
||||||
|
|
||||||
This custom field option applies only to table visualizations.
|
|
||||||
|
|
||||||
Choose how Grafana should align cell contents:
|
|
||||||
|
|
||||||
- Auto (default)
|
|
||||||
- Left
|
|
||||||
- Center
|
|
||||||
- Right
|
|
||||||
|
|
||||||
### Column width
|
|
||||||
|
|
||||||
This custom field option applies only to table visualizations.
|
|
||||||
|
|
||||||
By default, Grafana automatically calculates the column width based on the cell contents. In this field option, can override the setting and define the width for all columns in pixels.
|
|
||||||
|
|
||||||
For example, if you enter `100` in the field, then when you click outside the field, all the columns will be set to 100 pixels wide.
|
|
||||||
|
|
||||||
### Decimals
|
### Decimals
|
||||||
|
|
||||||
Number of decimals to render value with. Leave empty for Grafana to use the number of decimals provided by the data source.
|
Number of decimals to render value with. Leave empty for Grafana to use the number of decimals provided by the data source.
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ Transformations are available from the Transform tab in the bottom pane of the p
|
|||||||
|
|
||||||
1. Navigate to the panel that you want to add transformations, click the panel title and then click **Edit**.
|
1. Navigate to the panel that you want to add transformations, click the panel title and then click **Edit**.
|
||||||
1. Click the **Transform** tab.
|
1. Click the **Transform** tab.
|
||||||
1. Click a transformation to select it.
|
1. Click a transformation to select it.
|
||||||
|
|
||||||
A transformation row appears that allows you to configure the transformation options.
|
A transformation row appears that allows you to configure the transformation options.
|
||||||
|
|
||||||
Click **Add transformation** to apply another transformation. Keep in mind that the next transformation acts on the result set returned by the previous transformation.
|
Click **Add transformation** to apply another transformation. Keep in mind that the next transformation acts on the result set returned by the previous transformation.
|
||||||
@@ -107,25 +107,24 @@ In the example below, we have two queries returning table data. It is visualized
|
|||||||
Query A:
|
Query A:
|
||||||
|
|
||||||
| Time | Job | Uptime |
|
| Time | Job | Uptime |
|
||||||
|---------------------|---------|-----------|
|
| ------------------- | ------- | --------- |
|
||||||
| 2020-07-07 11:34:20 | node | 25260122 |
|
| 2020-07-07 11:34:20 | node | 25260122 |
|
||||||
| 2020-07-07 11:24:20 | postgre | 123001233 |
|
| 2020-07-07 11:24:20 | postgre | 123001233 |
|
||||||
|
|
||||||
Query B:
|
Query B:
|
||||||
|
|
||||||
| Time | Job | Errors |
|
| Time | Job | Errors |
|
||||||
|---------------------|---------|--------|
|
| ------------------- | ------- | ------ |
|
||||||
| 2020-07-07 11:34:20 | node | 15 |
|
| 2020-07-07 11:34:20 | node | 15 |
|
||||||
| 2020-07-07 11:24:20 | postgre | 5 |
|
| 2020-07-07 11:24:20 | postgre | 5 |
|
||||||
|
|
||||||
Here is the result after applying the `Merge` transformation.
|
Here is the result after applying the `Merge` transformation.
|
||||||
|
|
||||||
| Time | Job | Errors | Uptime |
|
| Time | Job | Errors | Uptime |
|
||||||
|---------------------|---------|--------|-----------|
|
| ------------------- | ------- | ------ | --------- |
|
||||||
| 2020-07-07 11:34:20 | node | 15 | 25260122 |
|
| 2020-07-07 11:34:20 | node | 15 | 25260122 |
|
||||||
| 2020-07-07 11:24:20 | postgre | 5 | 123001233 |
|
| 2020-07-07 11:24:20 | postgre | 5 | 123001233 |
|
||||||
|
|
||||||
|
|
||||||
### Filter by name
|
### Filter by name
|
||||||
|
|
||||||
Use this transformation to remove portions of the query results.
|
Use this transformation to remove portions of the query results.
|
||||||
@@ -170,7 +169,7 @@ Use this transformation to rename, reorder, or hide fields returned by the query
|
|||||||
Grafana displays a list of fields returned by the query. You can:
|
Grafana displays a list of fields returned by the query. You can:
|
||||||
|
|
||||||
- Change field order by hovering your cursor over a field. The cursor turns into a hand and then you can drag the field to its new place.
|
- Change field order by hovering your cursor over a field. The cursor turns into a hand and then you can drag the field to its new place.
|
||||||
- Hide or show a field by clicking the eye icon next to the field name.
|
- Hide or show a field by clicking the eye icon next to the field name.
|
||||||
- Rename fields by typing a new name in the **Rename <field>** box.
|
- Rename fields by typing a new name in the **Rename <field>** box.
|
||||||
|
|
||||||
In the example below, I hid the value field and renamed Max and Min.
|
In the example below, I hid the value field and renamed Max and Min.
|
||||||
@@ -209,12 +208,30 @@ In the example below, I added two fields together and named them Sum.
|
|||||||
|
|
||||||
### Labels to fields
|
### Labels to fields
|
||||||
|
|
||||||
Use this transformation to group series by time and return labels or tags as fields.
|
> **Note:** In order to apply this transformation, your query needs to returns labeled fields.
|
||||||
|
|
||||||
> **Note:** In order to apply this transformation, you must have a query to a data source that returns labeled fields.
|
|
||||||
|
|
||||||
When you select this transformation, Grafana automatically transforms all labeled data into fields.
|
When you select this transformation, Grafana automatically transforms all labeled data into fields.
|
||||||
|
|
||||||
|
Example: Given a query result of two time series
|
||||||
|
|
||||||
|
1: labels Server=Server A, Datacenter=EU
|
||||||
|
2: labels Server=Server B, Datacenter=EU
|
||||||
|
|
||||||
|
This would result in a table like this
|
||||||
|
|
||||||
|
| Time | Server | Datacenter | Value |
|
||||||
|
| ------------------- | -------- | ---------- | ----- |
|
||||||
|
| 2020-07-07 11:34:20 | Server A | EU | 1 |
|
||||||
|
| 2020-07-07 11:34:20 | Server B | EU | 2 |
|
||||||
|
|
||||||
|
**Value field name**
|
||||||
|
If you where to select `Server` as in the **Value field name** you would get one field for every value of the `Server`
|
||||||
|
label.
|
||||||
|
|
||||||
|
| Time | Datacenter | Server A | Server B |
|
||||||
|
| ------------------- | ---------- | -------- | -------- |
|
||||||
|
| 2020-07-07 11:34:20 | EU | 1 | 2 |
|
||||||
|
|
||||||
For this example, I manually defined labels in the Random Walk visualization of TestData DB.
|
For this example, I manually defined labels in the Random Walk visualization of TestData DB.
|
||||||
|
|
||||||
{{< docs-imagebox img="/img/docs/transformations/labels-to-fields-before-7-0.png" class="docs-image--no-shadow" max-width= "1100px" >}}
|
{{< docs-imagebox img="/img/docs/transformations/labels-to-fields-before-7-0.png" class="docs-image--no-shadow" max-width= "1100px" >}}
|
||||||
@@ -223,7 +240,6 @@ After I apply the transformation, my labels appear in the table as fields.
|
|||||||
|
|
||||||
{{< docs-imagebox img="/img/docs/transformations/labels-to-fields-after-7-0.png" class="docs-image--no-shadow" max-width= "1100px" >}}
|
{{< docs-imagebox img="/img/docs/transformations/labels-to-fields-after-7-0.png" class="docs-image--no-shadow" max-width= "1100px" >}}
|
||||||
|
|
||||||
|
|
||||||
### Group By
|
### Group By
|
||||||
|
|
||||||
> **Note:** This transformation is only available in Grafana 7.2+.
|
> **Note:** This transformation is only available in Grafana 7.2+.
|
||||||
@@ -232,57 +248,58 @@ This transformation groups the data by a specified field (column) value and proc
|
|||||||
|
|
||||||
Here's an example of original data.
|
Here's an example of original data.
|
||||||
|
|
||||||
| Time | Server ID | CPU Temperature | Server Status
|
| Time | Server ID | CPU Temperature | Server Status |
|
||||||
|---------------------|-------------|-----------------|----------
|
| ------------------- | --------- | --------------- | ------------- |
|
||||||
| 2020-07-07 11:34:20 | server 1 | 80 | Shutdown
|
| 2020-07-07 11:34:20 | server 1 | 80 | Shutdown |
|
||||||
| 2020-07-07 11:34:20 | server 3 | 62 | OK
|
| 2020-07-07 11:34:20 | server 3 | 62 | OK |
|
||||||
| 2020-07-07 10:32:20 | server 2 | 90 | Overload
|
| 2020-07-07 10:32:20 | server 2 | 90 | Overload |
|
||||||
| 2020-07-07 10:31:22 | server 3 | 55 | OK
|
| 2020-07-07 10:31:22 | server 3 | 55 | OK |
|
||||||
| 2020-07-07 09:30:57 | server 3 | 62 | Rebooting
|
| 2020-07-07 09:30:57 | server 3 | 62 | Rebooting |
|
||||||
| 2020-07-07 09:30:05 | server 2 | 88 | OK
|
| 2020-07-07 09:30:05 | server 2 | 88 | OK |
|
||||||
| 2020-07-07 09:28:06 | server 1 | 80 | OK
|
| 2020-07-07 09:28:06 | server 1 | 80 | OK |
|
||||||
| 2020-07-07 09:25:05 | server 2 | 88 | OK
|
| 2020-07-07 09:25:05 | server 2 | 88 | OK |
|
||||||
| 2020-07-07 09:23:07 | server 1 | 86 | OK
|
| 2020-07-07 09:23:07 | server 1 | 86 | OK |
|
||||||
|
|
||||||
This transformation goes in two steps. First you specify one or multiple fields to group the data by. This will group all the same values of those fields together, as if you sorted them. For instance if we `Group By` the `Server ID` field, it would group the data this way:
|
This transformation goes in two steps. First you specify one or multiple fields to group the data by. This will group all the same values of those fields together, as if you sorted them. For instance if we `Group By` the `Server ID` field, it would group the data this way:
|
||||||
|
|
||||||
| Time | Server ID | CPU Temperature | Server Status
|
| Time | Server ID | CPU Temperature | Server Status |
|
||||||
|---------------------|-------------|-----------------|----------
|
| ------------------- | ------------ | --------------- | ------------- |
|
||||||
| 2020-07-07 11:34:20 | **server 1** | 80 | Shutdown
|
| 2020-07-07 11:34:20 | **server 1** | 80 | Shutdown |
|
||||||
| 2020-07-07 09:28:06 | **server 1** | 80 | OK
|
| 2020-07-07 09:28:06 | **server 1** | 80 | OK |
|
||||||
| 2020-07-07 09:23:07 | **server 1** | 86 | OK
|
| 2020-07-07 09:23:07 | **server 1** | 86 | OK |
|
||||||
|
|
||||||
|
|
|
|
||||||
| 2020-07-07 10:32:20 | server 2 | 90 | Overload
|
| 2020-07-07 10:32:20 | server 2 | 90 | Overload
|
||||||
| 2020-07-07 09:30:05 | server 2 | 88 | OK
|
| 2020-07-07 09:30:05 | server 2 | 88 | OK
|
||||||
| 2020-07-07 09:25:05 | server 2 | 88 | OK
|
| 2020-07-07 09:25:05 | server 2 | 88 | OK
|
||||||
|
|
|
|
||||||
| 2020-07-07 11:34:20 | ***server 3*** | 62 | OK
|
| 2020-07-07 11:34:20 | **_server 3_** | 62 | OK
|
||||||
| 2020-07-07 10:31:22 | ***server 3*** | 55 | OK
|
| 2020-07-07 10:31:22 | **_server 3_** | 55 | OK
|
||||||
| 2020-07-07 09:30:57 | ***server 3*** | 62 | Rebooting
|
| 2020-07-07 09:30:57 | **_server 3_** | 62 | Rebooting
|
||||||
|
|
||||||
All rows with the same value of `Server ID` are grouped together.
|
All rows with the same value of `Server ID` are grouped together.
|
||||||
|
|
||||||
After choosing which field you want to group your data by, you can add various calculations on the other fields, and the calculation will be applied on each group of rows. For instance, we could want to calculate the average `CPU temperature` for each of those servers. So we can add the _mean_ calculation applied on the `CPU Temperature` field to get the following:
|
After choosing which field you want to group your data by, you can add various calculations on the other fields, and the calculation will be applied on each group of rows. For instance, we could want to calculate the average `CPU temperature` for each of those servers. So we can add the _mean_ calculation applied on the `CPU Temperature` field to get the following:
|
||||||
|
|
||||||
| Server ID | CPU Temperature (mean)
|
| Server ID | CPU Temperature (mean) |
|
||||||
|-----------|--------------------------
|
| --------- | ---------------------- |
|
||||||
| server 1 | 82
|
| server 1 | 82 |
|
||||||
| server 2 | 88.6
|
| server 2 | 88.6 |
|
||||||
| server 3 | 59.6
|
| server 3 | 59.6 |
|
||||||
|
|
||||||
And we can add more than one of those calculation. For instance :
|
And we can add more than one of those calculation. For instance :
|
||||||
|
|
||||||
- For field `Time`, we can calculate the *Last* value, to know when the last data point was received for each server
|
- For field `Time`, we can calculate the _Last_ value, to know when the last data point was received for each server
|
||||||
- For field `Server Status`, we can calculate the *Last* value to know what is the last state value for each server
|
- For field `Server Status`, we can calculate the _Last_ value to know what is the last state value for each server
|
||||||
- For field `Temperature`, we can also calculate the *Last* value to know what is the latest monitored temperature for each server
|
- For field `Temperature`, we can also calculate the _Last_ value to know what is the latest monitored temperature for each server
|
||||||
|
|
||||||
We would then get :
|
We would then get :
|
||||||
|
|
||||||
| Server ID | CPU Temperature (mean) | CPU Temperature (last) | Time (last) | Server Status (last)
|
| Server ID | CPU Temperature (mean) | CPU Temperature (last) | Time (last) | Server Status (last) |
|
||||||
|-----------|-------------------------- |------------------------|------------------|----------------------
|
| --------- | ---------------------- | ---------------------- | ------------------- | -------------------- |
|
||||||
| server 1 | 82 | 80 | 2020-07-07 11:34:20 | Shutdown
|
| server 1 | 82 | 80 | 2020-07-07 11:34:20 | Shutdown |
|
||||||
| server 2 | 88.6 | 90 | 2020-07-07 10:32:20 | Overload
|
| server 2 | 88.6 | 90 | 2020-07-07 10:32:20 | Overload |
|
||||||
| server 3 | 59.6 | 62 | 2020-07-07 11:34:20 | OK
|
| server 3 | 59.6 | 62 | 2020-07-07 11:34:20 | OK |
|
||||||
|
|
||||||
This transformation allows you to extract some key information out of your time series and display them in a convenient way.
|
This transformation allows you to extract some key information out of your time series and display them in a convenient way.
|
||||||
|
|
||||||
@@ -290,7 +307,7 @@ This transformation allows you to extract some key information out of your time
|
|||||||
|
|
||||||
> **Note:** This transformation is only available in Grafana 7.1+.
|
> **Note:** This transformation is only available in Grafana 7.1+.
|
||||||
|
|
||||||
Use this transformation to combine the result from multiple time series data queries into one single result. This is helpful when using the table panel visualization.
|
Use this transformation to combine the result from multiple time series data queries into one single result. This is helpful when using the table panel visualization.
|
||||||
|
|
||||||
The result from this transformation will contain three columns: `Time`, `Metric`, and `Value`. The `Metric` column is added so you easily can see from which query the metric originates from. Customize this value by defining `Label` on the source query.
|
The result from this transformation will contain three columns: `Time`, `Metric`, and `Value`. The `Metric` column is added so you easily can see from which query the metric originates from. Customize this value by defining `Label` on the source query.
|
||||||
|
|
||||||
@@ -299,7 +316,7 @@ In the example below, we have two queries returning time series data. It is visu
|
|||||||
Query A:
|
Query A:
|
||||||
|
|
||||||
| Time | Temperature |
|
| Time | Temperature |
|
||||||
|---------------------|-------------|
|
| ------------------- | ----------- |
|
||||||
| 2020-07-07 11:34:20 | 25 |
|
| 2020-07-07 11:34:20 | 25 |
|
||||||
| 2020-07-07 10:31:22 | 22 |
|
| 2020-07-07 10:31:22 | 22 |
|
||||||
| 2020-07-07 09:30:05 | 19 |
|
| 2020-07-07 09:30:05 | 19 |
|
||||||
@@ -307,7 +324,7 @@ Query A:
|
|||||||
Query B:
|
Query B:
|
||||||
|
|
||||||
| Time | Humidity |
|
| Time | Humidity |
|
||||||
|---------------------|----------|
|
| ------------------- | -------- |
|
||||||
| 2020-07-07 11:34:20 | 24 |
|
| 2020-07-07 11:34:20 | 24 |
|
||||||
| 2020-07-07 10:32:20 | 29 |
|
| 2020-07-07 10:32:20 | 29 |
|
||||||
| 2020-07-07 09:30:57 | 33 |
|
| 2020-07-07 09:30:57 | 33 |
|
||||||
@@ -315,7 +332,7 @@ Query B:
|
|||||||
Here is the result after applying the `Series to rows` transformation.
|
Here is the result after applying the `Series to rows` transformation.
|
||||||
|
|
||||||
| Time | Metric | Value |
|
| Time | Metric | Value |
|
||||||
|---------------------|-------------|-------|
|
| ------------------- | ----------- | ----- |
|
||||||
| 2020-07-07 11:34:20 | Temperature | 25 |
|
| 2020-07-07 11:34:20 | Temperature | 25 |
|
||||||
| 2020-07-07 11:34:20 | Humidity | 22 |
|
| 2020-07-07 11:34:20 | Humidity | 22 |
|
||||||
| 2020-07-07 10:32:20 | Humidity | 29 |
|
| 2020-07-07 10:32:20 | Humidity | 29 |
|
||||||
|
|||||||
@@ -27,6 +27,43 @@ Table visualizations allow you to apply:
|
|||||||
|
|
||||||
- **Show header -** Show or hide column names imported from your data source..
|
- **Show header -** Show or hide column names imported from your data source..
|
||||||
|
|
||||||
|
### Custom field options
|
||||||
|
|
||||||
|
- [Column width](#column-width)
|
||||||
|
- [Column alignment](#column-alignment)
|
||||||
|
- [Cell display mode](#cell-display-mode)
|
||||||
|
|
||||||
|
### Column alignment
|
||||||
|
|
||||||
|
This custom field option applies only to table visualizations.
|
||||||
|
|
||||||
|
Choose how Grafana should align cell contents:
|
||||||
|
|
||||||
|
- Auto (default)
|
||||||
|
- Left
|
||||||
|
- Center
|
||||||
|
- Right
|
||||||
|
|
||||||
|
### Column width
|
||||||
|
|
||||||
|
This custom field option applies only to table visualizations.
|
||||||
|
|
||||||
|
By default, Grafana automatically calculates the column width based on the cell contents. In this field option, can override the setting and define the width for all columns in pixels.
|
||||||
|
|
||||||
|
For example, if you enter `100` in the field, then when you click outside the field, all the columns will be set to 100 pixels wide.
|
||||||
|
|
||||||
|
#### Cell display mode
|
||||||
|
|
||||||
|
This custom field option applies only to table visualizations.
|
||||||
|
|
||||||
|
By default, Grafana automatically chooses display settings. You can override the settings by choosing one of the following options to change all fields.
|
||||||
|
|
||||||
|
- **Color text -** If thresholds are set, then the field text is displayed in the appropriate threshold color.
|
||||||
|
- **Color background -** If thresholds are set, then the field background is displayed in the appropriate threshold color.
|
||||||
|
- **Gradient gauge -** The threshold levels define a gradient.
|
||||||
|
- **LCD gauge -** The gauge is split up in small cells that are lit or unlit.
|
||||||
|
- **JSON view -** Shows value formatted as code. If a value is an object the JSON view allowing browsing the JSON object will appear on hover
|
||||||
|
|
||||||
## Tips
|
## Tips
|
||||||
|
|
||||||
### Display original string value
|
### Display original string value
|
||||||
|
|||||||
@@ -143,3 +143,13 @@ servers = ["test'1", "test2"]
|
|||||||
String to interpolate: '${servers:sqlstring}'
|
String to interpolate: '${servers:sqlstring}'
|
||||||
Interpolation result: "'test''1','test2'"
|
Interpolation result: "'test''1','test2'"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Text
|
||||||
|
|
||||||
|
Formats single- and multi-valued variables into their text representation. For a single variable it will just return the text representation. For multi-valued variables it will return the text representation combined with `+`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
servers = ["test1", "test2"]
|
||||||
|
String to interpolate: '${servers:text}'
|
||||||
|
Interpolation result: "test1 + test2"
|
||||||
|
```
|
||||||
@@ -33,11 +33,12 @@ e2e.scenario({
|
|||||||
`Server:doublequote = "A'A\\"A","BB\\B","CCC"`,
|
`Server:doublequote = "A'A\\"A","BB\\B","CCC"`,
|
||||||
`Server:sqlstring = 'A''A"A','BB\\\B','CCC'`,
|
`Server:sqlstring = 'A''A"A','BB\\\B','CCC'`,
|
||||||
`Server:date = null`,
|
`Server:date = null`,
|
||||||
|
`Server:text = All`,
|
||||||
];
|
];
|
||||||
|
|
||||||
e2e()
|
e2e()
|
||||||
.get('.markdown-html li')
|
.get('.markdown-html li')
|
||||||
.should('have.length', 21)
|
.should('have.length', 22)
|
||||||
.each(element => {
|
.each(element => {
|
||||||
items.push(element.text());
|
items.push(element.text());
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -75,44 +75,6 @@ e2e.scenario({
|
|||||||
|
|
||||||
e2e().wait('@apiPostQuery');
|
e2e().wait('@apiPostQuery');
|
||||||
|
|
||||||
// Change order or query rows
|
|
||||||
// Check the order of the rows before
|
|
||||||
e2e.components.QueryEditorRows.rows()
|
|
||||||
.eq(0)
|
|
||||||
.within(() => {
|
|
||||||
e2e.components.QueryEditorRow.title('B').should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
e2e.components.QueryEditorRows.rows()
|
|
||||||
.eq(1)
|
|
||||||
.within(() => {
|
|
||||||
e2e.components.QueryEditorRow.title('A').should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Change so A is first
|
|
||||||
e2e.components.QueryEditorRow.actionButton('Move query up')
|
|
||||||
.eq(1)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
e2e().wait('@apiPostQuery');
|
|
||||||
|
|
||||||
// Avoid flaky tests
|
|
||||||
// Maybe the virtual dom performs optimzations such as node position swapping, meaning 1 becomes 0 and it gets that element before the change because and never finds title 'A'
|
|
||||||
e2e().wait(250);
|
|
||||||
|
|
||||||
// Check the order of the rows after change
|
|
||||||
e2e.components.QueryEditorRows.rows()
|
|
||||||
.eq(0)
|
|
||||||
.within(() => {
|
|
||||||
e2e.components.QueryEditorRow.title('A').should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
e2e.components.QueryEditorRows.rows()
|
|
||||||
.eq(1)
|
|
||||||
.within(() => {
|
|
||||||
e2e.components.QueryEditorRow.title('B').should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Disable / enable row
|
// Disable / enable row
|
||||||
expectInspectorResultAndClose(keys => {
|
expectInspectorResultAndClose(keys => {
|
||||||
const length = keys.length;
|
const length = keys.length;
|
||||||
@@ -120,7 +82,7 @@ e2e.scenario({
|
|||||||
expect(keys[length - 1].innerText).equals('B:');
|
expect(keys[length - 1].innerText).equals('B:');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Disable row with refId B
|
// Disable row with refId A
|
||||||
e2e.components.QueryEditorRow.actionButton('Disable/enable query')
|
e2e.components.QueryEditorRow.actionButton('Disable/enable query')
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
@@ -130,7 +92,7 @@ e2e.scenario({
|
|||||||
|
|
||||||
expectInspectorResultAndClose(keys => {
|
expectInspectorResultAndClose(keys => {
|
||||||
const length = keys.length;
|
const length = keys.length;
|
||||||
expect(keys[length - 1].innerText).equals('A:');
|
expect(keys[length - 1].innerText).equals('B:');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Enable row with refId B
|
// Enable row with refId B
|
||||||
|
|||||||
17
e2e/suite1/specs/solo-route.spec.ts
Normal file
17
e2e/suite1/specs/solo-route.spec.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { e2e } from '@grafana/e2e';
|
||||||
|
|
||||||
|
e2e.scenario({
|
||||||
|
describeName: 'Solo Route',
|
||||||
|
itName: 'Can view panels with shared queries in fullsceen',
|
||||||
|
addScenarioDataSource: false,
|
||||||
|
addScenarioDashBoard: false,
|
||||||
|
skipScenario: false,
|
||||||
|
scenario: () => {
|
||||||
|
// open Panel Tests - Bar Gauge
|
||||||
|
e2e.pages.SoloPanel.visit('ZqZnVvFZz/datasource-tests-shared-queries?orgId=1&panelId=4');
|
||||||
|
|
||||||
|
e2e()
|
||||||
|
.get('canvas')
|
||||||
|
.should('have.length', 6);
|
||||||
|
},
|
||||||
|
});
|
||||||
68
e2e/suite1/specs/templating-dashboard-links-and-variables.ts
Normal file
68
e2e/suite1/specs/templating-dashboard-links-and-variables.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { e2e } from '@grafana/e2e';
|
||||||
|
|
||||||
|
e2e.scenario({
|
||||||
|
describeName: 'Templating',
|
||||||
|
itName: 'Tests dashboard links and variables in links',
|
||||||
|
addScenarioDataSource: false,
|
||||||
|
addScenarioDashBoard: false,
|
||||||
|
skipScenario: false,
|
||||||
|
scenario: () => {
|
||||||
|
e2e.flows.openDashboard({ uid: 'yBCC3aKGk' });
|
||||||
|
e2e().server();
|
||||||
|
e2e()
|
||||||
|
.route({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/search?tag=templating&limit=100',
|
||||||
|
})
|
||||||
|
.as('tagsTemplatingSearch');
|
||||||
|
e2e()
|
||||||
|
.route({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/search?tag=demo&limit=100',
|
||||||
|
})
|
||||||
|
.as('tagsDemoSearch');
|
||||||
|
|
||||||
|
// waiting for links to render, couldn't find a better way using routes for instance
|
||||||
|
e2e().wait(1000);
|
||||||
|
|
||||||
|
const verifyLinks = (variableValue: string) => {
|
||||||
|
e2e.components.DashboardLinks.link()
|
||||||
|
.should('be.visible')
|
||||||
|
.and(links => {
|
||||||
|
expect(links).to.have.length(13);
|
||||||
|
|
||||||
|
for (let index = 0; index < links.length; index++) {
|
||||||
|
expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${variableValue}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
e2e.components.DashboardLinks.dropDown()
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
.wait('@tagsTemplatingSearch')
|
||||||
|
.wait('@tagsDemoSearch');
|
||||||
|
|
||||||
|
// verify all links, should have All value
|
||||||
|
verifyLinks('All');
|
||||||
|
|
||||||
|
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All')
|
||||||
|
.should('be.visible')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('p2')
|
||||||
|
.should('be.visible')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
e2e.pages.Dashboard.Toolbar.navBar().click();
|
||||||
|
|
||||||
|
e2e.components.DashboardLinks.dropDown()
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
.wait('@tagsTemplatingSearch')
|
||||||
|
.wait('@tagsDemoSearch');
|
||||||
|
|
||||||
|
// verify all links, should have p2 value
|
||||||
|
verifyLinks('p2');
|
||||||
|
},
|
||||||
|
});
|
||||||
11
go.mod
11
go.mod
@@ -16,11 +16,10 @@ require (
|
|||||||
github.com/BurntSushi/toml v0.3.1
|
github.com/BurntSushi/toml v0.3.1
|
||||||
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
|
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
|
||||||
github.com/aws/aws-sdk-go v1.33.12
|
github.com/aws/aws-sdk-go v1.33.12
|
||||||
github.com/beevik/etree v1.1.0 // indirect
|
|
||||||
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3
|
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
||||||
github.com/centrifugal/centrifuge v0.10.0
|
github.com/centrifugal/centrifuge v0.11.0
|
||||||
github.com/crewjam/saml v0.0.0-20191031171751-c42136edf9b1
|
github.com/crewjam/saml v0.4.4-0.20201214083806-0dd2422c212e
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/deepmap/oapi-codegen v1.3.11 // indirect
|
github.com/deepmap/oapi-codegen v1.3.11 // indirect
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec
|
github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec
|
||||||
@@ -30,6 +29,7 @@ require (
|
|||||||
github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691 // indirect
|
github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691 // indirect
|
||||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||||
github.com/fatih/color v1.9.0
|
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/binding v0.0.0-20190806013118-0b4f37bab25b
|
||||||
github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07
|
github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07
|
||||||
github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659
|
github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659
|
||||||
@@ -50,6 +50,7 @@ require (
|
|||||||
github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec
|
github.com/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.0.1
|
||||||
github.com/jmespath/go-jmespath v0.3.0
|
github.com/jmespath/go-jmespath v0.3.0
|
||||||
|
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||||
github.com/jung-kurt/gofpdf v1.10.1
|
github.com/jung-kurt/gofpdf v1.10.1
|
||||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
|
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
|
||||||
github.com/lib/pq v1.3.0
|
github.com/lib/pq v1.3.0
|
||||||
@@ -68,7 +69,7 @@ require (
|
|||||||
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
||||||
github.com/robfig/cron/v3 v3.0.0
|
github.com/robfig/cron/v3 v3.0.0
|
||||||
github.com/smartystreets/goconvey v1.6.4
|
github.com/smartystreets/goconvey v1.6.4
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
|
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
|
||||||
github.com/timberio/go-datemath v0.1.1-0.20200323150745-74ddef604fff
|
github.com/timberio/go-datemath v0.1.1-0.20200323150745-74ddef604fff
|
||||||
github.com/ua-parser/uap-go v0.0.0-20190826212731-daf92ba38329
|
github.com/ua-parser/uap-go v0.0.0-20190826212731-daf92ba38329
|
||||||
@@ -79,7 +80,7 @@ require (
|
|||||||
github.com/yudai/gojsondiff v1.0.0
|
github.com/yudai/gojsondiff v1.0.0
|
||||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
||||||
github.com/yudai/pp v2.0.1+incompatible // indirect
|
github.com/yudai/pp v2.0.1+incompatible // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9
|
||||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc
|
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
||||||
|
|||||||
44
go.sum
44
go.sum
@@ -147,7 +147,6 @@ github.com/aws/aws-sdk-go v1.33.12 h1:eydMoSwfrSTD9PWKUJOiDL7+/UwDW8AjInUGVE5Llh
|
|||||||
github.com/aws/aws-sdk-go v1.33.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
github.com/aws/aws-sdk-go v1.33.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||||
github.com/beevik/etree v1.0.1/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
|
||||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||||
github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
|
github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
|
||||||
@@ -177,10 +176,10 @@ github.com/cenkalti/backoff v1.0.0/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQ
|
|||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/centrifugal/centrifuge v0.10.0 h1:Eften1Akke0mgHJwHPqhG8oR/6q/SO4y/+KKdz+Qr14=
|
github.com/centrifugal/centrifuge v0.11.0 h1:aaG74PBO+rBmz3hE9W8bCGz8kofiam1LNE4EAVtiMaA=
|
||||||
github.com/centrifugal/centrifuge v0.10.0/go.mod h1:mkuUdBuYFqz2GincQhkwWmHuVs9VWEHhMjzMcdUk6oA=
|
github.com/centrifugal/centrifuge v0.11.0/go.mod h1:jdFw/2dBFpME3OTisc5FVAC1ilqodnyMnQFjem0k0yg=
|
||||||
github.com/centrifugal/protocol v0.3.3 h1:GCNee3RFsjQu6SyKBX0Ir7ByUrp+Gw0MU/PsIc2CM2s=
|
github.com/centrifugal/protocol v0.3.4 h1:9q22iSp4CQOdQahfopvfmWWxDbj1Lo7ERG2j56mAxkE=
|
||||||
github.com/centrifugal/protocol v0.3.3/go.mod h1:2YbBCaDwQHl37ErRdMrKSj18X2yVvpkQYtSX6aVbe5A=
|
github.com/centrifugal/protocol v0.3.4/go.mod h1:2YbBCaDwQHl37ErRdMrKSj18X2yVvpkQYtSX6aVbe5A=
|
||||||
github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
@@ -224,8 +223,11 @@ github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/crewjam/saml v0.0.0-20191031171751-c42136edf9b1 h1:PKeiHI5SxrkdEtI8FVdk1ubBl2wjnOmHQf5D4ZJOKFE=
|
github.com/crewjam/httperr v0.0.0-20190612203328-a946449404da/go.mod h1:+rmNIXRvYMqLQeR4DHyTvs6y0MEMymTz4vyFpFkKTPs=
|
||||||
github.com/crewjam/saml v0.0.0-20191031171751-c42136edf9b1/go.mod h1:pzACCdpqjQKTvpPZs5P3FzFNQ+RSOJX5StwHwh7ZUgw=
|
github.com/crewjam/saml v0.4.1 h1:ZNSRJvdbypQDY2uApMngeIHNcxS6UCRAgiw3S+pmgRU=
|
||||||
|
github.com/crewjam/saml v0.4.1/go.mod h1:vHcshzXm2WkPOV1dcToZa99cCB1h3nPiKLtLYK+erBE=
|
||||||
|
github.com/crewjam/saml v0.4.4-0.20201214083806-0dd2422c212e h1:CFIpybPh+vrxRD6R3t2BCV9hdtlOQudsj1vB1ECXOo4=
|
||||||
|
github.com/crewjam/saml v0.4.4-0.20201214083806-0dd2422c212e/go.mod h1:qCJQpUtZte9R1ZjUBcW8qtCNlinbO363ooNl02S68bk=
|
||||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
||||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||||
github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
|
github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
|
||||||
@@ -317,6 +319,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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
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/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=
|
||||||
|
github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y=
|
||||||
|
github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34=
|
||||||
github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk=
|
github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk=
|
||||||
github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
|
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 v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
@@ -529,6 +533,7 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
|
|||||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
@@ -694,6 +699,11 @@ github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9
|
|||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||||
|
github.com/jonboulle/clockwork v0.2.1 h1:S/EaQvW6FpWMYAvYvY+OBDvpaM+izu0oiwo5y0MH7U0=
|
||||||
|
github.com/jonboulle/clockwork v0.2.1/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||||
|
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||||
|
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||||
github.com/joncrlsn/dque v2.2.1-0.20200515025108-956d14155fa2+incompatible/go.mod h1:hDZb8oMj3Kp8MxtbNLg9vrtAUDHjgI1yZvqivT4O8Iw=
|
github.com/joncrlsn/dque v2.2.1-0.20200515025108-956d14155fa2+incompatible/go.mod h1:hDZb8oMj3Kp8MxtbNLg9vrtAUDHjgI1yZvqivT4O8Iw=
|
||||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
@@ -709,6 +719,7 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
|||||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o=
|
github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o=
|
||||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
@@ -742,6 +753,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
|
|||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.0.0/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.0.0/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||||
@@ -783,6 +796,8 @@ github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
|
|||||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||||
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||||
|
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e h1:qqXczln0qwkVGcpQ+sQuPOVntt2FytYarXXxYSNJkgw=
|
||||||
|
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||||
github.com/mattetti/filebuffer v1.0.0 h1:ixTvQ0JjBTwWbdpDZ98lLrydo7KRi8xNRIi5RFszsbY=
|
github.com/mattetti/filebuffer v1.0.0 h1:ixTvQ0JjBTwWbdpDZ98lLrydo7KRi8xNRIi5RFszsbY=
|
||||||
github.com/mattetti/filebuffer v1.0.0/go.mod h1:X6nyAIge2JGVmuJt2MFCqmHrb/5IHiphfHtot0s5cnI=
|
github.com/mattetti/filebuffer v1.0.0/go.mod h1:X6nyAIge2JGVmuJt2MFCqmHrb/5IHiphfHtot0s5cnI=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
@@ -1021,6 +1036,10 @@ github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
|||||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 h1:J4AOUcOh/t1XbQcJfkEqhzgvMJ2tDxdCVvmHxW5QXao=
|
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 h1:J4AOUcOh/t1XbQcJfkEqhzgvMJ2tDxdCVvmHxW5QXao=
|
||||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7/go.mod h1:Oz4y6ImuOQZxynhbSXk7btjEfNBtGlj2dcaOvXl2FSM=
|
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7/go.mod h1:Oz4y6ImuOQZxynhbSXk7btjEfNBtGlj2dcaOvXl2FSM=
|
||||||
|
github.com/russellhaering/goxmldsig v0.0.0-20200902171629-2e1fbc2c5593 h1:wkyiSzH81tsd3tSoznvnXMIJo0cpHjbFuJhs/E9t/B8=
|
||||||
|
github.com/russellhaering/goxmldsig v0.0.0-20200902171629-2e1fbc2c5593/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o=
|
||||||
|
github.com/russellhaering/goxmldsig v1.1.0 h1:lK/zeJie2sqG52ZAlPNn1oBBqsIsEKypUUBGpYYF6lk=
|
||||||
|
github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||||
@@ -1097,6 +1116,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA=
|
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA=
|
||||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0=
|
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0=
|
||||||
@@ -1228,8 +1249,10 @@ golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604=
|
||||||
|
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -1411,6 +1434,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d h1:QQrM/CCYEzTs91GZylDCQjGHudbPTxF/1fvXdVh5lMo=
|
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d h1:QQrM/CCYEzTs91GZylDCQjGHudbPTxF/1fvXdVh5lMo=
|
||||||
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -1658,7 +1682,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"packages": ["packages/*"],
|
"packages": ["packages/*"],
|
||||||
"version": "7.2.0-pre.0"
|
"version": "7.2.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "grafana",
|
"name": "grafana",
|
||||||
"version": "7.2.0-beta1",
|
"version": "7.2.3",
|
||||||
"repository": "github:grafana/grafana",
|
"repository": "github:grafana/grafana",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
|
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "@grafana/data",
|
"name": "@grafana/data",
|
||||||
"version": "7.2.0-pre.0",
|
"version": "7.2.2",
|
||||||
"description": "Grafana Data Library",
|
"description": "Grafana Data Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"typescript"
|
"typescript"
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export function localTimeFormat(
|
|||||||
locale?: string | string[] | null,
|
locale?: string | string[] | null,
|
||||||
fallback?: string
|
fallback?: string
|
||||||
): string {
|
): string {
|
||||||
if (!window.Intl) {
|
if (missingIntlDateTimeFormatSupport()) {
|
||||||
return fallback ?? DEFAULT_SYSTEM_DATE_FORMAT;
|
return fallback ?? DEFAULT_SYSTEM_DATE_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,3 +115,7 @@ export function localTimeFormat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const systemDateFormats = new SystemDateFormatsState();
|
export const systemDateFormats = new SystemDateFormatsState();
|
||||||
|
|
||||||
|
const missingIntlDateTimeFormatSupport = (): boolean => {
|
||||||
|
return !('DateTimeFormat' in Intl) || !('formatToParts' in Intl.DateTimeFormat.prototype);
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Field, FieldConfig, FieldType, GrafanaTheme, Threshold, ThresholdsMode
|
|||||||
import { getScaleCalculator, sortThresholds } from './scale';
|
import { getScaleCalculator, sortThresholds } from './scale';
|
||||||
import { ArrayVector } from '../vector';
|
import { ArrayVector } from '../vector';
|
||||||
import { validateFieldConfig } from './fieldOverrides';
|
import { validateFieldConfig } from './fieldOverrides';
|
||||||
|
import { systemDateFormats } from '../datetime';
|
||||||
|
|
||||||
function getDisplayProcessorFromConfig(config: FieldConfig) {
|
function getDisplayProcessorFromConfig(config: FieldConfig) {
|
||||||
return getDisplayProcessor({
|
return getDisplayProcessor({
|
||||||
@@ -293,6 +294,23 @@ describe('Date display options', () => {
|
|||||||
expect(processor(0).text).toEqual('1970');
|
expect(processor(0).text).toEqual('1970');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should use system date format by default', () => {
|
||||||
|
const currentFormat = systemDateFormats.fullDate;
|
||||||
|
systemDateFormats.fullDate = 'YYYY-MM';
|
||||||
|
|
||||||
|
const processor = getDisplayProcessor({
|
||||||
|
timeZone: 'utc',
|
||||||
|
field: {
|
||||||
|
type: FieldType.time,
|
||||||
|
config: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(processor(0).text).toEqual('1970-01');
|
||||||
|
|
||||||
|
systemDateFormats.fullDate = currentFormat;
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle ISO string dates', () => {
|
it('should handle ISO string dates', () => {
|
||||||
const processor = getDisplayProcessor({
|
const processor = getDisplayProcessor({
|
||||||
timeZone: 'utc',
|
timeZone: 'utc',
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
|||||||
let hasDateUnit = unit && (timeFormats[unit] || unit.startsWith('time:'));
|
let hasDateUnit = unit && (timeFormats[unit] || unit.startsWith('time:'));
|
||||||
|
|
||||||
if (field.type === FieldType.time && !hasDateUnit) {
|
if (field.type === FieldType.time && !hasDateUnit) {
|
||||||
unit = `dateTimeAsIso`;
|
unit = `dateTimeAsSystem`;
|
||||||
hasDateUnit = true;
|
hasDateUnit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
GrafanaTheme,
|
GrafanaTheme,
|
||||||
InterpolateFunction,
|
InterpolateFunction,
|
||||||
ThresholdsMode,
|
ThresholdsMode,
|
||||||
|
ScopedVars,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { locationUtil, Registry } from '../utils';
|
import { locationUtil, Registry } from '../utils';
|
||||||
import { mockStandardProperties } from '../utils/tests/mockStandardProperties';
|
import { mockStandardProperties } from '../utils/tests/mockStandardProperties';
|
||||||
@@ -64,6 +65,16 @@ export const customFieldRegistry: FieldConfigOptionsRegistry = new Registry<Fiel
|
|||||||
return [property1, property2, property3, shouldApplyFalse, ...mockStandardProperties()];
|
return [property1, property2, property3, shouldApplyFalse, ...mockStandardProperties()];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
locationUtil.initialize({
|
||||||
|
getConfig: () => {
|
||||||
|
return { appSubUrl: '/subUrl' } as any;
|
||||||
|
},
|
||||||
|
// @ts-ignore
|
||||||
|
buildParamsFromVariables: () => {},
|
||||||
|
// @ts-ignore
|
||||||
|
getTimeRangeForUrl: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
describe('Global MinMax', () => {
|
describe('Global MinMax', () => {
|
||||||
it('find global min max', () => {
|
it('find global min max', () => {
|
||||||
const f0 = new MutableDataFrame();
|
const f0 = new MutableDataFrame();
|
||||||
@@ -93,6 +104,7 @@ describe('applyFieldOverrides', () => {
|
|||||||
defaults: {
|
defaults: {
|
||||||
unit: 'xyz',
|
unit: 'xyz',
|
||||||
decimals: 2,
|
decimals: 2,
|
||||||
|
links: [{ title: 'link', url: '${__value.text}' }],
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
@@ -244,6 +256,28 @@ describe('applyFieldOverrides', () => {
|
|||||||
// Don't Automatically pick the min value
|
// Don't Automatically pick the min value
|
||||||
expect(config.min).toEqual(-20);
|
expect(config.min).toEqual(-20);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('getLinks should use applied field config', () => {
|
||||||
|
const replaceVariablesCalls: any[] = [];
|
||||||
|
|
||||||
|
const data = applyFieldOverrides({
|
||||||
|
data: [f0], // the frame
|
||||||
|
fieldConfig: src as FieldConfigSource, // defaults + overrides
|
||||||
|
replaceVariables: ((value: string, variables: ScopedVars) => {
|
||||||
|
replaceVariablesCalls.push(variables);
|
||||||
|
return value;
|
||||||
|
}) as InterpolateFunction,
|
||||||
|
getDataSourceSettingsByUid: undefined as any,
|
||||||
|
theme: (undefined as any) as GrafanaTheme,
|
||||||
|
autoMinMax: true,
|
||||||
|
fieldConfigRegistry: customFieldRegistry,
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
data.fields[1].getLinks!({ valueRowIndex: 0 });
|
||||||
|
|
||||||
|
expect(data.fields[1].config.decimals).toEqual(1);
|
||||||
|
expect(replaceVariablesCalls[0].__value.value.text).toEqual('100.0');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setFieldConfigDefaults', () => {
|
describe('setFieldConfigDefaults', () => {
|
||||||
|
|||||||
@@ -101,6 +101,9 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
|
|||||||
}
|
}
|
||||||
|
|
||||||
return options.data.map((frame, index) => {
|
return options.data.map((frame, index) => {
|
||||||
|
// Need to define this new frame here as it's passed to the getLinkSupplier function inside the fields loop
|
||||||
|
const newFrame: DataFrame = { ...frame };
|
||||||
|
|
||||||
const scopedVars: ScopedVars = {
|
const scopedVars: ScopedVars = {
|
||||||
__series: { text: 'Series', value: { name: getFrameDisplayName(frame, index) } }, // might be missing
|
__series: { text: 'Series', value: { name: getFrameDisplayName(frame, index) } }, // might be missing
|
||||||
};
|
};
|
||||||
@@ -206,7 +209,7 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
|
|||||||
|
|
||||||
// Attach data links supplier
|
// Attach data links supplier
|
||||||
f.getLinks = getLinksSupplier(
|
f.getLinks = getLinksSupplier(
|
||||||
frame,
|
newFrame,
|
||||||
f,
|
f,
|
||||||
fieldScopedVars,
|
fieldScopedVars,
|
||||||
context.replaceVariables,
|
context.replaceVariables,
|
||||||
@@ -220,10 +223,8 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
|
|||||||
return f;
|
return f;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
newFrame.fields = fields;
|
||||||
...frame,
|
return newFrame;
|
||||||
fields,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ export class PanelPlugin<TOptions = any, TFieldConfigOptions extends object = an
|
|||||||
|
|
||||||
for (const customProp of builder.getRegistry().list()) {
|
for (const customProp of builder.getRegistry().list()) {
|
||||||
customProp.isCustom = true;
|
customProp.isCustom = true;
|
||||||
customProp.category = ['Custom options'].concat(customProp.category || []);
|
customProp.category = [`${this.meta.name} options`].concat(customProp.category || []);
|
||||||
// need to do something to make the custom items not conflict with standard ones
|
// need to do something to make the custom items not conflict with standard ones
|
||||||
// problem is id (registry index) is used as property path
|
// problem is id (registry index) is used as property path
|
||||||
// so sort of need a property path on the FieldPropertyEditorItem
|
// so sort of need a property path on the FieldPropertyEditorItem
|
||||||
|
|||||||
@@ -50,7 +50,16 @@ describe('Labels as Columns', () => {
|
|||||||
name: 'A',
|
name: 'A',
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'time', type: FieldType.time, values: [1000, 2000] },
|
{ name: 'time', type: FieldType.time, values: [1000, 2000] },
|
||||||
{ name: 'Value', type: FieldType.number, values: [1, 2], labels: { location: 'inside', name: 'Request' } },
|
{
|
||||||
|
name: 'Value',
|
||||||
|
type: FieldType.number,
|
||||||
|
values: [1, 2],
|
||||||
|
labels: { location: 'inside', name: 'Request' },
|
||||||
|
config: {
|
||||||
|
displayName: 'Custom1',
|
||||||
|
displayNameFromDS: 'Custom2',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ export const labelsToFieldsTransformer: DataTransformerInfo<LabelsToFieldsOption
|
|||||||
name,
|
name,
|
||||||
config: {
|
config: {
|
||||||
...field.config,
|
...field.config,
|
||||||
|
// we need to clear thes for this transform as these can contain label names that we no longer want
|
||||||
displayName: undefined,
|
displayName: undefined,
|
||||||
|
displayNameFromDS: undefined,
|
||||||
},
|
},
|
||||||
labels: undefined,
|
labels: undefined,
|
||||||
});
|
});
|
||||||
|
|||||||
87
packages/grafana-data/src/types/annotations.ts
Normal file
87
packages/grafana-data/src/types/annotations.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { DataQuery, QueryEditorProps } from './datasource';
|
||||||
|
import { DataFrame } from './dataFrame';
|
||||||
|
import { ComponentType } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This JSON object is stored in the dashboard json model.
|
||||||
|
*/
|
||||||
|
export interface AnnotationQuery<TQuery extends DataQuery = DataQuery> {
|
||||||
|
datasource: string;
|
||||||
|
enable: boolean;
|
||||||
|
name: string;
|
||||||
|
iconColor: string;
|
||||||
|
|
||||||
|
// Standard datasource query
|
||||||
|
target?: TQuery;
|
||||||
|
|
||||||
|
// Convert a dataframe to an AnnotationEvent
|
||||||
|
mappings?: AnnotationEventMappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnnotationEvent {
|
||||||
|
id?: string;
|
||||||
|
annotation?: any;
|
||||||
|
dashboardId?: number;
|
||||||
|
panelId?: number;
|
||||||
|
userId?: number;
|
||||||
|
login?: string;
|
||||||
|
email?: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
time?: number;
|
||||||
|
timeEnd?: number;
|
||||||
|
isRegion?: boolean;
|
||||||
|
title?: string;
|
||||||
|
text?: string;
|
||||||
|
type?: string;
|
||||||
|
tags?: string[];
|
||||||
|
|
||||||
|
// Currently used to merge annotations from alerts and dashboard
|
||||||
|
source?: any; // source.type === 'dashboard'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @alpha -- any value other than `field` is experimental
|
||||||
|
*/
|
||||||
|
export enum AnnotationEventFieldSource {
|
||||||
|
Field = 'field', // Default -- find the value with a matching key
|
||||||
|
Text = 'text', // Write a constant string into the value
|
||||||
|
Skip = 'skip', // Do not include the field
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnnotationEventFieldMapping {
|
||||||
|
source?: AnnotationEventFieldSource; // defautls to 'field'
|
||||||
|
value?: string;
|
||||||
|
regex?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AnnotationEventMappings = Partial<Record<keyof AnnotationEvent, AnnotationEventFieldMapping>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since Grafana 7.2
|
||||||
|
*
|
||||||
|
* This offers a generic approach to annotation processing
|
||||||
|
*/
|
||||||
|
export interface AnnotationSupport<TQuery extends DataQuery = DataQuery, TAnno = AnnotationQuery<TQuery>> {
|
||||||
|
/**
|
||||||
|
* This hook lets you manipulate any existing stored values before running them though the processor.
|
||||||
|
* This is particularly helpful when dealing with migrating old formats. ie query as a string vs object
|
||||||
|
*/
|
||||||
|
prepareAnnotation?(json: any): TAnno;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the stored JSON model to a standard datasource query object.
|
||||||
|
* This query will be executed in the datasource and the results converted into events.
|
||||||
|
* Returning an undefined result will quietly skip query execution
|
||||||
|
*/
|
||||||
|
prepareQuery?(anno: TAnno): TQuery | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the standard frame > event processing is insufficient, this allows explicit control of the mappings
|
||||||
|
*/
|
||||||
|
processEvents?(anno: TAnno, data: DataFrame[]): AnnotationEvent[] | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a custom QueryEditor for the annotation page. If not specified, the standard one will be used
|
||||||
|
*/
|
||||||
|
QueryEditor?: ComponentType<QueryEditorProps<any, TQuery>>;
|
||||||
|
}
|
||||||
@@ -42,7 +42,6 @@ export interface FeatureToggles {
|
|||||||
meta: boolean;
|
meta: boolean;
|
||||||
datasourceInsights: boolean;
|
datasourceInsights: boolean;
|
||||||
reportGrid: boolean;
|
reportGrid: boolean;
|
||||||
standaloneAlerts: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -132,27 +132,6 @@ export enum NullValueMode {
|
|||||||
AsZero = 'null as zero',
|
AsZero = 'null as zero',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnnotationEvent {
|
|
||||||
id?: string;
|
|
||||||
annotation?: any;
|
|
||||||
dashboardId?: number;
|
|
||||||
panelId?: number;
|
|
||||||
userId?: number;
|
|
||||||
login?: string;
|
|
||||||
email?: string;
|
|
||||||
avatarUrl?: string;
|
|
||||||
time?: number;
|
|
||||||
timeEnd?: number;
|
|
||||||
isRegion?: boolean;
|
|
||||||
title?: string;
|
|
||||||
text?: string;
|
|
||||||
type?: string;
|
|
||||||
tags?: string[];
|
|
||||||
|
|
||||||
// Currently used to merge annotations from alerts and dashboard
|
|
||||||
source?: any; // source.type === 'dashboard'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes and API for exposing panel specific data configurations.
|
* Describes and API for exposing panel specific data configurations.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { ComponentType } from 'react';
|
|||||||
import { GrafanaPlugin, PluginMeta } from './plugin';
|
import { GrafanaPlugin, PluginMeta } from './plugin';
|
||||||
import { PanelData } from './panel';
|
import { PanelData } from './panel';
|
||||||
import { LogRowModel } from './logs';
|
import { LogRowModel } from './logs';
|
||||||
import { AnnotationEvent, KeyValue, LoadingState, TableData, TimeSeries } from './data';
|
import { AnnotationEvent, AnnotationSupport } from './annotations';
|
||||||
|
import { KeyValue, LoadingState, TableData, TimeSeries } from './data';
|
||||||
import { DataFrame, DataFrameDTO } from './dataFrame';
|
import { DataFrame, DataFrameDTO } from './dataFrame';
|
||||||
import { RawTimeRange, TimeRange } from './time';
|
import { RawTimeRange, TimeRange } from './time';
|
||||||
import { ScopedVars } from './ScopedVars';
|
import { ScopedVars } from './ScopedVars';
|
||||||
@@ -155,8 +156,7 @@ export interface DataSourceConstructor<
|
|||||||
*/
|
*/
|
||||||
export abstract class DataSourceApi<
|
export abstract class DataSourceApi<
|
||||||
TQuery extends DataQuery = DataQuery,
|
TQuery extends DataQuery = DataQuery,
|
||||||
TOptions extends DataSourceJsonData = DataSourceJsonData,
|
TOptions extends DataSourceJsonData = DataSourceJsonData
|
||||||
TAnno = TQuery // defatult to direct query
|
|
||||||
> {
|
> {
|
||||||
/**
|
/**
|
||||||
* Set in constructor
|
* Set in constructor
|
||||||
@@ -267,13 +267,23 @@ export abstract class DataSourceApi<
|
|||||||
|
|
||||||
showContextToggle?(row?: LogRowModel): boolean;
|
showContextToggle?(row?: LogRowModel): boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* Can be optionally implemented to allow datasource to be a source of annotations for dashboard. To be visible
|
|
||||||
* in the annotation editor `annotations` capability also needs to be enabled in plugin.json.
|
|
||||||
*/
|
|
||||||
annotationQuery?(options: AnnotationQueryRequest<TAnno>): Promise<AnnotationEvent[]>;
|
|
||||||
|
|
||||||
interpolateVariablesInQueries?(queries: TQuery[], scopedVars: ScopedVars | {}): TQuery[];
|
interpolateVariablesInQueries?(queries: TQuery[], scopedVars: ScopedVars | {}): TQuery[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An annotation processor allows explict control for how annotations are managed.
|
||||||
|
*
|
||||||
|
* It is only necessary to configure an annotation processor if the default behavior is not desirable
|
||||||
|
*/
|
||||||
|
annotations?: AnnotationSupport<TQuery>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be optionally implemented to allow datasource to be a source of annotations for dashboard.
|
||||||
|
* This function will only be called if an angular {@link AnnotationsQueryCtrl} is configured and
|
||||||
|
* the {@link annotations} is undefined
|
||||||
|
*
|
||||||
|
* @deprecated -- prefer using {@link AnnotationSupport}
|
||||||
|
*/
|
||||||
|
annotationQuery?(options: AnnotationQueryRequest<TQuery>): Promise<AnnotationEvent[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetadataInspectorProps<
|
export interface MetadataInspectorProps<
|
||||||
@@ -473,12 +483,6 @@ export interface MetricFindValue {
|
|||||||
expandable?: boolean;
|
expandable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseAnnotationQuery {
|
|
||||||
datasource: string;
|
|
||||||
enable: boolean;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DataSourceJsonData {
|
export interface DataSourceJsonData {
|
||||||
authType?: string;
|
authType?: string;
|
||||||
defaultRegion?: string;
|
defaultRegion?: string;
|
||||||
@@ -547,20 +551,19 @@ export interface DataSourceSelectItem {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Options passed to the datasource.annotationQuery method. See docs/plugins/developing/datasource.md
|
* Options passed to the datasource.annotationQuery method. See docs/plugins/developing/datasource.md
|
||||||
|
*
|
||||||
|
* @deprecated -- use {@link AnnotationSupport}
|
||||||
*/
|
*/
|
||||||
export interface AnnotationQueryRequest<TAnno = {}> {
|
export interface AnnotationQueryRequest<MoreOptions = {}> {
|
||||||
range: TimeRange;
|
range: TimeRange;
|
||||||
rangeRaw: RawTimeRange;
|
rangeRaw: RawTimeRange;
|
||||||
interval: string;
|
|
||||||
intervalMs: number;
|
|
||||||
maxDataPoints?: number;
|
|
||||||
app: CoreApp | string;
|
|
||||||
|
|
||||||
// Should be DataModel but cannot import that here from the main app. Needs to be moved to package first.
|
// Should be DataModel but cannot import that here from the main app. Needs to be moved to package first.
|
||||||
dashboard: any;
|
dashboard: any;
|
||||||
|
annotation: {
|
||||||
// The annotation query and common properties
|
datasource: string;
|
||||||
annotation: BaseAnnotationQuery & TAnno;
|
enable: boolean;
|
||||||
|
name: string;
|
||||||
|
} & MoreOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HistoryItem<TQuery extends DataQuery = DataQuery> {
|
export interface HistoryItem<TQuery extends DataQuery = DataQuery> {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export interface FieldOverrideContext extends StandardEditorContext<any> {
|
|||||||
}
|
}
|
||||||
export interface FieldConfigEditorProps<TValue, TSettings>
|
export interface FieldConfigEditorProps<TValue, TSettings>
|
||||||
extends Omit<StandardEditorProps<TValue, TSettings>, 'item'> {
|
extends Omit<StandardEditorProps<TValue, TSettings>, 'item'> {
|
||||||
item: FieldConfigPropertyItem<TValue, TSettings>; // The property info
|
item: FieldConfigPropertyItem<any, TValue, TSettings>; // The property info
|
||||||
value: TValue;
|
value: TValue;
|
||||||
context: FieldOverrideContext;
|
context: FieldOverrideContext;
|
||||||
onChange: (value?: TValue) => void;
|
onChange: (value?: TValue) => void;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export * from './data';
|
export * from './data';
|
||||||
export * from './dataFrame';
|
export * from './dataFrame';
|
||||||
export * from './dataLink';
|
export * from './dataLink';
|
||||||
|
export * from './annotations';
|
||||||
export * from './logs';
|
export * from './logs';
|
||||||
export * from './navModel';
|
export * from './navModel';
|
||||||
export * from './select';
|
export * from './select';
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ import {
|
|||||||
toNanoSeconds,
|
toNanoSeconds,
|
||||||
toSeconds,
|
toSeconds,
|
||||||
toTimeTicks,
|
toTimeTicks,
|
||||||
|
dateTimeSystemFormatter,
|
||||||
} from './dateTimeFormatters';
|
} from './dateTimeFormatters';
|
||||||
import { toHex, sci, toHex0x, toPercent, toPercentUnit } from './arithmeticFormatters';
|
import { toHex, sci, toHex0x, toPercent, toPercentUnit } from './arithmeticFormatters';
|
||||||
import { binarySIPrefix, currency, decimalSIPrefix } from './symbolFormatters';
|
import { binaryPrefix, currency, SIPrefix } from './symbolFormatters';
|
||||||
|
|
||||||
export const getCategories = (): ValueFormatCategory[] => [
|
export const getCategories = (): ValueFormatCategory[] => [
|
||||||
{
|
{
|
||||||
@@ -75,14 +76,14 @@ export const getCategories = (): ValueFormatCategory[] => [
|
|||||||
{
|
{
|
||||||
name: 'Computation',
|
name: 'Computation',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'FLOP/s', id: 'flops', fn: decimalSIPrefix('FLOP/s') },
|
{ name: 'FLOP/s', id: 'flops', fn: SIPrefix('FLOP/s') },
|
||||||
{ name: 'MFLOP/s', id: 'mflops', fn: decimalSIPrefix('FLOP/s', 2) },
|
{ name: 'MFLOP/s', id: 'mflops', fn: SIPrefix('FLOP/s', 2) },
|
||||||
{ name: 'GFLOP/s', id: 'gflops', fn: decimalSIPrefix('FLOP/s', 3) },
|
{ name: 'GFLOP/s', id: 'gflops', fn: SIPrefix('FLOP/s', 3) },
|
||||||
{ name: 'TFLOP/s', id: 'tflops', fn: decimalSIPrefix('FLOP/s', 4) },
|
{ name: 'TFLOP/s', id: 'tflops', fn: SIPrefix('FLOP/s', 4) },
|
||||||
{ name: 'PFLOP/s', id: 'pflops', fn: decimalSIPrefix('FLOP/s', 5) },
|
{ name: 'PFLOP/s', id: 'pflops', fn: SIPrefix('FLOP/s', 5) },
|
||||||
{ name: 'EFLOP/s', id: 'eflops', fn: decimalSIPrefix('FLOP/s', 6) },
|
{ name: 'EFLOP/s', id: 'eflops', fn: SIPrefix('FLOP/s', 6) },
|
||||||
{ name: 'ZFLOP/s', id: 'zflops', fn: decimalSIPrefix('FLOP/s', 7) },
|
{ name: 'ZFLOP/s', id: 'zflops', fn: SIPrefix('FLOP/s', 7) },
|
||||||
{ name: 'YFLOP/s', id: 'yflops', fn: decimalSIPrefix('FLOP/s', 8) },
|
{ name: 'YFLOP/s', id: 'yflops', fn: SIPrefix('FLOP/s', 8) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -128,45 +129,52 @@ export const getCategories = (): ValueFormatCategory[] => [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Data (IEC)',
|
name: 'Data',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'bits(IEC)', id: 'bits', fn: binarySIPrefix('b') },
|
{ name: 'bytes(IEC)', id: 'bytes', fn: binaryPrefix('B') },
|
||||||
{ name: 'bytes(IEC)', id: 'bytes', fn: binarySIPrefix('B') },
|
{ name: 'bytes(SI)', id: 'decbytes', fn: SIPrefix('B') },
|
||||||
{ name: 'kibibytes', id: 'kbytes', fn: binarySIPrefix('B', 1) },
|
{ name: 'bits(IEC)', id: 'bits', fn: binaryPrefix('b') },
|
||||||
{ name: 'mebibytes', id: 'mbytes', fn: binarySIPrefix('B', 2) },
|
{ name: 'bits(SI)', id: 'decbits', fn: SIPrefix('b') },
|
||||||
{ name: 'gibibytes', id: 'gbytes', fn: binarySIPrefix('B', 3) },
|
{ name: 'kibibytes', id: 'kbytes', fn: binaryPrefix('B', 1) },
|
||||||
{ name: 'tebibytes', id: 'tbytes', fn: binarySIPrefix('B', 4) },
|
{ name: 'kilobytes', id: 'deckbytes', fn: SIPrefix('B', 1) },
|
||||||
{ name: 'pebibytes', id: 'pbytes', fn: binarySIPrefix('B', 5) },
|
{ name: 'mebibytes', id: 'mbytes', fn: binaryPrefix('B', 2) },
|
||||||
],
|
{ name: 'megabytes', id: 'decmbytes', fn: SIPrefix('B', 2) },
|
||||||
},
|
{ name: 'gibibytes', id: 'gbytes', fn: binaryPrefix('B', 3) },
|
||||||
{
|
{ name: 'gigabytes', id: 'decgbytes', fn: SIPrefix('B', 3) },
|
||||||
name: 'Data (metric)',
|
{ name: 'tebibytes', id: 'tbytes', fn: binaryPrefix('B', 4) },
|
||||||
formats: [
|
{ name: 'terabytes', id: 'dectbytes', fn: SIPrefix('B', 4) },
|
||||||
{ name: 'bits(Metric)', id: 'decbits', fn: decimalSIPrefix('b') },
|
{ name: 'pebibytes', id: 'pbytes', fn: binaryPrefix('B', 5) },
|
||||||
{ name: 'bytes(Metric)', id: 'decbytes', fn: decimalSIPrefix('B') },
|
{ name: 'petabytes', id: 'decpbytes', fn: SIPrefix('B', 5) },
|
||||||
{ name: 'kilobytes', id: 'deckbytes', fn: decimalSIPrefix('B', 1) },
|
|
||||||
{ name: 'megabytes', id: 'decmbytes', fn: decimalSIPrefix('B', 2) },
|
|
||||||
{ name: 'gigabytes', id: 'decgbytes', fn: decimalSIPrefix('B', 3) },
|
|
||||||
{ name: 'terabytes', id: 'dectbytes', fn: decimalSIPrefix('B', 4) },
|
|
||||||
{ name: 'petabytes', id: 'decpbytes', fn: decimalSIPrefix('B', 5) },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Data rate',
|
name: 'Data rate',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'packets/sec', id: 'pps', fn: decimalSIPrefix('pps') },
|
{ name: 'packets/sec', id: 'pps', fn: SIPrefix('p/s') },
|
||||||
{ name: 'bits/sec', id: 'bps', fn: decimalSIPrefix('bps') },
|
{ name: 'bytes/sec(IEC)', id: 'binBps', fn: binaryPrefix('B/s') },
|
||||||
{ name: 'bytes/sec', id: 'Bps', fn: decimalSIPrefix('B/s') },
|
{ name: 'bytes/sec(SI)', id: 'Bps', fn: SIPrefix('B/s') },
|
||||||
{ name: 'kilobytes/sec', id: 'KBs', fn: decimalSIPrefix('B/s', 1) },
|
{ name: 'bits/sec(IEC)', id: 'binbps', fn: binaryPrefix('b/s') },
|
||||||
{ name: 'kilobits/sec', id: 'Kbits', fn: decimalSIPrefix('bps', 1) },
|
{ name: 'bits/sec(SI)', id: 'bps', fn: SIPrefix('b/s') },
|
||||||
{ name: 'megabytes/sec', id: 'MBs', fn: decimalSIPrefix('B/s', 2) },
|
{ name: 'kibibytes/sec', id: 'KiBs', fn: binaryPrefix('B/s', 1) },
|
||||||
{ name: 'megabits/sec', id: 'Mbits', fn: decimalSIPrefix('bps', 2) },
|
{ name: 'kibibits/sec', id: 'Kibits', fn: binaryPrefix('b/s', 1) },
|
||||||
{ name: 'gigabytes/sec', id: 'GBs', fn: decimalSIPrefix('B/s', 3) },
|
{ name: 'kilobytes/sec', id: 'KBs', fn: SIPrefix('B/s', 1) },
|
||||||
{ name: 'gigabits/sec', id: 'Gbits', fn: decimalSIPrefix('bps', 3) },
|
{ name: 'kilobits/sec', id: 'Kbits', fn: SIPrefix('b/s', 1) },
|
||||||
{ name: 'terabytes/sec', id: 'TBs', fn: decimalSIPrefix('B/s', 4) },
|
{ name: 'mibibytes/sec', id: 'MiBs', fn: binaryPrefix('B/s', 2) },
|
||||||
{ name: 'terabits/sec', id: 'Tbits', fn: decimalSIPrefix('bps', 4) },
|
{ name: 'mibibits/sec', id: 'Mibits', fn: binaryPrefix('b/s', 2) },
|
||||||
{ name: 'petabytes/sec', id: 'PBs', fn: decimalSIPrefix('B/s', 5) },
|
{ name: 'megabytes/sec', id: 'MBs', fn: SIPrefix('B/s', 2) },
|
||||||
{ name: 'petabits/sec', id: 'Pbits', fn: decimalSIPrefix('bps', 5) },
|
{ name: 'megabits/sec', id: 'Mbits', fn: SIPrefix('b/s', 2) },
|
||||||
|
{ name: 'gibibytes/sec', id: 'GiBs', fn: binaryPrefix('B/s', 3) },
|
||||||
|
{ name: 'gibibits/sec', id: 'Gibits', fn: binaryPrefix('b/s', 3) },
|
||||||
|
{ name: 'gigabytes/sec', id: 'GBs', fn: SIPrefix('B/s', 3) },
|
||||||
|
{ name: 'gigabits/sec', id: 'Gbits', fn: SIPrefix('b/s', 3) },
|
||||||
|
{ name: 'tebibytes/sec', id: 'TiBs', fn: binaryPrefix('B/s', 4) },
|
||||||
|
{ name: 'tebibits/sec', id: 'Tibits', fn: binaryPrefix('b/s', 4) },
|
||||||
|
{ name: 'terabytes/sec', id: 'TBs', fn: SIPrefix('B/s', 4) },
|
||||||
|
{ name: 'terabits/sec', id: 'Tbits', fn: SIPrefix('b/s', 4) },
|
||||||
|
{ name: 'petibytes/sec', id: 'PiBs', fn: binaryPrefix('B/s', 5) },
|
||||||
|
{ name: 'petibits/sec', id: 'Pibits', fn: binaryPrefix('b/s', 5) },
|
||||||
|
{ name: 'petabytes/sec', id: 'PBs', fn: SIPrefix('B/s', 5) },
|
||||||
|
{ name: 'petabits/sec', id: 'Pbits', fn: SIPrefix('b/s', 5) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -177,50 +185,51 @@ export const getCategories = (): ValueFormatCategory[] => [
|
|||||||
{ name: 'Datetime US', id: 'dateTimeAsUS', fn: dateTimeAsUS },
|
{ name: 'Datetime US', id: 'dateTimeAsUS', fn: dateTimeAsUS },
|
||||||
{ name: 'Datetime US (No date if today)', id: 'dateTimeAsUSNoDateIfToday', fn: dateTimeAsUSNoDateIfToday },
|
{ name: 'Datetime US (No date if today)', id: 'dateTimeAsUSNoDateIfToday', fn: dateTimeAsUSNoDateIfToday },
|
||||||
{ name: 'Datetime local', id: 'dateTimeAsLocal', fn: getDateTimeAsLocalFormat() },
|
{ name: 'Datetime local', id: 'dateTimeAsLocal', fn: getDateTimeAsLocalFormat() },
|
||||||
|
{ name: 'Datetime default', id: 'dateTimeAsSystem', fn: dateTimeSystemFormatter },
|
||||||
{ name: 'From Now', id: 'dateTimeFromNow', fn: dateTimeFromNow },
|
{ name: 'From Now', id: 'dateTimeFromNow', fn: dateTimeFromNow },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Energy',
|
name: 'Energy',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'Watt (W)', id: 'watt', fn: decimalSIPrefix('W') },
|
{ name: 'Watt (W)', id: 'watt', fn: SIPrefix('W') },
|
||||||
{ name: 'Kilowatt (kW)', id: 'kwatt', fn: decimalSIPrefix('W', 1) },
|
{ name: 'Kilowatt (kW)', id: 'kwatt', fn: SIPrefix('W', 1) },
|
||||||
{ name: 'Megawatt (MW)', id: 'megwatt', fn: decimalSIPrefix('W', 2) },
|
{ name: 'Megawatt (MW)', id: 'megwatt', fn: SIPrefix('W', 2) },
|
||||||
{ name: 'Gigawatt (GW)', id: 'gwatt', fn: decimalSIPrefix('W', 3) },
|
{ name: 'Gigawatt (GW)', id: 'gwatt', fn: SIPrefix('W', 3) },
|
||||||
{ name: 'Milliwatt (mW)', id: 'mwatt', fn: decimalSIPrefix('W', -1) },
|
{ name: 'Milliwatt (mW)', id: 'mwatt', fn: SIPrefix('W', -1) },
|
||||||
{ name: 'Watt per square meter (W/m²)', id: 'Wm2', fn: toFixedUnit('W/m²') },
|
{ name: 'Watt per square meter (W/m²)', id: 'Wm2', fn: toFixedUnit('W/m²') },
|
||||||
{ name: 'Volt-ampere (VA)', id: 'voltamp', fn: decimalSIPrefix('VA') },
|
{ name: 'Volt-ampere (VA)', id: 'voltamp', fn: SIPrefix('VA') },
|
||||||
{ name: 'Kilovolt-ampere (kVA)', id: 'kvoltamp', fn: decimalSIPrefix('VA', 1) },
|
{ name: 'Kilovolt-ampere (kVA)', id: 'kvoltamp', fn: SIPrefix('VA', 1) },
|
||||||
{ name: 'Volt-ampere reactive (var)', id: 'voltampreact', fn: decimalSIPrefix('var') },
|
{ name: 'Volt-ampere reactive (var)', id: 'voltampreact', fn: SIPrefix('var') },
|
||||||
{ name: 'Kilovolt-ampere reactive (kvar)', id: 'kvoltampreact', fn: decimalSIPrefix('var', 1) },
|
{ name: 'Kilovolt-ampere reactive (kvar)', id: 'kvoltampreact', fn: SIPrefix('var', 1) },
|
||||||
{ name: 'Watt-hour (Wh)', id: 'watth', fn: decimalSIPrefix('Wh') },
|
{ name: 'Watt-hour (Wh)', id: 'watth', fn: SIPrefix('Wh') },
|
||||||
{ name: 'Watt-hour per Kilogram (Wh/kg)', id: 'watthperkg', fn: decimalSIPrefix('Wh/kg') },
|
{ name: 'Watt-hour per Kilogram (Wh/kg)', id: 'watthperkg', fn: SIPrefix('Wh/kg') },
|
||||||
{ name: 'Kilowatt-hour (kWh)', id: 'kwatth', fn: decimalSIPrefix('Wh', 1) },
|
{ name: 'Kilowatt-hour (kWh)', id: 'kwatth', fn: SIPrefix('Wh', 1) },
|
||||||
{ name: 'Kilowatt-min (kWm)', id: 'kwattm', fn: decimalSIPrefix('W-Min', 1) },
|
{ name: 'Kilowatt-min (kWm)', id: 'kwattm', fn: SIPrefix('W-Min', 1) },
|
||||||
{ name: 'Ampere-hour (Ah)', id: 'amph', fn: decimalSIPrefix('Ah') },
|
{ name: 'Ampere-hour (Ah)', id: 'amph', fn: SIPrefix('Ah') },
|
||||||
{ name: 'Kiloampere-hour (kAh)', id: 'kamph', fn: decimalSIPrefix('Ah', 1) },
|
{ name: 'Kiloampere-hour (kAh)', id: 'kamph', fn: SIPrefix('Ah', 1) },
|
||||||
{ name: 'Milliampere-hour (mAh)', id: 'mamph', fn: decimalSIPrefix('Ah', -1) },
|
{ name: 'Milliampere-hour (mAh)', id: 'mamph', fn: SIPrefix('Ah', -1) },
|
||||||
{ name: 'Joule (J)', id: 'joule', fn: decimalSIPrefix('J') },
|
{ name: 'Joule (J)', id: 'joule', fn: SIPrefix('J') },
|
||||||
{ name: 'Electron volt (eV)', id: 'ev', fn: decimalSIPrefix('eV') },
|
{ name: 'Electron volt (eV)', id: 'ev', fn: SIPrefix('eV') },
|
||||||
{ name: 'Ampere (A)', id: 'amp', fn: decimalSIPrefix('A') },
|
{ name: 'Ampere (A)', id: 'amp', fn: SIPrefix('A') },
|
||||||
{ name: 'Kiloampere (kA)', id: 'kamp', fn: decimalSIPrefix('A', 1) },
|
{ name: 'Kiloampere (kA)', id: 'kamp', fn: SIPrefix('A', 1) },
|
||||||
{ name: 'Milliampere (mA)', id: 'mamp', fn: decimalSIPrefix('A', -1) },
|
{ name: 'Milliampere (mA)', id: 'mamp', fn: SIPrefix('A', -1) },
|
||||||
{ name: 'Volt (V)', id: 'volt', fn: decimalSIPrefix('V') },
|
{ name: 'Volt (V)', id: 'volt', fn: SIPrefix('V') },
|
||||||
{ name: 'Kilovolt (kV)', id: 'kvolt', fn: decimalSIPrefix('V', 1) },
|
{ name: 'Kilovolt (kV)', id: 'kvolt', fn: SIPrefix('V', 1) },
|
||||||
{ name: 'Millivolt (mV)', id: 'mvolt', fn: decimalSIPrefix('V', -1) },
|
{ name: 'Millivolt (mV)', id: 'mvolt', fn: SIPrefix('V', -1) },
|
||||||
{ name: 'Decibel-milliwatt (dBm)', id: 'dBm', fn: decimalSIPrefix('dBm') },
|
{ name: 'Decibel-milliwatt (dBm)', id: 'dBm', fn: SIPrefix('dBm') },
|
||||||
{ name: 'Ohm (Ω)', id: 'ohm', fn: decimalSIPrefix('Ω') },
|
{ name: 'Ohm (Ω)', id: 'ohm', fn: SIPrefix('Ω') },
|
||||||
{ name: 'Kiloohm (kΩ)', id: 'kohm', fn: decimalSIPrefix('Ω', 1) },
|
{ name: 'Kiloohm (kΩ)', id: 'kohm', fn: SIPrefix('Ω', 1) },
|
||||||
{ name: 'Megaohm (MΩ)', id: 'Mohm', fn: decimalSIPrefix('Ω', 2) },
|
{ name: 'Megaohm (MΩ)', id: 'Mohm', fn: SIPrefix('Ω', 2) },
|
||||||
{ name: 'Farad (F)', id: 'farad', fn: decimalSIPrefix('F') },
|
{ name: 'Farad (F)', id: 'farad', fn: SIPrefix('F') },
|
||||||
{ name: 'Microfarad (µF)', id: 'µfarad', fn: decimalSIPrefix('F', -2) },
|
{ name: 'Microfarad (µF)', id: 'µfarad', fn: SIPrefix('F', -2) },
|
||||||
{ name: 'Nanofarad (nF)', id: 'nfarad', fn: decimalSIPrefix('F', -3) },
|
{ name: 'Nanofarad (nF)', id: 'nfarad', fn: SIPrefix('F', -3) },
|
||||||
{ name: 'Picofarad (pF)', id: 'pfarad', fn: decimalSIPrefix('F', -4) },
|
{ name: 'Picofarad (pF)', id: 'pfarad', fn: SIPrefix('F', -4) },
|
||||||
{ name: 'Femtofarad (fF)', id: 'ffarad', fn: decimalSIPrefix('F', -5) },
|
{ name: 'Femtofarad (fF)', id: 'ffarad', fn: SIPrefix('F', -5) },
|
||||||
{ name: 'Henry (H)', id: 'henry', fn: decimalSIPrefix('H') },
|
{ name: 'Henry (H)', id: 'henry', fn: SIPrefix('H') },
|
||||||
{ name: 'Millihenry (mH)', id: 'mhenry', fn: decimalSIPrefix('H', -1) },
|
{ name: 'Millihenry (mH)', id: 'mhenry', fn: SIPrefix('H', -1) },
|
||||||
{ name: 'Microhenry (µH)', id: 'µhenry', fn: decimalSIPrefix('H', -2) },
|
{ name: 'Microhenry (µH)', id: 'µhenry', fn: SIPrefix('H', -2) },
|
||||||
{ name: 'Lumens (Lm)', id: 'lumens', fn: decimalSIPrefix('Lm') },
|
{ name: 'Lumens (Lm)', id: 'lumens', fn: SIPrefix('Lm') },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -239,50 +248,50 @@ export const getCategories = (): ValueFormatCategory[] => [
|
|||||||
{
|
{
|
||||||
name: 'Force',
|
name: 'Force',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'Newton-meters (Nm)', id: 'forceNm', fn: decimalSIPrefix('Nm') },
|
{ name: 'Newton-meters (Nm)', id: 'forceNm', fn: SIPrefix('Nm') },
|
||||||
{ name: 'Kilonewton-meters (kNm)', id: 'forcekNm', fn: decimalSIPrefix('Nm', 1) },
|
{ name: 'Kilonewton-meters (kNm)', id: 'forcekNm', fn: SIPrefix('Nm', 1) },
|
||||||
{ name: 'Newtons (N)', id: 'forceN', fn: decimalSIPrefix('N') },
|
{ name: 'Newtons (N)', id: 'forceN', fn: SIPrefix('N') },
|
||||||
{ name: 'Kilonewtons (kN)', id: 'forcekN', fn: decimalSIPrefix('N', 1) },
|
{ name: 'Kilonewtons (kN)', id: 'forcekN', fn: SIPrefix('N', 1) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Hash rate',
|
name: 'Hash rate',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'hashes/sec', id: 'Hs', fn: decimalSIPrefix('H/s') },
|
{ name: 'hashes/sec', id: 'Hs', fn: SIPrefix('H/s') },
|
||||||
{ name: 'kilohashes/sec', id: 'KHs', fn: decimalSIPrefix('H/s', 1) },
|
{ name: 'kilohashes/sec', id: 'KHs', fn: SIPrefix('H/s', 1) },
|
||||||
{ name: 'megahashes/sec', id: 'MHs', fn: decimalSIPrefix('H/s', 2) },
|
{ name: 'megahashes/sec', id: 'MHs', fn: SIPrefix('H/s', 2) },
|
||||||
{ name: 'gigahashes/sec', id: 'GHs', fn: decimalSIPrefix('H/s', 3) },
|
{ name: 'gigahashes/sec', id: 'GHs', fn: SIPrefix('H/s', 3) },
|
||||||
{ name: 'terahashes/sec', id: 'THs', fn: decimalSIPrefix('H/s', 4) },
|
{ name: 'terahashes/sec', id: 'THs', fn: SIPrefix('H/s', 4) },
|
||||||
{ name: 'petahashes/sec', id: 'PHs', fn: decimalSIPrefix('H/s', 5) },
|
{ name: 'petahashes/sec', id: 'PHs', fn: SIPrefix('H/s', 5) },
|
||||||
{ name: 'exahashes/sec', id: 'EHs', fn: decimalSIPrefix('H/s', 6) },
|
{ name: 'exahashes/sec', id: 'EHs', fn: SIPrefix('H/s', 6) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Mass',
|
name: 'Mass',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'milligram (mg)', id: 'massmg', fn: decimalSIPrefix('g', -1) },
|
{ name: 'milligram (mg)', id: 'massmg', fn: SIPrefix('g', -1) },
|
||||||
{ name: 'gram (g)', id: 'massg', fn: decimalSIPrefix('g') },
|
{ name: 'gram (g)', id: 'massg', fn: SIPrefix('g') },
|
||||||
{ name: 'kilogram (kg)', id: 'masskg', fn: decimalSIPrefix('g', 1) },
|
{ name: 'kilogram (kg)', id: 'masskg', fn: SIPrefix('g', 1) },
|
||||||
{ name: 'metric ton (t)', id: 'masst', fn: toFixedUnit('t') },
|
{ name: 'metric ton (t)', id: 'masst', fn: toFixedUnit('t') },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Length',
|
name: 'Length',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'millimeter (mm)', id: 'lengthmm', fn: decimalSIPrefix('m', -1) },
|
{ name: 'millimeter (mm)', id: 'lengthmm', fn: SIPrefix('m', -1) },
|
||||||
{ name: 'feet (ft)', id: 'lengthft', fn: toFixedUnit('ft') },
|
{ name: 'feet (ft)', id: 'lengthft', fn: toFixedUnit('ft') },
|
||||||
{ name: 'meter (m)', id: 'lengthm', fn: decimalSIPrefix('m') },
|
{ name: 'meter (m)', id: 'lengthm', fn: SIPrefix('m') },
|
||||||
{ name: 'kilometer (km)', id: 'lengthkm', fn: decimalSIPrefix('m', 1) },
|
{ name: 'kilometer (km)', id: 'lengthkm', fn: SIPrefix('m', 1) },
|
||||||
{ name: 'mile (mi)', id: 'lengthmi', fn: toFixedUnit('mi') },
|
{ name: 'mile (mi)', id: 'lengthmi', fn: toFixedUnit('mi') },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Pressure',
|
name: 'Pressure',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'Millibars', id: 'pressurembar', fn: decimalSIPrefix('bar', -1) },
|
{ name: 'Millibars', id: 'pressurembar', fn: SIPrefix('bar', -1) },
|
||||||
{ name: 'Bars', id: 'pressurebar', fn: decimalSIPrefix('bar') },
|
{ name: 'Bars', id: 'pressurebar', fn: SIPrefix('bar') },
|
||||||
{ name: 'Kilobars', id: 'pressurekbar', fn: decimalSIPrefix('bar', 1) },
|
{ name: 'Kilobars', id: 'pressurekbar', fn: SIPrefix('bar', 1) },
|
||||||
{ name: 'Pascals', id: 'pressurepa', fn: decimalSIPrefix('Pa') },
|
{ name: 'Pascals', id: 'pressurepa', fn: SIPrefix('Pa') },
|
||||||
{ name: 'Hectopascals', id: 'pressurehpa', fn: toFixedUnit('hPa') },
|
{ name: 'Hectopascals', id: 'pressurehpa', fn: toFixedUnit('hPa') },
|
||||||
{ name: 'Kilopascals', id: 'pressurekpa', fn: toFixedUnit('kPa') },
|
{ name: 'Kilopascals', id: 'pressurekpa', fn: toFixedUnit('kPa') },
|
||||||
{ name: 'Inches of mercury', id: 'pressurehg', fn: toFixedUnit('"Hg') },
|
{ name: 'Inches of mercury', id: 'pressurehg', fn: toFixedUnit('"Hg') },
|
||||||
@@ -292,26 +301,26 @@ export const getCategories = (): ValueFormatCategory[] => [
|
|||||||
{
|
{
|
||||||
name: 'Radiation',
|
name: 'Radiation',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'Becquerel (Bq)', id: 'radbq', fn: decimalSIPrefix('Bq') },
|
{ name: 'Becquerel (Bq)', id: 'radbq', fn: SIPrefix('Bq') },
|
||||||
{ name: 'curie (Ci)', id: 'radci', fn: decimalSIPrefix('Ci') },
|
{ name: 'curie (Ci)', id: 'radci', fn: SIPrefix('Ci') },
|
||||||
{ name: 'Gray (Gy)', id: 'radgy', fn: decimalSIPrefix('Gy') },
|
{ name: 'Gray (Gy)', id: 'radgy', fn: SIPrefix('Gy') },
|
||||||
{ name: 'rad', id: 'radrad', fn: decimalSIPrefix('rad') },
|
{ name: 'rad', id: 'radrad', fn: SIPrefix('rad') },
|
||||||
{ name: 'Sievert (Sv)', id: 'radsv', fn: decimalSIPrefix('Sv') },
|
{ name: 'Sievert (Sv)', id: 'radsv', fn: SIPrefix('Sv') },
|
||||||
{ name: 'milliSievert (mSv)', id: 'radmsv', fn: decimalSIPrefix('Sv', -1) },
|
{ name: 'milliSievert (mSv)', id: 'radmsv', fn: SIPrefix('Sv', -1) },
|
||||||
{ name: 'microSievert (µSv)', id: 'radusv', fn: decimalSIPrefix('Sv', -2) },
|
{ name: 'microSievert (µSv)', id: 'radusv', fn: SIPrefix('Sv', -2) },
|
||||||
{ name: 'rem', id: 'radrem', fn: decimalSIPrefix('rem') },
|
{ name: 'rem', id: 'radrem', fn: SIPrefix('rem') },
|
||||||
{ name: 'Exposure (C/kg)', id: 'radexpckg', fn: decimalSIPrefix('C/kg') },
|
{ name: 'Exposure (C/kg)', id: 'radexpckg', fn: SIPrefix('C/kg') },
|
||||||
{ name: 'roentgen (R)', id: 'radr', fn: decimalSIPrefix('R') },
|
{ name: 'roentgen (R)', id: 'radr', fn: SIPrefix('R') },
|
||||||
{ name: 'Sievert/hour (Sv/h)', id: 'radsvh', fn: decimalSIPrefix('Sv/h') },
|
{ name: 'Sievert/hour (Sv/h)', id: 'radsvh', fn: SIPrefix('Sv/h') },
|
||||||
{ name: 'milliSievert/hour (mSv/h)', id: 'radmsvh', fn: decimalSIPrefix('Sv/h', -1) },
|
{ name: 'milliSievert/hour (mSv/h)', id: 'radmsvh', fn: SIPrefix('Sv/h', -1) },
|
||||||
{ name: 'microSievert/hour (µSv/h)', id: 'radusvh', fn: decimalSIPrefix('Sv/h', -2) },
|
{ name: 'microSievert/hour (µSv/h)', id: 'radusvh', fn: SIPrefix('Sv/h', -2) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Rotational Speed',
|
name: 'Rotational Speed',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'Revolutions per minute (rpm)', id: 'rotrpm', fn: toFixedUnit('rpm') },
|
{ name: 'Revolutions per minute (rpm)', id: 'rotrpm', fn: toFixedUnit('rpm') },
|
||||||
{ name: 'Hertz (Hz)', id: 'rothz', fn: decimalSIPrefix('Hz') },
|
{ name: 'Hertz (Hz)', id: 'rothz', fn: SIPrefix('Hz') },
|
||||||
{ name: 'Radians per second (rad/s)', id: 'rotrads', fn: toFixedUnit('rad/s') },
|
{ name: 'Radians per second (rad/s)', id: 'rotrads', fn: toFixedUnit('rad/s') },
|
||||||
{ name: 'Degrees per second (°/s)', id: 'rotdegs', fn: toFixedUnit('°/s') },
|
{ name: 'Degrees per second (°/s)', id: 'rotdegs', fn: toFixedUnit('°/s') },
|
||||||
],
|
],
|
||||||
@@ -327,7 +336,7 @@ export const getCategories = (): ValueFormatCategory[] => [
|
|||||||
{
|
{
|
||||||
name: 'Time',
|
name: 'Time',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'Hertz (1/s)', id: 'hertz', fn: decimalSIPrefix('Hz') },
|
{ name: 'Hertz (1/s)', id: 'hertz', fn: SIPrefix('Hz') },
|
||||||
{ name: 'nanoseconds (ns)', id: 'ns', fn: toNanoSeconds },
|
{ name: 'nanoseconds (ns)', id: 'ns', fn: toNanoSeconds },
|
||||||
{ name: 'microseconds (µs)', id: 'µs', fn: toMicroSeconds },
|
{ name: 'microseconds (µs)', id: 'µs', fn: toMicroSeconds },
|
||||||
{ name: 'milliseconds (ms)', id: 'ms', fn: toMilliSeconds },
|
{ name: 'milliseconds (ms)', id: 'ms', fn: toMilliSeconds },
|
||||||
@@ -371,8 +380,8 @@ export const getCategories = (): ValueFormatCategory[] => [
|
|||||||
{
|
{
|
||||||
name: 'Volume',
|
name: 'Volume',
|
||||||
formats: [
|
formats: [
|
||||||
{ name: 'millilitre (mL)', id: 'mlitre', fn: decimalSIPrefix('L', -1) },
|
{ name: 'millilitre (mL)', id: 'mlitre', fn: SIPrefix('L', -1) },
|
||||||
{ name: 'litre (L)', id: 'litre', fn: decimalSIPrefix('L') },
|
{ name: 'litre (L)', id: 'litre', fn: SIPrefix('L') },
|
||||||
{ name: 'cubic meter', id: 'm3', fn: toFixedUnit('m³') },
|
{ name: 'cubic meter', id: 'm3', fn: toFixedUnit('m³') },
|
||||||
{ name: 'Normal cubic meter', id: 'Nm3', fn: toFixedUnit('Nm³') },
|
{ name: 'Normal cubic meter', id: 'Nm3', fn: toFixedUnit('Nm³') },
|
||||||
{ name: 'cubic decimeter', id: 'dm3', fn: toFixedUnit('dm³') },
|
{ name: 'cubic decimeter', id: 'dm3', fn: toFixedUnit('dm³') },
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { toDuration as duration, toUtc, dateTime } from '../datetime/moment_wrap
|
|||||||
import { toFixed, toFixedScaled, FormattedValue, ValueFormatter } from './valueFormats';
|
import { toFixed, toFixedScaled, FormattedValue, ValueFormatter } from './valueFormats';
|
||||||
import { DecimalCount } from '../types/displayValue';
|
import { DecimalCount } from '../types/displayValue';
|
||||||
import { TimeZone } from '../types';
|
import { TimeZone } from '../types';
|
||||||
import { dateTimeFormat, dateTimeFormatTimeAgo, localTimeFormat } from '../datetime';
|
import { dateTimeFormat, dateTimeFormatTimeAgo, localTimeFormat, systemDateFormats } from '../datetime';
|
||||||
|
|
||||||
interface IntervalsInSeconds {
|
interface IntervalsInSeconds {
|
||||||
[interval: string]: number;
|
[interval: string]: number;
|
||||||
@@ -383,6 +383,15 @@ export function getDateTimeAsLocalFormat() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function dateTimeSystemFormatter(
|
||||||
|
value: number,
|
||||||
|
decimals: DecimalCount,
|
||||||
|
scaledDecimals: DecimalCount,
|
||||||
|
timeZone?: TimeZone
|
||||||
|
): FormattedValue {
|
||||||
|
return { text: dateTimeFormat(value, { format: systemDateFormats.fullDate, timeZone }) };
|
||||||
|
}
|
||||||
|
|
||||||
export function dateTimeFromNow(
|
export function dateTimeFromNow(
|
||||||
value: number,
|
value: number,
|
||||||
decimals: DecimalCount,
|
decimals: DecimalCount,
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function getOffsetFromSIPrefix(c: string): number {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function binarySIPrefix(unit: string, offset = 0): ValueFormatter {
|
export function binaryPrefix(unit: string, offset = 0): ValueFormatter {
|
||||||
const prefixes = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'].slice(offset);
|
const prefixes = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'].slice(offset);
|
||||||
const units = prefixes.map(p => {
|
const units = prefixes.map(p => {
|
||||||
return ' ' + p + unit;
|
return ' ' + p + unit;
|
||||||
@@ -61,7 +61,7 @@ export function binarySIPrefix(unit: string, offset = 0): ValueFormatter {
|
|||||||
return scaledUnits(1024, units);
|
return scaledUnits(1024, units);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decimalSIPrefix(unit: string, offset = 0): ValueFormatter {
|
export function SIPrefix(unit: string, offset = 0): ValueFormatter {
|
||||||
let prefixes = ['f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
let prefixes = ['f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
||||||
prefixes = prefixes.slice(5 + (offset || 0));
|
prefixes = prefixes.slice(5 + (offset || 0));
|
||||||
const units = prefixes.map(p => {
|
const units = prefixes.map(p => {
|
||||||
|
|||||||
@@ -66,6 +66,19 @@ const formatTests: ValueFormatTest[] = [
|
|||||||
// Time format
|
// Time format
|
||||||
{ id: 'time:YYYY', decimals: 0, value: dateTime(new Date(1999, 6, 2)).valueOf(), result: '1999' },
|
{ id: 'time:YYYY', decimals: 0, value: dateTime(new Date(1999, 6, 2)).valueOf(), result: '1999' },
|
||||||
{ id: 'time:YYYY.MM', decimals: 0, value: dateTime(new Date(2010, 6, 2)).valueOf(), result: '2010.07' },
|
{ id: 'time:YYYY.MM', decimals: 0, value: dateTime(new Date(2010, 6, 2)).valueOf(), result: '2010.07' },
|
||||||
|
{ id: 'dateTimeAsIso', decimals: 0, value: dateTime(new Date(2010, 6, 2)).valueOf(), result: '2010-07-02 00:00:00' },
|
||||||
|
{
|
||||||
|
id: 'dateTimeAsUS',
|
||||||
|
decimals: 0,
|
||||||
|
value: dateTime(new Date(2010, 6, 2)).valueOf(),
|
||||||
|
result: '07/02/2010 12:00:00 am',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'dateTimeAsSystem',
|
||||||
|
decimals: 0,
|
||||||
|
value: dateTime(new Date(2010, 6, 2)).valueOf(),
|
||||||
|
result: '2010-07-02 00:00:00',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('valueFormats', () => {
|
describe('valueFormats', () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getCategories } from './categories';
|
import { getCategories } from './categories';
|
||||||
import { DecimalCount } from '../types/displayValue';
|
import { DecimalCount } from '../types/displayValue';
|
||||||
import { toDateTimeValueFormatter } from './dateTimeFormatters';
|
import { toDateTimeValueFormatter } from './dateTimeFormatters';
|
||||||
import { getOffsetFromSIPrefix, decimalSIPrefix, currency } from './symbolFormatters';
|
import { getOffsetFromSIPrefix, SIPrefix, currency } from './symbolFormatters';
|
||||||
import { TimeZone } from '../types';
|
import { TimeZone } from '../types';
|
||||||
|
|
||||||
export interface FormattedValue {
|
export interface FormattedValue {
|
||||||
@@ -213,7 +213,7 @@ export function getValueFormat(id?: string | null): ValueFormatter {
|
|||||||
if (key === 'si') {
|
if (key === 'si') {
|
||||||
const offset = getOffsetFromSIPrefix(sub.charAt(0));
|
const offset = getOffsetFromSIPrefix(sub.charAt(0));
|
||||||
const unit = offset === 0 ? sub : sub.substring(1);
|
const unit = offset === 0 ? sub : sub.substring(1);
|
||||||
return decimalSIPrefix(unit, offset);
|
return SIPrefix(unit, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'count') {
|
if (key === 'count') {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "@grafana/e2e-selectors",
|
"name": "@grafana/e2e-selectors",
|
||||||
"version": "7.2.0-pre.0",
|
"version": "7.2.2",
|
||||||
"description": "Grafana End-to-End Test Selectors Library",
|
"description": "Grafana End-to-End Test Selectors Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cli",
|
"cli",
|
||||||
|
|||||||
@@ -153,4 +153,9 @@ export const Components = {
|
|||||||
section: 'Search section',
|
section: 'Search section',
|
||||||
items: 'Search items',
|
items: 'Search items',
|
||||||
},
|
},
|
||||||
|
DashboardLinks: {
|
||||||
|
container: 'Dashboard link container',
|
||||||
|
dropDown: 'Dashboard link dropdown',
|
||||||
|
link: 'Dashboard link',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -133,4 +133,7 @@ export const Pages = {
|
|||||||
navBar: () => '.explore-toolbar',
|
navBar: () => '.explore-toolbar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
SoloPanel: {
|
||||||
|
url: (page: string) => `/d-solo/${page}`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "@grafana/e2e",
|
"name": "@grafana/e2e",
|
||||||
"version": "7.2.0-pre.0",
|
"version": "7.2.2",
|
||||||
"description": "Grafana End-to-End Test Library",
|
"description": "Grafana End-to-End Test Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cli",
|
"cli",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cypress/webpack-preprocessor": "4.1.3",
|
"@cypress/webpack-preprocessor": "4.1.3",
|
||||||
"@grafana/e2e-selectors": "7.2.0-pre.0",
|
"@grafana/e2e-selectors": "7.2.2",
|
||||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||||
"@mochajs/json-file-reporter": "^1.2.0",
|
"@mochajs/json-file-reporter": "^1.2.0",
|
||||||
"blink-diff": "1.0.13",
|
"blink-diff": "1.0.13",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "@grafana/runtime",
|
"name": "@grafana/runtime",
|
||||||
"version": "7.2.0-pre.0",
|
"version": "7.2.2",
|
||||||
"description": "Grafana Runtime Library",
|
"description": "Grafana Runtime Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"grafana",
|
"grafana",
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grafana/data": "7.2.0-pre.0",
|
"@grafana/data": "7.2.2",
|
||||||
"@grafana/ui": "7.2.0-pre.0",
|
"@grafana/ui": "7.2.2",
|
||||||
"systemjs": "0.20.19",
|
"systemjs": "0.20.19",
|
||||||
"systemjs-plugin-css": "0.1.37"
|
"systemjs-plugin-css": "0.1.37"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
|||||||
meta: false,
|
meta: false,
|
||||||
datasourceInsights: false,
|
datasourceInsights: false,
|
||||||
reportGrid: false,
|
reportGrid: false,
|
||||||
standaloneAlerts: false,
|
|
||||||
};
|
};
|
||||||
licenseInfo: LicenseInfo = {} as LicenseInfo;
|
licenseInfo: LicenseInfo = {} as LicenseInfo;
|
||||||
rendererAvailable = false;
|
rendererAvailable = false;
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ export class DataSourceWithBackend<
|
|||||||
/**
|
/**
|
||||||
* Override to skip executing a query
|
* Override to skip executing a query
|
||||||
*
|
*
|
||||||
|
* @returns false if the query should be skipped
|
||||||
|
*
|
||||||
* @virtual
|
* @virtual
|
||||||
*/
|
*/
|
||||||
filterQuery?(query: TQuery): boolean;
|
filterQuery?(query: TQuery): boolean;
|
||||||
|
|||||||
@@ -100,6 +100,10 @@ Available options:
|
|||||||
|
|
||||||
This command creates a production-ready build of your plugin.
|
This command creates a production-ready build of your plugin.
|
||||||
|
|
||||||
|
Available options:
|
||||||
|
|
||||||
|
- `--coverage` - Reports code coverage after the test step of the build.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### Which version of grafana-toolkit should I use?
|
### Which version of grafana-toolkit should I use?
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "@grafana/toolkit",
|
"name": "@grafana/toolkit",
|
||||||
"version": "7.2.0-pre.0",
|
"version": "7.2.2",
|
||||||
"description": "Grafana Toolkit",
|
"description": "Grafana Toolkit",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"grafana",
|
"grafana",
|
||||||
|
|||||||
@@ -139,8 +139,9 @@ export const run = (includeInternalScripts = false) => {
|
|||||||
program
|
program
|
||||||
.command('plugin:build')
|
.command('plugin:build')
|
||||||
.description('Prepares plugin dist package')
|
.description('Prepares plugin dist package')
|
||||||
|
.option('--coverage', 'Run code coverage', false)
|
||||||
.action(async cmd => {
|
.action(async cmd => {
|
||||||
await execTask(pluginBuildTask)({ coverage: false, silent: true });
|
await execTask(pluginBuildTask)({ coverage: cmd.coverage, silent: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import execa = require('execa');
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { resolve as resolvePath } from 'path';
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { useSpinner } from '../utils/useSpinner';
|
import { useSpinner } from '../utils/useSpinner';
|
||||||
import { Task, TaskRunner } from './task';
|
import { Task, TaskRunner } from './task';
|
||||||
@@ -89,13 +88,13 @@ const moveFiles = () => {
|
|||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
|
||||||
const moveStaticFiles = async (pkg: any, cwd: string) => {
|
const moveStaticFiles = async (pkg: any) => {
|
||||||
if (pkg.name.endsWith('/ui')) {
|
if (pkg.name.endsWith('/ui')) {
|
||||||
const staticFiles = await globby(resolvePath(process.cwd(), 'src/**/*.+(png|svg|gif|jpg)'));
|
const staticFiles = await globby('src/**/*.{png,svg,gif,jpg}');
|
||||||
return useSpinner<void>(`Moving static files`, async () => {
|
return useSpinner<void>(`Moving static files`, async () => {
|
||||||
const promises = staticFiles.map(file => {
|
const promises = staticFiles.map(file => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fs.copyFile(file, `${cwd}/compiled/${file.replace(`${cwd}/src`, '')}`, (err: any) => {
|
fs.copyFile(file, file.replace(/^src/, 'compiled'), (err: any) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
@@ -130,7 +129,7 @@ const buildTaskRunner: TaskRunner<PackageBuildOptions> = async ({ scope }) => {
|
|||||||
|
|
||||||
await clean();
|
await clean();
|
||||||
await compile();
|
await compile();
|
||||||
await moveStaticFiles(pkg, cwd);
|
await moveStaticFiles(pkg);
|
||||||
await rollup();
|
await rollup();
|
||||||
await preparePackage(pkg);
|
await preparePackage(pkg);
|
||||||
await moveFiles();
|
await moveFiles();
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ const copyIfNonExistent = (srcPath: string, destPath: string) =>
|
|||||||
|
|
||||||
export const prepare = useSpinner<void>('Preparing', async () => {
|
export const prepare = useSpinner<void>('Preparing', async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
// Remove local dependencies for @grafana/data/node_modules
|
||||||
|
// See: https://github.com/grafana/grafana/issues/26748
|
||||||
|
rimraf(resolvePath(__dirname, 'node_modules/@grafana/data/node_modules')),
|
||||||
|
|
||||||
// Copy only if local tsconfig does not exist. Otherwise this will work, but have odd behavior
|
// Copy only if local tsconfig does not exist. Otherwise this will work, but have odd behavior
|
||||||
copyIfNonExistent(
|
copyIfNonExistent(
|
||||||
resolvePath(__dirname, '../../config/tsconfig.plugin.local.json'),
|
resolvePath(__dirname, '../../config/tsconfig.plugin.local.json'),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "@grafana/ui",
|
"name": "@grafana/ui",
|
||||||
"version": "7.2.0-pre.0",
|
"version": "7.2.2",
|
||||||
"description": "Grafana Components Library",
|
"description": "Grafana Components Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"grafana",
|
"grafana",
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/core": "^10.0.27",
|
"@emotion/core": "^10.0.27",
|
||||||
"@grafana/data": "7.2.0-pre.0",
|
"@grafana/data": "7.2.2",
|
||||||
"@grafana/e2e-selectors": "7.2.0-pre.0",
|
"@grafana/e2e-selectors": "7.2.2",
|
||||||
"@grafana/slate-react": "0.22.9-grafana",
|
"@grafana/slate-react": "0.22.9-grafana",
|
||||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||||
"@iconscout/react-unicons": "1.1.4",
|
"@iconscout/react-unicons": "1.1.4",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const buildCjsPackage = ({ env }) => {
|
|||||||
'monaco-editor', // Monaco should not be used directly
|
'monaco-editor', // Monaco should not be used directly
|
||||||
'monaco-editor/esm/vs/editor/editor.api', // Monaco should not be used directly
|
'monaco-editor/esm/vs/editor/editor.api', // Monaco should not be used directly
|
||||||
'react-monaco-editor',
|
'react-monaco-editor',
|
||||||
|
'jquery', // required to use jquery.plot, which is assigned externally
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
commonjs({
|
commonjs({
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
|
import { Meta, Props } from '@storybook/addon-docs/blocks';
|
||||||
import { Cascader } from './Cascader';
|
import { Cascader } from './Cascader';
|
||||||
|
|
||||||
<Meta title="MDX|Cascader" component={Cascader} />
|
<Meta title="MDX|Cascader" component={Cascader} />
|
||||||
|
|
||||||
# Cascader
|
# Cascader
|
||||||
|
|
||||||
The cascader component is a `Select` with a cascading flyout menu. When you have lots of options in your select, they can be hard to navigate from a regular dropdown list. In that case you can use the cascader to organize your options into groups hierarchically. Just like in the `Select` component, the cascader input doubles as a search field to quickly jump to a selection without navigating the list.
|
The cascader component is a `Select` with a cascading flyout menu. When you have lots of options in your select, they can be hard to navigate from a regular dropdown list. In that case you can use the cascader to organize your options into groups hierarchically. Just like in the `Select` component, the cascader input doubles as a search field to quickly jump to a selection without navigating the list.
|
||||||
|
|
||||||
You can either use the `Simple` cascader component for an empty input as default state or use the `initialValue` or `allowCustomValue` fields to pre-fill your cascader. Initial value means that one of the options from your cascaded list is pre-selected. Custom value means that apart from existing options from the list, your users can add custom values to the list by typing them in the `Select` input.
|
You can either use the `Simple` cascader component for an empty input as default state or use the `initialValue` or `allowCustomValue` fields to pre-fill your cascader. Initial value means that one of the options from your cascaded list is pre-selected. Custom value means that apart from existing options from the list, your users can add custom values to the list by typing them in the `Select` input.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { FC, ReactNode, useState } from 'react';
|
import React, { FC, ReactNode, useState } from 'react';
|
||||||
import { css } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { useStyles } from '../../themes';
|
import { useStyles } from '../../themes';
|
||||||
import { Icon } from '..';
|
import { Icon } from '..';
|
||||||
@@ -13,14 +13,20 @@ export interface Props {
|
|||||||
export const CollapsableSection: FC<Props> = ({ label, isOpen, children }) => {
|
export const CollapsableSection: FC<Props> = ({ label, isOpen, children }) => {
|
||||||
const [open, toggleOpen] = useState<boolean>(isOpen);
|
const [open, toggleOpen] = useState<boolean>(isOpen);
|
||||||
const styles = useStyles(collapsableSectionStyles);
|
const styles = useStyles(collapsableSectionStyles);
|
||||||
|
const headerClass = cx({
|
||||||
|
[styles.header]: true,
|
||||||
|
[styles.headerCollapsed]: !open,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tooltip = `Click to ${open ? 'collapse' : 'expand'}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div onClick={() => toggleOpen(!open)} className={styles.header}>
|
<div onClick={() => toggleOpen(!open)} className={headerClass} title={tooltip}>
|
||||||
<Icon name={open ? 'angle-down' : 'angle-right'} size="xl" />
|
|
||||||
{label}
|
{label}
|
||||||
|
<Icon name={open ? 'angle-down' : 'angle-right'} size="xl" className={styles.icon} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.content}>{open && children}</div>
|
{open && <div className={styles.content}>{children}</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -28,11 +34,19 @@ export const CollapsableSection: FC<Props> = ({ label, isOpen, children }) => {
|
|||||||
const collapsableSectionStyles = (theme: GrafanaTheme) => {
|
const collapsableSectionStyles = (theme: GrafanaTheme) => {
|
||||||
return {
|
return {
|
||||||
header: css`
|
header: css`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
font-size: ${theme.typography.size.lg};
|
font-size: ${theme.typography.size.lg};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
`,
|
`,
|
||||||
|
headerCollapsed: css`
|
||||||
|
border-bottom: 1px solid ${theme.colors.border2};
|
||||||
|
`,
|
||||||
|
icon: css`
|
||||||
|
color: ${theme.colors.textWeak};
|
||||||
|
`,
|
||||||
content: css`
|
content: css`
|
||||||
padding: ${theme.spacing.md} 0 ${theme.spacing.md} ${theme.spacing.md};
|
padding: ${theme.spacing.md} 0;
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,31 @@
|
|||||||
|
import { Meta, Props } from '@storybook/addon-docs/blocks';
|
||||||
|
import { ConfirmButton } from './ConfirmButton';
|
||||||
|
|
||||||
<Meta title="MDX|ConfirmButton" component={ConfirmButton} />
|
<Meta title="MDX|ConfirmButton" component={ConfirmButton} />
|
||||||
|
|
||||||
# ConfirmButton
|
# ConfirmButton
|
||||||
|
|
||||||
The ConfirmButton is an interactive component that adds a double-confirm option to a clickable action. When clicked, the action is replaced by an inline confirmation with the option to cancel. In Grafana, this is used for example for editing values in settings tables.
|
The ConfirmButton is an interactive component that adds a double-confirm option to a clickable action. When clicked, the action is replaced by an inline confirmation with the option to cancel. In Grafana, this is used, for example, for editing values in settings tables.
|
||||||
|
|
||||||
## Variants
|
## Variants
|
||||||
|
|
||||||
There are four variants of the `ConfirmButton`: primary, secondary, destructive, and link. The primary and secondary variants include a primary or secondary `Button` component. The primary and secondary variant should be used to confirm actions like saving or adding data. The destructive variant includes a destructive `Button` component. The destructive variant should be used to double-confirm a deletion or removal of an element. The link variant doesn't include any button and double-confirms as links instead.
|
There are four variants of the `ConfirmButton`: primary, secondary, destructive, and link. The primary and secondary variants include a primary or secondary `Button` component. The primary and secondary variant should be used to confirm actions like saving or adding data. The destructive variant includes a destructive `Button` component. The destructive variant should be used to double-confirm a deletion or removal of an element. The link variant doesn't include any button and double-confirms as links instead.
|
||||||
|
|
||||||
Apart from the button variant, you can also modify the button size and the button text.
|
Apart from the button variant, you can also modify the button size and the button text.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<ConfirmButton
|
||||||
|
closeOnConfirm
|
||||||
|
size='md'
|
||||||
|
confirmText='Are you sure?'
|
||||||
|
confirmVariant='secondary'
|
||||||
|
onConfirm={() => {
|
||||||
|
console.log('Action confirmed!')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Click me
|
||||||
|
</ConfirmButton>
|
||||||
|
```
|
||||||
|
<Props of={ConfirmButton} />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
|||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import { Button } from '../Button';
|
import { Button } from '../Button';
|
||||||
import { DeleteButton } from './DeleteButton';
|
import { DeleteButton } from './DeleteButton';
|
||||||
|
import mdx from './ConfirmButton.mdx';
|
||||||
|
|
||||||
const getKnobs = () => {
|
const getKnobs = () => {
|
||||||
return {
|
return {
|
||||||
@@ -31,6 +32,11 @@ export default {
|
|||||||
component: ConfirmButton,
|
component: ConfirmButton,
|
||||||
decorators: [withCenteredStory],
|
decorators: [withCenteredStory],
|
||||||
subcomponents: { DeleteButton },
|
subcomponents: { DeleteButton },
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
page: mdx,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const basic = () => {
|
export const basic = () => {
|
||||||
|
|||||||
@@ -53,15 +53,24 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export interface Props extends Themeable {
|
export interface Props extends Themeable {
|
||||||
|
/** Confirm action callback */
|
||||||
|
onConfirm(): void;
|
||||||
|
/** Custom button styles */
|
||||||
className?: string;
|
className?: string;
|
||||||
|
/** Button size */
|
||||||
size?: ComponentSize;
|
size?: ComponentSize;
|
||||||
|
/** Text for the Confirm button */
|
||||||
confirmText?: string;
|
confirmText?: string;
|
||||||
|
/** Disable button click action */
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
/** Variant of the Confirm button */
|
||||||
confirmVariant?: ButtonVariant;
|
confirmVariant?: ButtonVariant;
|
||||||
|
/** Hide confirm actions when after of them is clicked */
|
||||||
closeOnConfirm?: boolean;
|
closeOnConfirm?: boolean;
|
||||||
|
|
||||||
onConfirm(): void;
|
/** Optional on click handler for the original button */
|
||||||
onClick?(): void;
|
onClick?(): void;
|
||||||
|
/** Callback for the cancel action */
|
||||||
onCancel?(): void;
|
onCancel?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,13 +79,6 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class UnThemedConfirmButton extends PureComponent<Props, State> {
|
class UnThemedConfirmButton extends PureComponent<Props, State> {
|
||||||
static defaultProps: Partial<Props> = {
|
|
||||||
size: 'md',
|
|
||||||
confirmText: 'Save',
|
|
||||||
disabled: false,
|
|
||||||
confirmVariant: 'primary',
|
|
||||||
};
|
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
showConfirm: false,
|
showConfirm: false,
|
||||||
};
|
};
|
||||||
@@ -106,7 +108,7 @@ class UnThemedConfirmButton extends PureComponent<Props, State> {
|
|||||||
this.props.onCancel();
|
this.props.onCancel();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
onConfirm = (event: SyntheticEvent) => {
|
onConfirm = () => {
|
||||||
this.props.onConfirm();
|
this.props.onConfirm();
|
||||||
if (this.props.closeOnConfirm) {
|
if (this.props.closeOnConfirm) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -166,4 +168,13 @@ class UnThemedConfirmButton extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ConfirmButton = withTheme(UnThemedConfirmButton);
|
export const ConfirmButton = withTheme(UnThemedConfirmButton);
|
||||||
|
|
||||||
|
// Declare defaultProps directly on the themed component so they are displayed
|
||||||
|
// in the props table
|
||||||
|
ConfirmButton.defaultProps = {
|
||||||
|
size: 'md',
|
||||||
|
confirmText: 'Save',
|
||||||
|
disabled: false,
|
||||||
|
confirmVariant: 'primary',
|
||||||
|
};
|
||||||
ConfirmButton.displayName = 'ConfirmButton';
|
ConfirmButton.displayName = 'ConfirmButton';
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import { ComponentSize } from '../../types/size';
|
|||||||
import { Button } from '../Button';
|
import { Button } from '../Button';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
size?: ComponentSize;
|
/** Confirm action callback */
|
||||||
disabled?: boolean;
|
|
||||||
onConfirm(): void;
|
onConfirm(): void;
|
||||||
|
/** Button size */
|
||||||
|
size?: ComponentSize;
|
||||||
|
/** Disable button click action */
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteButton: FC<Props> = ({ size, disabled, onConfirm }) => {
|
export const DeleteButton: FC<Props> = ({ size, disabled, onConfirm }) => {
|
||||||
|
|||||||
@@ -7,4 +7,18 @@ import { ConfirmModal } from './ConfirmModal';
|
|||||||
|
|
||||||
Used to request user for action confirmation, e.g. deleting items. Triggers provided 'onConfirm' callback.
|
Used to request user for action confirmation, e.g. deleting items. Triggers provided 'onConfirm' callback.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
```jsx
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={false}
|
||||||
|
title='Delete user'
|
||||||
|
body='Are you sure you want to delete this user?'
|
||||||
|
confirmText='Confirm'
|
||||||
|
icon='exclamation-triangle'
|
||||||
|
onConfirm={() => console.log('Confirm action')}
|
||||||
|
onDismiss={() => console.log('Dismiss action')}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
<Props of={ConfirmModal} />
|
<Props of={ConfirmModal} />
|
||||||
|
|||||||
@@ -8,13 +8,21 @@ import { GrafanaTheme } from '@grafana/data';
|
|||||||
import { HorizontalGroup } from '..';
|
import { HorizontalGroup } from '..';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
/** Toggle modal's open/closed state */
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
/** Title for the modal header */
|
||||||
title: string;
|
title: string;
|
||||||
|
/** Modal content */
|
||||||
body: React.ReactNode;
|
body: React.ReactNode;
|
||||||
|
/** Text for confirm button */
|
||||||
confirmText: string;
|
confirmText: string;
|
||||||
|
/** Text for dismiss button */
|
||||||
dismissText?: string;
|
dismissText?: string;
|
||||||
|
/** Icon for the modal header */
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
|
/** Confirm action callback */
|
||||||
onConfirm(): void;
|
onConfirm(): void;
|
||||||
|
/** Dismiss action callback */
|
||||||
onDismiss(): void;
|
onDismiss(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export class Select<T> extends PureComponent<LegacySelectProps<T>> {
|
|||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
openMenuOnFocus={openMenuOnFocus}
|
openMenuOnFocus={openMenuOnFocus}
|
||||||
maxMenuHeight={maxMenuHeight}
|
maxMenuHeight={maxMenuHeight}
|
||||||
noOptionsMessage={() => noOptionsMessage}
|
noOptionsMessage={noOptionsMessage}
|
||||||
isMulti={isMulti}
|
isMulti={isMulti}
|
||||||
backspaceRemovesValue={backspaceRemovesValue}
|
backspaceRemovesValue={backspaceRemovesValue}
|
||||||
menuIsOpen={isOpen}
|
menuIsOpen={isOpen}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface IconProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
const getIconStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getIconStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
|
label: Icon;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
`,
|
`,
|
||||||
icon: css`
|
icon: css`
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ To add more context to the input you can add either text or an icon before or af
|
|||||||
|
|
||||||
## Usage in forms with Field
|
## Usage in forms with Field
|
||||||
|
|
||||||
`Input` should be used with the `Field` component to get labels and descriptions. It should also be used for validation. See the `Field` component for more information.
|
`Input` should be used with the `Field` component to get labels and descriptions. It can also be used for validation by using the `required` attribute. See the `Field` component for more information.
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<Field label="Important information" description="This information is very important, so you really need to fill it in">
|
<Field label="Important information" description="This information is very important, so you really need to fill it in">
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
export { Controller as InputControl } from 'react-hook-form';
|
/**
|
||||||
|
* Rollup does not support renamed exports so do not change this to export { Controller as InputControl } ...
|
||||||
|
*/
|
||||||
|
import { Controller } from 'react-hook-form';
|
||||||
|
export const InputControl = Controller;
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ const getStyles = stylesFactory(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
layout: css`
|
layout: css`
|
||||||
|
label: HorizontalGroup;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: ${orientation === Orientation.Vertical ? 'column' : 'row'};
|
flex-direction: ${orientation === Orientation.Vertical ? 'column' : 'row'};
|
||||||
flex-wrap: ${wrap ? 'wrap' : 'nowrap'};
|
flex-wrap: ${wrap ? 'wrap' : 'nowrap'};
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const fieldNameByRegexMatcherItem: FieldMatcherUIRegistryItem<string> = {
|
|||||||
id: FieldMatcherID.byRegexp,
|
id: FieldMatcherID.byRegexp,
|
||||||
component: FieldNameByRegexMatcherEditor,
|
component: FieldNameByRegexMatcherEditor,
|
||||||
matcher: fieldMatchers.get(FieldMatcherID.byRegexp),
|
matcher: fieldMatchers.get(FieldMatcherID.byRegexp),
|
||||||
name: 'Filter by field using regex',
|
name: 'Fields with name matching regex',
|
||||||
description: 'Set properties for fields with names matching provided regex',
|
description: 'Set properties for fields with names matching a regex',
|
||||||
optionsToLabel: options => options,
|
optionsToLabel: options => options,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ export const fieldNameMatcherItem: FieldMatcherUIRegistryItem<string> = {
|
|||||||
id: FieldMatcherID.byName,
|
id: FieldMatcherID.byName,
|
||||||
component: FieldNameMatcherEditor,
|
component: FieldNameMatcherEditor,
|
||||||
matcher: fieldMatchers.get(FieldMatcherID.byName),
|
matcher: fieldMatchers.get(FieldMatcherID.byName),
|
||||||
name: 'Filter by field',
|
name: 'Fields with name',
|
||||||
description: 'Set properties for fields matching the name',
|
description: 'Set properties for a specific field',
|
||||||
optionsToLabel: options => options,
|
optionsToLabel: options => options,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export const fieldTypeMatcherItem: FieldMatcherUIRegistryItem<string> = {
|
|||||||
id: FieldMatcherID.byType,
|
id: FieldMatcherID.byType,
|
||||||
component: FieldTypeMatcherEditor,
|
component: FieldTypeMatcherEditor,
|
||||||
matcher: fieldMatchers.get(FieldMatcherID.byType),
|
matcher: fieldMatchers.get(FieldMatcherID.byType),
|
||||||
name: 'Filter by type',
|
name: 'Fields with type',
|
||||||
description: 'Set properties for fields matching a type',
|
description: 'Set properties for fields of a specific type (number, string, boolean)',
|
||||||
optionsToLabel: options => options,
|
optionsToLabel: options => options,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const ColorValueEditor: React.FC<FieldConfigEditorProps<string, ColorFiel
|
|||||||
const color = value || (item.defaultValue as string) || theme.colors.panelBg;
|
const color = value || (item.defaultValue as string) || theme.colors.panelBg;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColorPicker color={color} onChange={onChange} enableNamedColors={!settings.disableNamedColors}>
|
<ColorPicker color={color} onChange={onChange} enableNamedColors={!settings?.disableNamedColors}>
|
||||||
{({ ref, showColorPicker, hideColorPicker }) => {
|
{({ ref, showColorPicker, hideColorPicker }) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.spot} onBlur={hideColorPicker}>
|
<div className={styles.spot} onBlur={hideColorPicker}>
|
||||||
@@ -36,9 +36,9 @@ export const ColorValueEditor: React.FC<FieldConfigEditorProps<string, ColorFiel
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.colorText} onClick={showColorPicker}>
|
<div className={styles.colorText} onClick={showColorPicker}>
|
||||||
{value ?? settings.textWhenUndefined ?? 'Pick Color'}
|
{value ?? settings?.textWhenUndefined ?? 'Pick Color'}
|
||||||
</div>
|
</div>
|
||||||
{value && settings.allowUndefined && (
|
{value && settings?.allowUndefined && (
|
||||||
<Icon className={styles.trashIcon} name="trash-alt" onClick={() => onChange(undefined)} />
|
<Icon className={styles.trashIcon} name="trash-alt" onClick={() => onChange(undefined)} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ export class ValueMappingsValueEditor extends React.PureComponent<
|
|||||||
value = [];
|
value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ValueMappingsEditor valueMappings={value} onChange={onChange} />;
|
return <ValueMappingsEditor value={value} onChange={onChange} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class SelectValueEditor<T> extends React.PureComponent<Props<T>, State<T>
|
|||||||
const now = this.props.item?.settings;
|
const now = this.props.item?.settings;
|
||||||
if (old !== now) {
|
if (old !== now) {
|
||||||
this.updateOptions();
|
this.updateOptions();
|
||||||
} else if (now.getOptions) {
|
} else if (now?.getOptions) {
|
||||||
const old = oldProps.context?.data;
|
const old = oldProps.context?.data;
|
||||||
const now = this.props.context?.data;
|
const now = this.props.context?.data;
|
||||||
if (old !== now) {
|
if (old !== now) {
|
||||||
@@ -53,7 +53,6 @@ export class SelectValueEditor<T> extends React.PureComponent<Props<T>, State<T>
|
|||||||
const { value, onChange, item } = this.props;
|
const { value, onChange, item } = this.props;
|
||||||
|
|
||||||
const { settings } = item;
|
const { settings } = item;
|
||||||
const { allowCustomValue } = settings;
|
|
||||||
let current = options.find(v => v.value === value);
|
let current = options.find(v => v.value === value);
|
||||||
if (!current && value) {
|
if (!current && value) {
|
||||||
current = {
|
current = {
|
||||||
@@ -66,7 +65,7 @@ export class SelectValueEditor<T> extends React.PureComponent<Props<T>, State<T>
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
value={current}
|
value={current}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
allowCustomValue={allowCustomValue}
|
allowCustomValue={settings?.allowCustomValue}
|
||||||
onChange={e => onChange(e.value)}
|
onChange={e => onChange(e.value)}
|
||||||
options={options}
|
options={options}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const StringValueEditor: React.FC<FieldConfigEditorProps<string, StringFi
|
|||||||
<Component
|
<Component
|
||||||
placeholder={item.settings?.placeholder}
|
placeholder={item.settings?.placeholder}
|
||||||
defaultValue={value || ''}
|
defaultValue={value || ''}
|
||||||
rows={item.settings?.useTextarea && item.settings.rows}
|
rows={(item.settings?.useTextarea && item.settings.rows) || 5}
|
||||||
onBlur={onValueChange}
|
onBlur={onValueChange}
|
||||||
onKeyDown={onValueChange}
|
onKeyDown={onValueChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
menu: css`
|
menu: css`
|
||||||
|
label: grafana-select-menu;
|
||||||
background: ${bgColor};
|
background: ${bgColor};
|
||||||
box-shadow: 0px 4px 4px ${menuShadowColor};
|
box-shadow: 0px 4px 4px ${menuShadowColor};
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -18,6 +19,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
`,
|
`,
|
||||||
option: css`
|
option: css`
|
||||||
|
label: grafana-select-option;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -31,22 +33,26 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
optionImage: css`
|
optionImage: css`
|
||||||
|
label: grafana-select-option-image;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
`,
|
`,
|
||||||
optionDescription: css`
|
optionDescription: css`
|
||||||
|
label: grafana-select-option-description;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: ${theme.typography.size.sm};
|
font-size: ${theme.typography.size.sm};
|
||||||
color: ${theme.colors.textWeak};
|
color: ${theme.colors.textWeak};
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
`,
|
`,
|
||||||
optionBody: css`
|
optionBody: css`
|
||||||
|
label: grafana-select-option-body;
|
||||||
display: flex;
|
display: flex;
|
||||||
font-weight: ${theme.typography.weight.semibold};
|
font-weight: ${theme.typography.weight.semibold};
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
`,
|
`,
|
||||||
optionFocused: css`
|
optionFocused: css`
|
||||||
|
label: grafana-select-option-focused;
|
||||||
background: ${optionBgHover};
|
background: ${optionBgHover};
|
||||||
border-image: linear-gradient(#f05a28 30%, #fbca0a 99%);
|
border-image: linear-gradient(#f05a28 30%, #fbca0a 99%);
|
||||||
border-image-slice: 1;
|
border-image-slice: 1;
|
||||||
@@ -57,6 +63,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
border-left-width: 2px;
|
border-left-width: 2px;
|
||||||
`,
|
`,
|
||||||
singleValue: css`
|
singleValue: css`
|
||||||
|
label: grafana-select-single-value;
|
||||||
color: ${theme.colors.formInputText};
|
color: ${theme.colors.formInputText};
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -65,6 +72,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
`,
|
`,
|
||||||
valueContainer: css`
|
valueContainer: css`
|
||||||
|
label: grafana-select-value-container;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -74,14 +82,17 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`,
|
`,
|
||||||
valueContainerMulti: css`
|
valueContainerMulti: css`
|
||||||
|
label: grafana-select-value-container-multi;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
`,
|
`,
|
||||||
loadingMessage: css`
|
loadingMessage: css`
|
||||||
|
label: grafana-select-loading-message;
|
||||||
padding: ${theme.spacing.sm};
|
padding: ${theme.spacing.sm};
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`,
|
`,
|
||||||
multiValueContainer: css`
|
multiValueContainer: css`
|
||||||
|
label: grafana-select-multi-value-container;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -93,6 +104,7 @@ export const getSelectStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
font-size: ${theme.typography.size.sm};
|
font-size: ${theme.typography.size.sm};
|
||||||
`,
|
`,
|
||||||
multiValueRemove: css`
|
multiValueRemove: css`
|
||||||
|
label: grafana-select-multi-value-remove;
|
||||||
margin: 0 ${theme.spacing.xs};
|
margin: 0 ${theme.spacing.xs};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { MappingType, RangeMap, SelectableValue, ValueMap, ValueMapping } from '
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
valueMapping: ValueMapping;
|
valueMapping: ValueMapping;
|
||||||
updateValueMapping: (valueMapping: ValueMapping) => void;
|
onUpdate: (value: ValueMapping) => void;
|
||||||
removeValueMapping: () => void;
|
onRemove: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAPPING_OPTIONS: Array<SelectableValue<MappingType>> = [
|
const MAPPING_OPTIONS: Array<SelectableValue<MappingType>> = [
|
||||||
@@ -16,27 +16,27 @@ const MAPPING_OPTIONS: Array<SelectableValue<MappingType>> = [
|
|||||||
{ value: MappingType.RangeToText, label: 'Range' },
|
{ value: MappingType.RangeToText, label: 'Range' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const MappingRow: React.FC<Props> = ({ valueMapping, updateValueMapping, removeValueMapping }) => {
|
export const MappingRow: React.FC<Props> = ({ valueMapping, onUpdate, onRemove }) => {
|
||||||
const { type } = valueMapping;
|
const { type } = valueMapping;
|
||||||
|
|
||||||
const onMappingValueChange = (value: string) => {
|
const onMappingValueChange = (value: string) => {
|
||||||
updateValueMapping({ ...valueMapping, value: value });
|
onUpdate({ ...valueMapping, value: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMappingFromChange = (value: string) => {
|
const onMappingFromChange = (value: string) => {
|
||||||
updateValueMapping({ ...valueMapping, from: value });
|
onUpdate({ ...valueMapping, from: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMappingToChange = (value: string) => {
|
const onMappingToChange = (value: string) => {
|
||||||
updateValueMapping({ ...valueMapping, to: value });
|
onUpdate({ ...valueMapping, to: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMappingTextChange = (value: string) => {
|
const onMappingTextChange = (value: string) => {
|
||||||
updateValueMapping({ ...valueMapping, text: value });
|
onUpdate({ ...valueMapping, text: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMappingTypeChange = (mappingType: MappingType) => {
|
const onMappingTypeChange = (mappingType: MappingType) => {
|
||||||
updateValueMapping({ ...valueMapping, type: mappingType });
|
onUpdate({ ...valueMapping, type: mappingType });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyDown = (handler: (value: string) => void) => (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const onKeyDown = (handler: (value: string) => void) => (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
@@ -103,7 +103,7 @@ export const MappingRow: React.FC<Props> = ({ valueMapping, updateValueMapping,
|
|||||||
const label = (
|
const label = (
|
||||||
<HorizontalGroup justify="space-between" align="center">
|
<HorizontalGroup justify="space-between" align="center">
|
||||||
<Label>Mapping type</Label>
|
<Label>Mapping type</Label>
|
||||||
<IconButton name="times" onClick={removeValueMapping} aria-label="ValueMappingsEditor remove button" />
|
<IconButton name="times" onClick={onRemove} aria-label="ValueMappingsEditor remove button" />
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,5 +8,5 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const basic = () => {
|
export const basic = () => {
|
||||||
return <ValueMappingsEditor valueMappings={[]} onChange={action('Mapping changed')} />;
|
return <ValueMappingsEditor value={[]} onChange={action('Mapping changed')} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const setup = (spy?: any, propOverrides?: object) => {
|
|||||||
spy(mappings);
|
spy(mappings);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
valueMappings: [
|
value: [
|
||||||
{ id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
{ id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
||||||
{ id: 2, type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
{ id: 2, type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
||||||
],
|
],
|
||||||
@@ -64,11 +64,11 @@ describe('Next id to add', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should default to 0', () => {
|
it('should default to id 1', () => {
|
||||||
const onChangeSpy = jest.fn();
|
const onChangeSpy = jest.fn();
|
||||||
const wrapper = setup(onChangeSpy, { valueMappings: [] });
|
const wrapper = setup(onChangeSpy, { value: [] });
|
||||||
const add = wrapper.find('*[aria-label="ValueMappingsEditor add mapping button"]');
|
const add = wrapper.find('*[aria-label="ValueMappingsEditor add mapping button"]');
|
||||||
add.at(0).simulate('click');
|
add.at(0).simulate('click');
|
||||||
expect(onChangeSpy).toBeCalledWith([{ id: 0, type: MappingType.ValueToText, from: '', to: '', text: '' }]);
|
expect(onChangeSpy).toBeCalledWith([{ id: 1, type: MappingType.ValueToText, from: '', to: '', text: '' }]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,65 +4,48 @@ import { Button } from '../Button/Button';
|
|||||||
import { MappingRow } from './MappingRow';
|
import { MappingRow } from './MappingRow';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
valueMappings?: ValueMapping[];
|
value: ValueMapping[];
|
||||||
onChange: (valueMappings: ValueMapping[]) => void;
|
onChange: (valueMappings: ValueMapping[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ValueMappingsEditor: React.FC<Props> = ({ valueMappings, onChange, children }) => {
|
export const ValueMappingsEditor: React.FC<Props> = ({ value, onChange, children }) => {
|
||||||
const onAdd = () => {
|
const onAdd = () => {
|
||||||
let update = valueMappings;
|
|
||||||
const defaultMapping = {
|
const defaultMapping = {
|
||||||
type: MappingType.ValueToText,
|
type: MappingType.ValueToText,
|
||||||
from: '',
|
from: '',
|
||||||
to: '',
|
to: '',
|
||||||
text: '',
|
text: '',
|
||||||
};
|
};
|
||||||
const id = update && update.length > 0 ? Math.max(...update.map(v => v.id)) + 1 : 0;
|
|
||||||
|
|
||||||
if (update) {
|
const id = Math.max(...value.map(v => v.id), 0) + 1;
|
||||||
update.push({
|
|
||||||
|
onChange([
|
||||||
|
...value,
|
||||||
|
{
|
||||||
id,
|
id,
|
||||||
...defaultMapping,
|
...defaultMapping,
|
||||||
});
|
},
|
||||||
} else {
|
]);
|
||||||
update = [
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
...defaultMapping,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange(update);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemove = (index: number) => {
|
const onRemove = (index: number) => {
|
||||||
const update = valueMappings;
|
onChange(value.filter((_, i) => i !== index));
|
||||||
update!.splice(index, 1);
|
|
||||||
onChange(update!);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMappingChange = (index: number, value: ValueMapping) => {
|
const onMappingChange = (update: ValueMapping) => {
|
||||||
const update = valueMappings;
|
onChange(value.map(item => (item.id === update.id ? update : item)));
|
||||||
update![index] = value;
|
|
||||||
onChange(update!);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{valueMappings && valueMappings.length > 0 && (
|
{value.map((valueMapping, index) => (
|
||||||
<>
|
<MappingRow
|
||||||
{valueMappings.length > 0 &&
|
key={`${valueMapping.text}-${index}`}
|
||||||
valueMappings.map((valueMapping, index) => (
|
valueMapping={valueMapping}
|
||||||
<MappingRow
|
onUpdate={onMappingChange}
|
||||||
key={`${valueMapping.text}-${index}`}
|
onRemove={() => onRemove(index)}
|
||||||
valueMapping={valueMapping}
|
/>
|
||||||
updateValueMapping={value => onMappingChange(index, value)}
|
))}
|
||||||
removeValueMapping={() => onRemove(index)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
icon="plus"
|
icon="plus"
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>>
|
|||||||
|
|
||||||
const repeaterStyle: React.CSSProperties = {
|
const repeaterStyle: React.CSSProperties = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
overflow: minVizHeight ? 'hidden scroll' : 'visible',
|
overflow: minVizHeight ? 'hidden auto' : 'hidden',
|
||||||
};
|
};
|
||||||
|
|
||||||
let vizHeight = height;
|
let vizHeight = height;
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export const getStandardFieldConfigs = () => {
|
|||||||
{ value: 80, color: 'red' },
|
{ value: 80, color: 'red' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
shouldApply: field => field.type === FieldType.number,
|
shouldApply: () => true,
|
||||||
category: ['Thresholds'],
|
category: ['Thresholds'],
|
||||||
getItemsCount: value => (value ? value.steps.length : 0),
|
getItemsCount: value => (value ? value.steps.length : 0),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@jaegertracing/jaeger-ui-components",
|
"name": "@jaegertracing/jaeger-ui-components",
|
||||||
"version": "7.2.0-pre.0",
|
"version": "7.2.2",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
"typescript": "3.9.3"
|
"typescript": "3.9.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grafana/data": "7.2.0-pre.0",
|
"@grafana/data": "7.2.2",
|
||||||
"@grafana/ui": "7.2.0-pre.0",
|
"@grafana/ui": "7.2.2",
|
||||||
"@types/classnames": "^2.2.7",
|
"@types/classnames": "^2.2.7",
|
||||||
"@types/deep-freeze": "^0.1.1",
|
"@types/deep-freeze": "^0.1.1",
|
||||||
"@types/hoist-non-react-statics": "^3.3.1",
|
"@types/hoist-non-react-statics": "^3.3.1",
|
||||||
|
|||||||
@@ -130,6 +130,10 @@ const getStyles = createStyle((theme: Theme) => {
|
|||||||
font-size: 1.78em;
|
font-size: 1.78em;
|
||||||
margin-right: 0.15em;
|
margin-right: 0.15em;
|
||||||
`,
|
`,
|
||||||
|
TracePageHeaderTraceId: css`
|
||||||
|
label: TracePageHeaderTraceId;
|
||||||
|
white-space: nowrap;
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -238,7 +242,7 @@ export default function TracePageHeader(props: TracePageHeaderEmbedProps) {
|
|||||||
const title = (
|
const title = (
|
||||||
<h1 className={cx(styles.TracePageHeaderTitle, canCollapse && styles.TracePageHeaderTitleCollapsible)}>
|
<h1 className={cx(styles.TracePageHeaderTitle, canCollapse && styles.TracePageHeaderTitleCollapsible)}>
|
||||||
<TraceName traceName={getTraceName(trace.spans)} />{' '}
|
<TraceName traceName={getTraceName(trace.spans)} />{' '}
|
||||||
<small className={uTxMuted}>{trace.traceID.slice(0, 7)}</small>
|
<small className={cx(styles.TracePageHeaderTraceId, uTxMuted)}>{trace.traceID}</small>
|
||||||
</h1>
|
</h1>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -225,16 +225,26 @@ export default function SpanDetail(props: SpanDetailProps) {
|
|||||||
label="Stack trace"
|
label="Stack trace"
|
||||||
data={stackTraces}
|
data={stackTraces}
|
||||||
isOpen={isStackTracesOpen}
|
isOpen={isStackTracesOpen}
|
||||||
TextComponent={textComponentProps => (
|
TextComponent={textComponentProps => {
|
||||||
<TextArea
|
let text;
|
||||||
className={styles.Textarea}
|
if (textComponentProps.data?.length > 1) {
|
||||||
style={{ cursor: 'unset' }}
|
text = textComponentProps.data
|
||||||
readOnly
|
.map((stackTrace, index) => `StackTrace ${index + 1}:\n${stackTrace}`)
|
||||||
cols={10}
|
.join('\n');
|
||||||
rows={10}
|
} else {
|
||||||
value={textComponentProps.data}
|
text = textComponentProps.data?.[0];
|
||||||
/>
|
}
|
||||||
)}
|
return (
|
||||||
|
<TextArea
|
||||||
|
className={styles.Textarea}
|
||||||
|
style={{ cursor: 'unset' }}
|
||||||
|
readOnly
|
||||||
|
cols={10}
|
||||||
|
rows={10}
|
||||||
|
value={text}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
onToggle={() => stackTracesToggle(spanID)}
|
onToggle={() => stackTracesToggle(spanID)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/alerting"
|
"github.com/grafana/grafana/pkg/services/alerting"
|
||||||
"github.com/grafana/grafana/pkg/services/guardian"
|
"github.com/grafana/grafana/pkg/services/guardian"
|
||||||
"github.com/grafana/grafana/pkg/services/search"
|
"github.com/grafana/grafana/pkg/services/search"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ValidateOrgAlert(c *models.ReqContext) {
|
func ValidateOrgAlert(c *models.ReqContext) {
|
||||||
@@ -287,13 +288,12 @@ func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotific
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.Dispatch(&cmd); err != nil {
|
if err := bus.Dispatch(&cmd); err != nil {
|
||||||
|
if err == models.ErrAlertNotificationNotFound {
|
||||||
|
return Error(404, err.Error(), err)
|
||||||
|
}
|
||||||
return Error(500, "Failed to update alert notification", err)
|
return Error(500, "Failed to update alert notification", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Result == nil {
|
|
||||||
return Error(404, "Alert notification not found", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := models.GetAlertNotificationsQuery{
|
query := models.GetAlertNotificationsQuery{
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
Id: cmd.Id,
|
Id: cmd.Id,
|
||||||
@@ -316,13 +316,12 @@ func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.Dispatch(&cmd); err != nil {
|
if err := bus.Dispatch(&cmd); err != nil {
|
||||||
|
if err == models.ErrAlertNotificationNotFound {
|
||||||
|
return Error(404, err.Error(), nil)
|
||||||
|
}
|
||||||
return Error(500, "Failed to update alert notification", err)
|
return Error(500, "Failed to update alert notification", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Result == nil {
|
|
||||||
return Error(404, "Alert notification not found", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := models.GetAlertNotificationsWithUidQuery{
|
query := models.GetAlertNotificationsWithUidQuery{
|
||||||
OrgId: cmd.OrgId,
|
OrgId: cmd.OrgId,
|
||||||
Uid: cmd.Uid,
|
Uid: cmd.Uid,
|
||||||
@@ -390,6 +389,9 @@ func DeleteAlertNotification(c *models.ReqContext) Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.Dispatch(&cmd); err != nil {
|
if err := bus.Dispatch(&cmd); err != nil {
|
||||||
|
if err == models.ErrAlertNotificationNotFound {
|
||||||
|
return Error(404, err.Error(), nil)
|
||||||
|
}
|
||||||
return Error(500, "Failed to delete alert notification", err)
|
return Error(500, "Failed to delete alert notification", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,10 +405,16 @@ func DeleteAlertNotificationByUID(c *models.ReqContext) Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.Dispatch(&cmd); err != nil {
|
if err := bus.Dispatch(&cmd); err != nil {
|
||||||
|
if err == models.ErrAlertNotificationNotFound {
|
||||||
|
return Error(404, err.Error(), nil)
|
||||||
|
}
|
||||||
return Error(500, "Failed to delete alert notification", err)
|
return Error(500, "Failed to delete alert notification", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Success("Notification deleted")
|
return JSON(200, util.DynMap{
|
||||||
|
"message": "Notification deleted",
|
||||||
|
"id": cmd.DeletedAlertNotificationId,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//POST /api/alert-notifications/test
|
//POST /api/alert-notifications/test
|
||||||
|
|||||||
@@ -436,8 +436,5 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
r.Get("/api/snapshots-delete/:deleteKey", reqSnapshotPublicModeOrSignedIn, Wrap(DeleteDashboardSnapshotByDeleteKey))
|
r.Get("/api/snapshots-delete/:deleteKey", reqSnapshotPublicModeOrSignedIn, Wrap(DeleteDashboardSnapshotByDeleteKey))
|
||||||
r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot))
|
r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot))
|
||||||
|
|
||||||
// Health check
|
|
||||||
r.Get("/api/health", hs.healthHandler)
|
|
||||||
|
|
||||||
r.Get("/*", reqSignedIn, hs.Index)
|
r.Get("/*", reqSignedIn, hs.Index)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -336,7 +336,12 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
|
|||||||
Delims: macaron.Delims{Left: "[[", Right: "]]"},
|
Delims: macaron.Delims{Left: "[[", Right: "]]"},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// These endpoints are used for monitoring the Grafana instance
|
||||||
|
// and should not be redirect or rejected.
|
||||||
|
m.Use(hs.healthzHandler)
|
||||||
|
m.Use(hs.apiHealthHandler)
|
||||||
m.Use(hs.metricsEndpoint)
|
m.Use(hs.metricsEndpoint)
|
||||||
|
|
||||||
m.Use(middleware.GetContextHandler(
|
m.Use(middleware.GetContextHandler(
|
||||||
hs.AuthTokenService,
|
hs.AuthTokenService,
|
||||||
hs.RemoteCacheService,
|
hs.RemoteCacheService,
|
||||||
@@ -375,7 +380,24 @@ func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) {
|
|||||||
ServeHTTP(ctx.Resp, ctx.Req.Request)
|
ServeHTTP(ctx.Resp, ctx.Req.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) healthHandler(ctx *macaron.Context) {
|
// healthzHandler always return 200 - Ok if Grafana's web server is running
|
||||||
|
func (hs *HTTPServer) healthzHandler(ctx *macaron.Context) {
|
||||||
|
notHeadOrGet := ctx.Req.Method != http.MethodGet && ctx.Req.Method != http.MethodHead
|
||||||
|
if notHeadOrGet || ctx.Req.URL.Path != "/healthz" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.WriteHeader(200)
|
||||||
|
_, err := ctx.Resp.Write([]byte("Ok"))
|
||||||
|
if err != nil {
|
||||||
|
hs.log.Error("could not write to response", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiHealthHandler will return ok if Grafana's web server is running and it
|
||||||
|
// can access the database. If the database cannot be access it will return
|
||||||
|
// http status code 503.
|
||||||
|
func (hs *HTTPServer) apiHealthHandler(ctx *macaron.Context) {
|
||||||
notHeadOrGet := ctx.Req.Method != http.MethodGet && ctx.Req.Method != http.MethodHead
|
notHeadOrGet := ctx.Req.Method != http.MethodGet && ctx.Req.Method != http.MethodHead
|
||||||
if notHeadOrGet || ctx.Req.URL.Path != "/api/health" {
|
if notHeadOrGet || ctx.Req.URL.Path != "/api/health" {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -327,10 +327,6 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
|
|||||||
Children: []*dtos.NavLink{},
|
Children: []*dtos.NavLink{},
|
||||||
})
|
})
|
||||||
|
|
||||||
sort.SliceStable(navTree, func(i, j int) bool {
|
|
||||||
return navTree[i].SortWeight < navTree[j].SortWeight
|
|
||||||
})
|
|
||||||
|
|
||||||
return navTree, nil
|
return navTree, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,6 +430,10 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
|
|||||||
|
|
||||||
hs.HooksService.RunIndexDataHooks(&data, c)
|
hs.HooksService.RunIndexDataHooks(&data, c)
|
||||||
|
|
||||||
|
sort.SliceStable(data.NavTree, func(i, j int) bool {
|
||||||
|
return data.NavTree[i].SortWeight < data.NavTree[j].SortWeight
|
||||||
|
})
|
||||||
|
|
||||||
return &data, nil
|
return &data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ func (hs *HTTPServer) loginUserWithUser(user *models.User, c *models.ReqContext)
|
|||||||
}
|
}
|
||||||
|
|
||||||
hs.log.Info("Successful Login", "User", user.Email)
|
hs.log.Info("Successful Login", "User", user.Email)
|
||||||
middleware.WriteSessionCookie(c, userToken.UnhashedToken, hs.Cfg.LoginMaxLifetimeDays)
|
middleware.WriteSessionCookie(c, userToken.UnhashedToken, hs.Cfg.LoginMaxLifetime)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,10 @@ func inviteExistingUserToOrg(c *models.ReqContext, user *models.User, inviteDto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Success(fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName))
|
return JSON(200, util.DynMap{
|
||||||
|
"message": fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName),
|
||||||
|
"userId": user.Id,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func RevokeInvite(c *models.ReqContext) Response {
|
func RevokeInvite(c *models.ReqContext) Response {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// POST /api/org/users
|
// POST /api/org/users
|
||||||
@@ -35,12 +36,18 @@ func addOrgUserHelper(cmd models.AddOrgUserCommand) Response {
|
|||||||
|
|
||||||
if err := bus.Dispatch(&cmd); err != nil {
|
if err := bus.Dispatch(&cmd); err != nil {
|
||||||
if err == models.ErrOrgUserAlreadyAdded {
|
if err == models.ErrOrgUserAlreadyAdded {
|
||||||
return Error(409, "User is already member of this organization", nil)
|
return JSON(409, util.DynMap{
|
||||||
|
"message": "User is already member of this organization",
|
||||||
|
"userId": cmd.UserId,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return Error(500, "Could not add user to organization", err)
|
return Error(500, "Could not add user to organization", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Success("User added to organization")
|
return JSON(200, util.DynMap{
|
||||||
|
"message": "User added to organization",
|
||||||
|
"userId": cmd.UserId,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/org/users
|
// GET /api/org/users
|
||||||
|
|||||||
@@ -22,22 +22,24 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
|
|||||||
SecureJsonData: ds.SecureJsonData.Decrypt(),
|
SecureJsonData: ds.SecureJsonData.Decrypt(),
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolatedURL, err := InterpolateString(route.URL, data)
|
if len(route.URL) > 0 {
|
||||||
if err != nil {
|
interpolatedURL, err := InterpolateString(route.URL, data)
|
||||||
logger.Error("Error interpolating proxy url", "error", err)
|
if err != nil {
|
||||||
return
|
logger.Error("Error interpolating proxy url", "error", err)
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
routeURL, err := url.Parse(interpolatedURL)
|
routeURL, err := url.Parse(interpolatedURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error parsing plugin route url", "error", err)
|
logger.Error("Error parsing plugin route url", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.URL.Scheme = routeURL.Scheme
|
req.URL.Scheme = routeURL.Scheme
|
||||||
req.URL.Host = routeURL.Host
|
req.URL.Host = routeURL.Host
|
||||||
req.Host = routeURL.Host
|
req.Host = routeURL.Host
|
||||||
req.URL.Path = util.JoinURLFragments(routeURL.Path, proxyPath)
|
req.URL.Path = util.JoinURLFragments(routeURL.Path, proxyPath)
|
||||||
|
}
|
||||||
|
|
||||||
if err := addQueryString(req, route, data); err != nil {
|
if err := addQueryString(req, route, data); err != nil {
|
||||||
logger.Error("Failed to render plugin URL query string", "error", err)
|
logger.Error("Failed to render plugin URL query string", "error", err)
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ func TestDSRouteRule(t *testing.T) {
|
|||||||
{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
|
{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Path: "api/restricted",
|
||||||
|
ReqRole: models.ROLE_ADMIN,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +120,17 @@ func TestDSRouteRule(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("When matching route path with no url", func() {
|
||||||
|
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
proxy.route = plugin.Routes[4]
|
||||||
|
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
|
||||||
|
|
||||||
|
Convey("Should not replace request url", func() {
|
||||||
|
So(req.URL.String(), ShouldEqual, "http://localhost/asd")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Convey("Validating request", func() {
|
Convey("Validating request", func() {
|
||||||
Convey("plugin route with valid role", func() {
|
Convey("plugin route with valid role", func() {
|
||||||
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", &setting.Cfg{})
|
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", &setting.Cfg{})
|
||||||
|
|||||||
@@ -513,6 +513,26 @@ func SetBuildInformation(version, revision, branch string) {
|
|||||||
grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version(), edition).Set(1)
|
grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version(), edition).Set(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetEnvironmentInformation exposes environment values provided by the operators as an `_info` metric.
|
||||||
|
// If there are no environment metrics labels configured, this metric will not be exposed.
|
||||||
|
func SetEnvironmentInformation(labels map[string]string) error {
|
||||||
|
if len(labels) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
grafanaEnvironmentInfo := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "environment_info",
|
||||||
|
Help: "A metric with a constant '1' value labeled by environment information about the running instance.",
|
||||||
|
Namespace: ExporterName,
|
||||||
|
ConstLabels: labels,
|
||||||
|
})
|
||||||
|
|
||||||
|
prometheus.MustRegister(grafanaEnvironmentInfo)
|
||||||
|
|
||||||
|
grafanaEnvironmentInfo.Set(1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func SetPluginBuildInformation(pluginID, pluginType, version string) {
|
func SetPluginBuildInformation(pluginID, pluginType, version string) {
|
||||||
grafanaPluginBuildInfoDesc.WithLabelValues(pluginID, pluginType, version).Set(1)
|
grafanaPluginBuildInfoDesc.WithLabelValues(pluginID, pluginType, version).Set(1)
|
||||||
}
|
}
|
||||||
|
|||||||
31
pkg/infra/metrics/metricutil/utils.go
Normal file
31
pkg/infra/metrics/metricutil/utils.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package metricutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SanitizeLabelName removes all invalid chars from the label name.
|
||||||
|
// If the label name is empty or contains only invalid chars, it
|
||||||
|
// will return an error.
|
||||||
|
func SanitizeLabelName(name string) (string, error) {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return "", errors.New("label name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := strings.Builder{}
|
||||||
|
for i, b := range name {
|
||||||
|
if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0) {
|
||||||
|
out.WriteRune(b)
|
||||||
|
} else if b == ' ' {
|
||||||
|
out.WriteRune('_')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Len() == 0 {
|
||||||
|
return "", fmt.Errorf("label name only contains invalid chars: %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.String(), nil
|
||||||
|
}
|
||||||
32
pkg/infra/metrics/metricutil/utils_test.go
Normal file
32
pkg/infra/metrics/metricutil/utils_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package metricutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLabelNameSanitization(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{input: "job", expected: "job"},
|
||||||
|
{input: "job._loal['", expected: "job_loal"},
|
||||||
|
{input: "", expected: "", err: true},
|
||||||
|
{input: ";;;", expected: "", err: true},
|
||||||
|
{input: "Data source", expected: "Data_source"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
got, err := SanitizeLabelName(tc.input)
|
||||||
|
if tc.err {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -261,22 +261,21 @@ func rotateEndOfRequestFunc(ctx *models.ReqContext, authTokenService models.User
|
|||||||
}
|
}
|
||||||
|
|
||||||
if rotated {
|
if rotated {
|
||||||
WriteSessionCookie(ctx, token.UnhashedToken, setting.LoginMaxLifetimeDays)
|
WriteSessionCookie(ctx, token.UnhashedToken, setting.LoginMaxLifetime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteSessionCookie(ctx *models.ReqContext, value string, maxLifetimeDays int) {
|
func WriteSessionCookie(ctx *models.ReqContext, value string, maxLifetime time.Duration) {
|
||||||
if setting.Env == setting.DEV {
|
if setting.Env == setting.DEV {
|
||||||
ctx.Logger.Info("New token", "unhashed token", value)
|
ctx.Logger.Info("New token", "unhashed token", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxAge int
|
var maxAge int
|
||||||
if maxLifetimeDays <= 0 {
|
if maxLifetime <= 0 {
|
||||||
maxAge = -1
|
maxAge = -1
|
||||||
} else {
|
} else {
|
||||||
maxAgeHours := (time.Duration(setting.LoginMaxLifetimeDays) * 24 * time.Hour) + time.Hour
|
maxAge = int(maxLifetime.Seconds())
|
||||||
maxAge = int(maxAgeHours.Seconds())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteCookie(ctx.Resp, setting.LoginCookieName, url.QueryEscape(value), maxAge, newCookieOptions)
|
WriteCookie(ctx.Resp, setting.LoginCookieName, url.QueryEscape(value), maxAge, newCookieOptions)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
|
"github.com/grafana/grafana/pkg/components/gtime"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||||
authproxy "github.com/grafana/grafana/pkg/middleware/auth_proxy"
|
authproxy "github.com/grafana/grafana/pkg/middleware/auth_proxy"
|
||||||
@@ -253,8 +254,7 @@ func TestMiddlewareContext(t *testing.T) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
maxAgeHours := (time.Duration(setting.LoginMaxLifetimeDays) * 24 * time.Hour)
|
maxAge := int(setting.LoginMaxLifetime.Seconds())
|
||||||
maxAge := (maxAgeHours + time.Hour).Seconds()
|
|
||||||
|
|
||||||
sameSitePolicies := []http.SameSite{
|
sameSitePolicies := []http.SameSite{
|
||||||
http.SameSiteNoneMode,
|
http.SameSiteNoneMode,
|
||||||
@@ -272,7 +272,7 @@ func TestMiddlewareContext(t *testing.T) {
|
|||||||
Value: "rotated",
|
Value: "rotated",
|
||||||
Path: expectedCookiePath,
|
Path: expectedCookiePath,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
MaxAge: int(maxAge),
|
MaxAge: maxAge,
|
||||||
Secure: setting.CookieSecure,
|
Secure: setting.CookieSecure,
|
||||||
SameSite: sameSitePolicy,
|
SameSite: sameSitePolicy,
|
||||||
}
|
}
|
||||||
@@ -303,7 +303,7 @@ func TestMiddlewareContext(t *testing.T) {
|
|||||||
Value: "rotated",
|
Value: "rotated",
|
||||||
Path: expectedCookiePath,
|
Path: expectedCookiePath,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
MaxAge: int(maxAge),
|
MaxAge: maxAge,
|
||||||
Secure: setting.CookieSecure,
|
Secure: setting.CookieSecure,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,7 +546,7 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc) {
|
|||||||
defer bus.ClearBusHandlers()
|
defer bus.ClearBusHandlers()
|
||||||
|
|
||||||
setting.LoginCookieName = "grafana_session"
|
setting.LoginCookieName = "grafana_session"
|
||||||
setting.LoginMaxLifetimeDays = 30
|
setting.LoginMaxLifetime, _ = gtime.ParseInterval("30d")
|
||||||
|
|
||||||
sc := &scenarioContext{}
|
sc := &scenarioContext{}
|
||||||
|
|
||||||
@@ -637,7 +637,7 @@ func TestTokenRotationAtEndOfRequest(t *testing.T) {
|
|||||||
|
|
||||||
func initTokenRotationTest(ctx context.Context) (*models.ReqContext, *httptest.ResponseRecorder, error) {
|
func initTokenRotationTest(ctx context.Context) (*models.ReqContext, *httptest.ResponseRecorder, error) {
|
||||||
setting.LoginCookieName = "login_token"
|
setting.LoginCookieName = "login_token"
|
||||||
setting.LoginMaxLifetimeDays = 7
|
setting.LoginMaxLifetime, _ = gtime.ParseInterval("7d")
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
req, err := http.NewRequestWithContext(ctx, "", "", nil)
|
req, err := http.NewRequestWithContext(ctx, "", "", nil)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user