mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
commit
8011a6f45b
@ -127,7 +127,7 @@ jobs:
|
|||||||
|
|
||||||
build-all:
|
build-all:
|
||||||
docker:
|
docker:
|
||||||
- image: grafana/build-container:1.2.1
|
- image: grafana/build-container:1.2.2
|
||||||
working_directory: /go/src/github.com/grafana/grafana
|
working_directory: /go/src/github.com/grafana/grafana
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@ -200,51 +200,51 @@ jobs:
|
|||||||
- dist/grafana*
|
- dist/grafana*
|
||||||
|
|
||||||
grafana-docker-master:
|
grafana-docker-master:
|
||||||
docker:
|
machine:
|
||||||
- image: docker:stable-git
|
image: circleci/classic:201808-01
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
- setup_remote_docker
|
|
||||||
- run: docker info
|
- run: docker info
|
||||||
- run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker
|
- run: docker run --privileged linuxkit/binfmt:v0.6
|
||||||
|
- run: cp dist/grafana-latest.linux-*.tar.gz packaging/docker
|
||||||
- run: cd packaging/docker && ./build-deploy.sh "master-${CIRCLE_SHA1}"
|
- run: cd packaging/docker && ./build-deploy.sh "master-${CIRCLE_SHA1}"
|
||||||
- run: rm packaging/docker/grafana-latest.linux-x64.tar.gz
|
- run: rm packaging/docker/grafana-latest.linux-*.tar.gz
|
||||||
- run: cp enterprise-dist/grafana-enterprise-*.linux-amd64.tar.gz packaging/docker/grafana-latest.linux-x64.tar.gz
|
- run: cp enterprise-dist/grafana-enterprise-*.linux-amd64.tar.gz packaging/docker/grafana-latest.linux-x64.tar.gz
|
||||||
- run: cd packaging/docker && ./build-enterprise.sh "master"
|
- run: cd packaging/docker && ./build-enterprise.sh "master"
|
||||||
|
|
||||||
|
|
||||||
grafana-docker-pr:
|
grafana-docker-pr:
|
||||||
docker:
|
machine:
|
||||||
- image: docker:stable-git
|
image: circleci/classic:201808-01
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
- setup_remote_docker
|
|
||||||
- run: docker info
|
- run: docker info
|
||||||
- run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker
|
- run: docker run --privileged linuxkit/binfmt:v0.6
|
||||||
|
- run: cp dist/grafana-latest.linux-*.tar.gz packaging/docker
|
||||||
- run: cd packaging/docker && ./build.sh "${CIRCLE_SHA1}"
|
- run: cd packaging/docker && ./build.sh "${CIRCLE_SHA1}"
|
||||||
|
|
||||||
grafana-docker-release:
|
grafana-docker-release:
|
||||||
docker:
|
machine:
|
||||||
- image: docker:stable-git
|
image: circleci/classic:201808-01
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
- setup_remote_docker
|
- run: docker info
|
||||||
- run: docker info
|
- run: docker run --privileged linuxkit/binfmt:v0.6
|
||||||
- run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker
|
- run: cp dist/grafana-latest.linux-*.tar.gz packaging/docker
|
||||||
- run: cd packaging/docker && ./build-deploy.sh "${CIRCLE_TAG}"
|
- run: cd packaging/docker && ./build-deploy.sh "${CIRCLE_TAG}"
|
||||||
- run: rm packaging/docker/grafana-latest.linux-x64.tar.gz
|
- run: rm packaging/docker/grafana-latest.linux-*.tar.gz
|
||||||
- run: cp enterprise-dist/grafana-enterprise-*.linux-amd64.tar.gz packaging/docker/grafana-latest.linux-x64.tar.gz
|
- run: cp enterprise-dist/grafana-enterprise-*.linux-amd64.tar.gz packaging/docker/grafana-latest.linux-x64.tar.gz
|
||||||
- run: cd packaging/docker && ./build-enterprise.sh "${CIRCLE_TAG}"
|
- run: cd packaging/docker && ./build-enterprise.sh "${CIRCLE_TAG}"
|
||||||
|
|
||||||
build-enterprise:
|
build-enterprise:
|
||||||
docker:
|
docker:
|
||||||
- image: grafana/build-container:1.2.1
|
- image: grafana/build-container:1.2.2
|
||||||
working_directory: /go/src/github.com/grafana/grafana
|
working_directory: /go/src/github.com/grafana/grafana
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@ -276,7 +276,7 @@ jobs:
|
|||||||
|
|
||||||
build-all-enterprise:
|
build-all-enterprise:
|
||||||
docker:
|
docker:
|
||||||
- image: grafana/build-container:1.2.1
|
- image: grafana/build-container:1.2.2
|
||||||
working_directory: /go/src/github.com/grafana/grafana
|
working_directory: /go/src/github.com/grafana/grafana
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@ -323,7 +323,7 @@ jobs:
|
|||||||
|
|
||||||
deploy-enterprise-master:
|
deploy-enterprise-master:
|
||||||
docker:
|
docker:
|
||||||
- image: grafana/grafana-ci-deploy:1.0.0
|
- image: grafana/grafana-ci-deploy:1.1.0
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
@ -346,7 +346,7 @@ jobs:
|
|||||||
|
|
||||||
deploy-enterprise-release:
|
deploy-enterprise-release:
|
||||||
docker:
|
docker:
|
||||||
- image: grafana/grafana-ci-deploy:1.0.0
|
- image: grafana/grafana-ci-deploy:1.1.0
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
@ -365,10 +365,20 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Deploy to Grafana.com
|
name: Deploy to Grafana.com
|
||||||
command: './scripts/build/publish.sh --enterprise'
|
command: './scripts/build/publish.sh --enterprise'
|
||||||
|
- run:
|
||||||
|
name: Load GPG private key
|
||||||
|
command: './scripts/build/load-signing-key.sh'
|
||||||
|
- run:
|
||||||
|
name: Update Debian repository
|
||||||
|
command: './scripts/build/update_repo/update-deb.sh "enterprise" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
|
||||||
|
- run:
|
||||||
|
name: Update RPM repository
|
||||||
|
command: './scripts/build/update_repo/update-rpm.sh "enterprise" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
|
||||||
|
|
||||||
|
|
||||||
deploy-master:
|
deploy-master:
|
||||||
docker:
|
docker:
|
||||||
- image: grafana/grafana-ci-deploy:1.0.0
|
- image: grafana/grafana-ci-deploy:1.1.0
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
@ -398,8 +408,9 @@ jobs:
|
|||||||
|
|
||||||
deploy-release:
|
deploy-release:
|
||||||
docker:
|
docker:
|
||||||
- image: grafana/grafana-ci-deploy:1.0.0
|
- image: grafana/grafana-ci-deploy:1.1.0
|
||||||
steps:
|
steps:
|
||||||
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
- run:
|
- run:
|
||||||
@ -417,6 +428,15 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Deploy to Grafana.com
|
name: Deploy to Grafana.com
|
||||||
command: './scripts/build/publish.sh'
|
command: './scripts/build/publish.sh'
|
||||||
|
- run:
|
||||||
|
name: Load GPG private key
|
||||||
|
command: './scripts/build/load-signing-key.sh'
|
||||||
|
- run:
|
||||||
|
name: Update Debian repository
|
||||||
|
command: './scripts/build/update_repo/update-deb.sh "oss" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
|
||||||
|
- run:
|
||||||
|
name: Update RPM repository
|
||||||
|
command: './scripts/build/update_repo/update-rpm.sh "oss" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
|
21
CHANGELOG.md
21
CHANGELOG.md
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
* **Alerting**: Adds support for Google Hangouts Chat notifications [#11221](https://github.com/grafana/grafana/issues/11221), thx [@PatrickSchuster](https://github.com/PatrickSchuster)
|
* **Alerting**: Adds support for Google Hangouts Chat notifications [#11221](https://github.com/grafana/grafana/issues/11221), thx [@PatrickSchuster](https://github.com/PatrickSchuster)
|
||||||
|
* **Elasticsearch**: Support bucket script pipeline aggregations [#5968](https://github.com/grafana/grafana/issues/5968)
|
||||||
* **Snapshots**: Enable deletion of public snapshot [#14109](https://github.com/grafana/grafana/issues/14109)
|
* **Snapshots**: Enable deletion of public snapshot [#14109](https://github.com/grafana/grafana/issues/14109)
|
||||||
|
|
||||||
### Minor
|
### Minor
|
||||||
@ -11,15 +12,33 @@
|
|||||||
* **Auth**: Prevent password reset when login form is disabled or either LDAP or Auth Proxy is enabled [#14246](https://github.com/grafana/grafana/issues/14246), thx [@SilverFire](https://github.com/SilverFire)
|
* **Auth**: Prevent password reset when login form is disabled or either LDAP or Auth Proxy is enabled [#14246](https://github.com/grafana/grafana/issues/14246), thx [@SilverFire](https://github.com/SilverFire)
|
||||||
* **Dataproxy**: Override incoming Authorization header [#13815](https://github.com/grafana/grafana/issues/13815), thx [@kornholi](https://github.com/kornholi)
|
* **Dataproxy**: Override incoming Authorization header [#13815](https://github.com/grafana/grafana/issues/13815), thx [@kornholi](https://github.com/kornholi)
|
||||||
* **Admin**: Fix prevent removing last grafana admin permissions [#11067](https://github.com/grafana/grafana/issues/11067), thx [@danielbh](https://github.com/danielbh)
|
* **Admin**: Fix prevent removing last grafana admin permissions [#11067](https://github.com/grafana/grafana/issues/11067), thx [@danielbh](https://github.com/danielbh)
|
||||||
* **Templating**: Escaping "Custom" template variables [#13754](https://github.com/grafana/grafana/issues/13754), thx [@IntegersOfK](https://github.com/IntegersOfK)
|
* **Templating**: Escaping "Custom" template variables [#13754](https://github.com/grafana/grafana/issues/13754), thx [@IntegersOfK]req(https://github.com/IntegersOfK)
|
||||||
* **Admin**: When multiple user invitations, all links are the same as the first user who was invited [#14483](https://github.com/grafana/grafana/issues/14483)
|
* **Admin**: When multiple user invitations, all links are the same as the first user who was invited [#14483](https://github.com/grafana/grafana/issues/14483)
|
||||||
* **LDAP**: Upgrade go-ldap to v3 [#14548](https://github.com/grafana/grafana/issues/14548)
|
* **LDAP**: Upgrade go-ldap to v3 [#14548](https://github.com/grafana/grafana/issues/14548)
|
||||||
* **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard)
|
* **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard)
|
||||||
* **OAuth**: Support OAuth providers that are not RFC6749 compliant [#14562](https://github.com/grafana/grafana/issues/14562), thx [@tdabasinskas](https://github.com/tdabasinskas)
|
* **OAuth**: Support OAuth providers that are not RFC6749 compliant [#14562](https://github.com/grafana/grafana/issues/14562), thx [@tdabasinskas](https://github.com/tdabasinskas)
|
||||||
* **Units**: Add blood glucose level units mg/dL and mmol/L [#14519](https://github.com/grafana/grafana/issues/14519), thx [@kjedamzik](https://github.com/kjedamzik)
|
* **Units**: Add blood glucose level units mg/dL and mmol/L [#14519](https://github.com/grafana/grafana/issues/14519), thx [@kjedamzik](https://github.com/kjedamzik)
|
||||||
|
* **Stackdriver**: Aggregating series returns more than one series [#14581](https://github.com/grafana/grafana/issues/14581) and [#13914](https://github.com/grafana/grafana/issues/13914), thx [@kinok](https://github.com/kinok)
|
||||||
|
* **Provisioning**: Fixes bug causing infinite growth in dashboard_version table. [#12864](https://github.com/grafana/grafana/issues/12864)
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
* **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486)
|
* **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486)
|
||||||
|
* **Prometheus**: Query for annotation always uses 60s step regardless of dashboard range, fixes [#14795](https://github.com/grafana/grafana/issues/14795)
|
||||||
|
|
||||||
|
# 5.4.3 (2019-01-14)
|
||||||
|
|
||||||
|
### Tech
|
||||||
|
|
||||||
|
* **Docker**: Build and publish docker images for armv7 and arm64 [#14617](https://github.com/grafana/grafana/pull/14617), thx [@johanneswuerbach](https://github.com/johanneswuerbach)
|
||||||
|
* **Backend**: Upgrade to golang 1.11.4 [#14580](https://github.com/grafana/grafana/issues/14580)
|
||||||
|
* **MySQL** only update session in mysql database when required [#14540](https://github.com/grafana/grafana/pull/14540)
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
* **Alerting** Invalid frequency causes division by zero in alert scheduler [#14810](https://github.com/grafana/grafana/issues/14810)
|
||||||
|
* **Dashboard** Dashboard links do not update when time range changes [#14493](https://github.com/grafana/grafana/issues/14493)
|
||||||
|
* **Limits** Support more than 1000 datasources per org [#13883](https://github.com/grafana/grafana/issues/13883)
|
||||||
|
* **Backend** fix signed in user for orgId=0 result should return active org id [#14574](https://github.com/grafana/grafana/pull/14574)
|
||||||
|
* **Provisioning** Adds orgId to user dto for provisioned dashboards [#14678](https://github.com/grafana/grafana/pull/14678)
|
||||||
|
|
||||||
# 5.4.2 (2018-12-13)
|
# 5.4.2 (2018-12-13)
|
||||||
|
|
||||||
|
@ -131,7 +131,9 @@ GRAFANA_TEST_DB=postgres go test ./pkg/...
|
|||||||
|
|
||||||
If you have any idea for an improvement or found a bug, do not hesitate to open an issue.
|
If you have any idea for an improvement or found a bug, do not hesitate to open an issue.
|
||||||
And if you have time clone this repo and submit a pull request and help me make Grafana
|
And if you have time clone this repo and submit a pull request and help me make Grafana
|
||||||
the kickass metrics & devops dashboard we all dream about!
|
the kickass metrics & devops dashboard we all dream about!
|
||||||
|
|
||||||
|
Read the [contributing](https://github.com/grafana/grafana/blob/master/CONTRIBUTING.md) guide then check the [`beginner friendly`](https://github.com/grafana/grafana/issues?q=is%3Aopen+is%3Aissue+label%3A%22beginner+friendly%22) label to find issues that are easy and that we would like help with.
|
||||||
|
|
||||||
## Plugin development
|
## Plugin development
|
||||||
|
|
||||||
|
2
build.go
2
build.go
@ -164,6 +164,8 @@ func makeLatestDistCopies() {
|
|||||||
"_amd64.deb": "dist/grafana_latest_amd64.deb",
|
"_amd64.deb": "dist/grafana_latest_amd64.deb",
|
||||||
".x86_64.rpm": "dist/grafana-latest-1.x86_64.rpm",
|
".x86_64.rpm": "dist/grafana-latest-1.x86_64.rpm",
|
||||||
".linux-amd64.tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
|
".linux-amd64.tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
|
||||||
|
".linux-armv7.tar.gz": "dist/grafana-latest.linux-armv7.tar.gz",
|
||||||
|
".linux-arm64.tar.gz": "dist/grafana-latest.linux-arm64.tar.gz",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
@ -4,6 +4,6 @@ providers:
|
|||||||
- name: 'gdev dashboards'
|
- name: 'gdev dashboards'
|
||||||
folder: 'gdev dashboards'
|
folder: 'gdev dashboards'
|
||||||
type: file
|
type: file
|
||||||
|
updateIntervalSeconds: 15
|
||||||
options:
|
options:
|
||||||
path: devenv/dev-dashboards
|
path: devenv/dev-dashboards
|
||||||
|
|
||||||
|
1674
devenv/dev-dashboards-without-uid/panel_tests_graph.json
Normal file
1674
devenv/dev-dashboards-without-uid/panel_tests_graph.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,510 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": "-- Grafana --",
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"gnetId": null,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "gdev-testdata",
|
||||||
|
"fill": 2,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 2,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 5,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"scenarioId": "random_walk",
|
||||||
|
"target": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [
|
||||||
|
{
|
||||||
|
"colorMode": "gray",
|
||||||
|
"fill": true,
|
||||||
|
"fillColor": "rgba(255, 255, 255, 0.03)",
|
||||||
|
"from": "08:30",
|
||||||
|
"fromDayOfWeek": 1,
|
||||||
|
"line": false,
|
||||||
|
"lineColor": "rgba(255, 255, 255, 0.2)",
|
||||||
|
"op": "time",
|
||||||
|
"to": "16:45",
|
||||||
|
"toDayOfWeek": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Business Hours",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "gdev-testdata",
|
||||||
|
"fill": 2,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 8
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 2,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 5,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "",
|
||||||
|
"format": "time_series",
|
||||||
|
"intervalFactor": 1,
|
||||||
|
"refId": "A",
|
||||||
|
"scenarioId": "random_walk",
|
||||||
|
"target": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [
|
||||||
|
{
|
||||||
|
"colorMode": "red",
|
||||||
|
"fill": true,
|
||||||
|
"fillColor": "rgba(255, 255, 255, 0.03)",
|
||||||
|
"from": "20:00",
|
||||||
|
"fromDayOfWeek": 7,
|
||||||
|
"line": false,
|
||||||
|
"lineColor": "rgba(255, 255, 255, 0.2)",
|
||||||
|
"op": "time",
|
||||||
|
"to": "23:00",
|
||||||
|
"toDayOfWeek": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Sunday's 20-23",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {
|
||||||
|
"A-series": "#d683ce"
|
||||||
|
},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "gdev-testdata",
|
||||||
|
"fill": 2,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 2,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 0.5,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"scenarioId": "random_walk",
|
||||||
|
"target": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [
|
||||||
|
{
|
||||||
|
"colorMode": "custom",
|
||||||
|
"fill": true,
|
||||||
|
"fillColor": "rgba(255, 0, 0, 0.22)",
|
||||||
|
"from": "",
|
||||||
|
"fromDayOfWeek": 1,
|
||||||
|
"line": true,
|
||||||
|
"lineColor": "rgba(255, 0, 0, 0.32)",
|
||||||
|
"op": "time",
|
||||||
|
"to": "",
|
||||||
|
"toDayOfWeek": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"colorMode": "custom",
|
||||||
|
"fill": true,
|
||||||
|
"fillColor": "rgba(255, 127, 0, 0.22)",
|
||||||
|
"fromDayOfWeek": 2,
|
||||||
|
"line": true,
|
||||||
|
"lineColor": "rgba(255, 127, 0, 0.32)",
|
||||||
|
"op": "time",
|
||||||
|
"toDayOfWeek": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"colorMode": "custom",
|
||||||
|
"fill": true,
|
||||||
|
"fillColor": "rgba(255, 255, 0, 0.22)",
|
||||||
|
"fromDayOfWeek": 3,
|
||||||
|
"line": true,
|
||||||
|
"lineColor": "rgba(255, 255, 0, 0.22)",
|
||||||
|
"op": "time",
|
||||||
|
"toDayOfWeek": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"colorMode": "custom",
|
||||||
|
"fill": true,
|
||||||
|
"fillColor": "rgba(0, 255, 0, 0.22)",
|
||||||
|
"fromDayOfWeek": 4,
|
||||||
|
"line": true,
|
||||||
|
"lineColor": "rgba(0, 255, 0, 0.32)",
|
||||||
|
"op": "time",
|
||||||
|
"toDayOfWeek": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"colorMode": "custom",
|
||||||
|
"fill": true,
|
||||||
|
"fillColor": "rgba(0, 0, 255, 0.22)",
|
||||||
|
"fromDayOfWeek": 5,
|
||||||
|
"line": true,
|
||||||
|
"lineColor": "rgba(0, 0, 255, 0.32)",
|
||||||
|
"op": "time",
|
||||||
|
"toDayOfWeek": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"colorMode": "custom",
|
||||||
|
"fill": true,
|
||||||
|
"fillColor": "rgba(75, 0, 130, 0.22)",
|
||||||
|
"fromDayOfWeek": 6,
|
||||||
|
"line": true,
|
||||||
|
"lineColor": "rgba(75, 0, 130, 0.32)",
|
||||||
|
"op": "time",
|
||||||
|
"toDayOfWeek": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"colorMode": "custom",
|
||||||
|
"fill": true,
|
||||||
|
"fillColor": "rgba(148, 0, 211, 0.22)",
|
||||||
|
"fromDayOfWeek": 7,
|
||||||
|
"line": true,
|
||||||
|
"lineColor": "rgba(148, 0, 211, 0.32)",
|
||||||
|
"op": "time",
|
||||||
|
"toDayOfWeek": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Each day of week",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "gdev-testdata",
|
||||||
|
"fill": 2,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 24
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 2,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 5,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "",
|
||||||
|
"format": "time_series",
|
||||||
|
"intervalFactor": 1,
|
||||||
|
"refId": "A",
|
||||||
|
"scenarioId": "random_walk",
|
||||||
|
"target": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [
|
||||||
|
{
|
||||||
|
"colorMode": "red",
|
||||||
|
"fill": false,
|
||||||
|
"from": "05:00",
|
||||||
|
"line": true,
|
||||||
|
"op": "time"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "05:00",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": false,
|
||||||
|
"schemaVersion": 16,
|
||||||
|
"style": "dark",
|
||||||
|
"tags": [
|
||||||
|
"gdev",
|
||||||
|
"panel-tests"
|
||||||
|
],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-30d",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {
|
||||||
|
"refresh_intervals": [
|
||||||
|
"5s",
|
||||||
|
"10s",
|
||||||
|
"30s",
|
||||||
|
"1m",
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"30m",
|
||||||
|
"1h",
|
||||||
|
"2h",
|
||||||
|
"1d"
|
||||||
|
],
|
||||||
|
"time_options": [
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"1h",
|
||||||
|
"6h",
|
||||||
|
"12h",
|
||||||
|
"24h",
|
||||||
|
"2d",
|
||||||
|
"7d",
|
||||||
|
"30d"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timezone": "browser",
|
||||||
|
"title": "Panel Tests - Graph (Time Regions)",
|
||||||
|
"version": 1
|
||||||
|
}
|
3342
devenv/dev-dashboards-without-uid/panel_tests_polystat.json
Normal file
3342
devenv/dev-dashboards-without-uid/panel_tests_polystat.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,7 @@
|
|||||||
"editable": true,
|
"editable": true,
|
||||||
"gnetId": null,
|
"gnetId": null,
|
||||||
"graphTooltip": 0,
|
"graphTooltip": 0,
|
||||||
"iteration": 1542304484522,
|
"iteration": 1545263815779,
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"icon": "external link",
|
"icon": "external link",
|
||||||
@ -66,6 +66,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -168,6 +169,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -270,6 +272,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -372,6 +375,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -474,6 +478,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -576,6 +581,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -2249,6 +2255,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -2366,6 +2373,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -2483,6 +2491,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -2600,6 +2609,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -2717,6 +2727,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -2834,6 +2845,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -2951,6 +2963,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -3068,6 +3081,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -3185,6 +3199,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -3302,6 +3317,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -3419,6 +3435,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -3536,6 +3553,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -3667,6 +3685,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -3780,6 +3799,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -3893,6 +3913,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -4006,6 +4027,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -4119,6 +4141,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -4232,6 +4255,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -4345,6 +4369,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -4458,6 +4483,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -4571,6 +4597,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -4684,6 +4711,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -4797,6 +4825,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -4910,6 +4939,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -5008,6 +5038,512 @@
|
|||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 4
|
"y": 4
|
||||||
},
|
},
|
||||||
|
"id": 60,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "$version_one",
|
||||||
|
"fill": 1,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"id": 63,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"bucketAggs": [
|
||||||
|
{
|
||||||
|
"field": "@timestamp",
|
||||||
|
"id": "2",
|
||||||
|
"settings": {
|
||||||
|
"interval": "auto",
|
||||||
|
"min_doc_count": 0,
|
||||||
|
"trimEdges": 0
|
||||||
|
},
|
||||||
|
"type": "date_histogram"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"field": "select field",
|
||||||
|
"hide": true,
|
||||||
|
"id": "1",
|
||||||
|
"type": "count"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "select field",
|
||||||
|
"id": "3",
|
||||||
|
"meta": {},
|
||||||
|
"pipelineVariables": [
|
||||||
|
{
|
||||||
|
"name": "var1",
|
||||||
|
"pipelineAgg": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"script": "params.var1 * 1000"
|
||||||
|
},
|
||||||
|
"type": "bucket_script"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refId": "A",
|
||||||
|
"timeField": "@timestamp"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "count * 1000 (version one) - interval auto",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "$version_two",
|
||||||
|
"fill": 1,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"id": 64,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"bucketAggs": [
|
||||||
|
{
|
||||||
|
"field": "@timestamp",
|
||||||
|
"id": "2",
|
||||||
|
"settings": {
|
||||||
|
"interval": "auto",
|
||||||
|
"min_doc_count": 0,
|
||||||
|
"trimEdges": 0
|
||||||
|
},
|
||||||
|
"type": "date_histogram"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"field": "select field",
|
||||||
|
"hide": true,
|
||||||
|
"id": "1",
|
||||||
|
"type": "count"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "select field",
|
||||||
|
"id": "3",
|
||||||
|
"meta": {},
|
||||||
|
"pipelineVariables": [
|
||||||
|
{
|
||||||
|
"name": "var1",
|
||||||
|
"pipelineAgg": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"script": "params.var1 * 1000"
|
||||||
|
},
|
||||||
|
"type": "bucket_script"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refId": "A",
|
||||||
|
"timeField": "@timestamp"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "count * 1000 (version two) - interval auto",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "$version_one",
|
||||||
|
"fill": 1,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 13
|
||||||
|
},
|
||||||
|
"id": 65,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"bucketAggs": [
|
||||||
|
{
|
||||||
|
"field": "@timestamp",
|
||||||
|
"id": "2",
|
||||||
|
"settings": {
|
||||||
|
"interval": "auto",
|
||||||
|
"min_doc_count": 0,
|
||||||
|
"trimEdges": 0
|
||||||
|
},
|
||||||
|
"type": "date_histogram"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"field": "select field",
|
||||||
|
"hide": true,
|
||||||
|
"id": "1",
|
||||||
|
"type": "count"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "@value",
|
||||||
|
"hide": true,
|
||||||
|
"id": "3",
|
||||||
|
"meta": {},
|
||||||
|
"settings": {},
|
||||||
|
"type": "avg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "select field",
|
||||||
|
"id": "4",
|
||||||
|
"meta": {},
|
||||||
|
"pipelineVariables": [
|
||||||
|
{
|
||||||
|
"name": "var1",
|
||||||
|
"pipelineAgg": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "var2",
|
||||||
|
"pipelineAgg": "3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"script": "params.var1 * params.var2"
|
||||||
|
},
|
||||||
|
"type": "bucket_script"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refId": "A",
|
||||||
|
"timeField": "@timestamp"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "count * avg (version one) - interval auto",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"datasource": "$version_two",
|
||||||
|
"fill": 1,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 13
|
||||||
|
},
|
||||||
|
"id": 66,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"links": [],
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": false,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"bucketAggs": [
|
||||||
|
{
|
||||||
|
"field": "@timestamp",
|
||||||
|
"id": "2",
|
||||||
|
"settings": {
|
||||||
|
"interval": "auto",
|
||||||
|
"min_doc_count": 0,
|
||||||
|
"trimEdges": 0
|
||||||
|
},
|
||||||
|
"type": "date_histogram"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"field": "select field",
|
||||||
|
"hide": true,
|
||||||
|
"id": "1",
|
||||||
|
"type": "count"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "@value",
|
||||||
|
"hide": true,
|
||||||
|
"id": "3",
|
||||||
|
"meta": {},
|
||||||
|
"settings": {},
|
||||||
|
"type": "avg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "select field",
|
||||||
|
"id": "4",
|
||||||
|
"meta": {},
|
||||||
|
"pipelineVariables": [
|
||||||
|
{
|
||||||
|
"name": "var1",
|
||||||
|
"pipelineAgg": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "var2",
|
||||||
|
"pipelineAgg": "3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"script": "params.var1 * params.var2"
|
||||||
|
},
|
||||||
|
"type": "bucket_script"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refId": "A",
|
||||||
|
"timeField": "@timestamp"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "count * avg (version two) - interval auto",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Basic date histogram with bucket script aggregation",
|
||||||
|
"type": "row"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsed": true,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 1,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
"id": 54,
|
"id": 54,
|
||||||
"panels": [
|
"panels": [
|
||||||
{
|
{
|
||||||
@ -5042,6 +5578,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -5193,6 +5730,7 @@
|
|||||||
"linewidth": 1,
|
"linewidth": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"nullPointMode": "null",
|
"nullPointMode": "null",
|
||||||
|
"paceLength": 10,
|
||||||
"percentage": false,
|
"percentage": false,
|
||||||
"pointradius": 5,
|
"pointradius": 5,
|
||||||
"points": false,
|
"points": false,
|
||||||
@ -5328,8 +5866,8 @@
|
|||||||
"list": [
|
"list": [
|
||||||
{
|
{
|
||||||
"current": {
|
"current": {
|
||||||
"text": "gdev-elasticsearch-v2-metrics",
|
"text": "gdev-elasticsearch-v5-metrics",
|
||||||
"value": "gdev-elasticsearch-v2-metrics"
|
"value": "gdev-elasticsearch-v5-metrics"
|
||||||
},
|
},
|
||||||
"hide": 0,
|
"hide": 0,
|
||||||
"label": "Version One",
|
"label": "Version One",
|
||||||
@ -5343,8 +5881,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"current": {
|
"current": {
|
||||||
"text": "gdev-elasticsearch-v5-metrics",
|
"text": "gdev-elasticsearch-v6-metrics",
|
||||||
"value": "gdev-elasticsearch-v5-metrics"
|
"value": "gdev-elasticsearch-v6-metrics"
|
||||||
},
|
},
|
||||||
"hide": 0,
|
"hide": 0,
|
||||||
"label": "Version Two",
|
"label": "Version Two",
|
||||||
@ -5359,7 +5897,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"from": "now-3h",
|
"from": "now-1h",
|
||||||
"to": "now"
|
"to": "now"
|
||||||
},
|
},
|
||||||
"timepicker": {
|
"timepicker": {
|
||||||
@ -5390,5 +5928,5 @@
|
|||||||
"timezone": "",
|
"timezone": "",
|
||||||
"title": "Datasource tests - Elasticsearch comparison",
|
"title": "Datasource tests - Elasticsearch comparison",
|
||||||
"uid": "fuFWehBmk",
|
"uid": "fuFWehBmk",
|
||||||
"version": 10
|
"version": 4
|
||||||
}
|
}
|
@ -69,6 +69,7 @@ reporting-disabled = false
|
|||||||
|
|
||||||
unix-socket-enabled = false # enable http service over unix domain socket
|
unix-socket-enabled = false # enable http service over unix domain socket
|
||||||
# bind-socket = "/var/run/influxdb.sock"
|
# bind-socket = "/var/run/influxdb.sock"
|
||||||
|
flux-enabled = true
|
||||||
|
|
||||||
[subscriber]
|
[subscriber]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title = "Explore"
|
title = "Explore"
|
||||||
|
keywords = ["explore", "loki", "logs"]
|
||||||
type = "docs"
|
type = "docs"
|
||||||
[menu.docs]
|
[menu.docs]
|
||||||
name = "Explore"
|
name = "Explore"
|
||||||
@ -8,7 +9,11 @@ parent = "features"
|
|||||||
weight = 5
|
weight = 5
|
||||||
+++
|
+++
|
||||||
|
|
||||||
# Introduction
|
# Explore
|
||||||
|
|
||||||
|
> Explore is only available in Grafana 6.0 and above.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
One of the major new features of Grafana 6.0 is the new query-focused Explore workflow for troubleshooting and/or for data exploration.
|
One of the major new features of Grafana 6.0 is the new query-focused Explore workflow for troubleshooting and/or for data exploration.
|
||||||
|
|
||||||
|
@ -285,7 +285,7 @@ Content-Type: application/json
|
|||||||
HTTP/1.1 200
|
HTTP/1.1 200
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{message: "User permissions updated"}
|
{"message": "User permissions updated"}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Delete global User
|
## Delete global User
|
||||||
@ -308,7 +308,7 @@ Content-Type: application/json
|
|||||||
HTTP/1.1 200
|
HTTP/1.1 200
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{message: "User deleted"}
|
{"message": "User deleted"}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pause all alerts
|
## Pause all alerts
|
||||||
@ -339,5 +339,5 @@ JSON Body schema:
|
|||||||
HTTP/1.1 200
|
HTTP/1.1 200
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{state: "new state", message: "alerts pause/un paused", "alertsAffected": 100}
|
{"state": "new state", "message": "alerts pause/un paused", "alertsAffected": 100}
|
||||||
```
|
```
|
||||||
|
@ -105,7 +105,7 @@ POST /api/folders/nErXDvCkzz/permissions
|
|||||||
Accept: application/json
|
Accept: application/json
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
|
{
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"role": "Viewer",
|
"role": "Viewer",
|
||||||
|
@ -34,32 +34,29 @@ sudo dpkg -i grafana_<version>_amd64.deb
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.1.4_amd64.deb
|
wget https://dl.grafana.com/oss/release/grafana_5.4.2_amd64.deb
|
||||||
sudo apt-get install -y adduser libfontconfig
|
sudo apt-get install -y adduser libfontconfig
|
||||||
sudo dpkg -i grafana_5.1.4_amd64.deb
|
sudo dpkg -i grafana_5.4.2_amd64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
## APT Repository
|
## APT Repository
|
||||||
|
|
||||||
Add the following line to your `/etc/apt/sources.list` file.
|
Create a file `/etc/apt/sources.list.d/grafana.list` and add the following to it.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
deb https://packagecloud.io/grafana/stable/debian/ stretch main
|
deb https://packages.grafana.com/oss/deb stable main
|
||||||
```
|
```
|
||||||
|
|
||||||
Use the above line even if you are on Ubuntu or another Debian version.
|
There is a separate repository if you want beta releases.
|
||||||
There is also a testing repository if you want beta or release
|
|
||||||
candidates.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
deb https://packagecloud.io/grafana/testing/debian/ stretch main
|
deb https://packages.grafana.com/oss/deb beta main
|
||||||
```
|
```
|
||||||
|
|
||||||
Then add the [Package Cloud](https://packagecloud.io/grafana) key. This
|
Use the above line even if you are on Ubuntu or another Debian version. Then add our gpg key. This allows you to install signed packages.
|
||||||
allows you to install signed packages.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl https://packagecloud.io/gpg.key | sudo apt-key add -
|
curl https://packages.grafana.com/gpg.key | sudo apt-key add -
|
||||||
```
|
```
|
||||||
|
|
||||||
Update your Apt repositories and install Grafana
|
Update your Apt repositories and install Grafana
|
||||||
|
@ -32,7 +32,7 @@ $ sudo yum install <rpm package url>
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.4-1.x86_64.rpm
|
$ sudo yum install https://dl.grafana.com/oss/release/grafana-5.4.2-1.x86_64.rpm
|
||||||
```
|
```
|
||||||
|
|
||||||
Or install manually using `rpm`. First execute
|
Or install manually using `rpm`. First execute
|
||||||
@ -44,7 +44,7 @@ $ wget <rpm package url>
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.1.4-1.x86_64.rpm
|
$ wget https://dl.grafana.com/oss/release/grafana-5.4.2-1.x86_64.rpm
|
||||||
```
|
```
|
||||||
|
|
||||||
### On CentOS / Fedora / Redhat:
|
### On CentOS / Fedora / Redhat:
|
||||||
@ -67,19 +67,27 @@ Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
|
|||||||
```bash
|
```bash
|
||||||
[grafana]
|
[grafana]
|
||||||
name=grafana
|
name=grafana
|
||||||
baseurl=https://packagecloud.io/grafana/stable/el/7/$basearch
|
baseurl=https://packages.grafana.com/oss/rpm
|
||||||
repo_gpgcheck=1
|
repo_gpgcheck=1
|
||||||
enabled=1
|
enabled=1
|
||||||
gpgcheck=1
|
gpgcheck=1
|
||||||
gpgkey=https://packagecloud.io/gpg.key https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana
|
gpgkey=https://packages.grafana.com/gpg.key
|
||||||
sslverify=1
|
sslverify=1
|
||||||
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
|
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
|
||||||
```
|
```
|
||||||
|
|
||||||
There is also a testing repository if you want beta or release candidates.
|
There is a separate repository if you want beta releases.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
baseurl=https://packagecloud.io/grafana/testing/el/7/$basearch
|
[grafana]
|
||||||
|
name=grafana
|
||||||
|
baseurl=https://packages.grafana.com/oss/rpm-beta
|
||||||
|
repo_gpgcheck=1
|
||||||
|
enabled=1
|
||||||
|
gpgcheck=1
|
||||||
|
gpgkey=https://packages.grafana.com/gpg.key
|
||||||
|
sslverify=1
|
||||||
|
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
|
||||||
```
|
```
|
||||||
|
|
||||||
Then install Grafana via the `yum` command.
|
Then install Grafana via the `yum` command.
|
||||||
@ -91,7 +99,7 @@ $ sudo yum install grafana
|
|||||||
### RPM GPG Key
|
### RPM GPG Key
|
||||||
|
|
||||||
The RPMs are signed, you can verify the signature with this [public GPG
|
The RPMs are signed, you can verify the signature with this [public GPG
|
||||||
key](https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana).
|
key](https://packages.grafana.com/gpg.key).
|
||||||
|
|
||||||
## Package details
|
## Package details
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ When a user creates a new dashboard, a new dashboard JSON object is initialized
|
|||||||
"list": []
|
"list": []
|
||||||
},
|
},
|
||||||
"refresh": "5s",
|
"refresh": "5s",
|
||||||
"schemaVersion": 16,
|
"schemaVersion": 17,
|
||||||
"version": 0,
|
"version": 0,
|
||||||
"links": []
|
"links": []
|
||||||
}
|
}
|
||||||
|
@ -292,9 +292,11 @@ The `direction` controls how the panels will be arranged.
|
|||||||
|
|
||||||
By choosing `horizontal` the panels will be arranged side-by-side. Grafana will automatically adjust the width
|
By choosing `horizontal` the panels will be arranged side-by-side. Grafana will automatically adjust the width
|
||||||
of each repeated panel so that the whole row is filled. Currently, you cannot mix other panels on a row with a repeated
|
of each repeated panel so that the whole row is filled. Currently, you cannot mix other panels on a row with a repeated
|
||||||
panel. Each panel will never be smaller that the provided `Min width` if you have many selected values.
|
panel.
|
||||||
|
|
||||||
By choosing `vertical` the panels will be arranged from top to bottom in a column. The `Min width` doesn't have any effect in this case. The width of the repeated panels will be the same as of the first panel (the original template) being repeated.
|
Set `Max per row` to tell grafana how many panels per row you want at most. It defaults to *4* if you don't set anything.
|
||||||
|
|
||||||
|
By choosing `vertical` the panels will be arranged from top to bottom in a column. The width of the repeated panels will be the same as of the first panel (the original template) being repeated.
|
||||||
|
|
||||||
Only make changes to the first panel (the original template). To have the changes take effect on all panels you need to trigger a dynamic dashboard re-build.
|
Only make changes to the first panel (the original template). To have the changes take effect on all panels you need to trigger a dynamic dashboard re-build.
|
||||||
You can do this by either changing the variable value (that is the basis for the repeat) or reload the dashboard.
|
You can do this by either changing the variable value (that is the basis for the repeat) or reload the dashboard.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"stable": "5.4.2",
|
"stable": "5.4.3",
|
||||||
"testing": "5.4.2"
|
"testing": "5.4.3"
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
"@types/jquery": "^1.10.35",
|
"@types/jquery": "^1.10.35",
|
||||||
"@types/node": "^8.0.31",
|
"@types/node": "^8.0.31",
|
||||||
"@types/react": "^16.7.6",
|
"@types/react": "^16.7.6",
|
||||||
"@types/react-custom-scrollbars": "^4.0.5",
|
|
||||||
"@types/react-dom": "^16.0.9",
|
"@types/react-dom": "^16.0.9",
|
||||||
"@types/react-select": "^2.0.4",
|
"@types/react-select": "^2.0.4",
|
||||||
"angular-mocks": "1.6.6",
|
"angular-mocks": "1.6.6",
|
||||||
@ -65,6 +64,7 @@
|
|||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"husky": "^0.14.3",
|
"husky": "^0.14.3",
|
||||||
"jest": "^23.6.0",
|
"jest": "^23.6.0",
|
||||||
|
"jest-date-mock": "^1.0.6",
|
||||||
"lint-staged": "^6.0.0",
|
"lint-staged": "^6.0.0",
|
||||||
"load-grunt-tasks": "3.5.2",
|
"load-grunt-tasks": "3.5.2",
|
||||||
"mini-css-extract-plugin": "^0.4.0",
|
"mini-css-extract-plugin": "^0.4.0",
|
||||||
@ -72,8 +72,8 @@
|
|||||||
"ng-annotate-loader": "^0.6.1",
|
"ng-annotate-loader": "^0.6.1",
|
||||||
"ng-annotate-webpack-plugin": "^0.3.0",
|
"ng-annotate-webpack-plugin": "^0.3.0",
|
||||||
"ngtemplate-loader": "^2.0.1",
|
"ngtemplate-loader": "^2.0.1",
|
||||||
"npm": "^5.4.2",
|
|
||||||
"node-sass": "^4.11.0",
|
"node-sass": "^4.11.0",
|
||||||
|
"npm": "^5.4.2",
|
||||||
"optimize-css-assets-webpack-plugin": "^4.0.2",
|
"optimize-css-assets-webpack-plugin": "^4.0.2",
|
||||||
"phantomjs-prebuilt": "^2.1.15",
|
"phantomjs-prebuilt": "^2.1.15",
|
||||||
"postcss-browser-reporter": "^0.5.0",
|
"postcss-browser-reporter": "^0.5.0",
|
||||||
@ -167,7 +167,6 @@
|
|||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"rc-cascader": "^0.14.0",
|
"rc-cascader": "^0.14.0",
|
||||||
"react": "^16.6.3",
|
"react": "^16.6.3",
|
||||||
"react-custom-scrollbars": "^4.2.1",
|
|
||||||
"react-dom": "^16.6.3",
|
"react-dom": "^16.6.3",
|
||||||
"react-grid-layout": "0.16.6",
|
"react-grid-layout": "0.16.6",
|
||||||
"react-highlight-words": "0.11.0",
|
"react-highlight-words": "0.11.0",
|
||||||
|
@ -11,23 +11,34 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@torkelo/react-select": "2.1.1",
|
"@torkelo/react-select": "2.1.1",
|
||||||
|
"@types/react-test-renderer": "^16.0.3",
|
||||||
|
"@types/react-transition-group": "^2.0.15",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"jquery": "^3.2.1",
|
"jquery": "^3.2.1",
|
||||||
"lodash": "^4.17.10",
|
"lodash": "^4.17.10",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"react": "^16.6.3",
|
"react": "^16.6.3",
|
||||||
|
"react-custom-scrollbars": "^4.2.1",
|
||||||
"react-dom": "^16.6.3",
|
"react-dom": "^16.6.3",
|
||||||
"react-highlight-words": "0.11.0",
|
"react-highlight-words": "0.11.0",
|
||||||
"react-popper": "^1.3.0",
|
"react-popper": "^1.3.0",
|
||||||
"react-transition-group": "^2.2.1",
|
"react-transition-group": "^2.2.1",
|
||||||
"react-virtualized": "^9.21.0"
|
"react-virtualized": "^9.21.0",
|
||||||
|
"tether": "^1.4.0",
|
||||||
|
"tether-drop": "https://github.com/torkelo/drop/tarball/master",
|
||||||
|
"tinycolor2": "^1.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/classnames": "^2.2.6",
|
||||||
"@types/jest": "^23.3.2",
|
"@types/jest": "^23.3.2",
|
||||||
|
"@types/jquery": "^1.10.35",
|
||||||
"@types/lodash": "^4.14.119",
|
"@types/lodash": "^4.14.119",
|
||||||
"@types/react": "^16.7.6",
|
"@types/react": "^16.7.6",
|
||||||
"@types/classnames": "^2.2.6",
|
"@types/react-custom-scrollbars": "^4.0.5",
|
||||||
"@types/jquery": "^1.10.35",
|
"@types/react-test-renderer": "^16.0.3",
|
||||||
|
"@types/tether-drop": "^1.4.8",
|
||||||
|
"@types/tinycolor2": "^1.4.1",
|
||||||
|
"react-test-renderer": "^16.7.0",
|
||||||
"typescript": "^3.2.2"
|
"typescript": "^3.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import { ColorPalette } from '../components/colorpicker/ColorPalette';
|
import { ColorPalette } from './ColorPalette';
|
||||||
|
|
||||||
describe('CollorPalette', () => {
|
describe('CollorPalette', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { sortedColors } from 'app/core/utils/colors';
|
import { sortedColors } from '../../utils';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
color: string;
|
color: string;
|
||||||
@ -9,13 +9,13 @@ export interface Props {
|
|||||||
export class ColorPalette extends React.Component<Props, any> {
|
export class ColorPalette extends React.Component<Props, any> {
|
||||||
paletteColors: string[];
|
paletteColors: string[];
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.paletteColors = sortedColors;
|
this.paletteColors = sortedColors;
|
||||||
this.onColorSelect = this.onColorSelect.bind(this);
|
this.onColorSelect = this.onColorSelect.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onColorSelect(color) {
|
onColorSelect(color: string) {
|
||||||
return () => {
|
return () => {
|
||||||
this.props.onColorSelect(color);
|
this.props.onColorSelect(color);
|
||||||
};
|
};
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import Drop from 'tether-drop';
|
import Drop from 'tether-drop';
|
||||||
import { ColorPickerPopover } from './ColorPickerPopover';
|
import { ColorPickerPopover } from './ColorPickerPopover';
|
||||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
color: string;
|
color: string;
|
||||||
@ -10,7 +9,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ColorPicker extends React.Component<Props, any> {
|
export class ColorPicker extends React.Component<Props, any> {
|
||||||
pickerElem: HTMLElement;
|
pickerElem: HTMLElement | null;
|
||||||
colorPickerDrop: any;
|
colorPickerDrop: any;
|
||||||
|
|
||||||
openColorPicker = () => {
|
openColorPicker = () => {
|
||||||
@ -20,7 +19,7 @@ export class ColorPicker extends React.Component<Props, any> {
|
|||||||
ReactDOM.render(dropContent, dropContentElem);
|
ReactDOM.render(dropContent, dropContentElem);
|
||||||
|
|
||||||
const drop = new Drop({
|
const drop = new Drop({
|
||||||
target: this.pickerElem,
|
target: this.pickerElem as Element,
|
||||||
content: dropContentElem,
|
content: dropContentElem,
|
||||||
position: 'top center',
|
position: 'top center',
|
||||||
classes: 'drop-popover',
|
classes: 'drop-popover',
|
||||||
@ -28,6 +27,7 @@ export class ColorPicker extends React.Component<Props, any> {
|
|||||||
hoverCloseDelay: 200,
|
hoverCloseDelay: 200,
|
||||||
tetherOptions: {
|
tetherOptions: {
|
||||||
constraints: [{ to: 'scrollParent', attachment: 'none both' }],
|
constraints: [{ to: 'scrollParent', attachment: 'none both' }],
|
||||||
|
attachment: 'bottom center',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ export class ColorPicker extends React.Component<Props, any> {
|
|||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
onColorSelect = color => {
|
onColorSelect = (color: string) => {
|
||||||
this.props.onChange(color);
|
this.props.onChange(color);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,8 +59,3 @@ export class ColorPicker extends React.Component<Props, any> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
react2AngularDirective('colorPicker', ColorPicker, [
|
|
||||||
'color',
|
|
||||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
|
||||||
]);
|
|
@ -14,7 +14,7 @@ export interface Props {
|
|||||||
export class ColorPickerPopover extends React.Component<Props, any> {
|
export class ColorPickerPopover extends React.Component<Props, any> {
|
||||||
pickerNavElem: any;
|
pickerNavElem: any;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
tab: 'palette',
|
tab: 'palette',
|
||||||
@ -23,60 +23,51 @@ export class ColorPickerPopover extends React.Component<Props, any> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setPickerNavElem(elem) {
|
setPickerNavElem(elem: any) {
|
||||||
this.pickerNavElem = $(elem);
|
this.pickerNavElem = $(elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
setColor(color) {
|
setColor(color: string) {
|
||||||
const newColor = tinycolor(color);
|
const newColor = tinycolor(color);
|
||||||
if (newColor.isValid()) {
|
if (newColor.isValid()) {
|
||||||
this.setState({
|
this.setState({ color: newColor.toString(), colorString: newColor.toString() });
|
||||||
color: newColor.toString(),
|
|
||||||
colorString: newColor.toString(),
|
|
||||||
});
|
|
||||||
this.props.onColorSelect(color);
|
this.props.onColorSelect(color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sampleColorSelected(color) {
|
sampleColorSelected(color: string) {
|
||||||
this.setColor(color);
|
this.setColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
spectrumColorSelected(color) {
|
spectrumColorSelected(color: any) {
|
||||||
const rgbColor = color.toRgbString();
|
const rgbColor = color.toRgbString();
|
||||||
this.setColor(rgbColor);
|
this.setColor(rgbColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
onColorStringChange(e) {
|
onColorStringChange(e: any) {
|
||||||
const colorString = e.target.value;
|
const colorString = e.target.value;
|
||||||
this.setState({
|
this.setState({ colorString: colorString });
|
||||||
colorString: colorString,
|
|
||||||
});
|
|
||||||
|
|
||||||
const newColor = tinycolor(colorString);
|
const newColor = tinycolor(colorString);
|
||||||
if (newColor.isValid()) {
|
if (newColor.isValid()) {
|
||||||
// Update only color state
|
// Update only color state
|
||||||
const newColorString = newColor.toString();
|
const newColorString = newColor.toString();
|
||||||
this.setState({
|
this.setState({ color: newColorString });
|
||||||
color: newColorString,
|
|
||||||
});
|
|
||||||
this.props.onColorSelect(newColorString);
|
this.props.onColorSelect(newColorString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onColorStringBlur(e) {
|
onColorStringBlur(e: any) {
|
||||||
const colorString = e.target.value;
|
const colorString = e.target.value;
|
||||||
this.setColor(colorString);
|
this.setColor(colorString);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.pickerNavElem.find('li:first').addClass('active');
|
this.pickerNavElem.find('li:first').addClass('active');
|
||||||
this.pickerNavElem.on('show', e => {
|
this.pickerNavElem.on('show', (e: any) => {
|
||||||
// use href attr (#name => name)
|
// use href attr (#name => name)
|
||||||
const tab = e.target.hash.slice(1);
|
const tab = e.target.hash.slice(1);
|
||||||
this.setState({
|
this.setState({ tab: tab });
|
||||||
tab: tab,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
|
|||||||
onToggleAxis: () => {},
|
onToggleAxis: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: SeriesColorPickerProps) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +51,7 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
|
|||||||
remove: true,
|
remove: true,
|
||||||
tetherOptions: {
|
tetherOptions: {
|
||||||
constraints: [{ to: 'scrollParent', attachment: 'none both' }],
|
constraints: [{ to: 'scrollParent', attachment: 'none both' }],
|
||||||
|
attachment: 'bottom center',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ColorPickerPopover } from './ColorPickerPopover';
|
import { ColorPickerPopover } from './ColorPickerPopover';
|
||||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
|
||||||
|
|
||||||
export interface SeriesColorPickerPopoverProps {
|
export interface SeriesColorPickerPopoverProps {
|
||||||
color: string;
|
color: string;
|
||||||
@ -22,7 +21,7 @@ export class SeriesColorPickerPopover extends React.PureComponent<SeriesColorPic
|
|||||||
|
|
||||||
interface AxisSelectorProps {
|
interface AxisSelectorProps {
|
||||||
yaxis: number;
|
yaxis: number;
|
||||||
onToggleAxis: () => void;
|
onToggleAxis?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AxisSelectorState {
|
interface AxisSelectorState {
|
||||||
@ -30,7 +29,7 @@ interface AxisSelectorState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSelectorState> {
|
export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSelectorState> {
|
||||||
constructor(props) {
|
constructor(props: AxisSelectorProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
yaxis: this.props.yaxis,
|
yaxis: this.props.yaxis,
|
||||||
@ -42,7 +41,10 @@ export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSel
|
|||||||
this.setState({
|
this.setState({
|
||||||
yaxis: this.state.yaxis === 2 ? 1 : 2,
|
yaxis: this.state.yaxis === 2 ? 1 : 2,
|
||||||
});
|
});
|
||||||
this.props.onToggleAxis();
|
|
||||||
|
if (this.props.onToggleAxis) {
|
||||||
|
this.props.onToggleAxis();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -62,9 +64,3 @@ export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopover, [
|
|
||||||
'series',
|
|
||||||
'onColorChange',
|
|
||||||
'onToggleAxis',
|
|
||||||
]);
|
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import 'vendor/spectrum';
|
import '../../vendor/spectrum';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
color: string;
|
color: string;
|
||||||
@ -13,17 +13,17 @@ export class SpectrumPicker extends React.Component<Props, any> {
|
|||||||
elem: any;
|
elem: any;
|
||||||
isMoving: boolean;
|
isMoving: boolean;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.onSpectrumMove = this.onSpectrumMove.bind(this);
|
this.onSpectrumMove = this.onSpectrumMove.bind(this);
|
||||||
this.setComponentElem = this.setComponentElem.bind(this);
|
this.setComponentElem = this.setComponentElem.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
setComponentElem(elem) {
|
setComponentElem(elem: any) {
|
||||||
this.elem = $(elem);
|
this.elem = $(elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSpectrumMove(color) {
|
onSpectrumMove(color: any) {
|
||||||
this.isMoving = true;
|
this.isMoving = true;
|
||||||
this.props.onColorSelect(color);
|
this.props.onColorSelect(color);
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ export class SpectrumPicker extends React.Component<Props, any> {
|
|||||||
this.elem.spectrum('set', this.props.color);
|
this.elem.spectrum('set', this.props.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate(nextProps) {
|
componentWillUpdate(nextProps: any) {
|
||||||
// If user move pointer over spectrum field this produce 'move' event and component
|
// If user move pointer over spectrum field this produce 'move' event and component
|
||||||
// may update props.color. We don't want to update spectrum color in this case, so we can use
|
// may update props.color. We don't want to update spectrum color in this case, so we can use
|
||||||
// isMoving flag for tracking moving state. Flag should be cleared in componentDidUpdate() which
|
// isMoving flag for tracking moving state. Flag should be cleared in componentDidUpdate() which
|
@ -6,36 +6,39 @@ interface Props {
|
|||||||
autoHide?: boolean;
|
autoHide?: boolean;
|
||||||
autoHideTimeout?: number;
|
autoHideTimeout?: number;
|
||||||
autoHideDuration?: number;
|
autoHideDuration?: number;
|
||||||
|
autoMaxHeight?: string;
|
||||||
hideTracksWhenNotNeeded?: boolean;
|
hideTracksWhenNotNeeded?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps component into <Scrollbars> component from `react-custom-scrollbars`
|
* Wraps component into <Scrollbars> component from `react-custom-scrollbars`
|
||||||
*/
|
*/
|
||||||
class CustomScrollbar extends PureComponent<Props> {
|
export class CustomScrollbar extends PureComponent<Props> {
|
||||||
static defaultProps: Partial<Props> = {
|
static defaultProps: Partial<Props> = {
|
||||||
customClassName: 'custom-scrollbars',
|
customClassName: 'custom-scrollbars',
|
||||||
autoHide: true,
|
autoHide: true,
|
||||||
autoHideTimeout: 200,
|
autoHideTimeout: 200,
|
||||||
autoHideDuration: 200,
|
autoHideDuration: 200,
|
||||||
|
autoMaxHeight: '100%',
|
||||||
hideTracksWhenNotNeeded: false,
|
hideTracksWhenNotNeeded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { customClassName, children, ...scrollProps } = this.props;
|
const { customClassName, children, autoMaxHeight } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Scrollbars
|
<Scrollbars
|
||||||
className={customClassName}
|
className={customClassName}
|
||||||
autoHeight={true}
|
autoHeight={true}
|
||||||
autoHeightMin={'inherit'}
|
// These autoHeightMin & autoHeightMax options affect firefox and chrome differently.
|
||||||
autoHeightMax={'inherit'}
|
// Before these where set to inhert but that caused problems with cut of legends in firefox
|
||||||
|
autoHeightMin={'0'}
|
||||||
|
autoHeightMax={autoMaxHeight}
|
||||||
renderTrackHorizontal={props => <div {...props} className="track-horizontal" />}
|
renderTrackHorizontal={props => <div {...props} className="track-horizontal" />}
|
||||||
renderTrackVertical={props => <div {...props} className="track-vertical" />}
|
renderTrackVertical={props => <div {...props} className="track-vertical" />}
|
||||||
renderThumbHorizontal={props => <div {...props} className="thumb-horizontal" />}
|
renderThumbHorizontal={props => <div {...props} className="thumb-horizontal" />}
|
||||||
renderThumbVertical={props => <div {...props} className="thumb-vertical" />}
|
renderThumbVertical={props => <div {...props} className="thumb-vertical" />}
|
||||||
renderView={props => <div {...props} className="view" />}
|
renderView={props => <div {...props} className="view" />}
|
||||||
{...scrollProps}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Scrollbars>
|
</Scrollbars>
|
@ -0,0 +1,40 @@
|
|||||||
|
.custom-scrollbars {
|
||||||
|
// Fix for Firefox. For some reason sometimes .view container gets a height of its content, but in order to
|
||||||
|
// make scroll working it should fit outer container size (scroll appears only when inner container size is
|
||||||
|
// greater than outer one).
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.view {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-vertical {
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 6px !important;
|
||||||
|
right: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-horizontal {
|
||||||
|
border-radius: 3px;
|
||||||
|
height: 6px !important;
|
||||||
|
|
||||||
|
right: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-vertical {
|
||||||
|
@include gradient-vertical($scrollbarBackground, $scrollbarBackground2);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-horizontal {
|
||||||
|
@include gradient-horizontal($scrollbarBackground, $scrollbarBackground2);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,8 @@ exports[`CustomScrollbar renders correctly 1`] = `
|
|||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"height": "auto",
|
"height": "auto",
|
||||||
"maxHeight": "inherit",
|
"maxHeight": "100%",
|
||||||
"minHeight": "inherit",
|
"minHeight": "0",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
"position": "relative",
|
"position": "relative",
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
@ -23,8 +23,8 @@ exports[`CustomScrollbar renders correctly 1`] = `
|
|||||||
"left": undefined,
|
"left": undefined,
|
||||||
"marginBottom": 0,
|
"marginBottom": 0,
|
||||||
"marginRight": 0,
|
"marginRight": 0,
|
||||||
"maxHeight": "calc(inherit + 0px)",
|
"maxHeight": "calc(100% + 0px)",
|
||||||
"minHeight": "calc(inherit + 0px)",
|
"minHeight": "calc(0 + 0px)",
|
||||||
"overflow": "scroll",
|
"overflow": "scroll",
|
||||||
"position": "relative",
|
"position": "relative",
|
||||||
"right": undefined,
|
"right": undefined,
|
||||||
@ -42,9 +42,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
|
|||||||
Object {
|
Object {
|
||||||
"display": "none",
|
"display": "none",
|
||||||
"height": 6,
|
"height": 6,
|
||||||
"opacity": 0,
|
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"transition": "opacity 200ms",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -64,9 +62,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
|
|||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"display": "none",
|
"display": "none",
|
||||||
"opacity": 0,
|
|
||||||
"position": "absolute",
|
"position": "absolute",
|
||||||
"transition": "opacity 200ms",
|
|
||||||
"width": 6,
|
"width": 6,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -98,6 +98,7 @@ export class Graph extends PureComponent<GraphProps> {
|
|||||||
$.plot(this.element, timeSeries, flotOptions);
|
$.plot(this.element, timeSeries, flotOptions);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Graph rendering error', err, flotOptions, timeSeries);
|
console.log('Graph rendering error', err, flotOptions, timeSeries);
|
||||||
|
throw new Error('Error rendering panel');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
import React, { SFC, ReactNode } from 'react';
|
import React, { SFC, ReactNode } from 'react';
|
||||||
import Tooltip from '../Tooltip/Tooltip';
|
import { Tooltip } from '../Tooltip/Tooltip';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
@ -14,8 +14,10 @@ export const Label: SFC<Props> = props => {
|
|||||||
<span className={`gf-form-label width-${props.width ? props.width : '10'}`}>
|
<span className={`gf-form-label width-${props.width ? props.width : '10'}`}>
|
||||||
<span>{props.children}</span>
|
<span>{props.children}</span>
|
||||||
{props.tooltip && (
|
{props.tooltip && (
|
||||||
<Tooltip className="gf-form-help-icon--right-normal" placement="auto" content={props.tooltip}>
|
<Tooltip placement="auto" content={props.tooltip}>
|
||||||
<i className="gicon gicon-question gicon--has-hover" />
|
<div className="gf-form-help-icon--right-normal">
|
||||||
|
<i className="gicon gicon-question gicon--has-hover" />
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
@ -0,0 +1,11 @@
|
|||||||
|
import React, { SFC } from 'react';
|
||||||
|
|
||||||
|
interface LoadingPlaceholderProps {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadingPlaceholder: SFC<LoadingPlaceholderProps> = ({ text }) => (
|
||||||
|
<div className="gf-form-group">
|
||||||
|
{text} <i className="fa fa-spinner fa-spin" />
|
||||||
|
</div>
|
||||||
|
);
|
@ -0,0 +1,15 @@
|
|||||||
|
import React, { SFC } from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
cols?: number;
|
||||||
|
children: JSX.Element[] | JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PanelOptionsGrid: SFC<Props> = ({ children }) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="panel-options-grid">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,10 @@
|
|||||||
|
.panel-options-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(1, 1fr);
|
||||||
|
grid-row-gap: 10px;
|
||||||
|
grid-column-gap: 10px;
|
||||||
|
|
||||||
|
@include media-breakpoint-up(lg) {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
}
|
@ -7,11 +7,11 @@ interface Props {
|
|||||||
children: JSX.Element | JSX.Element[];
|
children: JSX.Element | JSX.Element[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PanelOptionSection: SFC<Props> = props => {
|
export const PanelOptionsGroup: SFC<Props> = props => {
|
||||||
return (
|
return (
|
||||||
<div className="panel-option-section">
|
<div className="panel-options-group">
|
||||||
{props.title && (
|
{props.title && (
|
||||||
<div className="panel-option-section__header">
|
<div className="panel-options-group__header">
|
||||||
{props.title}
|
{props.title}
|
||||||
{props.onClose && (
|
{props.onClose && (
|
||||||
<button className="btn btn-link" onClick={props.onClose}>
|
<button className="btn btn-link" onClick={props.onClose}>
|
||||||
@ -20,7 +20,7 @@ export const PanelOptionSection: SFC<Props> = props => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="panel-option-section__body">{props.children}</div>
|
<div className="panel-options-group__body">{props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
.panel-options-group {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: $panel-options-group-border;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
background: $page-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-options-group__header {
|
||||||
|
padding: 4px 20px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
background: $panel-options-group-header-bg;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-options-group__body {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
&--queries {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
}
|
@ -6,16 +6,13 @@ interface Props {
|
|||||||
root?: HTMLElement;
|
root?: HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class BodyPortal extends PureComponent<Props> {
|
export class Portal extends PureComponent<Props> {
|
||||||
node: HTMLElement = document.createElement('div');
|
node: HTMLElement = document.createElement('div');
|
||||||
portalRoot: HTMLElement;
|
portalRoot: HTMLElement;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
const {
|
const { className, root = document.body } = this.props;
|
||||||
className,
|
|
||||||
root = document.body
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (className) {
|
if (className) {
|
||||||
this.node.classList.add(className);
|
this.node.classList.add(className);
|
@ -1,7 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
|
|
||||||
export const IndicatorsContainer = props => {
|
export const IndicatorsContainer = (props: any) => {
|
||||||
const isOpen = props.selectProps.menuIsOpen;
|
const isOpen = props.selectProps.menuIsOpen;
|
||||||
return (
|
return (
|
||||||
<components.IndicatorsContainer {...props}>
|
<components.IndicatorsContainer {...props}>
|
@ -1,5 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
|
// @ts-ignore
|
||||||
import { OptionProps } from '@torkelo/react-select/lib/components/Option';
|
import { OptionProps } from '@torkelo/react-select/lib/components/Option';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
@ -1,17 +1,22 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { default as ReactSelect } from '@torkelo/react-select';
|
import { default as ReactSelect } from '@torkelo/react-select';
|
||||||
|
// @ts-ignore
|
||||||
import { default as ReactAsyncSelect } from '@torkelo/react-select/lib/Async';
|
import { default as ReactAsyncSelect } from '@torkelo/react-select/lib/Async';
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Option, SingleValue } from './PickerOption';
|
import { SelectOption, SingleValue } from './SelectOption';
|
||||||
import OptionGroup from './OptionGroup';
|
import SelectOptionGroup from './SelectOptionGroup';
|
||||||
import IndicatorsContainer from './IndicatorsContainer';
|
import IndicatorsContainer from './IndicatorsContainer';
|
||||||
import NoOptionsMessage from './NoOptionsMessage';
|
import NoOptionsMessage from './NoOptionsMessage';
|
||||||
import ResetStyles from './ResetStyles';
|
import resetSelectStyles from './resetSelectStyles';
|
||||||
import CustomScrollbar from '../CustomScrollbar/CustomScrollbar';
|
import { CustomScrollbar } from '@grafana/ui';
|
||||||
|
|
||||||
export interface SelectOptionItem {
|
export interface SelectOptionItem {
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -53,10 +58,10 @@ interface AsyncProps {
|
|||||||
loadingMessage?: () => string;
|
loadingMessage?: () => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MenuList = props => {
|
export const MenuList = (props: any) => {
|
||||||
return (
|
return (
|
||||||
<components.MenuList {...props}>
|
<components.MenuList {...props}>
|
||||||
<CustomScrollbar autoHide={false}>{props.children}</CustomScrollbar>
|
<CustomScrollbar autoHide={false} autoMaxHeight="inherit">{props.children}</CustomScrollbar>
|
||||||
</components.MenuList>
|
</components.MenuList>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -112,11 +117,11 @@ export class Select extends PureComponent<CommonProps & SelectProps> {
|
|||||||
classNamePrefix="gf-form-select-box"
|
classNamePrefix="gf-form-select-box"
|
||||||
className={selectClassNames}
|
className={selectClassNames}
|
||||||
components={{
|
components={{
|
||||||
Option,
|
Option: SelectOption,
|
||||||
SingleValue,
|
SingleValue,
|
||||||
IndicatorsContainer,
|
IndicatorsContainer,
|
||||||
MenuList,
|
MenuList,
|
||||||
Group: OptionGroup,
|
Group: SelectOptionGroup,
|
||||||
}}
|
}}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
value={value}
|
value={value}
|
||||||
@ -127,7 +132,7 @@ export class Select extends PureComponent<CommonProps & SelectProps> {
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={options}
|
options={options}
|
||||||
placeholder={placeholder || 'Choose'}
|
placeholder={placeholder || 'Choose'}
|
||||||
styles={ResetStyles}
|
styles={resetSelectStyles()}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isClearable={isClearable}
|
isClearable={isClearable}
|
||||||
@ -212,7 +217,7 @@ export class AsyncSelect extends PureComponent<CommonProps & AsyncProps> {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
defaultOptions={defaultOptions}
|
defaultOptions={defaultOptions}
|
||||||
placeholder={placeholder || 'Choose'}
|
placeholder={placeholder || 'Choose'}
|
||||||
styles={ResetStyles}
|
styles={resetSelectStyles()}
|
||||||
loadingMessage={loadingMessage}
|
loadingMessage={loadingMessage}
|
||||||
noOptionsMessage={noOptionsMessage}
|
noOptionsMessage={noOptionsMessage}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import PickerOption from './PickerOption';
|
import SelectOption from './SelectOption';
|
||||||
|
import { OptionProps } from 'react-select/lib/components/Option';
|
||||||
|
|
||||||
const model = {
|
const model: OptionProps<any> = {
|
||||||
|
data: jest.fn(),
|
||||||
cx: jest.fn(),
|
cx: jest.fn(),
|
||||||
clearValue: jest.fn(),
|
clearValue: jest.fn(),
|
||||||
onSelect: jest.fn(),
|
|
||||||
getStyles: jest.fn(),
|
getStyles: jest.fn(),
|
||||||
getValue: jest.fn(),
|
getValue: jest.fn(),
|
||||||
hasValue: true,
|
hasValue: true,
|
||||||
@ -18,21 +19,31 @@ const model = {
|
|||||||
isFocused: false,
|
isFocused: false,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
innerRef: null,
|
innerRef: null,
|
||||||
innerProps: null,
|
innerProps: {
|
||||||
label: 'Option label',
|
id: '',
|
||||||
type: null,
|
key: '',
|
||||||
children: 'Model title',
|
onClick: jest.fn(),
|
||||||
data: {
|
onMouseOver: jest.fn(),
|
||||||
title: 'Model title',
|
tabIndex: 1,
|
||||||
imgUrl: 'url/to/avatar',
|
|
||||||
label: 'User picker label',
|
|
||||||
},
|
},
|
||||||
|
label: 'Option label',
|
||||||
|
type: 'option',
|
||||||
|
children: 'Model title',
|
||||||
className: 'class-for-user-picker',
|
className: 'class-for-user-picker',
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('PickerOption', () => {
|
describe('SelectOption', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const tree = renderer.create(<PickerOption {...model} />).toJSON();
|
const tree = renderer
|
||||||
|
.create(
|
||||||
|
<SelectOption
|
||||||
|
{...model}
|
||||||
|
data={{
|
||||||
|
imgUrl: 'url/to/avatar',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
.toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,4 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
import { OptionProps } from 'react-select/lib/components/Option';
|
import { OptionProps } from 'react-select/lib/components/Option';
|
||||||
|
|
||||||
@ -10,7 +13,7 @@ interface ExtendedOptionProps extends OptionProps<any> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Option = (props: ExtendedOptionProps) => {
|
export const SelectOption = (props: ExtendedOptionProps) => {
|
||||||
const { children, isSelected, data } = props;
|
const { children, isSelected, data } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -28,7 +31,7 @@ export const Option = (props: ExtendedOptionProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// was not able to type this without typescript error
|
// was not able to type this without typescript error
|
||||||
export const SingleValue = props => {
|
export const SingleValue = (props: any) => {
|
||||||
const { children, data } = props;
|
const { children, data } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,4 +44,4 @@ export const SingleValue = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Option;
|
export default SelectOption;
|
@ -2,21 +2,27 @@ import React, { PureComponent } from 'react';
|
|||||||
import { GroupProps } from 'react-select/lib/components/Group';
|
import { GroupProps } from 'react-select/lib/components/Group';
|
||||||
|
|
||||||
interface ExtendedGroupProps extends GroupProps<any> {
|
interface ExtendedGroupProps extends GroupProps<any> {
|
||||||
data: any;
|
data: {
|
||||||
|
label: string;
|
||||||
|
expanded: boolean;
|
||||||
|
options: any[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class OptionGroup extends PureComponent<ExtendedGroupProps, State> {
|
export default class SelectOptionGroup extends PureComponent<ExtendedGroupProps, State> {
|
||||||
state = {
|
state = {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.props.selectProps) {
|
if (this.props.data.expanded) {
|
||||||
const value = this.props.selectProps.value[this.props.selectProps.value.length - 1];
|
this.setState({ expanded: true });
|
||||||
|
} else if (this.props.selectProps && this.props.selectProps.value) {
|
||||||
|
const { value } = this.props.selectProps.value;
|
||||||
|
|
||||||
if (value && this.props.options.some(option => option.value === value)) {
|
if (value && this.props.options.some(option => option.value === value)) {
|
||||||
this.setState({ expanded: true });
|
this.setState({ expanded: true });
|
||||||
@ -24,7 +30,7 @@ export default class OptionGroup extends PureComponent<ExtendedGroupProps, State
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(nextProps) {
|
componentDidUpdate(nextProps: ExtendedGroupProps) {
|
||||||
if (nextProps.selectProps.inputValue !== '') {
|
if (nextProps.selectProps.inputValue !== '') {
|
||||||
this.setState({ expanded: true });
|
this.setState({ expanded: true });
|
||||||
}
|
}
|
@ -63,6 +63,7 @@ $select-input-bg-disabled: $input-bg-disabled;
|
|||||||
.gf-form-select-box__menu-list {
|
.gf-form-select-box__menu-list {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-filter .gf-form-select-box__menu {
|
.tag-filter .gf-form-select-box__menu {
|
@ -1,7 +1,12 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`PickerOption renders correctly 1`] = `
|
exports[`SelectOption renders correctly 1`] = `
|
||||||
<div>
|
<div
|
||||||
|
id=""
|
||||||
|
onClick={[MockFunction]}
|
||||||
|
onMouseOver={[MockFunction]}
|
||||||
|
tabIndex={1}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="gf-form-select-box__desc-option"
|
className="gf-form-select-box__desc-option"
|
||||||
>
|
>
|
@ -0,0 +1,27 @@
|
|||||||
|
export default function resetSelectStyles() {
|
||||||
|
return {
|
||||||
|
clearIndicator: () => ({}),
|
||||||
|
container: () => ({}),
|
||||||
|
control: () => ({}),
|
||||||
|
dropdownIndicator: () => ({}),
|
||||||
|
group: () => ({}),
|
||||||
|
groupHeading: () => ({}),
|
||||||
|
indicatorsContainer: () => ({}),
|
||||||
|
indicatorSeparator: () => ({}),
|
||||||
|
input: () => ({}),
|
||||||
|
loadingIndicator: () => ({}),
|
||||||
|
loadingMessage: () => ({}),
|
||||||
|
menu: () => ({}),
|
||||||
|
menuList: ({ maxHeight }: { maxHeight: number }) => ({
|
||||||
|
maxHeight,
|
||||||
|
}),
|
||||||
|
multiValue: () => ({}),
|
||||||
|
multiValueLabel: () => ({}),
|
||||||
|
multiValueRemove: () => ({}),
|
||||||
|
noOptionsMessage: () => ({}),
|
||||||
|
option: () => ({}),
|
||||||
|
placeholder: () => ({}),
|
||||||
|
singleValue: () => ({}),
|
||||||
|
valueContainer: () => ({}),
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,173 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import { ThresholdsEditor, Props } from './ThresholdsEditor';
|
||||||
|
|
||||||
|
const setup = (propOverrides?: object) => {
|
||||||
|
const props: Props = {
|
||||||
|
onChange: jest.fn(),
|
||||||
|
thresholds: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
|
return shallow(<ThresholdsEditor {...props} />).instance() as ThresholdsEditor;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Initialization', () => {
|
||||||
|
it('should add a base threshold if missing', () => {
|
||||||
|
const instance = setup();
|
||||||
|
|
||||||
|
expect(instance.state.thresholds).toEqual([{ index: 0, value: -Infinity, color: '#7EB26D' }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Add threshold', () => {
|
||||||
|
it('should not add threshold at index 0', () => {
|
||||||
|
const instance = setup();
|
||||||
|
|
||||||
|
instance.onAddThreshold(0);
|
||||||
|
|
||||||
|
expect(instance.state.thresholds).toEqual([{ index: 0, value: -Infinity, color: '#7EB26D' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add threshold', () => {
|
||||||
|
const instance = setup();
|
||||||
|
|
||||||
|
instance.onAddThreshold(1);
|
||||||
|
|
||||||
|
expect(instance.state.thresholds).toEqual([
|
||||||
|
{ index: 1, value: 50, color: '#EAB839' },
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add another threshold above a first', () => {
|
||||||
|
const instance = setup({
|
||||||
|
thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }, { index: 1, value: 50, color: '#EAB839' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.onAddThreshold(2);
|
||||||
|
|
||||||
|
expect(instance.state.thresholds).toEqual([
|
||||||
|
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||||
|
{ index: 1, value: 50, color: '#EAB839' },
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add another threshold between first and second index', () => {
|
||||||
|
const instance = setup({
|
||||||
|
thresholds: [
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
{ index: 1, value: 50, color: '#EAB839' },
|
||||||
|
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.onAddThreshold(2);
|
||||||
|
|
||||||
|
expect(instance.state.thresholds).toEqual([
|
||||||
|
{ index: 3, value: 75, color: '#6ED0E0' },
|
||||||
|
{ index: 2, value: 62.5, color: '#EF843C' },
|
||||||
|
{ index: 1, value: 50, color: '#EAB839' },
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Remove threshold', () => {
|
||||||
|
it('should not remove threshold at index 0', () => {
|
||||||
|
const thresholds = [
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
{ index: 1, value: 50, color: '#EAB839' },
|
||||||
|
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||||
|
];
|
||||||
|
const instance = setup({ thresholds });
|
||||||
|
|
||||||
|
instance.onRemoveThreshold(thresholds[0]);
|
||||||
|
|
||||||
|
expect(instance.state.thresholds).toEqual(thresholds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove threshold', () => {
|
||||||
|
const thresholds = [
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
{ index: 1, value: 50, color: '#EAB839' },
|
||||||
|
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||||
|
];
|
||||||
|
const instance = setup({
|
||||||
|
thresholds,
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.onRemoveThreshold(thresholds[1]);
|
||||||
|
|
||||||
|
expect(instance.state.thresholds).toEqual([
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
{ index: 1, value: 75, color: '#6ED0E0' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('change threshold value', () => {
|
||||||
|
it('should not change threshold at index 0', () => {
|
||||||
|
const thresholds = [
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
{ index: 1, value: 50, color: '#EAB839' },
|
||||||
|
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||||
|
];
|
||||||
|
const instance = setup({ thresholds });
|
||||||
|
|
||||||
|
const mockEvent = { target: { value: 12 } };
|
||||||
|
|
||||||
|
instance.onChangeThresholdValue(mockEvent, thresholds[0]);
|
||||||
|
|
||||||
|
expect(instance.state.thresholds).toEqual(thresholds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update value', () => {
|
||||||
|
const instance = setup();
|
||||||
|
const thresholds = [
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
{ index: 1, value: 50, color: '#EAB839' },
|
||||||
|
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||||
|
];
|
||||||
|
|
||||||
|
instance.state = {
|
||||||
|
thresholds,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockEvent = { target: { value: 78 } };
|
||||||
|
|
||||||
|
instance.onChangeThresholdValue(mockEvent, thresholds[1]);
|
||||||
|
|
||||||
|
expect(instance.state.thresholds).toEqual([
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
{ index: 1, value: 78, color: '#EAB839' },
|
||||||
|
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('on blur threshold value', () => {
|
||||||
|
it('should resort rows and update indexes', () => {
|
||||||
|
const instance = setup();
|
||||||
|
const thresholds = [
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
{ index: 1, value: 78, color: '#EAB839' },
|
||||||
|
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||||
|
];
|
||||||
|
|
||||||
|
instance.state = {
|
||||||
|
thresholds,
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.onBlur();
|
||||||
|
|
||||||
|
expect(instance.state.thresholds).toEqual([
|
||||||
|
{ index: 2, value: 78, color: '#EAB839' },
|
||||||
|
{ index: 1, value: 75, color: '#6ED0E0' },
|
||||||
|
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,206 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
// import tinycolor, { ColorInput } from 'tinycolor2';
|
||||||
|
|
||||||
|
import { Threshold } from '../../types';
|
||||||
|
import { ColorPicker } from '../ColorPicker/ColorPicker';
|
||||||
|
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
|
||||||
|
import { colors } from '../../utils';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
thresholds: Threshold[];
|
||||||
|
onChange: (thresholds: Threshold[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
thresholds: Threshold[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const thresholds: Threshold[] =
|
||||||
|
props.thresholds.length > 0 ? props.thresholds : [{ index: 0, value: -Infinity, color: colors[0] }];
|
||||||
|
this.state = { thresholds };
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddThreshold = (index: number) => {
|
||||||
|
const { thresholds } = this.state;
|
||||||
|
const maxValue = 100;
|
||||||
|
const minValue = 0;
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newThresholds = thresholds.map(threshold => {
|
||||||
|
if (threshold.index >= index) {
|
||||||
|
const index = threshold.index + 1;
|
||||||
|
threshold = { ...threshold, index };
|
||||||
|
}
|
||||||
|
return threshold;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setting value to a value between the previous thresholds
|
||||||
|
const beforeThreshold = newThresholds.filter(t => t.index === index - 1 && t.index !== 0)[0];
|
||||||
|
const afterThreshold = newThresholds.filter(t => t.index === index + 1 && t.index !== 0)[0];
|
||||||
|
const beforeThresholdValue = beforeThreshold !== undefined ? beforeThreshold.value : minValue;
|
||||||
|
const afterThresholdValue = afterThreshold !== undefined ? afterThreshold.value : maxValue;
|
||||||
|
const value = afterThresholdValue - (afterThresholdValue - beforeThresholdValue) / 2;
|
||||||
|
|
||||||
|
// Set a color
|
||||||
|
const color = colors.filter(c => newThresholds.some(t => t.color === c) === false)[0];
|
||||||
|
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
thresholds: this.sortThresholds([
|
||||||
|
...newThresholds,
|
||||||
|
{
|
||||||
|
index,
|
||||||
|
value: value as number,
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
() => this.updateGauge()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
onRemoveThreshold = (threshold: Threshold) => {
|
||||||
|
if (threshold.index === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(
|
||||||
|
prevState => {
|
||||||
|
const newThresholds = prevState.thresholds.map(t => {
|
||||||
|
if (t.index > threshold.index) {
|
||||||
|
const index = t.index - 1;
|
||||||
|
t = { ...t, index };
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
thresholds: newThresholds.filter(t => t !== threshold),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
() => this.updateGauge()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeThresholdValue = (event: any, threshold: Threshold) => {
|
||||||
|
if (threshold.index === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { thresholds } = this.state;
|
||||||
|
const parsedValue = parseInt(event.target.value, 10);
|
||||||
|
const value = isNaN(parsedValue) ? null : parsedValue;
|
||||||
|
|
||||||
|
const newThresholds = thresholds.map(t => {
|
||||||
|
if (t === threshold) {
|
||||||
|
t = { ...t, value: value as number };
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({ thresholds: newThresholds });
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeThresholdColor = (threshold: Threshold, color: string) => {
|
||||||
|
const { thresholds } = this.state;
|
||||||
|
|
||||||
|
const newThresholds = thresholds.map(t => {
|
||||||
|
if (t === threshold) {
|
||||||
|
t = { ...t, color: color };
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
thresholds: newThresholds,
|
||||||
|
},
|
||||||
|
() => this.updateGauge()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
onChangeBaseColor = (color: string) => this.props.onChange(this.state.thresholds);
|
||||||
|
onBlur = () => {
|
||||||
|
this.setState(prevState => {
|
||||||
|
const sortThresholds = this.sortThresholds([...prevState.thresholds]);
|
||||||
|
let index = sortThresholds.length - 1;
|
||||||
|
sortThresholds.forEach(t => {
|
||||||
|
t.index = index--;
|
||||||
|
});
|
||||||
|
return { thresholds: sortThresholds };
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateGauge();
|
||||||
|
};
|
||||||
|
|
||||||
|
updateGauge = () => {
|
||||||
|
this.props.onChange(this.state.thresholds);
|
||||||
|
};
|
||||||
|
|
||||||
|
sortThresholds = (thresholds: Threshold[]) => {
|
||||||
|
return thresholds.sort((t1, t2) => {
|
||||||
|
return t2.value - t1.value;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
renderInput = (threshold: Threshold) => {
|
||||||
|
const value = threshold.index === 0 ? 'Base' : threshold.value;
|
||||||
|
return (
|
||||||
|
<div className="thresholds-row-input-inner">
|
||||||
|
<span className="thresholds-row-input-inner-arrow" />
|
||||||
|
<div className="thresholds-row-input-inner-color">
|
||||||
|
{threshold.color && (
|
||||||
|
<div className="thresholds-row-input-inner-color-colorpicker">
|
||||||
|
<ColorPicker color={threshold.color} onChange={color => this.onChangeThresholdColor(threshold, color)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="thresholds-row-input-inner-value">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
onChange={event => this.onChangeThresholdValue(event, threshold)}
|
||||||
|
value={value}
|
||||||
|
onBlur={this.onBlur}
|
||||||
|
readOnly={threshold.index === 0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{threshold.index > 0 && (
|
||||||
|
<div className="thresholds-row-input-inner-remove" onClick={() => this.onRemoveThreshold(threshold)}>
|
||||||
|
<i className="fa fa-times" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { thresholds } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelOptionsGroup title="Thresholds">
|
||||||
|
<div className="thresholds">
|
||||||
|
{thresholds.map((threshold, index) => {
|
||||||
|
return (
|
||||||
|
<div className="thresholds-row" key={`${threshold.index}-${index}`}>
|
||||||
|
<div className="thresholds-row-add-button" onClick={() => this.onAddThreshold(threshold.index + 1)}>
|
||||||
|
<i className="fa fa-plus" />
|
||||||
|
</div>
|
||||||
|
<div className="thresholds-row-color-indicator" style={{ backgroundColor: threshold.color }} />
|
||||||
|
<div className="thresholds-row-input">{this.renderInput(threshold)}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</PanelOptionsGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
.thresholds {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row:first-child > .thresholds-row-color-indicator {
|
||||||
|
border-top-left-radius: $border-radius;
|
||||||
|
border-top-right-radius: $border-radius;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row:last-child > .thresholds-row-color-indicator {
|
||||||
|
border-bottom-left-radius: $border-radius;
|
||||||
|
border-bottom-right-radius: $border-radius;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row-add-button {
|
||||||
|
align-self: center;
|
||||||
|
margin-right: 5px;
|
||||||
|
color: $green;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
background-color: $green;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row-add-button > i {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row-color-indicator {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row-input {
|
||||||
|
margin-top: 49px;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row-input-inner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row-input-inner > *:last-child {
|
||||||
|
border-top-right-radius: $border-radius;
|
||||||
|
border-bottom-right-radius: $border-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row-input-inner-arrow {
|
||||||
|
align-self: center;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 6px solid transparent;
|
||||||
|
border-bottom: 6px solid transparent;
|
||||||
|
border-right: 6px solid $input-label-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row-input-inner-value > input {
|
||||||
|
height: $gf-form-input-height;
|
||||||
|
padding: $input-padding-y $input-padding-x;
|
||||||
|
width: 150px;
|
||||||
|
border-top: 1px solid $input-label-border-color;
|
||||||
|
border-bottom: 1px solid $input-label-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row-input-inner-color {
|
||||||
|
width: 42px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: $input-bg;
|
||||||
|
border: 1px solid $input-label-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row-input-inner-color-colorpicker {
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thresholds-row-input-inner-remove {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: $gf-form-input-height;
|
||||||
|
padding: $input-padding-y $input-padding-x;
|
||||||
|
width: 42px;
|
||||||
|
background-color: $input-label-bg;
|
||||||
|
border: 1px solid $input-label-border-color;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@ -1,49 +1,54 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import Portal from 'app/core/components/Portal/Portal';
|
import * as PopperJS from 'popper.js';
|
||||||
import { Manager, Popper as ReactPopper, Reference } from 'react-popper';
|
import { Manager, Popper as ReactPopper } from 'react-popper';
|
||||||
|
import { Portal } from '@grafana/ui';
|
||||||
import Transition from 'react-transition-group/Transition';
|
import Transition from 'react-transition-group/Transition';
|
||||||
|
|
||||||
|
export enum Themes {
|
||||||
|
Default = 'popper__background--default',
|
||||||
|
Error = 'popper__background--error',
|
||||||
|
Brand = 'popper__background--brand',
|
||||||
|
}
|
||||||
|
|
||||||
const defaultTransitionStyles = {
|
const defaultTransitionStyles = {
|
||||||
transition: 'opacity 200ms linear',
|
transition: 'opacity 200ms linear',
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const transitionStyles = {
|
const transitionStyles: {[key: string]: object} = {
|
||||||
exited: { opacity: 0 },
|
exited: { opacity: 0 },
|
||||||
entering: { opacity: 0 },
|
entering: { opacity: 0 },
|
||||||
entered: { opacity: 1 },
|
entered: { opacity: 1 },
|
||||||
exiting: { opacity: 0 },
|
exiting: { opacity: 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props extends React.DOMAttributes<HTMLDivElement> {
|
||||||
renderContent: (content: any) => any;
|
renderContent: (content: any) => any;
|
||||||
show: boolean;
|
show: boolean;
|
||||||
placement?: any;
|
placement?: PopperJS.Placement;
|
||||||
content: string | ((props: any) => JSX.Element);
|
content: string | ((props: any) => JSX.Element);
|
||||||
refClassName?: string;
|
referenceElement: PopperJS.ReferenceObject;
|
||||||
|
theme?: Themes;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Popper extends PureComponent<Props> {
|
class Popper extends PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { children, renderContent, show, placement, refClassName } = this.props;
|
const { renderContent, show, placement, onMouseEnter, onMouseLeave, theme } = this.props;
|
||||||
const { content } = this.props;
|
const { content } = this.props;
|
||||||
|
|
||||||
|
const popperBackgroundClassName = 'popper__background' + (theme ? ' ' + theme : '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Manager>
|
<Manager>
|
||||||
<Reference>
|
|
||||||
{({ ref }) => (
|
|
||||||
<div className={`popper_ref ${refClassName || ''}`} ref={ref}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Reference>
|
|
||||||
<Transition in={show} timeout={100} mountOnEnter={true} unmountOnExit={true}>
|
<Transition in={show} timeout={100} mountOnEnter={true} unmountOnExit={true}>
|
||||||
{transitionState => (
|
{transitionState => (
|
||||||
<Portal>
|
<Portal>
|
||||||
<ReactPopper placement={placement}>
|
<ReactPopper placement={placement} referenceElement={this.props.referenceElement}>
|
||||||
{({ ref, style, placement, arrowProps }) => {
|
{({ ref, style, placement, arrowProps }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={{
|
style={{
|
||||||
...style,
|
...style,
|
||||||
@ -53,7 +58,7 @@ class Popper extends PureComponent<Props> {
|
|||||||
data-placement={placement}
|
data-placement={placement}
|
||||||
className="popper"
|
className="popper"
|
||||||
>
|
>
|
||||||
<div className="popper__background">
|
<div className={popperBackgroundClassName}>
|
||||||
{renderContent(content)}
|
{renderContent(content)}
|
||||||
<div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" />
|
<div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" />
|
||||||
</div>
|
</div>
|
@ -0,0 +1,99 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import * as PopperJS from 'popper.js';
|
||||||
|
import { Themes } from './Popper';
|
||||||
|
|
||||||
|
type PopperContent = string | (() => JSX.Element);
|
||||||
|
|
||||||
|
export interface UsingPopperProps {
|
||||||
|
show?: boolean;
|
||||||
|
placement?: PopperJS.Placement;
|
||||||
|
content: PopperContent;
|
||||||
|
children: JSX.Element;
|
||||||
|
renderContent?: (content: PopperContent) => JSX.Element;
|
||||||
|
theme?: Themes;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PopperControllerRenderProp = (
|
||||||
|
showPopper: () => void,
|
||||||
|
hidePopper: () => void,
|
||||||
|
popperProps: {
|
||||||
|
show: boolean;
|
||||||
|
placement: PopperJS.Placement;
|
||||||
|
content: string | ((props: any) => JSX.Element);
|
||||||
|
renderContent: (content: any) => any;
|
||||||
|
theme?: Themes;
|
||||||
|
}
|
||||||
|
) => JSX.Element;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
placement?: PopperJS.Placement;
|
||||||
|
content: PopperContent;
|
||||||
|
className?: string;
|
||||||
|
children: PopperControllerRenderProp;
|
||||||
|
theme?: Themes;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
placement: PopperJS.Placement;
|
||||||
|
show: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PopperController extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
placement: this.props.placement || 'auto',
|
||||||
|
show: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps: Props) {
|
||||||
|
if (nextProps.placement && nextProps.placement !== this.state.placement) {
|
||||||
|
this.setState((prevState: State) => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
placement: nextProps.placement || 'auto',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showPopper = () => {
|
||||||
|
this.setState(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
show: true,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
hidePopper = () => {
|
||||||
|
this.setState(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
show: false,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
renderContent(content: PopperContent) {
|
||||||
|
if (typeof content === 'function') {
|
||||||
|
// If it's a function we assume it's a React component
|
||||||
|
const ReactComponent = content;
|
||||||
|
return <ReactComponent />;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children, content, theme } = this.props;
|
||||||
|
const { show, placement } = this.state;
|
||||||
|
|
||||||
|
return children(this.showPopper, this.hidePopper, {
|
||||||
|
show,
|
||||||
|
placement,
|
||||||
|
content,
|
||||||
|
renderContent: this.renderContent,
|
||||||
|
theme,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PopperController;
|
@ -1,13 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import Tooltip from './Tooltip';
|
import { Tooltip } from './Tooltip';
|
||||||
|
|
||||||
describe('Tooltip', () => {
|
describe('Tooltip', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const tree = renderer
|
const tree = renderer
|
||||||
.create(
|
.create(
|
||||||
<Tooltip className="test-class" placement="auto" content="Tooltip text">
|
<Tooltip placement="auto" content="Tooltip text">
|
||||||
<a href="http://www.grafana.com">Link with tooltip</a>
|
<a className="test-class" href="http://www.grafana.com">
|
||||||
|
Link with tooltip
|
||||||
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
.toJSON();
|
.toJSON();
|
32
packages/grafana-ui/src/components/Tooltip/Tooltip.tsx
Normal file
32
packages/grafana-ui/src/components/Tooltip/Tooltip.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React, { createRef } from 'react';
|
||||||
|
import * as PopperJS from 'popper.js';
|
||||||
|
import Popper from './Popper';
|
||||||
|
import PopperController, { UsingPopperProps } from './PopperController';
|
||||||
|
|
||||||
|
export const Tooltip = ({ children, renderContent, ...controllerProps }: UsingPopperProps) => {
|
||||||
|
const tooltipTriggerRef = createRef<PopperJS.ReferenceObject>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PopperController {...controllerProps}>
|
||||||
|
{(showPopper, hidePopper, popperProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{tooltipTriggerRef.current && (
|
||||||
|
<Popper
|
||||||
|
{...popperProps}
|
||||||
|
onMouseEnter={showPopper}
|
||||||
|
onMouseLeave={hidePopper}
|
||||||
|
referenceElement={tooltipTriggerRef.current}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{React.cloneElement(children, {
|
||||||
|
ref: tooltipTriggerRef,
|
||||||
|
onMouseEnter: showPopper,
|
||||||
|
onMouseLeave: hidePopper,
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</PopperController>
|
||||||
|
);
|
||||||
|
};
|
@ -1,5 +1,13 @@
|
|||||||
$popper-margin-from-ref: 5px;
|
$popper-margin-from-ref: 5px;
|
||||||
|
|
||||||
|
|
||||||
|
@mixin popper-theme($backgroundColor, $arrowColor) {
|
||||||
|
background: $backgroundColor;
|
||||||
|
.popper__arrow {
|
||||||
|
border-color: $arrowColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.popper {
|
.popper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: $zindex-tooltip;
|
z-index: $zindex-tooltip;
|
||||||
@ -8,7 +16,24 @@ $popper-margin-from-ref: 5px;
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popper .popper__arrow {
|
.popper__background {
|
||||||
|
background: $tooltipBackground;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
// Themes
|
||||||
|
&.popper__background--error {
|
||||||
|
@include popper-theme($tooltipBackgroundError, $tooltipBackgroundError);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.popper__background--brand {
|
||||||
|
@include popper-theme($tooltipBackgroundBrand, $tooltipBackgroundBrand);
|
||||||
|
@include gradient-vertical($red, $orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popper__arrow {
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
@ -16,17 +41,10 @@ $popper-margin-from-ref: 5px;
|
|||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popper .popper__arrow {
|
.popper__arrow {
|
||||||
border-color: $tooltipBackground;
|
border-color: $tooltipBackground;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popper__background {
|
|
||||||
background: $tooltipBackground;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top
|
// Top
|
||||||
.popper[data-placement^='top'] {
|
.popper[data-placement^='top'] {
|
||||||
padding-bottom: $popper-margin-from-ref;
|
padding-bottom: $popper-margin-from-ref;
|
@ -0,0 +1,12 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Tooltip renders correctly 1`] = `
|
||||||
|
<a
|
||||||
|
className="test-class"
|
||||||
|
href="http://www.grafana.com"
|
||||||
|
onMouseEnter={[Function]}
|
||||||
|
onMouseLeave={[Function]}
|
||||||
|
>
|
||||||
|
Link with tooltip
|
||||||
|
</a>
|
||||||
|
`;
|
@ -1,22 +1,23 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
|
||||||
import { Select } from 'app/core/components/Select/Select';
|
|
||||||
import { MappingType, RangeMap, ValueMap } from 'app/types';
|
|
||||||
|
|
||||||
interface Props {
|
import { MappingType, ValueMapping } from '../../types/panel';
|
||||||
mapping: ValueMap | RangeMap;
|
import { Label } from '../Label/Label';
|
||||||
updateMapping: (mapping) => void;
|
import { Select } from '../Select/Select';
|
||||||
removeMapping: () => void;
|
|
||||||
|
export interface Props {
|
||||||
|
valueMapping: ValueMapping;
|
||||||
|
updateValueMapping: (valueMapping: ValueMapping) => void;
|
||||||
|
removeValueMapping: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
from: string;
|
from?: string;
|
||||||
id: number;
|
id: number;
|
||||||
operator: string;
|
operator: string;
|
||||||
text: string;
|
text: string;
|
||||||
to: string;
|
to?: string;
|
||||||
type: MappingType;
|
type: MappingType;
|
||||||
value: string;
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappingOptions = [
|
const mappingOptions = [
|
||||||
@ -25,36 +26,34 @@ const mappingOptions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default class MappingRow extends PureComponent<Props, State> {
|
export default class MappingRow extends PureComponent<Props, State> {
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = { ...props.valueMapping };
|
||||||
...props.mapping,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMappingValueChange = event => {
|
onMappingValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ value: event.target.value });
|
this.setState({ value: event.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMappingFromChange = event => {
|
onMappingFromChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ from: event.target.value });
|
this.setState({ from: event.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMappingToChange = event => {
|
onMappingToChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ to: event.target.value });
|
this.setState({ to: event.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMappingTextChange = event => {
|
onMappingTextChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ text: event.target.value });
|
this.setState({ text: event.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMappingTypeChange = mappingType => {
|
onMappingTypeChange = (mappingType: MappingType) => {
|
||||||
this.setState({ type: mappingType });
|
this.setState({ type: mappingType });
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMapping = () => {
|
updateMapping = () => {
|
||||||
this.props.updateMapping({ ...this.state });
|
this.props.updateValueMapping({ ...this.state } as ValueMapping);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderRow() {
|
renderRow() {
|
||||||
@ -136,7 +135,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
{this.renderRow()}
|
{this.renderRow()}
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<button onClick={this.props.removeMapping} className="gf-form-label gf-form-label--btn">
|
<button onClick={this.props.removeValueMapping} className="gf-form-label gf-form-label--btn">
|
||||||
<i className="fa fa-times" />
|
<i className="fa fa-times" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
@ -1,26 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import ValueMappings from './ValueMappings';
|
|
||||||
import { defaultProps, OptionModuleProps } from './module';
|
import { ValueMappingsEditor, Props } from './ValueMappingsEditor';
|
||||||
import { MappingType } from 'app/types';
|
import { MappingType } from '../../types/panel';
|
||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props: OptionModuleProps = {
|
const props: Props = {
|
||||||
onChange: jest.fn(),
|
onChange: jest.fn(),
|
||||||
options: {
|
valueMappings: [
|
||||||
...defaultProps.options,
|
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
||||||
mappings: [
|
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
||||||
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
],
|
||||||
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(props, propOverrides);
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
const wrapper = shallow(<ValueMappings {...props} />);
|
const wrapper = shallow(<ValueMappingsEditor {...props} />);
|
||||||
|
|
||||||
const instance = wrapper.instance() as ValueMappings;
|
const instance = wrapper.instance() as ValueMappingsEditor;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
instance,
|
instance,
|
||||||
@ -39,18 +36,20 @@ describe('Render', () => {
|
|||||||
describe('On remove mapping', () => {
|
describe('On remove mapping', () => {
|
||||||
it('Should remove mapping with id 0', () => {
|
it('Should remove mapping with id 0', () => {
|
||||||
const { instance } = setup();
|
const { instance } = setup();
|
||||||
|
|
||||||
instance.onRemoveMapping(1);
|
instance.onRemoveMapping(1);
|
||||||
|
|
||||||
expect(instance.state.mappings).toEqual([
|
expect(instance.state.valueMappings).toEqual([
|
||||||
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
{ id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove mapping with id 1', () => {
|
it('should remove mapping with id 1', () => {
|
||||||
const { instance } = setup();
|
const { instance } = setup();
|
||||||
|
|
||||||
instance.onRemoveMapping(2);
|
instance.onRemoveMapping(2);
|
||||||
|
|
||||||
expect(instance.state.mappings).toEqual([
|
expect(instance.state.valueMappings).toEqual([
|
||||||
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
{ id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@ -66,7 +65,7 @@ describe('Next id to add', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should default to 1', () => {
|
it('should default to 1', () => {
|
||||||
const { instance } = setup({ options: { ...defaultProps.options } });
|
const { instance } = setup({ valueMappings: [] });
|
||||||
|
|
||||||
expect(instance.state.nextIdToAdd).toEqual(1);
|
expect(instance.state.nextIdToAdd).toEqual(1);
|
||||||
});
|
});
|
@ -0,0 +1,105 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
import MappingRow from './MappingRow';
|
||||||
|
import { MappingType, ValueMapping } from '../../types/panel';
|
||||||
|
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
valueMappings: ValueMapping[];
|
||||||
|
onChange: (valueMappings: ValueMapping[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
valueMappings: ValueMapping[];
|
||||||
|
nextIdToAdd: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ValueMappingsEditor extends PureComponent<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const mappings = props.valueMappings;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
valueMappings: mappings,
|
||||||
|
nextIdToAdd: mappings.length > 0 ? this.getMaxIdFromValueMappings(mappings) : 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getMaxIdFromValueMappings(mappings: ValueMapping[]) {
|
||||||
|
return Math.max.apply(null, mappings.map(mapping => mapping.id).map(m => m)) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
addMapping = () =>
|
||||||
|
this.setState(prevState => ({
|
||||||
|
valueMappings: [
|
||||||
|
...prevState.valueMappings,
|
||||||
|
{
|
||||||
|
id: prevState.nextIdToAdd,
|
||||||
|
operator: '',
|
||||||
|
value: '',
|
||||||
|
text: '',
|
||||||
|
type: MappingType.ValueToText,
|
||||||
|
from: '',
|
||||||
|
to: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nextIdToAdd: prevState.nextIdToAdd + 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
onRemoveMapping = (id: number) => {
|
||||||
|
this.setState(
|
||||||
|
prevState => ({
|
||||||
|
valueMappings: prevState.valueMappings.filter(m => {
|
||||||
|
return m.id !== id;
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
() => {
|
||||||
|
this.props.onChange(this.state.valueMappings);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateGauge = (mapping: ValueMapping) => {
|
||||||
|
this.setState(
|
||||||
|
prevState => ({
|
||||||
|
valueMappings: prevState.valueMappings.map(m => {
|
||||||
|
if (m.id === mapping.id) {
|
||||||
|
return { ...mapping };
|
||||||
|
}
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
() => {
|
||||||
|
this.props.onChange(this.state.valueMappings);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { valueMappings } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelOptionsGroup title="Value Mappings">
|
||||||
|
<div>
|
||||||
|
{valueMappings.length > 0 &&
|
||||||
|
valueMappings.map((valueMapping, index) => (
|
||||||
|
<MappingRow
|
||||||
|
key={`${valueMapping.text}-${index}`}
|
||||||
|
valueMapping={valueMapping}
|
||||||
|
updateValueMapping={this.updateGauge}
|
||||||
|
removeValueMapping={() => this.onRemoveMapping(valueMapping.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="add-mapping-row" onClick={this.addMapping}>
|
||||||
|
<div className="add-mapping-row-icon">
|
||||||
|
<i className="fa fa-plus" />
|
||||||
|
</div>
|
||||||
|
<div className="add-mapping-row-label">Add mapping</div>
|
||||||
|
</div>
|
||||||
|
</PanelOptionsGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,15 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Render should render component 1`] = `
|
exports[`Render should render component 1`] = `
|
||||||
<div
|
<Component
|
||||||
className="section gf-form-group"
|
title="Value Mappings"
|
||||||
>
|
>
|
||||||
<h5
|
|
||||||
className="section-heading"
|
|
||||||
>
|
|
||||||
Value mappings
|
|
||||||
</h5>
|
|
||||||
<div>
|
<div>
|
||||||
<MappingRow
|
<MappingRow
|
||||||
key="Ok-0"
|
key="Ok-0"
|
||||||
mapping={
|
removeValueMapping={[Function]}
|
||||||
|
updateValueMapping={[Function]}
|
||||||
|
valueMapping={
|
||||||
Object {
|
Object {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"operator": "",
|
"operator": "",
|
||||||
@ -21,12 +18,12 @@ exports[`Render should render component 1`] = `
|
|||||||
"value": "20",
|
"value": "20",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeMapping={[Function]}
|
|
||||||
updateMapping={[Function]}
|
|
||||||
/>
|
/>
|
||||||
<MappingRow
|
<MappingRow
|
||||||
key="Meh-1"
|
key="Meh-1"
|
||||||
mapping={
|
removeValueMapping={[Function]}
|
||||||
|
updateValueMapping={[Function]}
|
||||||
|
valueMapping={
|
||||||
Object {
|
Object {
|
||||||
"from": "21",
|
"from": "21",
|
||||||
"id": 2,
|
"id": 2,
|
||||||
@ -36,8 +33,6 @@ exports[`Render should render component 1`] = `
|
|||||||
"type": 2,
|
"type": 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeMapping={[Function]}
|
|
||||||
updateMapping={[Function]}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -57,5 +52,5 @@ exports[`Render should render component 1`] = `
|
|||||||
Add mapping
|
Add mapping
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Component>
|
||||||
`;
|
`;
|
@ -1 +1,9 @@
|
|||||||
|
@import 'CustomScrollbar/CustomScrollbar';
|
||||||
@import 'DeleteButton/DeleteButton';
|
@import 'DeleteButton/DeleteButton';
|
||||||
|
@import 'ThresholdsEditor/ThresholdsEditor';
|
||||||
|
@import 'Tooltip/Tooltip';
|
||||||
|
@import 'Select/Select';
|
||||||
|
@import 'PanelOptionsGroup/PanelOptionsGroup';
|
||||||
|
@import 'PanelOptionsGrid/PanelOptionsGrid';
|
||||||
|
@import 'ColorPicker/ColorPicker';
|
||||||
|
@import 'ValueMappingsEditor/ValueMappingsEditor';
|
||||||
|
@ -1 +1,22 @@
|
|||||||
export { DeleteButton } from './DeleteButton/DeleteButton';
|
export { DeleteButton } from './DeleteButton/DeleteButton';
|
||||||
|
export { Tooltip } from './Tooltip/Tooltip';
|
||||||
|
export { Portal } from './Portal/Portal';
|
||||||
|
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
|
||||||
|
export { Label } from './Label/Label';
|
||||||
|
|
||||||
|
// Select
|
||||||
|
export { Select, AsyncSelect, SelectOptionItem } from './Select/Select';
|
||||||
|
export { IndicatorsContainer } from './Select/IndicatorsContainer';
|
||||||
|
export { NoOptionsMessage } from './Select/NoOptionsMessage';
|
||||||
|
export { default as resetSelectStyles } from './Select/resetSelectStyles';
|
||||||
|
|
||||||
|
export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
|
||||||
|
export { ColorPicker } from './ColorPicker/ColorPicker';
|
||||||
|
export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
|
||||||
|
export { SeriesColorPicker } from './ColorPicker/SeriesColorPicker';
|
||||||
|
export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor';
|
||||||
|
export { GfFormLabel } from './GfFormLabel/GfFormLabel';
|
||||||
|
export { Graph } from './Graph/Graph';
|
||||||
|
export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup';
|
||||||
|
export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid';
|
||||||
|
export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor';
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export { GfFormLabel } from './GfFormLabel/GfFormLabel';
|
|
@ -1 +1,3 @@
|
|||||||
|
@import 'vendor/spectrum';
|
||||||
@import 'components/index';
|
@import 'components/index';
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
export * from './components';
|
export * from './components';
|
||||||
export * from './visualizations';
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
export * from './forms';
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { TimeSeries, LoadingState } from './series';
|
import { TimeSeries, LoadingState } from './series';
|
||||||
import { TimeRange } from './time';
|
import { TimeRange } from './time';
|
||||||
|
|
||||||
|
export type InterpolateFunction = (value: string, format?: string | Function) => string;
|
||||||
|
|
||||||
export interface PanelProps<T = any> {
|
export interface PanelProps<T = any> {
|
||||||
timeSeries: TimeSeries[];
|
timeSeries: TimeSeries[];
|
||||||
timeRange: TimeRange;
|
timeRange: TimeRange;
|
||||||
@ -9,6 +11,7 @@ export interface PanelProps<T = any> {
|
|||||||
renderCounter: number;
|
renderCounter: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
onInterpolate: InterpolateFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PanelOptionsProps<T = any> {
|
export interface PanelOptionsProps<T = any> {
|
||||||
@ -29,3 +32,37 @@ export interface PanelMenuItem {
|
|||||||
shortcut?: string;
|
shortcut?: string;
|
||||||
subMenu?: PanelMenuItem[];
|
subMenu?: PanelMenuItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Threshold {
|
||||||
|
index: number;
|
||||||
|
value: number;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BasicGaugeColor {
|
||||||
|
Green = '#299c46',
|
||||||
|
Red = '#d44a3a',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MappingType {
|
||||||
|
ValueToText = 1,
|
||||||
|
RangeToText = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseMap {
|
||||||
|
id: number;
|
||||||
|
operator: string;
|
||||||
|
text: string;
|
||||||
|
type: MappingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ValueMapping = ValueMap | RangeMap;
|
||||||
|
|
||||||
|
export interface ValueMap extends BaseMap {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RangeMap extends BaseMap {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
93
packages/grafana-ui/src/utils/colors.ts
Normal file
93
packages/grafana-ui/src/utils/colors.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
|
export const PALETTE_ROWS = 4;
|
||||||
|
export const PALETTE_COLUMNS = 14;
|
||||||
|
export const DEFAULT_ANNOTATION_COLOR = 'rgba(0, 211, 255, 1)';
|
||||||
|
export const OK_COLOR = 'rgba(11, 237, 50, 1)';
|
||||||
|
export const ALERTING_COLOR = 'rgba(237, 46, 24, 1)';
|
||||||
|
export const NO_DATA_COLOR = 'rgba(150, 150, 150, 1)';
|
||||||
|
export const PENDING_COLOR = 'rgba(247, 149, 32, 1)';
|
||||||
|
export const REGION_FILL_ALPHA = 0.09;
|
||||||
|
|
||||||
|
export const colors = [
|
||||||
|
'#7EB26D', // 0: pale green
|
||||||
|
'#EAB839', // 1: mustard
|
||||||
|
'#6ED0E0', // 2: light blue
|
||||||
|
'#EF843C', // 3: orange
|
||||||
|
'#E24D42', // 4: red
|
||||||
|
'#1F78C1', // 5: ocean
|
||||||
|
'#BA43A9', // 6: purple
|
||||||
|
'#705DA0', // 7: violet
|
||||||
|
'#508642', // 8: dark green
|
||||||
|
'#CCA300', // 9: dark sand
|
||||||
|
'#447EBC',
|
||||||
|
'#C15C17',
|
||||||
|
'#890F02',
|
||||||
|
'#0A437C',
|
||||||
|
'#6D1F62',
|
||||||
|
'#584477',
|
||||||
|
'#B7DBAB',
|
||||||
|
'#F4D598',
|
||||||
|
'#70DBED',
|
||||||
|
'#F9BA8F',
|
||||||
|
'#F29191',
|
||||||
|
'#82B5D8',
|
||||||
|
'#E5A8E2',
|
||||||
|
'#AEA2E0',
|
||||||
|
'#629E51',
|
||||||
|
'#E5AC0E',
|
||||||
|
'#64B0C8',
|
||||||
|
'#E0752D',
|
||||||
|
'#BF1B00',
|
||||||
|
'#0A50A1',
|
||||||
|
'#962D82',
|
||||||
|
'#614D93',
|
||||||
|
'#9AC48A',
|
||||||
|
'#F2C96D',
|
||||||
|
'#65C5DB',
|
||||||
|
'#F9934E',
|
||||||
|
'#EA6460',
|
||||||
|
'#5195CE',
|
||||||
|
'#D683CE',
|
||||||
|
'#806EB7',
|
||||||
|
'#3F6833',
|
||||||
|
'#967302',
|
||||||
|
'#2F575E',
|
||||||
|
'#99440A',
|
||||||
|
'#58140C',
|
||||||
|
'#052B51',
|
||||||
|
'#511749',
|
||||||
|
'#3F2B5B',
|
||||||
|
'#E0F9D7',
|
||||||
|
'#FCEACA',
|
||||||
|
'#CFFAFF',
|
||||||
|
'#F9E2D2',
|
||||||
|
'#FCE2DE',
|
||||||
|
'#BADFF4',
|
||||||
|
'#F9D9F9',
|
||||||
|
'#DEDAF7',
|
||||||
|
];
|
||||||
|
|
||||||
|
function sortColorsByHue(hexColors: string[]) {
|
||||||
|
const hslColors = _.map(hexColors, hexToHsl);
|
||||||
|
|
||||||
|
const sortedHSLColors = _.sortBy(hslColors, ['h']);
|
||||||
|
const chunkedHSLColors = _.chunk(sortedHSLColors, PALETTE_ROWS);
|
||||||
|
const sortedChunkedHSLColors = _.map(chunkedHSLColors, chunk => {
|
||||||
|
return _.sortBy(chunk, 'l');
|
||||||
|
});
|
||||||
|
const flattenedZippedSortedChunkedHSLColors = _.flattenDeep(_.zip(...sortedChunkedHSLColors));
|
||||||
|
|
||||||
|
return _.map(flattenedZippedSortedChunkedHSLColors, hslToHex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToHsl(color: string) {
|
||||||
|
return tinycolor(color).toHsl();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hslToHex(color: any) {
|
||||||
|
return tinycolor(color).toHexString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export let sortedColors = sortColorsByHue(colors);
|
@ -1 +1,3 @@
|
|||||||
export * from './processTimeSeries';
|
export * from './processTimeSeries';
|
||||||
|
export * from './valueFormats/valueFormats';
|
||||||
|
export * from './colors';
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
import { toHex, toHex0x } from './arithmeticFormatters';
|
||||||
|
|
||||||
|
describe('hex', () => {
|
||||||
|
it('positive integer', () => {
|
||||||
|
const str = toHex(100, 0);
|
||||||
|
expect(str).toBe('64');
|
||||||
|
});
|
||||||
|
it('negative integer', () => {
|
||||||
|
const str = toHex(-100, 0);
|
||||||
|
expect(str).toBe('-64');
|
||||||
|
});
|
||||||
|
it('positive float', () => {
|
||||||
|
const str = toHex(50.52, 1);
|
||||||
|
expect(str).toBe('32.8');
|
||||||
|
});
|
||||||
|
it('negative float', () => {
|
||||||
|
const str = toHex(-50.333, 2);
|
||||||
|
expect(str).toBe('-32.547AE147AE14');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hex 0x', () => {
|
||||||
|
it('positive integeter', () => {
|
||||||
|
const str = toHex0x(7999, 0);
|
||||||
|
expect(str).toBe('0x1F3F');
|
||||||
|
});
|
||||||
|
it('negative integer', () => {
|
||||||
|
const str = toHex0x(-584, 0);
|
||||||
|
expect(str).toBe('-0x248');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('positive float', () => {
|
||||||
|
const str = toHex0x(74.443, 3);
|
||||||
|
expect(str).toBe('0x4A.716872B020C4');
|
||||||
|
});
|
||||||
|
it('negative float', () => {
|
||||||
|
const str = toHex0x(-65.458, 1);
|
||||||
|
expect(str).toBe('-0x41.8');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,42 @@
|
|||||||
|
import { toFixed } from './valueFormats';
|
||||||
|
|
||||||
|
export function toPercent(size: number, decimals: number) {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return toFixed(size, decimals) + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toPercentUnit(size: number, decimals: number) {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return toFixed(100 * size, decimals) + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toHex0x(value: number, decimals: number) {
|
||||||
|
if (value == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const hexString = toHex(value, decimals);
|
||||||
|
if (hexString.substring(0, 1) === '-') {
|
||||||
|
return '-0x' + hexString.substring(1);
|
||||||
|
}
|
||||||
|
return '0x' + hexString;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toHex(value: number, decimals: number) {
|
||||||
|
if (value == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return parseFloat(toFixed(value, decimals))
|
||||||
|
.toString(16)
|
||||||
|
.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sci(value: number, decimals: number) {
|
||||||
|
if (value == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return value.toExponential(decimals);
|
||||||
|
}
|
322
packages/grafana-ui/src/utils/valueFormats/categories.ts
Normal file
322
packages/grafana-ui/src/utils/valueFormats/categories.ts
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
import { locale, scaledUnits, simpleCountUnit, toFixed, toFixedUnit, ValueFormatCategory } from './valueFormats';
|
||||||
|
import {
|
||||||
|
dateTimeAsIso,
|
||||||
|
dateTimeAsUS,
|
||||||
|
dateTimeFromNow,
|
||||||
|
toClockMilliseconds,
|
||||||
|
toClockSeconds,
|
||||||
|
toDays,
|
||||||
|
toDurationInHoursMinutesSeconds,
|
||||||
|
toDurationInMilliseconds,
|
||||||
|
toDurationInSeconds,
|
||||||
|
toHours,
|
||||||
|
toMicroSeconds,
|
||||||
|
toMilliSeconds,
|
||||||
|
toMinutes,
|
||||||
|
toNanoSeconds,
|
||||||
|
toSeconds,
|
||||||
|
toTimeTicks,
|
||||||
|
} from './dateTimeFormatters';
|
||||||
|
import { toHex, sci, toHex0x, toPercent, toPercentUnit } from './arithmeticFormatters';
|
||||||
|
import { binarySIPrefix, currency, decimalSIPrefix } from './symbolFormatters';
|
||||||
|
|
||||||
|
export const getCategories = (): ValueFormatCategory[] => [
|
||||||
|
{
|
||||||
|
name: 'Misc',
|
||||||
|
formats: [
|
||||||
|
{ name: 'none', id: 'none', fn: toFixed },
|
||||||
|
{
|
||||||
|
name: 'short',
|
||||||
|
id: 'short',
|
||||||
|
fn: scaledUnits(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Quadr', ' Quint', ' Sext', ' Sept']),
|
||||||
|
},
|
||||||
|
{ name: 'percent (0-100)', id: 'percent', fn: toPercent },
|
||||||
|
{ name: 'percent (0.0-1.0)', id: 'percentunit', fn: toPercentUnit },
|
||||||
|
{ name: 'Humidity (%H)', id: 'humidity', fn: toFixedUnit('%H') },
|
||||||
|
{ name: 'decibel', id: 'dB', fn: toFixedUnit('dB') },
|
||||||
|
{ name: 'hexadecimal (0x)', id: 'hex0x', fn: toHex0x },
|
||||||
|
{ name: 'hexadecimal', id: 'hex', fn: toHex },
|
||||||
|
{ name: 'scientific notation', id: 'sci', fn: sci },
|
||||||
|
{ name: 'locale format', id: 'locale', fn: locale },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Acceleration',
|
||||||
|
formats: [
|
||||||
|
{ name: 'Meters/sec²', id: 'accMS2', fn: toFixedUnit('m/sec²') },
|
||||||
|
{ name: 'Feet/sec²', id: 'accFS2', fn: toFixedUnit('f/sec²') },
|
||||||
|
{ name: 'G unit', id: 'accG', fn: toFixedUnit('g') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Angle',
|
||||||
|
formats: [
|
||||||
|
{ name: 'Degrees (°)', id: 'degree', fn: toFixedUnit('°') },
|
||||||
|
{ name: 'Radians', id: 'radian', fn: toFixedUnit('rad') },
|
||||||
|
{ name: 'Gradian', id: 'grad', fn: toFixedUnit('grad') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Area',
|
||||||
|
formats: [
|
||||||
|
{ name: 'Square Meters (m²)', id: 'areaM2', fn: toFixedUnit('m²') },
|
||||||
|
{ name: 'Square Feet (ft²)', id: 'areaF2', fn: toFixedUnit('ft²') },
|
||||||
|
{ name: 'Square Miles (mi²)', id: 'areaMI2', fn: toFixedUnit('mi²') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Computation',
|
||||||
|
formats: [
|
||||||
|
{ name: 'FLOP/s', id: 'flops', fn: decimalSIPrefix('FLOP/s') },
|
||||||
|
{ name: 'MFLOP/s', id: 'mflops', fn: decimalSIPrefix('FLOP/s', 2) },
|
||||||
|
{ name: 'GFLOP/s', id: 'gflops', fn: decimalSIPrefix('FLOP/s', 3) },
|
||||||
|
{ name: 'TFLOP/s', id: 'tflops', fn: decimalSIPrefix('FLOP/s', 4) },
|
||||||
|
{ name: 'PFLOP/s', id: 'pflops', fn: decimalSIPrefix('FLOP/s', 5) },
|
||||||
|
{ name: 'EFLOP/s', id: 'eflops', fn: decimalSIPrefix('FLOP/s', 6) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Concentration',
|
||||||
|
formats: [
|
||||||
|
{ name: 'parts-per-million (ppm)', id: 'ppm', fn: toFixedUnit('ppm') },
|
||||||
|
{ name: 'parts-per-billion (ppb)', id: 'conppb', fn: toFixedUnit('ppb') },
|
||||||
|
{ name: 'nanogram per cubic meter (ng/m³)', id: 'conngm3', fn: toFixedUnit('ng/m³') },
|
||||||
|
{ name: 'nanogram per normal cubic meter (ng/Nm³)', id: 'conngNm3', fn: toFixedUnit('ng/Nm³') },
|
||||||
|
{ name: 'microgram per cubic meter (μg/m³)', id: 'conμgm3', fn: toFixedUnit('μg/m³') },
|
||||||
|
{ name: 'microgram per normal cubic meter (μg/Nm³)', id: 'conμgNm3', fn: toFixedUnit('μg/Nm³') },
|
||||||
|
{ name: 'milligram per cubic meter (mg/m³)', id: 'conmgm3', fn: toFixedUnit('mg/m³') },
|
||||||
|
{ name: 'milligram per normal cubic meter (mg/Nm³)', id: 'conmgNm3', fn: toFixedUnit('mg/Nm³') },
|
||||||
|
{ name: 'gram per cubic meter (g/m³)', id: 'congm3', fn: toFixedUnit('g/m³') },
|
||||||
|
{ name: 'gram per normal cubic meter (g/Nm³)', id: 'congNm3', fn: toFixedUnit('g/Nm³') },
|
||||||
|
{ name: 'milligrams per decilitre (mg/dL)', id: 'conmgdL', fn: toFixedUnit('mg/dL') },
|
||||||
|
{ name: 'millimoles per litre (mmol/L)', id: 'conmmolL', fn: toFixedUnit('mmol/L') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Currency',
|
||||||
|
formats: [
|
||||||
|
{ name: 'Dollars ($)', id: 'currencyUSD', fn: currency('$') },
|
||||||
|
{ name: 'Pounds (£)', id: 'currencyGBP', fn: currency('£') },
|
||||||
|
{ name: 'Euro (€)', id: 'currencyEUR', fn: currency('€') },
|
||||||
|
{ name: 'Yen (¥)', id: 'currencyJPY', fn: currency('¥') },
|
||||||
|
{ name: 'Rubles (₽)', id: 'currencyRUB', fn: currency('₽') },
|
||||||
|
{ name: 'Hryvnias (₴)', id: 'currencyUAH', fn: currency('₴') },
|
||||||
|
{ name: 'Real (R$)', id: 'currencyBRL', fn: currency('R$') },
|
||||||
|
{ name: 'Danish Krone (kr)', id: 'currencyDKK', fn: currency('kr') },
|
||||||
|
{ name: 'Icelandic Króna (kr)', id: 'currencyISK', fn: currency('kr') },
|
||||||
|
{ name: 'Norwegian Krone (kr)', id: 'currencyNOK', fn: currency('kr') },
|
||||||
|
{ name: 'Swedish Krona (kr)', id: 'currencySEK', fn: currency('kr') },
|
||||||
|
{ name: 'Czech koruna (czk)', id: 'currencyCZK', fn: currency('czk') },
|
||||||
|
{ name: 'Swiss franc (CHF)', id: 'currencyCHF', fn: currency('CHF') },
|
||||||
|
{ name: 'Polish Złoty (PLN)', id: 'currencyPLN', fn: currency('PLN') },
|
||||||
|
{ name: 'Bitcoin (฿)', id: 'currencyBTC', fn: currency('฿') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Data (IEC)',
|
||||||
|
formats: [
|
||||||
|
{ name: 'bits', id: 'bits', fn: binarySIPrefix('b') },
|
||||||
|
{ name: 'bytes', id: 'bytes', fn: binarySIPrefix('B') },
|
||||||
|
{ name: 'kibibytes', id: 'kbytes', fn: binarySIPrefix('B', 1) },
|
||||||
|
{ name: 'mebibytes', id: 'mbytes', fn: binarySIPrefix('B', 2) },
|
||||||
|
{ name: 'gibibytes', id: 'gbytes', fn: binarySIPrefix('B', 3) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Data (Metric)',
|
||||||
|
formats: [
|
||||||
|
{ name: 'bits', id: 'decbits', fn: decimalSIPrefix('d') },
|
||||||
|
{ name: 'bytes', id: 'decbytes', fn: decimalSIPrefix('B') },
|
||||||
|
{ name: 'kilobytes', id: 'deckbytes', fn: decimalSIPrefix('B', 1) },
|
||||||
|
{ name: 'megabytes', id: 'decmbytes', fn: decimalSIPrefix('B', 2) },
|
||||||
|
{ name: 'gigabytes', id: 'decgbytes', fn: decimalSIPrefix('B', 3) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Data Rate',
|
||||||
|
formats: [
|
||||||
|
{ name: 'packets/sec', id: 'pps', fn: decimalSIPrefix('pps') },
|
||||||
|
{ name: 'bits/sec', id: 'bps', fn: decimalSIPrefix('bps') },
|
||||||
|
{ name: 'bytes/sec', id: 'Bps', fn: decimalSIPrefix('B/s') },
|
||||||
|
{ name: 'kilobytes/sec', id: 'KBs', fn: decimalSIPrefix('Bs', 1) },
|
||||||
|
{ name: 'kilobits/sec', id: 'Kbits', fn: decimalSIPrefix('bps', 1) },
|
||||||
|
{ name: 'megabytes/sec', id: 'MBs', fn: decimalSIPrefix('Bs', 2) },
|
||||||
|
{ name: 'megabits/sec', id: 'Mbits', fn: decimalSIPrefix('bps', 2) },
|
||||||
|
{ name: 'gigabytes/sec', id: 'GBs', fn: decimalSIPrefix('Bs', 3) },
|
||||||
|
{ name: 'gigabits/sec', id: 'Gbits', fn: decimalSIPrefix('bps', 3) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Date & Time',
|
||||||
|
formats: [
|
||||||
|
{ name: 'YYYY-MM-DD HH:mm:ss', id: 'dateTimeAsIso', fn: dateTimeAsIso },
|
||||||
|
{ name: 'DD/MM/YYYY h:mm:ss a', id: 'dateTimeAsUS', fn: dateTimeAsUS },
|
||||||
|
{ name: 'From Now', id: 'dateTimeFromNow', fn: dateTimeFromNow },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Energy',
|
||||||
|
formats: [
|
||||||
|
{ name: 'Watt (W)', id: 'watt', fn: decimalSIPrefix('W') },
|
||||||
|
{ name: 'Kilowatt (kW)', id: 'kwatt', fn: decimalSIPrefix('W', 1) },
|
||||||
|
{ name: 'Milliwatt (mW)', id: 'mwatt', fn: decimalSIPrefix('W', -1) },
|
||||||
|
{ name: 'Watt per square meter (W/m²)', id: 'Wm2', fn: toFixedUnit('W/m²') },
|
||||||
|
{ name: 'Volt-ampere (VA)', id: 'voltamp', fn: decimalSIPrefix('VA') },
|
||||||
|
{ name: 'Kilovolt-ampere (kVA)', id: 'kvoltamp', fn: decimalSIPrefix('VA', 1) },
|
||||||
|
{ name: 'Volt-ampere reactive (var)', id: 'voltampreact', fn: decimalSIPrefix('var') },
|
||||||
|
{ name: 'Kilovolt-ampere reactive (kvar)', id: 'kvoltampreact', fn: decimalSIPrefix('var', 1) },
|
||||||
|
{ name: 'Watt-hour (Wh)', id: 'watth', fn: decimalSIPrefix('Wh') },
|
||||||
|
{ name: 'Kilowatt-hour (kWh)', id: 'kwatth', fn: decimalSIPrefix('Wh', 1) },
|
||||||
|
{ name: 'Kilowatt-min (kWm)', id: 'kwattm', fn: decimalSIPrefix('W/Min', 1) },
|
||||||
|
{ name: 'Joule (J)', id: 'joule', fn: decimalSIPrefix('J') },
|
||||||
|
{ name: 'Electron volt (eV)', id: 'ev', fn: decimalSIPrefix('eV') },
|
||||||
|
{ name: 'Ampere (A)', id: 'amp', fn: decimalSIPrefix('A') },
|
||||||
|
{ name: 'Kiloampere (kA)', id: 'kamp', fn: decimalSIPrefix('A', 1) },
|
||||||
|
{ name: 'Milliampere (mA)', id: 'mamp', fn: decimalSIPrefix('A', -1) },
|
||||||
|
{ name: 'Volt (V)', id: 'volt', fn: decimalSIPrefix('V') },
|
||||||
|
{ name: 'Kilovolt (kV)', id: 'kvolt', fn: decimalSIPrefix('V', 1) },
|
||||||
|
{ name: 'Millivolt (mV)', id: 'mvolt', fn: decimalSIPrefix('V', -1) },
|
||||||
|
{ name: 'Decibel-milliwatt (dBm)', id: 'dBm', fn: decimalSIPrefix('dBm') },
|
||||||
|
{ name: 'Ohm (Ω)', id: 'ohm', fn: decimalSIPrefix('Ω') },
|
||||||
|
{ name: 'Lumens (Lm)', id: 'lumens', fn: decimalSIPrefix('Lm') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Flow',
|
||||||
|
formats: [
|
||||||
|
{ name: 'Gallons/min (gpm)', id: 'flowgpm', fn: toFixedUnit('gpm') },
|
||||||
|
{ name: 'Cubic meters/sec (cms)', id: 'flowcms', fn: toFixedUnit('cms') },
|
||||||
|
{ name: 'Cubic feet/sec (cfs)', id: 'flowcfs', fn: toFixedUnit('cfs') },
|
||||||
|
{ name: 'Cubic feet/min (cfm)', id: 'flowcfm', fn: toFixedUnit('cfm') },
|
||||||
|
{ name: 'Litre/hour', id: 'litreh', fn: toFixedUnit('l/h') },
|
||||||
|
{ name: 'Litre/min (l/min)', id: 'flowlpm', fn: toFixedUnit('l/min') },
|
||||||
|
{ name: 'milliLitre/min (mL/min)', id: 'flowmlpm', fn: toFixedUnit('mL/min') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Force',
|
||||||
|
formats: [
|
||||||
|
{ name: 'Newton-meters (Nm)', id: 'forceNm', fn: decimalSIPrefix('Nm') },
|
||||||
|
{ name: 'Kilonewton-meters (kNm)', id: 'forcekNm', fn: decimalSIPrefix('Nm', 1) },
|
||||||
|
{ name: 'Newtons (N)', id: 'forceN', fn: decimalSIPrefix('N') },
|
||||||
|
{ name: 'Kilonewtons (kN)', id: 'forcekN', fn: decimalSIPrefix('N', 1) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Hash Rate',
|
||||||
|
formats: [
|
||||||
|
{ name: 'hashes/sec', id: 'Hs', fn: decimalSIPrefix('H/s') },
|
||||||
|
{ name: 'kilohashes/sec', id: 'KHs', fn: decimalSIPrefix('H/s', 1) },
|
||||||
|
{ name: 'megahashes/sec', id: 'MHs', fn: decimalSIPrefix('H/s', 2) },
|
||||||
|
{ name: 'gigahashes/sec', id: 'GHs', fn: decimalSIPrefix('H/s', 3) },
|
||||||
|
{ name: 'terahashes/sec', id: 'THs', fn: decimalSIPrefix('H/s', 4) },
|
||||||
|
{ name: 'petahashes/sec', id: 'PHs', fn: decimalSIPrefix('H/s', 5) },
|
||||||
|
{ name: 'exahashes/sec', id: 'EHs', fn: decimalSIPrefix('H/s', 6) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mass',
|
||||||
|
formats: [
|
||||||
|
{ name: 'milligram (mg)', id: 'massmg', fn: decimalSIPrefix('g', -1) },
|
||||||
|
{ name: 'gram (g)', id: 'massg', fn: decimalSIPrefix('g') },
|
||||||
|
{ name: 'kilogram (kg)', id: 'masskg', fn: decimalSIPrefix('g', 1) },
|
||||||
|
{ name: 'metric ton (t)', id: 'masst', fn: toFixedUnit('t') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'length',
|
||||||
|
formats: [
|
||||||
|
{ name: 'millimetre (mm)', id: 'lengthmm', fn: decimalSIPrefix('m', -1) },
|
||||||
|
{ name: 'feet (ft)', id: 'lengthft', fn: toFixedUnit('ft') },
|
||||||
|
{ name: 'meter (m)', id: 'lengthm', fn: decimalSIPrefix('m') },
|
||||||
|
{ name: 'kilometer (km)', id: 'lengthkm', fn: decimalSIPrefix('m', 1) },
|
||||||
|
{ name: 'mile (mi)', id: 'lengthmi', fn: toFixedUnit('mi') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Pressure',
|
||||||
|
formats: [
|
||||||
|
{ name: 'Millibars', id: 'pressurembar', fn: decimalSIPrefix('bar', -1) },
|
||||||
|
{ name: 'Bars', id: 'pressurebar', fn: decimalSIPrefix('bar') },
|
||||||
|
{ name: 'Kilobars', id: 'pressurekbar', fn: decimalSIPrefix('bar', 1) },
|
||||||
|
{ name: 'Hectopascals', id: 'pressurehpa', fn: toFixedUnit('hPa') },
|
||||||
|
{ name: 'Kilopascals', id: 'pressurekpa', fn: toFixedUnit('kPa') },
|
||||||
|
{ name: 'Inches of mercury', id: 'pressurehg', fn: toFixedUnit('"Hg') },
|
||||||
|
{ name: 'PSI', id: 'pressurepsi', fn: scaledUnits(1000, ['psi', 'ksi', 'Mpsi']) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Radiation',
|
||||||
|
formats: [
|
||||||
|
{ name: 'Becquerel (Bq)', id: 'radbq', fn: decimalSIPrefix('Bq') },
|
||||||
|
{ name: 'curie (Ci)', id: 'radci', fn: decimalSIPrefix('Ci') },
|
||||||
|
{ name: 'Gray (Gy)', id: 'radgy', fn: decimalSIPrefix('Gy') },
|
||||||
|
{ name: 'rad', id: 'radrad', fn: decimalSIPrefix('rad') },
|
||||||
|
{ name: 'Sievert (Sv)', id: 'radsv', fn: decimalSIPrefix('Sv') },
|
||||||
|
{ name: 'rem', id: 'radrem', fn: decimalSIPrefix('rem') },
|
||||||
|
{ name: 'Exposure (C/kg)', id: 'radexpckg', fn: decimalSIPrefix('C/kg') },
|
||||||
|
{ name: 'roentgen (R)', id: 'radr', fn: decimalSIPrefix('R') },
|
||||||
|
{ name: 'Sievert/hour (Sv/h)', id: 'radsvh', fn: decimalSIPrefix('Sv/h') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Temperature',
|
||||||
|
formats: [
|
||||||
|
{ name: 'Celsius (°C)', id: 'celsius', fn: toFixedUnit('°C') },
|
||||||
|
{ name: 'Farenheit (°F)', id: 'farenheit', fn: toFixedUnit('°F') },
|
||||||
|
{ name: 'Kelvin (K)', id: 'kelvin', fn: toFixedUnit('K') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Time',
|
||||||
|
formats: [
|
||||||
|
{ name: 'Hertz (1/s)', id: 'hertz', fn: decimalSIPrefix('Hz') },
|
||||||
|
{ name: 'nanoseconds (ns)', id: 'ns', fn: toNanoSeconds },
|
||||||
|
{ name: 'microseconds (µs)', id: 'µs', fn: toMicroSeconds },
|
||||||
|
{ name: 'milliseconds (ms)', id: 'ms', fn: toMilliSeconds },
|
||||||
|
{ name: 'seconds (s)', id: 's', fn: toSeconds },
|
||||||
|
{ name: 'minutes (m)', id: 'm', fn: toMinutes },
|
||||||
|
{ name: 'hours (h)', id: 'h', fn: toHours },
|
||||||
|
{ name: 'days (d)', id: 'd', fn: toDays },
|
||||||
|
{ name: 'duration (ms)', id: 'dtdurationms', fn: toDurationInMilliseconds },
|
||||||
|
{ name: 'duration (s)', id: 'dtdurations', fn: toDurationInSeconds },
|
||||||
|
{ name: 'duration (hh:mm:ss)', id: 'dthms', fn: toDurationInHoursMinutesSeconds },
|
||||||
|
{ name: 'Timeticks (s/100)', id: 'timeticks', fn: toTimeTicks },
|
||||||
|
{ name: 'clock (ms)', id: 'clockms', fn: toClockMilliseconds },
|
||||||
|
{ name: 'clock (s)', id: 'clocks', fn: toClockSeconds },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Throughput',
|
||||||
|
formats: [
|
||||||
|
{ name: 'ops/sec (ops)', id: 'ops', fn: simpleCountUnit('ops') },
|
||||||
|
{ name: 'requests/sec (rps)', id: 'reqps', fn: simpleCountUnit('reqps') },
|
||||||
|
{ name: 'reads/sec (rps)', id: 'rps', fn: simpleCountUnit('rps') },
|
||||||
|
{ name: 'writes/sec (wps)', id: 'wps', fn: simpleCountUnit('wps') },
|
||||||
|
{ name: 'I/O ops/sec (iops)', id: 'iops', fn: simpleCountUnit('iops') },
|
||||||
|
{ name: 'ops/min (opm)', id: 'opm', fn: simpleCountUnit('opm') },
|
||||||
|
{ name: 'reads/min (rpm)', id: 'rpm', fn: simpleCountUnit('rpm') },
|
||||||
|
{ name: 'writes/min (wpm)', id: 'wpm', fn: simpleCountUnit('wpm') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Velocity',
|
||||||
|
formats: [
|
||||||
|
{ name: 'metres/second (m/s)', id: 'velocityms', fn: toFixedUnit('m/s') },
|
||||||
|
{ name: 'kilometers/hour (km/h)', id: 'velocitykmh', fn: toFixedUnit('km/h') },
|
||||||
|
{ name: 'miles/hour (mph)', id: 'velocitymph', fn: toFixedUnit('mph') },
|
||||||
|
{ name: 'knot (kn)', id: 'velocityknot', fn: toFixedUnit('kn') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Volume',
|
||||||
|
formats: [
|
||||||
|
{ name: 'millilitre (mL)', id: 'mlitre', fn: decimalSIPrefix('L', -1) },
|
||||||
|
{ name: 'litre (L)', id: 'litre', fn: decimalSIPrefix('L') },
|
||||||
|
{ name: 'cubic metre', id: 'm3', fn: toFixedUnit('m³') },
|
||||||
|
{ name: 'Normal cubic metre', id: 'Nm3', fn: toFixedUnit('Nm³') },
|
||||||
|
{ name: 'cubic decimetre', id: 'dm3', fn: toFixedUnit('dm³') },
|
||||||
|
{ name: 'gallons', id: 'gallons', fn: toFixedUnit('gal') },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
];
|
@ -0,0 +1,231 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import {
|
||||||
|
dateTimeAsIso,
|
||||||
|
dateTimeAsUS,
|
||||||
|
dateTimeFromNow,
|
||||||
|
Interval,
|
||||||
|
toClock,
|
||||||
|
toDuration,
|
||||||
|
toDurationInMilliseconds,
|
||||||
|
toDurationInSeconds,
|
||||||
|
} from './dateTimeFormatters';
|
||||||
|
|
||||||
|
describe('date time formats', () => {
|
||||||
|
const epoch = 1505634997920;
|
||||||
|
const utcTime = moment.utc(epoch);
|
||||||
|
const browserTime = moment(epoch);
|
||||||
|
|
||||||
|
it('should format as iso date', () => {
|
||||||
|
const expected = browserTime.format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
const actual = dateTimeAsIso(epoch, 0, 0, false);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format as iso date (in UTC)', () => {
|
||||||
|
const expected = utcTime.format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
const actual = dateTimeAsIso(epoch, 0, 0, true);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format as iso date and skip date when today', () => {
|
||||||
|
const now = moment();
|
||||||
|
const expected = now.format('HH:mm:ss');
|
||||||
|
const actual = dateTimeAsIso(now.valueOf(), 0, 0, false);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format as iso date (in UTC) and skip date when today', () => {
|
||||||
|
const now = moment.utc();
|
||||||
|
const expected = now.format('HH:mm:ss');
|
||||||
|
const actual = dateTimeAsIso(now.valueOf(), 0, 0, true);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format as US date', () => {
|
||||||
|
const expected = browserTime.format('MM/DD/YYYY h:mm:ss a');
|
||||||
|
const actual = dateTimeAsUS(epoch, 0, 0, false);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format as US date (in UTC)', () => {
|
||||||
|
const expected = utcTime.format('MM/DD/YYYY h:mm:ss a');
|
||||||
|
const actual = dateTimeAsUS(epoch, 0, 0, true);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format as US date and skip date when today', () => {
|
||||||
|
const now = moment();
|
||||||
|
const expected = now.format('h:mm:ss a');
|
||||||
|
const actual = dateTimeAsUS(now.valueOf(), 0, 0, false);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format as US date (in UTC) and skip date when today', () => {
|
||||||
|
const now = moment.utc();
|
||||||
|
const expected = now.format('h:mm:ss a');
|
||||||
|
const actual = dateTimeAsUS(now.valueOf(), 0, 0, true);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format as from now with days', () => {
|
||||||
|
const daysAgo = moment().add(-7, 'd');
|
||||||
|
const expected = '7 days ago';
|
||||||
|
const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, false);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format as from now with days (in UTC)', () => {
|
||||||
|
const daysAgo = moment.utc().add(-7, 'd');
|
||||||
|
const expected = '7 days ago';
|
||||||
|
const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, true);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format as from now with minutes', () => {
|
||||||
|
const daysAgo = moment().add(-2, 'm');
|
||||||
|
const expected = '2 minutes ago';
|
||||||
|
const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, false);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format as from now with minutes (in UTC)', () => {
|
||||||
|
const daysAgo = moment.utc().add(-2, 'm');
|
||||||
|
const expected = '2 minutes ago';
|
||||||
|
const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, true);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('duration', () => {
|
||||||
|
it('0 milliseconds', () => {
|
||||||
|
const str = toDurationInMilliseconds(0, 0);
|
||||||
|
expect(str).toBe('0 milliseconds');
|
||||||
|
});
|
||||||
|
it('1 millisecond', () => {
|
||||||
|
const str = toDurationInMilliseconds(1, 0);
|
||||||
|
expect(str).toBe('1 millisecond');
|
||||||
|
});
|
||||||
|
it('-1 millisecond', () => {
|
||||||
|
const str = toDurationInMilliseconds(-1, 0);
|
||||||
|
expect(str).toBe('1 millisecond ago');
|
||||||
|
});
|
||||||
|
it('seconds', () => {
|
||||||
|
const str = toDurationInSeconds(1, 0);
|
||||||
|
expect(str).toBe('1 second');
|
||||||
|
});
|
||||||
|
it('minutes', () => {
|
||||||
|
const str = toDuration(1, 0, Interval.Minute);
|
||||||
|
expect(str).toBe('1 minute');
|
||||||
|
});
|
||||||
|
it('hours', () => {
|
||||||
|
const str = toDuration(1, 0, Interval.Hour);
|
||||||
|
expect(str).toBe('1 hour');
|
||||||
|
});
|
||||||
|
it('days', () => {
|
||||||
|
const str = toDuration(1, 0, Interval.Day);
|
||||||
|
expect(str).toBe('1 day');
|
||||||
|
});
|
||||||
|
it('weeks', () => {
|
||||||
|
const str = toDuration(1, 0, Interval.Week);
|
||||||
|
expect(str).toBe('1 week');
|
||||||
|
});
|
||||||
|
it('months', () => {
|
||||||
|
const str = toDuration(1, 0, Interval.Month);
|
||||||
|
expect(str).toBe('1 month');
|
||||||
|
});
|
||||||
|
it('years', () => {
|
||||||
|
const str = toDuration(1, 0, Interval.Year);
|
||||||
|
expect(str).toBe('1 year');
|
||||||
|
});
|
||||||
|
it('decimal days', () => {
|
||||||
|
const str = toDuration(1.5, 2, Interval.Day);
|
||||||
|
expect(str).toBe('1 day, 12 hours, 0 minutes');
|
||||||
|
});
|
||||||
|
it('decimal months', () => {
|
||||||
|
const str = toDuration(1.5, 3, Interval.Month);
|
||||||
|
expect(str).toBe('1 month, 2 weeks, 1 day, 0 hours');
|
||||||
|
});
|
||||||
|
it('no decimals', () => {
|
||||||
|
const str = toDuration(38898367008, 0, Interval.Millisecond);
|
||||||
|
expect(str).toBe('1 year');
|
||||||
|
});
|
||||||
|
it('1 decimal', () => {
|
||||||
|
const str = toDuration(38898367008, 1, Interval.Millisecond);
|
||||||
|
expect(str).toBe('1 year, 2 months');
|
||||||
|
});
|
||||||
|
it('too many decimals', () => {
|
||||||
|
const str = toDuration(38898367008, 20, Interval.Millisecond);
|
||||||
|
expect(str).toBe('1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, 7 seconds, 8 milliseconds');
|
||||||
|
});
|
||||||
|
it('floating point error', () => {
|
||||||
|
const str = toDuration(36993906007, 8, Interval.Millisecond);
|
||||||
|
expect(str).toBe('1 year, 2 months, 0 weeks, 3 days, 4 hours, 5 minutes, 6 seconds, 7 milliseconds');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('clock', () => {
|
||||||
|
it('size less than 1 second', () => {
|
||||||
|
const str = toClock(999, 0);
|
||||||
|
expect(str).toBe('999ms');
|
||||||
|
});
|
||||||
|
describe('size less than 1 minute', () => {
|
||||||
|
it('default', () => {
|
||||||
|
const str = toClock(59999);
|
||||||
|
expect(str).toBe('59s:999ms');
|
||||||
|
});
|
||||||
|
it('decimals equals 0', () => {
|
||||||
|
const str = toClock(59999, 0);
|
||||||
|
expect(str).toBe('59s');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('size less than 1 hour', () => {
|
||||||
|
it('default', () => {
|
||||||
|
const str = toClock(3599999);
|
||||||
|
expect(str).toBe('59m:59s:999ms');
|
||||||
|
});
|
||||||
|
it('decimals equals 0', () => {
|
||||||
|
const str = toClock(3599999, 0);
|
||||||
|
expect(str).toBe('59m');
|
||||||
|
});
|
||||||
|
it('decimals equals 1', () => {
|
||||||
|
const str = toClock(3599999, 1);
|
||||||
|
expect(str).toBe('59m:59s');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('size greater than or equal 1 hour', () => {
|
||||||
|
it('default', () => {
|
||||||
|
const str = toClock(7199999);
|
||||||
|
expect(str).toBe('01h:59m:59s:999ms');
|
||||||
|
});
|
||||||
|
it('decimals equals 0', () => {
|
||||||
|
const str = toClock(7199999, 0);
|
||||||
|
expect(str).toBe('01h');
|
||||||
|
});
|
||||||
|
it('decimals equals 1', () => {
|
||||||
|
const str = toClock(7199999, 1);
|
||||||
|
expect(str).toBe('01h:59m');
|
||||||
|
});
|
||||||
|
it('decimals equals 2', () => {
|
||||||
|
const str = toClock(7199999, 2);
|
||||||
|
expect(str).toBe('01h:59m:59s');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('size greater than or equal 1 day', () => {
|
||||||
|
it('default', () => {
|
||||||
|
const str = toClock(89999999);
|
||||||
|
expect(str).toBe('24h:59m:59s:999ms');
|
||||||
|
});
|
||||||
|
it('decimals equals 0', () => {
|
||||||
|
const str = toClock(89999999, 0);
|
||||||
|
expect(str).toBe('24h');
|
||||||
|
});
|
||||||
|
it('decimals equals 1', () => {
|
||||||
|
const str = toClock(89999999, 1);
|
||||||
|
expect(str).toBe('24h:59m');
|
||||||
|
});
|
||||||
|
it('decimals equals 2', () => {
|
||||||
|
const str = toClock(89999999, 2);
|
||||||
|
expect(str).toBe('24h:59m:59s');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
312
packages/grafana-ui/src/utils/valueFormats/dateTimeFormatters.ts
Normal file
312
packages/grafana-ui/src/utils/valueFormats/dateTimeFormatters.ts
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
import { toFixed, toFixedScaled } from './valueFormats';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
interface IntervalsInSeconds {
|
||||||
|
[interval: string]: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Interval {
|
||||||
|
Year = 'year',
|
||||||
|
Month = 'month',
|
||||||
|
Week = 'week',
|
||||||
|
Day = 'day',
|
||||||
|
Hour = 'hour',
|
||||||
|
Minute = 'minute',
|
||||||
|
Second = 'second',
|
||||||
|
Millisecond = 'millisecond',
|
||||||
|
}
|
||||||
|
|
||||||
|
const INTERVALS_IN_SECONDS: IntervalsInSeconds = {
|
||||||
|
[Interval.Year]: 31536000,
|
||||||
|
[Interval.Month]: 2592000,
|
||||||
|
[Interval.Week]: 604800,
|
||||||
|
[Interval.Day]: 86400,
|
||||||
|
[Interval.Hour]: 3600,
|
||||||
|
[Interval.Minute]: 60,
|
||||||
|
[Interval.Second]: 1,
|
||||||
|
[Interval.Millisecond]: 0.001,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function toNanoSeconds(size: number, decimals: number, scaledDecimals: number) {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(size) < 1000) {
|
||||||
|
return toFixed(size, decimals) + ' ns';
|
||||||
|
} else if (Math.abs(size) < 1000000) {
|
||||||
|
return toFixedScaled(size / 1000, decimals, scaledDecimals, 3, ' µs');
|
||||||
|
} else if (Math.abs(size) < 1000000000) {
|
||||||
|
return toFixedScaled(size / 1000000, decimals, scaledDecimals, 6, ' ms');
|
||||||
|
} else if (Math.abs(size) < 60000000000) {
|
||||||
|
return toFixedScaled(size / 1000000000, decimals, scaledDecimals, 9, ' s');
|
||||||
|
} else {
|
||||||
|
return toFixedScaled(size / 60000000000, decimals, scaledDecimals, 12, ' min');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toMicroSeconds(size: number, decimals: number, scaledDecimals: number) {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(size) < 1000) {
|
||||||
|
return toFixed(size, decimals) + ' µs';
|
||||||
|
} else if (Math.abs(size) < 1000000) {
|
||||||
|
return toFixedScaled(size / 1000, decimals, scaledDecimals, 3, ' ms');
|
||||||
|
} else {
|
||||||
|
return toFixedScaled(size / 1000000, decimals, scaledDecimals, 6, ' s');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toMilliSeconds(size: number, decimals: number, scaledDecimals: number) {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(size) < 1000) {
|
||||||
|
return toFixed(size, decimals) + ' ms';
|
||||||
|
} else if (Math.abs(size) < 60000) {
|
||||||
|
// Less than 1 min
|
||||||
|
return toFixedScaled(size / 1000, decimals, scaledDecimals, 3, ' s');
|
||||||
|
} else if (Math.abs(size) < 3600000) {
|
||||||
|
// Less than 1 hour, divide in minutes
|
||||||
|
return toFixedScaled(size / 60000, decimals, scaledDecimals, 5, ' min');
|
||||||
|
} else if (Math.abs(size) < 86400000) {
|
||||||
|
// Less than one day, divide in hours
|
||||||
|
return toFixedScaled(size / 3600000, decimals, scaledDecimals, 7, ' hour');
|
||||||
|
} else if (Math.abs(size) < 31536000000) {
|
||||||
|
// Less than one year, divide in days
|
||||||
|
return toFixedScaled(size / 86400000, decimals, scaledDecimals, 8, ' day');
|
||||||
|
}
|
||||||
|
|
||||||
|
return toFixedScaled(size / 31536000000, decimals, scaledDecimals, 10, ' year');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toSeconds(size: number, decimals: number, scaledDecimals: number) {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less than 1 µs, divide in ns
|
||||||
|
if (Math.abs(size) < 0.000001) {
|
||||||
|
return toFixedScaled(size * 1e9, decimals, scaledDecimals - decimals, -9, ' ns');
|
||||||
|
}
|
||||||
|
// Less than 1 ms, divide in µs
|
||||||
|
if (Math.abs(size) < 0.001) {
|
||||||
|
return toFixedScaled(size * 1e6, decimals, scaledDecimals - decimals, -6, ' µs');
|
||||||
|
}
|
||||||
|
// Less than 1 second, divide in ms
|
||||||
|
if (Math.abs(size) < 1) {
|
||||||
|
return toFixedScaled(size * 1e3, decimals, scaledDecimals - decimals, -3, ' ms');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(size) < 60) {
|
||||||
|
return toFixed(size, decimals) + ' s';
|
||||||
|
} else if (Math.abs(size) < 3600) {
|
||||||
|
// Less than 1 hour, divide in minutes
|
||||||
|
return toFixedScaled(size / 60, decimals, scaledDecimals, 1, ' min');
|
||||||
|
} else if (Math.abs(size) < 86400) {
|
||||||
|
// Less than one day, divide in hours
|
||||||
|
return toFixedScaled(size / 3600, decimals, scaledDecimals, 4, ' hour');
|
||||||
|
} else if (Math.abs(size) < 604800) {
|
||||||
|
// Less than one week, divide in days
|
||||||
|
return toFixedScaled(size / 86400, decimals, scaledDecimals, 5, ' day');
|
||||||
|
} else if (Math.abs(size) < 31536000) {
|
||||||
|
// Less than one year, divide in week
|
||||||
|
return toFixedScaled(size / 604800, decimals, scaledDecimals, 6, ' week');
|
||||||
|
}
|
||||||
|
|
||||||
|
return toFixedScaled(size / 3.15569e7, decimals, scaledDecimals, 7, ' year');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toMinutes(size: number, decimals: number, scaledDecimals: number) {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(size) < 60) {
|
||||||
|
return toFixed(size, decimals) + ' min';
|
||||||
|
} else if (Math.abs(size) < 1440) {
|
||||||
|
return toFixedScaled(size / 60, decimals, scaledDecimals, 2, ' hour');
|
||||||
|
} else if (Math.abs(size) < 10080) {
|
||||||
|
return toFixedScaled(size / 1440, decimals, scaledDecimals, 3, ' day');
|
||||||
|
} else if (Math.abs(size) < 604800) {
|
||||||
|
return toFixedScaled(size / 10080, decimals, scaledDecimals, 4, ' week');
|
||||||
|
} else {
|
||||||
|
return toFixedScaled(size / 5.25948e5, decimals, scaledDecimals, 5, ' year');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toHours(size: number, decimals: number, scaledDecimals: number) {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(size) < 24) {
|
||||||
|
return toFixed(size, decimals) + ' hour';
|
||||||
|
} else if (Math.abs(size) < 168) {
|
||||||
|
return toFixedScaled(size / 24, decimals, scaledDecimals, 2, ' day');
|
||||||
|
} else if (Math.abs(size) < 8760) {
|
||||||
|
return toFixedScaled(size / 168, decimals, scaledDecimals, 3, ' week');
|
||||||
|
} else {
|
||||||
|
return toFixedScaled(size / 8760, decimals, scaledDecimals, 4, ' year');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toDays(size: number, decimals: number, scaledDecimals: number) {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(size) < 7) {
|
||||||
|
return toFixed(size, decimals) + ' day';
|
||||||
|
} else if (Math.abs(size) < 365) {
|
||||||
|
return toFixedScaled(size / 7, decimals, scaledDecimals, 2, ' week');
|
||||||
|
} else {
|
||||||
|
return toFixedScaled(size / 365, decimals, scaledDecimals, 3, ' year');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toDuration(size: number, decimals: number, timeScale: Interval): string {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (size === 0) {
|
||||||
|
return '0 ' + timeScale + 's';
|
||||||
|
}
|
||||||
|
if (size < 0) {
|
||||||
|
return toDuration(-size, decimals, timeScale) + ' ago';
|
||||||
|
}
|
||||||
|
|
||||||
|
const units = [
|
||||||
|
{ long: Interval.Year },
|
||||||
|
{ long: Interval.Month },
|
||||||
|
{ long: Interval.Week },
|
||||||
|
{ long: Interval.Day },
|
||||||
|
{ long: Interval.Hour },
|
||||||
|
{ long: Interval.Minute },
|
||||||
|
{ long: Interval.Second },
|
||||||
|
{ long: Interval.Millisecond },
|
||||||
|
];
|
||||||
|
// convert $size to milliseconds
|
||||||
|
// intervals_in_seconds uses seconds (duh), convert them to milliseconds here to minimize floating point errors
|
||||||
|
size *= INTERVALS_IN_SECONDS[timeScale] * 1000;
|
||||||
|
|
||||||
|
const strings = [];
|
||||||
|
// after first value >= 1 print only $decimals more
|
||||||
|
let decrementDecimals = false;
|
||||||
|
for (let i = 0; i < units.length && decimals >= 0; i++) {
|
||||||
|
const interval = INTERVALS_IN_SECONDS[units[i].long] * 1000;
|
||||||
|
const value = size / interval;
|
||||||
|
if (value >= 1 || decrementDecimals) {
|
||||||
|
decrementDecimals = true;
|
||||||
|
const floor = Math.floor(value);
|
||||||
|
const unit = units[i].long + (floor !== 1 ? 's' : '');
|
||||||
|
strings.push(floor + ' ' + unit);
|
||||||
|
size = size % interval;
|
||||||
|
decimals--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toClock(size: number, decimals?: number) {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// < 1 second
|
||||||
|
if (size < 1000) {
|
||||||
|
return moment.utc(size).format('SSS\\m\\s');
|
||||||
|
}
|
||||||
|
|
||||||
|
// < 1 minute
|
||||||
|
if (size < 60000) {
|
||||||
|
let format = 'ss\\s:SSS\\m\\s';
|
||||||
|
if (decimals === 0) {
|
||||||
|
format = 'ss\\s';
|
||||||
|
}
|
||||||
|
return moment.utc(size).format(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
// < 1 hour
|
||||||
|
if (size < 3600000) {
|
||||||
|
let format = 'mm\\m:ss\\s:SSS\\m\\s';
|
||||||
|
if (decimals === 0) {
|
||||||
|
format = 'mm\\m';
|
||||||
|
} else if (decimals === 1) {
|
||||||
|
format = 'mm\\m:ss\\s';
|
||||||
|
}
|
||||||
|
return moment.utc(size).format(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
let format = 'mm\\m:ss\\s:SSS\\m\\s';
|
||||||
|
|
||||||
|
const hours = `${('0' + Math.floor(moment.duration(size, 'milliseconds').asHours())).slice(-2)}h`;
|
||||||
|
|
||||||
|
if (decimals === 0) {
|
||||||
|
format = '';
|
||||||
|
} else if (decimals === 1) {
|
||||||
|
format = 'mm\\m';
|
||||||
|
} else if (decimals === 2) {
|
||||||
|
format = 'mm\\m:ss\\s';
|
||||||
|
}
|
||||||
|
|
||||||
|
return format ? `${hours}:${moment.utc(size).format(format)}` : hours;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toDurationInMilliseconds(size: number, decimals: number) {
|
||||||
|
return toDuration(size, decimals, Interval.Millisecond);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toDurationInSeconds(size: number, decimals: number) {
|
||||||
|
return toDuration(size, decimals, Interval.Second);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toDurationInHoursMinutesSeconds(size: number) {
|
||||||
|
const strings = [];
|
||||||
|
const numHours = Math.floor(size / 3600);
|
||||||
|
const numMinutes = Math.floor((size % 3600) / 60);
|
||||||
|
const numSeconds = Math.floor((size % 3600) % 60);
|
||||||
|
numHours > 9 ? strings.push('' + numHours) : strings.push('0' + numHours);
|
||||||
|
numMinutes > 9 ? strings.push('' + numMinutes) : strings.push('0' + numMinutes);
|
||||||
|
numSeconds > 9 ? strings.push('' + numSeconds) : strings.push('0' + numSeconds);
|
||||||
|
return strings.join(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toTimeTicks(size: number, decimals: number, scaledDecimals: number) {
|
||||||
|
return toSeconds(size, decimals, scaledDecimals);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toClockMilliseconds(size: number, decimals: number) {
|
||||||
|
return toClock(size, decimals);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toClockSeconds(size: number, decimals: number) {
|
||||||
|
return toClock(size * 1000, decimals);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dateTimeAsIso(value: number, decimals: number, scaledDecimals: number, isUtc: boolean) {
|
||||||
|
const time = isUtc ? moment.utc(value) : moment(value);
|
||||||
|
|
||||||
|
if (moment().isSame(value, 'day')) {
|
||||||
|
return time.format('HH:mm:ss');
|
||||||
|
}
|
||||||
|
return time.format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dateTimeAsUS(value: number, decimals: number, scaledDecimals: number, isUtc: boolean) {
|
||||||
|
const time = isUtc ? moment.utc(value) : moment(value);
|
||||||
|
|
||||||
|
if (moment().isSame(value, 'day')) {
|
||||||
|
return time.format('h:mm:ss a');
|
||||||
|
}
|
||||||
|
return time.format('MM/DD/YYYY h:mm:ss a');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dateTimeFromNow(value: number, decimals: number, scaledDecimals: number, isUtc: boolean) {
|
||||||
|
const time = isUtc ? moment.utc(value) : moment(value);
|
||||||
|
return time.fromNow();
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import { currency } from './symbolFormatters';
|
||||||
|
|
||||||
|
describe('Currency', () => {
|
||||||
|
it('should format as usd', () => {
|
||||||
|
expect(currency('$')(1532.82, 1, -1)).toEqual('$1.53K');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,30 @@
|
|||||||
|
import { scaledUnits } from './valueFormats';
|
||||||
|
|
||||||
|
export function currency(symbol: string) {
|
||||||
|
const units = ['', 'K', 'M', 'B', 'T'];
|
||||||
|
const scaler = scaledUnits(1000, units);
|
||||||
|
return (size: number, decimals: number, scaledDecimals: number) => {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const scaled = scaler(size, decimals, scaledDecimals);
|
||||||
|
return symbol + scaled;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function binarySIPrefix(unit: string, offset = 0) {
|
||||||
|
const prefixes = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'].slice(offset);
|
||||||
|
const units = prefixes.map(p => {
|
||||||
|
return ' ' + p + unit;
|
||||||
|
});
|
||||||
|
return scaledUnits(1024, units);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decimalSIPrefix(unit: string, offset = 0) {
|
||||||
|
let prefixes = ['n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
||||||
|
prefixes = prefixes.slice(3 + (offset || 0));
|
||||||
|
const units = prefixes.map(p => {
|
||||||
|
return ' ' + p + unit;
|
||||||
|
});
|
||||||
|
return scaledUnits(1000, units);
|
||||||
|
}
|
166
packages/grafana-ui/src/utils/valueFormats/valueFormats.ts
Normal file
166
packages/grafana-ui/src/utils/valueFormats/valueFormats.ts
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import { getCategories } from './categories';
|
||||||
|
|
||||||
|
type ValueFormatter = (value: number, decimals?: number, scaledDecimals?: number, isUtc?: boolean) => string;
|
||||||
|
|
||||||
|
interface ValueFormat {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
fn: ValueFormatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ValueFormatCategory {
|
||||||
|
name: string;
|
||||||
|
formats: ValueFormat[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ValueFormatterIndex {
|
||||||
|
[id: string]: ValueFormatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Globals & formats cache
|
||||||
|
let categories: ValueFormatCategory[] = [];
|
||||||
|
const index: ValueFormatterIndex = {};
|
||||||
|
let hasBuiltIndex = false;
|
||||||
|
|
||||||
|
export function toFixed(value: number, decimals?: number): string {
|
||||||
|
if (value === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const factor = decimals ? Math.pow(10, Math.max(0, decimals)) : 1;
|
||||||
|
const formatted = String(Math.round(value * factor) / factor);
|
||||||
|
|
||||||
|
// if exponent return directly
|
||||||
|
if (formatted.indexOf('e') !== -1 || value === 0) {
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If tickDecimals was specified, ensure that we have exactly that
|
||||||
|
// much precision; otherwise default to the value's own precision.
|
||||||
|
if (decimals != null) {
|
||||||
|
const decimalPos = formatted.indexOf('.');
|
||||||
|
const precision = decimalPos === -1 ? 0 : formatted.length - decimalPos - 1;
|
||||||
|
if (precision < decimals) {
|
||||||
|
return (precision ? formatted : formatted + '.') + String(factor).substr(1, decimals - precision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toFixedScaled(
|
||||||
|
value: number,
|
||||||
|
decimals: number,
|
||||||
|
scaledDecimals: number,
|
||||||
|
additionalDecimals: number,
|
||||||
|
ext: string
|
||||||
|
) {
|
||||||
|
if (scaledDecimals === null) {
|
||||||
|
return toFixed(value, decimals) + ext;
|
||||||
|
} else {
|
||||||
|
return toFixed(value, scaledDecimals + additionalDecimals) + ext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toFixedUnit(unit: string) {
|
||||||
|
return (size: number, decimals: number) => {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return toFixed(size, decimals) + ' ' + unit;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatter which scales the unit string geometrically according to the given
|
||||||
|
// numeric factor. Repeatedly scales the value down by the factor until it is
|
||||||
|
// less than the factor in magnitude, or the end of the array is reached.
|
||||||
|
export function scaledUnits(factor: number, extArray: string[]) {
|
||||||
|
return (size: number, decimals: number, scaledDecimals: number) => {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
let steps = 0;
|
||||||
|
const limit = extArray.length;
|
||||||
|
|
||||||
|
while (Math.abs(size) >= factor) {
|
||||||
|
steps++;
|
||||||
|
size /= factor;
|
||||||
|
|
||||||
|
if (steps >= limit) {
|
||||||
|
return 'NA';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (steps > 0 && scaledDecimals !== null) {
|
||||||
|
decimals = scaledDecimals + 3 * steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toFixed(size, decimals) + extArray[steps];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function locale(value: number, decimals: number) {
|
||||||
|
if (value == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return value.toLocaleString(undefined, { maximumFractionDigits: decimals });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function simpleCountUnit(symbol: string) {
|
||||||
|
const units = ['', 'K', 'M', 'B', 'T'];
|
||||||
|
const scaler = scaledUnits(1000, units);
|
||||||
|
return (size: number, decimals: number, scaledDecimals: number) => {
|
||||||
|
if (size === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const scaled = scaler(size, decimals, scaledDecimals);
|
||||||
|
return scaled + ' ' + symbol;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFormats() {
|
||||||
|
categories = getCategories();
|
||||||
|
|
||||||
|
for (const cat of categories) {
|
||||||
|
for (const format of cat.formats) {
|
||||||
|
index[format.id] = format.fn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasBuiltIndex = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValueFormat(id: string): ValueFormatter {
|
||||||
|
if (!hasBuiltIndex) {
|
||||||
|
buildFormats();
|
||||||
|
}
|
||||||
|
|
||||||
|
return index[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValueFormatterIndex(): ValueFormatterIndex {
|
||||||
|
if (!hasBuiltIndex) {
|
||||||
|
buildFormats();
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValueFormats() {
|
||||||
|
if (!hasBuiltIndex) {
|
||||||
|
buildFormats();
|
||||||
|
}
|
||||||
|
|
||||||
|
return categories.map(cat => {
|
||||||
|
return {
|
||||||
|
text: cat.name,
|
||||||
|
submenu: cat.formats.map(format => {
|
||||||
|
return {
|
||||||
|
text: format.name,
|
||||||
|
value: format.id,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
export { Graph } from './Graph/Graph';
|
|
@ -1,4 +1,5 @@
|
|||||||
FROM debian:stretch-slim
|
ARG BASE_IMAGE=debian:stretch-slim
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
ARG GRAFANA_TGZ="grafana-latest.linux-x64.tar.gz"
|
ARG GRAFANA_TGZ="grafana-latest.linux-x64.tar.gz"
|
||||||
|
|
||||||
@ -10,7 +11,8 @@ COPY ${GRAFANA_TGZ} /tmp/grafana.tar.gz
|
|||||||
|
|
||||||
RUN mkdir /tmp/grafana && tar xfvz /tmp/grafana.tar.gz --strip-components=1 -C /tmp/grafana
|
RUN mkdir /tmp/grafana && tar xfvz /tmp/grafana.tar.gz --strip-components=1 -C /tmp/grafana
|
||||||
|
|
||||||
FROM debian:stretch-slim
|
ARG BASE_IMAGE=debian:stretch-slim
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
ARG GF_UID="472"
|
ARG GF_UID="472"
|
||||||
ARG GF_GID="472"
|
ARG GF_GID="472"
|
||||||
|
@ -8,6 +8,5 @@ docker login -u "$DOCKER_USER" -p "$DOCKER_PASS"
|
|||||||
./push_to_docker_hub.sh "$_grafana_version"
|
./push_to_docker_hub.sh "$_grafana_version"
|
||||||
|
|
||||||
if echo "$_grafana_version" | grep -q "^master-"; then
|
if echo "$_grafana_version" | grep -q "^master-"; then
|
||||||
apk add --no-cache curl
|
|
||||||
./deploy_to_k8s.sh "grafana/grafana-dev:$_grafana_version"
|
./deploy_to_k8s.sh "grafana/grafana-dev:$_grafana_version"
|
||||||
fi
|
fi
|
||||||
|
@ -1,25 +1,49 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
_grafana_tag=$1
|
_grafana_tag=${1:-}
|
||||||
|
_docker_repo=${2:-grafana/grafana}
|
||||||
|
|
||||||
# If the tag starts with v, treat this as a official release
|
# If the tag starts with v, treat this as a official release
|
||||||
if echo "$_grafana_tag" | grep -q "^v"; then
|
if echo "$_grafana_tag" | grep -q "^v"; then
|
||||||
_grafana_version=$(echo "${_grafana_tag}" | cut -d "v" -f 2)
|
_grafana_version=$(echo "${_grafana_tag}" | cut -d "v" -f 2)
|
||||||
_docker_repo=${2:-grafana/grafana}
|
|
||||||
else
|
else
|
||||||
_grafana_version=$_grafana_tag
|
_grafana_version=$_grafana_tag
|
||||||
_docker_repo=${2:-grafana/grafana-dev}
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Building ${_docker_repo}:${_grafana_version}"
|
echo "Building ${_docker_repo}:${_grafana_version}"
|
||||||
|
|
||||||
docker build \
|
export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||||
--tag "${_docker_repo}:${_grafana_version}" \
|
|
||||||
--no-cache=true .
|
# Build grafana image for a specific arch
|
||||||
|
docker_build () {
|
||||||
|
base_image=$1
|
||||||
|
grafana_tgz=$2
|
||||||
|
tag=$3
|
||||||
|
|
||||||
|
docker build \
|
||||||
|
--build-arg BASE_IMAGE=${base_image} \
|
||||||
|
--build-arg GRAFANA_TGZ=${grafana_tgz} \
|
||||||
|
--tag "${tag}" \
|
||||||
|
--no-cache=true .
|
||||||
|
}
|
||||||
|
|
||||||
|
# Tag docker images of all architectures
|
||||||
|
docker_tag_all () {
|
||||||
|
repo=$1
|
||||||
|
tag=$2
|
||||||
|
docker tag "${_docker_repo}:${_grafana_version}" "${repo}:${tag}"
|
||||||
|
docker tag "${_docker_repo}-arm32v7-linux:${_grafana_version}" "${repo}-arm32v7-linux:${tag}"
|
||||||
|
docker tag "${_docker_repo}-arm64v8-linux:${_grafana_version}" "${repo}-arm64v8-linux:${tag}"
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_build "debian:stretch-slim" "grafana-latest.linux-x64.tar.gz" "${_docker_repo}:${_grafana_version}"
|
||||||
|
docker_build "arm32v7/debian:stretch-slim" "grafana-latest.linux-armv7.tar.gz" "${_docker_repo}-arm32v7-linux:${_grafana_version}"
|
||||||
|
docker_build "arm64v8/debian:stretch-slim" "grafana-latest.linux-arm64.tar.gz" "${_docker_repo}-arm64v8-linux:${_grafana_version}"
|
||||||
|
|
||||||
# Tag as 'latest' for official release; otherwise tag as grafana/grafana:master
|
# Tag as 'latest' for official release; otherwise tag as grafana/grafana:master
|
||||||
if echo "$_grafana_tag" | grep -q "^v"; then
|
if echo "$_grafana_tag" | grep -q "^v"; then
|
||||||
docker tag "${_docker_repo}:${_grafana_version}" "${_docker_repo}:latest"
|
docker_tag_all "${_docker_repo}" "latest"
|
||||||
else
|
else
|
||||||
docker tag "${_docker_repo}:${_grafana_version}" "grafana/grafana:master"
|
docker_tag_all "${_docker_repo}" "master"
|
||||||
|
docker tag "${_docker_repo}:${_grafana_version}" "grafana/grafana-dev:${_grafana_version}"
|
||||||
fi
|
fi
|
||||||
|
@ -1,24 +1,46 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
_grafana_tag=$1
|
_grafana_tag=${1:-}
|
||||||
|
_docker_repo=${2:-grafana/grafana}
|
||||||
|
|
||||||
# If the tag starts with v, treat this as a official release
|
# If the tag starts with v, treat this as a official release
|
||||||
if echo "$_grafana_tag" | grep -q "^v"; then
|
if echo "$_grafana_tag" | grep -q "^v"; then
|
||||||
_grafana_version=$(echo "${_grafana_tag}" | cut -d "v" -f 2)
|
_grafana_version=$(echo "${_grafana_tag}" | cut -d "v" -f 2)
|
||||||
_docker_repo=${2:-grafana/grafana}
|
|
||||||
else
|
else
|
||||||
_grafana_version=$_grafana_tag
|
_grafana_version=$_grafana_tag
|
||||||
_docker_repo=${2:-grafana/grafana-dev}
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||||
|
|
||||||
echo "pushing ${_docker_repo}:${_grafana_version}"
|
echo "pushing ${_docker_repo}:${_grafana_version}"
|
||||||
docker push "${_docker_repo}:${_grafana_version}"
|
|
||||||
|
|
||||||
|
docker_push_all () {
|
||||||
|
repo=$1
|
||||||
|
tag=$2
|
||||||
|
|
||||||
|
# Push each image individually
|
||||||
|
docker push "${repo}:${tag}"
|
||||||
|
docker push "${repo}-arm32v7-linux:${tag}"
|
||||||
|
docker push "${repo}-arm64v8-linux:${tag}"
|
||||||
|
|
||||||
|
# Create and push a multi-arch manifest
|
||||||
|
docker manifest create "${repo}:${tag}" \
|
||||||
|
"${repo}:${tag}" \
|
||||||
|
"${repo}-arm32v7-linux:${tag}" \
|
||||||
|
"${repo}-arm64v8-linux:${tag}"
|
||||||
|
|
||||||
|
docker manifest push "${repo}:${tag}"
|
||||||
|
}
|
||||||
|
|
||||||
if echo "$_grafana_tag" | grep -q "^v" && echo "$_grafana_tag" | grep -vq "beta"; then
|
if echo "$_grafana_tag" | grep -q "^v" && echo "$_grafana_tag" | grep -vq "beta"; then
|
||||||
echo "pushing ${_docker_repo}:latest"
|
echo "pushing ${_docker_repo}:latest"
|
||||||
docker push "${_docker_repo}:latest"
|
docker_push_all "${_docker_repo}" "latest"
|
||||||
|
docker_push_all "${_docker_repo}" "${_grafana_version}"
|
||||||
|
elif echo "$_grafana_tag" | grep -q "^v" && echo "$_grafana_tag" | grep -q "beta"; then
|
||||||
|
docker_push_all "${_docker_repo}" "${_grafana_version}"
|
||||||
elif echo "$_grafana_tag" | grep -q "master"; then
|
elif echo "$_grafana_tag" | grep -q "master"; then
|
||||||
echo "pushing grafana/grafana:master"
|
docker_push_all "${_docker_repo}" "master"
|
||||||
docker push grafana/grafana:master
|
docker push "grafana/grafana-dev:${_grafana_version}"
|
||||||
fi
|
fi
|
||||||
|
@ -212,6 +212,10 @@ func GetAlertNotificationByID(c *m.ReqContext) Response {
|
|||||||
return Error(500, "Failed to get alert notifications", err)
|
return Error(500, "Failed to get alert notifications", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Result == nil {
|
||||||
|
return Error(404, "Alert notification not found", nil)
|
||||||
|
}
|
||||||
|
|
||||||
return JSON(200, dtos.NewAlertNotification(query.Result))
|
return JSON(200, dtos.NewAlertNotification(query.Result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +119,12 @@ func TestAlertingApiEndpoint(t *testing.T) {
|
|||||||
So(getAlertsQuery.Limit, ShouldEqual, 5)
|
So(getAlertsQuery.Limit, ShouldEqual, 5)
|
||||||
So(getAlertsQuery.Query, ShouldEqual, "alertQuery")
|
So(getAlertsQuery.Query, ShouldEqual, "alertQuery")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alert-notifications/1", "/alert-notifications/:notificationId", m.ROLE_ADMIN, func(sc *scenarioContext) {
|
||||||
|
sc.handlerFunc = GetAlertNotificationByID
|
||||||
|
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||||
|
So(sc.resp.Code, ShouldEqual, 404)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,10 +206,9 @@ func (f *JSONFormatter) processObject(object map[string]interface{}, deltas []di
|
|||||||
|
|
||||||
// Added
|
// Added
|
||||||
for _, delta := range deltas {
|
for _, delta := range deltas {
|
||||||
switch delta.(type) {
|
switch delta := delta.(type) {
|
||||||
case *diff.Added:
|
case *diff.Added:
|
||||||
d := delta.(*diff.Added)
|
f.printRecursive(delta.Position.String(), delta.Value, ChangeAdded)
|
||||||
f.printRecursive(d.Position.String(), d.Value, ChangeAdded)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,9 +221,8 @@ func (f *JSONFormatter) processItem(value interface{}, deltas []diff.Delta, posi
|
|||||||
if len(matchedDeltas) > 0 {
|
if len(matchedDeltas) > 0 {
|
||||||
for _, matchedDelta := range matchedDeltas {
|
for _, matchedDelta := range matchedDeltas {
|
||||||
|
|
||||||
switch matchedDelta.(type) {
|
switch matchedDelta := matchedDelta.(type) {
|
||||||
case *diff.Object:
|
case *diff.Object:
|
||||||
d := matchedDelta.(*diff.Object)
|
|
||||||
switch value.(type) {
|
switch value.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
//ok
|
//ok
|
||||||
@ -238,7 +236,7 @@ func (f *JSONFormatter) processItem(value interface{}, deltas []diff.Delta, posi
|
|||||||
f.print("{")
|
f.print("{")
|
||||||
f.closeLine()
|
f.closeLine()
|
||||||
f.push(positionStr, len(o), false)
|
f.push(positionStr, len(o), false)
|
||||||
f.processObject(o, d.Deltas)
|
f.processObject(o, matchedDelta.Deltas)
|
||||||
f.pop()
|
f.pop()
|
||||||
f.newLine(ChangeNil)
|
f.newLine(ChangeNil)
|
||||||
f.print("}")
|
f.print("}")
|
||||||
@ -246,7 +244,6 @@ func (f *JSONFormatter) processItem(value interface{}, deltas []diff.Delta, posi
|
|||||||
f.closeLine()
|
f.closeLine()
|
||||||
|
|
||||||
case *diff.Array:
|
case *diff.Array:
|
||||||
d := matchedDelta.(*diff.Array)
|
|
||||||
switch value.(type) {
|
switch value.(type) {
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
//ok
|
//ok
|
||||||
@ -260,7 +257,7 @@ func (f *JSONFormatter) processItem(value interface{}, deltas []diff.Delta, posi
|
|||||||
f.print("[")
|
f.print("[")
|
||||||
f.closeLine()
|
f.closeLine()
|
||||||
f.push(positionStr, len(a), true)
|
f.push(positionStr, len(a), true)
|
||||||
f.processArray(a, d.Deltas)
|
f.processArray(a, matchedDelta.Deltas)
|
||||||
f.pop()
|
f.pop()
|
||||||
f.newLine(ChangeNil)
|
f.newLine(ChangeNil)
|
||||||
f.print("]")
|
f.print("]")
|
||||||
@ -268,27 +265,23 @@ func (f *JSONFormatter) processItem(value interface{}, deltas []diff.Delta, posi
|
|||||||
f.closeLine()
|
f.closeLine()
|
||||||
|
|
||||||
case *diff.Added:
|
case *diff.Added:
|
||||||
d := matchedDelta.(*diff.Added)
|
f.printRecursive(positionStr, matchedDelta.Value, ChangeAdded)
|
||||||
f.printRecursive(positionStr, d.Value, ChangeAdded)
|
|
||||||
f.size[len(f.size)-1]++
|
f.size[len(f.size)-1]++
|
||||||
|
|
||||||
case *diff.Modified:
|
case *diff.Modified:
|
||||||
d := matchedDelta.(*diff.Modified)
|
|
||||||
savedSize := f.size[len(f.size)-1]
|
savedSize := f.size[len(f.size)-1]
|
||||||
f.printRecursive(positionStr, d.OldValue, ChangeOld)
|
f.printRecursive(positionStr, matchedDelta.OldValue, ChangeOld)
|
||||||
f.size[len(f.size)-1] = savedSize
|
f.size[len(f.size)-1] = savedSize
|
||||||
f.printRecursive(positionStr, d.NewValue, ChangeNew)
|
f.printRecursive(positionStr, matchedDelta.NewValue, ChangeNew)
|
||||||
|
|
||||||
case *diff.TextDiff:
|
case *diff.TextDiff:
|
||||||
savedSize := f.size[len(f.size)-1]
|
savedSize := f.size[len(f.size)-1]
|
||||||
d := matchedDelta.(*diff.TextDiff)
|
f.printRecursive(positionStr, matchedDelta.OldValue, ChangeOld)
|
||||||
f.printRecursive(positionStr, d.OldValue, ChangeOld)
|
|
||||||
f.size[len(f.size)-1] = savedSize
|
f.size[len(f.size)-1] = savedSize
|
||||||
f.printRecursive(positionStr, d.NewValue, ChangeNew)
|
f.printRecursive(positionStr, matchedDelta.NewValue, ChangeNew)
|
||||||
|
|
||||||
case *diff.Deleted:
|
case *diff.Deleted:
|
||||||
d := matchedDelta.(*diff.Deleted)
|
f.printRecursive(positionStr, matchedDelta.Value, ChangeDeleted)
|
||||||
f.printRecursive(positionStr, d.Value, ChangeDeleted)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return errors.New("Unknown Delta type detected")
|
return errors.New("Unknown Delta type detected")
|
||||||
@ -305,13 +298,13 @@ func (f *JSONFormatter) processItem(value interface{}, deltas []diff.Delta, posi
|
|||||||
func (f *JSONFormatter) searchDeltas(deltas []diff.Delta, position diff.Position) (results []diff.Delta) {
|
func (f *JSONFormatter) searchDeltas(deltas []diff.Delta, position diff.Position) (results []diff.Delta) {
|
||||||
results = make([]diff.Delta, 0)
|
results = make([]diff.Delta, 0)
|
||||||
for _, delta := range deltas {
|
for _, delta := range deltas {
|
||||||
switch delta.(type) {
|
switch typedDelta := delta.(type) {
|
||||||
case diff.PostDelta:
|
case diff.PostDelta:
|
||||||
if delta.(diff.PostDelta).PostPosition() == position {
|
if typedDelta.PostPosition() == position {
|
||||||
results = append(results, delta)
|
results = append(results, delta)
|
||||||
}
|
}
|
||||||
case diff.PreDelta:
|
case diff.PreDelta:
|
||||||
if delta.(diff.PreDelta).PrePosition() == position {
|
if typedDelta.PrePosition() == position {
|
||||||
results = append(results, delta)
|
results = append(results, delta)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -417,20 +410,19 @@ func (f *JSONFormatter) print(a string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *JSONFormatter) printRecursive(name string, value interface{}, change ChangeType) {
|
func (f *JSONFormatter) printRecursive(name string, value interface{}, change ChangeType) {
|
||||||
switch value.(type) {
|
switch value := value.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
f.newLine(change)
|
f.newLine(change)
|
||||||
f.printKey(name)
|
f.printKey(name)
|
||||||
f.print("{")
|
f.print("{")
|
||||||
f.closeLine()
|
f.closeLine()
|
||||||
|
|
||||||
m := value.(map[string]interface{})
|
size := len(value)
|
||||||
size := len(m)
|
|
||||||
f.push(name, size, false)
|
f.push(name, size, false)
|
||||||
|
|
||||||
keys := sortKeys(m)
|
keys := sortKeys(value)
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
f.printRecursive(key, m[key], change)
|
f.printRecursive(key, value[key], change)
|
||||||
}
|
}
|
||||||
f.pop()
|
f.pop()
|
||||||
|
|
||||||
@ -445,10 +437,9 @@ func (f *JSONFormatter) printRecursive(name string, value interface{}, change Ch
|
|||||||
f.print("[")
|
f.print("[")
|
||||||
f.closeLine()
|
f.closeLine()
|
||||||
|
|
||||||
s := value.([]interface{})
|
size := len(value)
|
||||||
size := len(s)
|
|
||||||
f.push("", size, true)
|
f.push("", size, true)
|
||||||
for _, item := range s {
|
for _, item := range value {
|
||||||
f.printRecursive("", item, change)
|
f.printRecursive("", item, change)
|
||||||
}
|
}
|
||||||
f.pop()
|
f.pop()
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,6 +20,10 @@ func (NopImageUploader) Upload(ctx context.Context, path string) (string, error)
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger = log.New("imguploader")
|
||||||
|
)
|
||||||
|
|
||||||
func NewImageUploader() (ImageUploader, error) {
|
func NewImageUploader() (ImageUploader, error) {
|
||||||
|
|
||||||
switch setting.ImageUploadProvider {
|
switch setting.ImageUploadProvider {
|
||||||
@ -94,7 +97,7 @@ func NewImageUploader() (ImageUploader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if setting.ImageUploadProvider != "" {
|
if setting.ImageUploadProvider != "" {
|
||||||
log.Error2("The external image storage configuration is invalid", "unsupported provider", setting.ImageUploadProvider)
|
logger.Error("The external image storage configuration is invalid", "unsupported provider", setting.ImageUploadProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NopImageUploader{}, nil
|
return NopImageUploader{}, nil
|
||||||
|
@ -10,13 +10,11 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/ini.v1"
|
|
||||||
|
|
||||||
"github.com/go-stack/stack"
|
"github.com/go-stack/stack"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
"github.com/inconshreveable/log15"
|
"github.com/inconshreveable/log15"
|
||||||
isatty "github.com/mattn/go-isatty"
|
isatty "github.com/mattn/go-isatty"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var Root log15.Logger
|
var Root log15.Logger
|
||||||
@ -58,10 +56,6 @@ func Debug(format string, v ...interface{}) {
|
|||||||
Root.Debug(message)
|
Root.Debug(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debug2(message string, v ...interface{}) {
|
|
||||||
Root.Debug(message, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Info(format string, v ...interface{}) {
|
func Info(format string, v ...interface{}) {
|
||||||
var message string
|
var message string
|
||||||
if len(v) > 0 {
|
if len(v) > 0 {
|
||||||
@ -73,10 +67,6 @@ func Info(format string, v ...interface{}) {
|
|||||||
Root.Info(message)
|
Root.Info(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Info2(message string, v ...interface{}) {
|
|
||||||
Root.Info(message, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Warn(format string, v ...interface{}) {
|
func Warn(format string, v ...interface{}) {
|
||||||
var message string
|
var message string
|
||||||
if len(v) > 0 {
|
if len(v) > 0 {
|
||||||
@ -88,18 +78,10 @@ func Warn(format string, v ...interface{}) {
|
|||||||
Root.Warn(message)
|
Root.Warn(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warn2(message string, v ...interface{}) {
|
|
||||||
Root.Warn(message, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Error(skip int, format string, v ...interface{}) {
|
func Error(skip int, format string, v ...interface{}) {
|
||||||
Root.Error(fmt.Sprintf(format, v...))
|
Root.Error(fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Error2(message string, v ...interface{}) {
|
|
||||||
Root.Error(message, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Critical(skip int, format string, v ...interface{}) {
|
func Critical(skip int, format string, v ...interface{}) {
|
||||||
Root.Crit(fmt.Sprintf(format, v...))
|
Root.Crit(fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,10 @@ func init() {
|
|||||||
bus.AddHandler("auth", UpsertUser)
|
bus.AddHandler("auth", UpsertUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger = log.New("login.ext_user")
|
||||||
|
)
|
||||||
|
|
||||||
func UpsertUser(cmd *m.UpsertUserCommand) error {
|
func UpsertUser(cmd *m.UpsertUserCommand) error {
|
||||||
extUser := cmd.ExternalUser
|
extUser := cmd.ExternalUser
|
||||||
|
|
||||||
@ -135,7 +139,7 @@ func updateUser(user *m.User, extUser *m.ExternalUserInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug2("Syncing user info", "id", user.Id, "update", updateCmd)
|
logger.Debug("Syncing user info", "id", user.Id, "update", updateCmd)
|
||||||
return bus.Dispatch(updateCmd)
|
return bus.Dispatch(updateCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ func NewDashboard(title string) *Dashboard {
|
|||||||
func NewDashboardFolder(title string) *Dashboard {
|
func NewDashboardFolder(title string) *Dashboard {
|
||||||
folder := NewDashboard(title)
|
folder := NewDashboard(title)
|
||||||
folder.IsFolder = true
|
folder.IsFolder = true
|
||||||
folder.Data.Set("schemaVersion", 16)
|
folder.Data.Set("schemaVersion", 17)
|
||||||
folder.Data.Set("version", 0)
|
folder.Data.Set("version", 0)
|
||||||
folder.IsFolder = true
|
folder.IsFolder = true
|
||||||
return folder
|
return folder
|
||||||
|
@ -112,7 +112,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
|
|||||||
|
|
||||||
frequency, err := getTimeDurationStringToSeconds(jsonAlert.Get("frequency").MustString())
|
frequency, err := getTimeDurationStringToSeconds(jsonAlert.Get("frequency").MustString())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ValidationError{Reason: "Could not parse frequency"}
|
return nil, ValidationError{Reason: err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
rawFor := jsonAlert.Get("for").MustString()
|
rawFor := jsonAlert.Get("for").MustString()
|
||||||
|
@ -130,7 +130,7 @@ func (this *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.Eval
|
|||||||
defer func() {
|
defer func() {
|
||||||
err := imageFile.Close()
|
err := imageFile.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error2("Could not close Telegram inline image.", "err", err)
|
this.log.Error("Could not close Telegram inline image.", "err", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
package alerting
|
package alerting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFrequencyCannotBeZeroOrLess = errors.New(`"evaluate every" cannot be zero or below`)
|
||||||
|
ErrFrequencyCouldNotBeParsed = errors.New(`"evaluate every" field could not be parsed`)
|
||||||
|
)
|
||||||
|
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
Id int64
|
Id int64
|
||||||
OrgId int64
|
OrgId int64
|
||||||
@ -76,7 +81,7 @@ func getTimeDurationStringToSeconds(str string) (int64, error) {
|
|||||||
matches := ValueFormatRegex.FindAllString(str, 1)
|
matches := ValueFormatRegex.FindAllString(str, 1)
|
||||||
|
|
||||||
if len(matches) <= 0 {
|
if len(matches) <= 0 {
|
||||||
return 0, fmt.Errorf("Frequency could not be parsed")
|
return 0, ErrFrequencyCouldNotBeParsed
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := strconv.Atoi(matches[0])
|
value, err := strconv.Atoi(matches[0])
|
||||||
@ -84,6 +89,10 @@ func getTimeDurationStringToSeconds(str string) (int64, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if value == 0 {
|
||||||
|
return 0, ErrFrequencyCannotBeZeroOrLess
|
||||||
|
}
|
||||||
|
|
||||||
unit := UnitFormatRegex.FindAllString(str, 1)[0]
|
unit := UnitFormatRegex.FindAllString(str, 1)[0]
|
||||||
|
|
||||||
if val, ok := unitMultiplier[unit]; ok {
|
if val, ok := unitMultiplier[unit]; ok {
|
||||||
@ -101,7 +110,6 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
|
|||||||
model.PanelId = ruleDef.PanelId
|
model.PanelId = ruleDef.PanelId
|
||||||
model.Name = ruleDef.Name
|
model.Name = ruleDef.Name
|
||||||
model.Message = ruleDef.Message
|
model.Message = ruleDef.Message
|
||||||
model.Frequency = ruleDef.Frequency
|
|
||||||
model.State = ruleDef.State
|
model.State = ruleDef.State
|
||||||
model.LastStateChange = ruleDef.NewStateDate
|
model.LastStateChange = ruleDef.NewStateDate
|
||||||
model.For = ruleDef.For
|
model.For = ruleDef.For
|
||||||
@ -109,6 +117,13 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
|
|||||||
model.ExecutionErrorState = m.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting"))
|
model.ExecutionErrorState = m.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting"))
|
||||||
model.StateChanges = ruleDef.StateChanges
|
model.StateChanges = ruleDef.StateChanges
|
||||||
|
|
||||||
|
model.Frequency = ruleDef.Frequency
|
||||||
|
// frequency cannot be zero since that would not execute the alert rule.
|
||||||
|
// so we fallback to 60 seconds if `Freqency` is missing
|
||||||
|
if model.Frequency == 0 {
|
||||||
|
model.Frequency = 60
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
|
for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
|
||||||
jsonModel := simplejson.NewFromAny(v)
|
jsonModel := simplejson.NewFromAny(v)
|
||||||
id, err := jsonModel.Get("id").Int64()
|
id, err := jsonModel.Get("id").Int64()
|
||||||
|
@ -14,6 +14,36 @@ func (f *FakeCondition) Eval(context *EvalContext) (*ConditionResult, error) {
|
|||||||
return &ConditionResult{}, nil
|
return &ConditionResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAlertRuleFrequencyParsing(t *testing.T) {
|
||||||
|
tcs := []struct {
|
||||||
|
input string
|
||||||
|
err error
|
||||||
|
result int64
|
||||||
|
}{
|
||||||
|
{input: "10s", result: 10},
|
||||||
|
{input: "10m", result: 600},
|
||||||
|
{input: "1h", result: 3600},
|
||||||
|
{input: "1o", result: 1},
|
||||||
|
{input: "0s", err: ErrFrequencyCannotBeZeroOrLess},
|
||||||
|
{input: "0m", err: ErrFrequencyCannotBeZeroOrLess},
|
||||||
|
{input: "0h", err: ErrFrequencyCannotBeZeroOrLess},
|
||||||
|
{input: "0", err: ErrFrequencyCannotBeZeroOrLess},
|
||||||
|
{input: "-1s", err: ErrFrequencyCouldNotBeParsed},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
r, err := getTimeDurationStringToSeconds(tc.input)
|
||||||
|
if err != tc.err {
|
||||||
|
t.Errorf("expected error: '%v' got: '%v'", tc.err, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r != tc.result {
|
||||||
|
t.Errorf("expected result: %d got %d", tc.result, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAlertRuleModel(t *testing.T) {
|
func TestAlertRuleModel(t *testing.T) {
|
||||||
Convey("Testing alert rule", t, func() {
|
Convey("Testing alert rule", t, func() {
|
||||||
|
|
||||||
@ -21,26 +51,6 @@ func TestAlertRuleModel(t *testing.T) {
|
|||||||
return &FakeCondition{}, nil
|
return &FakeCondition{}, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Can parse seconds", func() {
|
|
||||||
seconds, _ := getTimeDurationStringToSeconds("10s")
|
|
||||||
So(seconds, ShouldEqual, 10)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Can parse minutes", func() {
|
|
||||||
seconds, _ := getTimeDurationStringToSeconds("10m")
|
|
||||||
So(seconds, ShouldEqual, 600)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Can parse hours", func() {
|
|
||||||
seconds, _ := getTimeDurationStringToSeconds("1h")
|
|
||||||
So(seconds, ShouldEqual, 3600)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("defaults to seconds", func() {
|
|
||||||
seconds, _ := getTimeDurationStringToSeconds("1o")
|
|
||||||
So(seconds, ShouldEqual, 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("should return err for empty string", func() {
|
Convey("should return err for empty string", func() {
|
||||||
_, err := getTimeDurationStringToSeconds("")
|
_, err := getTimeDurationStringToSeconds("")
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
@ -89,5 +99,35 @@ func TestAlertRuleModel(t *testing.T) {
|
|||||||
So(len(alertRule.Notifications), ShouldEqual, 2)
|
So(len(alertRule.Notifications), ShouldEqual, 2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("can construct alert rule model with invalid frequency", func() {
|
||||||
|
json := `
|
||||||
|
{
|
||||||
|
"name": "name2",
|
||||||
|
"description": "desc2",
|
||||||
|
"noDataMode": "critical",
|
||||||
|
"enabled": true,
|
||||||
|
"frequency": "0s",
|
||||||
|
"conditions": [ { "type": "test", "prop": 123 } ],
|
||||||
|
"notifications": []
|
||||||
|
}`
|
||||||
|
|
||||||
|
alertJSON, jsonErr := simplejson.NewJson([]byte(json))
|
||||||
|
So(jsonErr, ShouldBeNil)
|
||||||
|
|
||||||
|
alert := &m.Alert{
|
||||||
|
Id: 1,
|
||||||
|
OrgId: 1,
|
||||||
|
DashboardId: 1,
|
||||||
|
PanelId: 1,
|
||||||
|
Frequency: 0,
|
||||||
|
|
||||||
|
Settings: alertJSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
alertRule, err := NewRuleFromDBAlert(alert)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(alertRule.Frequency, ShouldEqual, 60)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,12 @@ type NotificationTestCommand struct {
|
|||||||
Settings *simplejson.Json
|
Settings *simplejson.Json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger = log.New("alerting.testnotification")
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
bus.AddHandler("alerting", handleNotificationTestCommand)
|
bus.AddHandler("alerting", handleNotificationTestCommand)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleNotificationTestCommand(cmd *NotificationTestCommand) error {
|
func handleNotificationTestCommand(cmd *NotificationTestCommand) error {
|
||||||
@ -35,7 +38,7 @@ func handleNotificationTestCommand(cmd *NotificationTestCommand) error {
|
|||||||
notifiers, err := InitNotifier(model)
|
notifiers, err := InitNotifier(model)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error2("Failed to create notifier", "error", err.Error())
|
logger.Error("Failed to create notifier", "error", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user