Bladeren bron

Merge branch 'master' into nanosecond-postgresql

Marcus Efraimsson 7 jaren geleden
bovenliggende
commit
7023c957d7
100 gewijzigde bestanden met toevoegingen van 9842 en 459 verwijderingen
  1. 59 53
      .circleci/config.yml
  2. 40 4
      CHANGELOG.md
  3. 1 1
      CODE_OF_CONDUCT.md
  4. 4 2
      Dockerfile
  5. 2 2
      PLUGIN_DEV.md
  6. 8 6
      README.md
  7. 11 7
      ROADMAP.md
  8. 1 1
      appveyor.yml
  9. 16 2
      build.go
  10. 24 6
      conf/defaults.ini
  11. 28 6
      conf/sample.ini
  12. 1 1
      devenv/dashboards.yaml
  13. 1674 0
      devenv/dev-dashboards-without-uid/panel_tests_graph.json
  14. 510 0
      devenv/dev-dashboards-without-uid/panel_tests_graph_time_regions.json
  15. 3342 0
      devenv/dev-dashboards-without-uid/panel_tests_polystat.json
  16. 545 7
      devenv/dev-dashboards/datasource_tests_elasticsearch_compare.json
  17. 1250 0
      devenv/dev-dashboards/panel_tests_gauge.json
  18. 1 0
      devenv/docker/blocks/influxdb/influxdb.conf
  19. 2 1
      devenv/docker/ha_test/docker-compose.yaml
  20. 69 0
      devenv/docker/loadtest/README.md
  21. 71 0
      devenv/docker/loadtest/auth_token_test.js
  22. 187 0
      devenv/docker/loadtest/modules/client.js
  23. 35 0
      devenv/docker/loadtest/modules/util.js
  24. 24 0
      devenv/docker/loadtest/run.sh
  25. 1 1
      docs/sources/auth/gitlab.md
  26. 1 1
      docs/sources/features/datasources/cloudwatch.md
  27. 6 1
      docs/sources/features/explore/index.md
  28. 3 3
      docs/sources/http_api/admin.md
  29. 2 2
      docs/sources/http_api/data_source.md
  30. 1 1
      docs/sources/http_api/folder_permissions.md
  31. 26 1
      docs/sources/http_api/other.md
  32. 17 0
      docs/sources/installation/configuration.md
  33. 8 11
      docs/sources/installation/debian.md
  34. 15 7
      docs/sources/installation/rpm.md
  35. 1 1
      docs/sources/reference/dashboard.md
  36. 5 2
      docs/sources/reference/templating.md
  37. 2 2
      latest.json
  38. 5 5
      package.json
  39. 14 3
      packages/grafana-ui/package.json
  40. 1 1
      packages/grafana-ui/src/components/ColorPicker/ColorPalette.test.tsx
  41. 3 3
      packages/grafana-ui/src/components/ColorPicker/ColorPalette.tsx
  42. 4 9
      packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx
  43. 12 21
      packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx
  44. 2 1
      packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx
  45. 6 10
      packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx
  46. 5 5
      packages/grafana-ui/src/components/ColorPicker/SpectrumPicker.tsx
  47. 0 0
      packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss
  48. 0 0
      packages/grafana-ui/src/components/ColorPicker/__snapshots__/ColorPalette.test.tsx.snap
  49. 0 0
      packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.test.tsx
  50. 96 0
      packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx
  51. 40 0
      packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss
  52. 4 8
      packages/grafana-ui/src/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap
  53. 24 0
      packages/grafana-ui/src/components/FormField/FormField.test.tsx
  54. 25 0
      packages/grafana-ui/src/components/FormField/FormField.tsx
  55. 12 0
      packages/grafana-ui/src/components/FormField/_FormField.scss
  56. 19 0
      packages/grafana-ui/src/components/FormField/__snapshots__/FormField.test.tsx.snap
  57. 42 0
      packages/grafana-ui/src/components/FormLabel/FormLabel.tsx
  58. 147 0
      packages/grafana-ui/src/components/Gauge/Gauge.test.tsx
  59. 62 76
      packages/grafana-ui/src/components/Gauge/Gauge.tsx
  60. 1 0
      packages/grafana-ui/src/components/Graph/Graph.tsx
  61. 11 0
      packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx
  62. 15 0
      packages/grafana-ui/src/components/PanelOptionsGrid/PanelOptionsGrid.tsx
  63. 10 0
      packages/grafana-ui/src/components/PanelOptionsGrid/_PanelOptionsGrid.scss
  64. 4 4
      packages/grafana-ui/src/components/PanelOptionsGroup/PanelOptionsGroup.tsx
  65. 27 0
      packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss
  66. 3 6
      packages/grafana-ui/src/components/Portal/Portal.tsx
  67. 4 1
      packages/grafana-ui/src/components/Select/IndicatorsContainer.tsx
  68. 4 0
      packages/grafana-ui/src/components/Select/NoOptionsMessage.tsx
  69. 16 11
      packages/grafana-ui/src/components/Select/Select.tsx
  70. 23 12
      packages/grafana-ui/src/components/Select/SelectOption.test.tsx
  71. 6 3
      packages/grafana-ui/src/components/Select/SelectOption.tsx
  72. 11 5
      packages/grafana-ui/src/components/Select/SelectOptionGroup.tsx
  73. 2 0
      packages/grafana-ui/src/components/Select/_Select.scss
  74. 7 2
      packages/grafana-ui/src/components/Select/__snapshots__/SelectOption.test.tsx.snap
  75. 27 0
      packages/grafana-ui/src/components/Select/resetSelectStyles.ts
  76. 173 0
      packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx
  77. 211 0
      packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx
  78. 105 0
      packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss
  79. 21 16
      packages/grafana-ui/src/components/Tooltip/Popper.tsx
  80. 99 0
      packages/grafana-ui/src/components/Tooltip/PopperController.tsx
  81. 5 3
      packages/grafana-ui/src/components/Tooltip/Tooltip.test.tsx
  82. 32 0
      packages/grafana-ui/src/components/Tooltip/Tooltip.tsx
  83. 27 9
      packages/grafana-ui/src/components/Tooltip/_Tooltip.scss
  84. 12 0
      packages/grafana-ui/src/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap
  85. 50 55
      packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx
  86. 15 16
      packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.test.tsx
  87. 105 0
      packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx
  88. 0 0
      packages/grafana-ui/src/components/ValueMappingsEditor/_ValueMappingsEditor.scss
  89. 9 14
      packages/grafana-ui/src/components/ValueMappingsEditor/__snapshots__/ValueMappingsEditor.test.tsx.snap
  90. 9 0
      packages/grafana-ui/src/components/index.scss
  91. 24 0
      packages/grafana-ui/src/components/index.ts
  92. 0 23
      packages/grafana-ui/src/forms/GfFormLabel/GfFormLabel.tsx
  93. 0 1
      packages/grafana-ui/src/forms/index.ts
  94. 2 0
      packages/grafana-ui/src/index.scss
  95. 0 2
      packages/grafana-ui/src/index.ts
  96. 89 0
      packages/grafana-ui/src/types/datasource.ts
  97. 2 0
      packages/grafana-ui/src/types/index.ts
  98. 44 0
      packages/grafana-ui/src/types/panel.ts
  99. 118 0
      packages/grafana-ui/src/types/plugin.ts
  100. 3 2
      packages/grafana-ui/src/types/series.ts

+ 59 - 53
.circleci/config.yml

@@ -19,7 +19,7 @@ version: 2
 jobs:
   mysql-integration-test:
     docker:
-      - image: circleci/golang:1.11.4
+      - image: circleci/golang:1.11.5
       - image: circleci/mysql:5.6-ram
         environment:
           MYSQL_ROOT_PASSWORD: rootpass
@@ -39,7 +39,7 @@ jobs:
 
   postgres-integration-test:
     docker:
-      - image: circleci/golang:1.11.4
+      - image: circleci/golang:1.11.5
       - image: circleci/postgres:9.3-ram
         environment:
           POSTGRES_USER: grafanatest
@@ -74,27 +74,16 @@ jobs:
 
   gometalinter:
     docker:
-      - image: circleci/golang:1.11.4
+      - image: circleci/golang:1.11.5
         environment:
           # we need CGO because of go-sqlite3
           CGO_ENABLED: 1
     working_directory: /go/src/github.com/grafana/grafana
     steps:
       - checkout
-      - run: 'go get -u github.com/alecthomas/gometalinter'
-      - run: 'go get -u github.com/tsenart/deadcode'
-      - run: 'go get -u github.com/jgautheron/goconst/cmd/goconst'
-      - run: 'go get -u github.com/gordonklaus/ineffassign'
-      - run: 'go get -u honnef.co/go/tools/cmd/megacheck'
-      - run: 'go get -u github.com/opennota/check/cmd/structcheck'
-      - run: 'go get -u github.com/mdempsky/unconvert'
-      - run: 'go get -u github.com/opennota/check/cmd/varcheck'
-      - run:
-          name: run linters
-          command: 'gometalinter --enable-gc --vendor --deadline 10m --disable-all --enable=deadcode --enable=goconst --enable=gofmt --enable=ineffassign --enable=megacheck --enable=structcheck --enable=unconvert --enable=varcheck ./...'
-      - run:
-          name: run go vet
-          command: 'go vet ./pkg/...'
+      - run:
+          name: Gometalinter tests
+          command: './scripts/gometalinter.sh'
 
   test-frontend:
     docker:
@@ -117,7 +106,7 @@ jobs:
 
   test-backend:
     docker:
-      - image: circleci/golang:1.11.4
+      - image: circleci/golang:1.11.5
     working_directory: /go/src/github.com/grafana/grafana
     steps:
       - checkout
@@ -127,7 +116,7 @@ jobs:
 
   build-all:
     docker:
-     - image: grafana/build-container:1.2.1
+     - image: grafana/build-container:1.2.3
     working_directory: /go/src/github.com/grafana/grafana
     steps:
       - checkout
@@ -158,9 +147,6 @@ jobs:
       - run:
           name: sha-sum packages
           command: 'go run build.go sha-dist'
-      - run:
-          name: Build Grafana.com master publisher
-          command: 'go build -o scripts/publish scripts/build/publish.go'
       - run:
           name: Test and build Grafana.com release publisher
           command: 'cd scripts/build/release_publisher && go test . && go build -o release_publisher .'
@@ -169,13 +155,12 @@ jobs:
           paths:
             - dist/grafana*
             - scripts/*.sh
-            - scripts/publish
             - scripts/build/release_publisher/release_publisher
             - scripts/build/publish.sh
 
   build:
     docker:
-     - image: grafana/build-container:1.2.2
+     - image: grafana/build-container:1.2.3
     working_directory: /go/src/github.com/grafana/grafana
     steps:
       - checkout
@@ -200,51 +185,51 @@ jobs:
             - dist/grafana*
 
   grafana-docker-master:
-    docker:
-      - image: docker:stable-git
+    machine:
+      image: circleci/classic:201808-01
     steps:
       - checkout
       - attach_workspace:
           at: .
-      - setup_remote_docker
       - 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: 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: cd packaging/docker && ./build-enterprise.sh "master"
 
 
   grafana-docker-pr:
-    docker:
-      - image: docker:stable-git
+    machine:
+      image: circleci/classic:201808-01
     steps:
       - checkout
       - attach_workspace:
           at: .
-      - setup_remote_docker
       - 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}"
 
   grafana-docker-release:
-      docker:
-        - image: docker:stable-git
-      steps:
-        - checkout
-        - attach_workspace:
-            at: .
-        - setup_remote_docker
-        - run: docker info
-        - run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker
-        - run: cd packaging/docker && ./build-deploy.sh "${CIRCLE_TAG}"
-        - run: rm 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}"
+    machine:
+      image: circleci/classic:201808-01
+    steps:
+      - checkout
+      - attach_workspace:
+          at: .
+      - run: docker info
+      - 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 "${CIRCLE_TAG}"
+      - 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: cd packaging/docker && ./build-enterprise.sh "${CIRCLE_TAG}"
 
   build-enterprise:
     docker:
-     - image: grafana/build-container:1.2.1
+     - image: grafana/build-container:1.2.3
     working_directory: /go/src/github.com/grafana/grafana
     steps:
       - checkout
@@ -276,7 +261,7 @@ jobs:
 
   build-all-enterprise:
     docker:
-    - image: grafana/build-container:1.2.1
+    - image: grafana/build-container:1.2.3
     working_directory: /go/src/github.com/grafana/grafana
     steps:
     - checkout
@@ -323,7 +308,7 @@ jobs:
 
   deploy-enterprise-master:
     docker:
-      - image: grafana/grafana-ci-deploy:1.0.0
+      - image: grafana/grafana-ci-deploy:1.2.0
     steps:
       - attach_workspace:
           at: .
@@ -346,7 +331,7 @@ jobs:
 
   deploy-enterprise-release:
     docker:
-    - image: grafana/grafana-ci-deploy:1.0.0
+    - image: grafana/grafana-ci-deploy:1.2.0
     steps:
       - attach_workspace:
          at: .
@@ -365,10 +350,20 @@ jobs:
       - run:
           name: Deploy to Grafana.com
           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" "enterprise-dist"'
+      - run:
+          name: Update RPM repository
+          command: './scripts/build/update_repo/update-rpm.sh "enterprise" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG" "enterprise-dist"'
+
 
   deploy-master:
     docker:
-      - image: grafana/grafana-ci-deploy:1.0.0
+      - image: grafana/grafana-ci-deploy:1.2.0
     steps:
       - attach_workspace:
           at: .
@@ -394,12 +389,14 @@ jobs:
           name: Publish to Grafana.com
           command: |
             rm dist/grafana-master-$(echo "${CIRCLE_SHA1}" | cut -b1-7).linux-x64.tar.gz
-            ./scripts/publish -apiKey ${GRAFANA_COM_API_KEY}
+            rm dist/*latest*
+            cd dist && ../scripts/build/release_publisher/release_publisher -apikey ${GRAFANA_COM_API_KEY} -from-local
 
   deploy-release:
     docker:
-      - image: grafana/grafana-ci-deploy:1.0.0
+      - image: grafana/grafana-ci-deploy:1.2.0
     steps:
+      - checkout
       - attach_workspace:
           at: .
       - run:
@@ -417,6 +414,15 @@ jobs:
       - run:
           name: Deploy to Grafana.com
           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" "dist"'
+      - run:
+          name: Update RPM repository
+          command: './scripts/build/update_repo/update-rpm.sh "oss" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG" "dist"'
 
 workflows:
   version: 2

+ 40 - 4
CHANGELOG.md

@@ -1,25 +1,61 @@
-# 5.5.0 (unreleased)
+# 6.0.0-beta1 (unreleased)
 
 ### New Features
 * **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)
+* **Influxdb**: Add support for time zone (`tz`) clause [#10322](https://github.com/grafana/grafana/issues/10322), thx [@cykl](https://github.com/cykl)
 * **Snapshots**: Enable deletion of public snapshot [#14109](https://github.com/grafana/grafana/issues/14109)
 
 ### Minor
 
+* **Alerting**: Use seperate timeouts for alert evals and notifications [#14701](https://github.com/grafana/grafana/issues/14701), thx [@sharkpc0813](https://github.com/sharkpc0813)
 * **Elasticsearch**: Add support for offset in date histogram aggregation [#12653](https://github.com/grafana/grafana/issues/12653), thx [@mattiarossi](https://github.com/mattiarossi)
 * **Elasticsearch**: Add support for moving average and derivative using doc count (metric count) [#8843](https://github.com/grafana/grafana/issues/8843) [#11175](https://github.com/grafana/grafana/issues/11175)
+* **Elasticsearch**: Add support for template variable interpolation in alias field [#4075](https://github.com/grafana/grafana/issues/4075), thx [@SamuelToh](https://github.com/SamuelToh)
+* **Influxdb**: Fix autocomplete of measurements does not escape search string properly [#11503](https://github.com/grafana/grafana/issues/11503), thx [@SamuelToh](https://github.com/SamuelToh)
+* **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)
+* **Cloudwatch**: Fix Assume Role Arn [#14722](https://github.com/grafana/grafana/issues/14722), thx [@jaken551](https://github.com/jaken551)
+* **Provisioning**: Fixes bug causing infinite growth in dashboard_version table. [#12864](https://github.com/grafana/grafana/issues/12864)
 * **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)
 * **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)
 * **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)
-* **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)
+* **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard)
+* **Dashboard**: `Min width` changed to `Max per row` for repeating panels. This lets you specify the maximum number of panels to show per row and by that repeated panels will always take up full width of row [#12991](https://github.com/grafana/grafana/pull/12991), thx [@pgiraud](https://github.com/pgiraud)
+* **Dashboard**: Retain decimal precision when exporting CSV [#13929](https://github.com/grafana/grafana/issues/13929), thx [@cinaglia](https://github.com/cinaglia)
+* **Templating**: Escaping "Custom" template variables [#13754](https://github.com/grafana/grafana/issues/13754), thx [@IntegersOfK](https://github.com/IntegersOfK)
 * **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 Floating Point Operations per Second units [#14558](https://github.com/grafana/grafana/pull/14558), thx [@hahnjo](https://github.com/hahnjo)
+* **Table**: Renders epoch string as date if date column style [#14484](https://github.com/grafana/grafana/issues/14484)
+* **Piechart/Flot**: Fixes multiple piechart instances with donut bug [#15062](https://github.com/grafana/grafana/pull/15062)
+* **Dataproxy**: Override incoming Authorization header [#13815](https://github.com/grafana/grafana/issues/13815), thx [@kornholi](https://github.com/kornholi)
+* **Dataproxy**: Add global datasource proxy timeout setting [#5699](https://github.com/grafana/grafana/issues/5699), thx [@RangerRick](https://github.com/RangerRick)
+* **Database**: Support specifying database host using IPV6 for backend database and sql datasources [#13711](https://github.com/grafana/grafana/issues/13711), thx [@ellisvlad](https://github.com/ellisvlad)
 
 ### Bug fixes
 * **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)
+* **Annotations**: Fix creating annotation when graph panel has no data points position the popup outside viewport [#13765](https://github.com/grafana/grafana/issues/13765), thx [@banjeremy](https://github.com/banjeremy)
+
+### Breaking changes
+* **Text Panel**: The text panel does no longer by default allow unsantizied HTML. [#4117](https://github.com/grafana/grafana/issues/4117). This means that if you have text panels with scripts tags they will no longer work as before. To enable unsafe javascript execution in text panels enable the settings `disable_sanitize_html` under the section `[panels]` in your Grafana ini file, or set env variable  `GF_PANELS_DISABLE_SANITIZE_HTML=true`.
+* **Dashboard**: Panel property `minSpan` replaced by `maxPerRow`. Dashboard migration will automatically migrate all dashboard panels using the `minSpan` property to the new `maxPerRow` property [#12991](https://github.com/grafana/grafana/pull/12991)
+
+# 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)
 

+ 1 - 1
CODE_OF_CONDUCT.md

@@ -2,7 +2,7 @@
 
 ## Our Pledge
 
-In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
 
 ## Our Standards
 

+ 4 - 2
Dockerfile

@@ -1,5 +1,5 @@
 # Golang build container
-FROM golang:1.11.4
+FROM golang:1.11.5
 
 WORKDIR $GOPATH/src/github.com/grafana/grafana
 
@@ -19,11 +19,13 @@ COPY package.json package.json
 RUN go run build.go build
 
 # Node build container
-FROM node:8
+FROM node:10.14.2
 
 WORKDIR /usr/src/app/
 
 COPY package.json yarn.lock ./
+COPY packages packages
+
 RUN yarn install --pure-lockfile --no-progress
 
 COPY Gruntfile.js tsconfig.json tslint.json ./

+ 2 - 2
PLUGIN_DEV.md

@@ -1,7 +1,7 @@
 # Plugin Development 
 
-This document is not meant as complete guide for developing plugins but more as a changelog for changes in
-Grafana that can impact plugin development. When ever you as plugin author encounter an issue with your plugin after
+This document is not meant as a complete guide for developing plugins but more as a changelog for changes in
+Grafana that can impact plugin development. Whenever you as a plugin author encounter an issue with your plugin after
 upgrading Grafana please check here before creating an issue. 
 
 ## Links

+ 8 - 6
README.md

@@ -19,7 +19,7 @@ If you have any problems please read the [troubleshooting guide](http://docs.gra
 Be sure to read the [getting started guide](http://docs.grafana.org/guides/gettingstarted/) and the other feature guides.
 
 ## Run from master
-If you want to build a package yourself, or contribute - Here is a guide for how to do that. You can always find
+If you want to build a package yourself, or contribute - here is a guide for how to do that. You can always find
 the latest master builds [here](https://grafana.com/grafana/download)
 
 ### Dependencies
@@ -71,7 +71,7 @@ Open grafana in your browser (default: `http://localhost:3000`) and login with a
 
 ### Building a Docker image
 
-There are two different ways to build a Grafana docker image. If you're machine is setup for Grafana development and you run linux/amd64 you can build just the image. Otherwise, there is the option to build Grafana completely within Docker.
+There are two different ways to build a Grafana docker image. If your machine is setup for Grafana development and you run linux/amd64 you can build just the image. Otherwise, there is the option to build Grafana completely within Docker.
 
 Run the image you have built using: `docker run --rm -p 3000:3000 grafana/grafana:dev`
 
@@ -90,7 +90,7 @@ Choose this option to build on platforms other than linux/amd64 and/or not have
 
 The resulting image will be tagged as `grafana/grafana:dev`
 
-Notice: If you are using Docker for MacOS, be sure to let limit of Memory bigger than 2 GiB (at docker -> Preferences -> Advanced), otherwize you may faild at `grunt build`
+Notice: If you are using Docker for MacOS, be sure to set the memory limit to be larger than 2 GiB (at docker -> Preferences -> Advanced), otherwise `grunt build` may fail.
 
 ### Dev config
 
@@ -129,9 +129,11 @@ GRAFANA_TEST_DB=postgres go test ./pkg/...
 
 ## Contribute
 
-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
-the kickass metrics & devops dashboard we all dream about!
+If you have any ideas for improvement or have found a bug, do not hesitate to open an issue.
+And if you have time, clone this repo and submit a pull request to help me make Grafana
+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
 

+ 11 - 7
ROADMAP.md

@@ -5,18 +5,22 @@ But it will give you an idea of our current vision and plan.
   
 ### Short term (1-2 months)
   - PRs & Bugs
-  - Multi-Stat panel
+  - React Panel Support
+  - React Query Editor Support
   - Metrics & Log Explore UI 
- 
+  - Grafana UI library shared between grafana & plugins
+  - Seperate visualization from panels
+  - More reuse between Explore & dashboard
+  - Explore logging support for more data sources 
+   
 ### Mid term (2-4 months)  
-  - React Panels 
-  - Change visualization (panel type) on the fly. 
-  - Templating Query Editor UI Plugin hook
-  - Backend plugins
+  - Drilldown links
+  - Dashboards as code workflows 
+  - React migration
+  - New panels 
   
 ### Long term (4 - 8 months)
  - Alerting improvements (silence, per series tracking, etc)
- - Progress on React migration
 
 ### In a distant future far far away
  - Meta queries 

+ 1 - 1
appveyor.yml

@@ -7,7 +7,7 @@ clone_folder: c:\gopath\src\github.com\grafana\grafana
 environment:
   nodejs_version: "8"
   GOPATH: C:\gopath
-  GOVERSION: 1.11.4
+  GOVERSION: 1.11.5
 
 install:
   - rmdir c:\go /s /q

+ 16 - 2
build.go

@@ -46,6 +46,8 @@ var (
 	binaries              []string = []string{"grafana-server", "grafana-cli"}
 	isDev                 bool     = false
 	enterprise            bool     = false
+	skipRpmGen            bool     = false
+	skipDebGen            bool     = false
 )
 
 func main() {
@@ -67,6 +69,8 @@ func main() {
 	flag.BoolVar(&enterprise, "enterprise", enterprise, "Build enterprise version of Grafana")
 	flag.StringVar(&buildIdRaw, "buildId", "0", "Build ID from CI system")
 	flag.BoolVar(&isDev, "dev", isDev, "optimal for development, skips certain steps")
+	flag.BoolVar(&skipRpmGen, "skipRpm", skipRpmGen, "skip rpm package generation (default: false)")
+	flag.BoolVar(&skipDebGen, "skipDeb", skipDebGen, "skip deb package generation (default: false)")
 	flag.Parse()
 
 	buildId = shortenBuildId(buildIdRaw)
@@ -164,6 +168,9 @@ func makeLatestDistCopies() {
 		"_amd64.deb":          "dist/grafana_latest_amd64.deb",
 		".x86_64.rpm":         "dist/grafana-latest-1.x86_64.rpm",
 		".linux-amd64.tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
+		".linux-armv7.tar.gz": "dist/grafana-latest.linux-armv7.tar.gz",
+		".linux-armv6.tar.gz": "dist/grafana-latest.linux-armv6.tar.gz",
+		".linux-arm64.tar.gz": "dist/grafana-latest.linux-arm64.tar.gz",
 	}
 
 	for _, file := range files {
@@ -237,6 +244,8 @@ func createDebPackages() {
 	previousPkgArch := pkgArch
 	if pkgArch == "armv7" {
 		pkgArch = "armhf"
+	} else if pkgArch == "armv6" {
+		pkgArch = "armel"
 	}
 	createPackage(linuxPackageOptions{
 		packageType:            "deb",
@@ -287,8 +296,13 @@ func createRpmPackages() {
 }
 
 func createLinuxPackages() {
-	createDebPackages()
-	createRpmPackages()
+	if !skipDebGen {
+		createDebPackages()
+	}
+
+	if !skipRpmGen {
+		createRpmPackages()
+	}
 }
 
 func createPackage(options linuxPackageOptions) {

+ 24 - 6
conf/defaults.ini

@@ -106,6 +106,22 @@ path = grafana.db
 # For "sqlite3" only. cache mode setting used for connecting to the database
 cache_mode = private
 
+#################################### Login ###############################
+
+[login]
+
+# Login cookie name
+cookie_name = grafana_session
+
+# How many days an session can be unused before we inactivate it
+login_remember_days = 7
+
+# How often should the login token be rotated. default to '10m'
+rotate_token_minutes = 10
+
+# How long should Grafana keep expired tokens before deleting them
+delete_expired_token_after_days = 30
+
 #################################### Session #############################
 [session]
 # Either "memory", "file", "redis", "mysql", "postgres", "memcache", default is "file"
@@ -143,6 +159,9 @@ conn_max_lifetime = 14400
 # This enables data proxy logging, default is false
 logging = false
 
+# How long the data proxy should wait before timing out default is 30 (seconds)
+timeout = 30
+
 #################################### Analytics ###########################
 [analytics]
 # Server reporting, sends usage counters to stats.grafana.org every 24 hours.
@@ -175,11 +194,6 @@ admin_password = admin
 # used for signing
 secret_key = SW2YcwTIb9zpOOhoPsMm
 
-# Auto-login remember days
-login_remember_days = 7
-cookie_username = grafana_user
-cookie_remember_name = grafana_remember
-
 # disable gravatar profile images
 disable_gravatar = false
 
@@ -189,6 +203,9 @@ data_source_proxy_whitelist =
 # disable protection against brute force login attempts
 disable_brute_force_login_protection = false
 
+# set cookies as https only. default is false
+https_flag_cookies = false
+
 #################################### Snapshots ###########################
 [snapshots]
 # snapshot sharing options
@@ -490,7 +507,7 @@ concurrent_render_limit = 5
 #################################### Explore #############################
 [explore]
 # Enable the Explore section
-enabled = false
+enabled = true
 
 #################################### Internal Grafana Metrics ############
 # Metrics available at HTTP API Url /metrics
@@ -570,6 +587,7 @@ callback_url =
 
 [panels]
 enable_alpha = false
+disable_sanitize_html = false
 
 [enterprise]
 license_path =

+ 28 - 6
conf/sample.ini

@@ -102,6 +102,22 @@ log_queries =
 # For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared)
 ;cache_mode = private
 
+#################################### Login ###############################
+
+[login]
+
+# Login cookie name
+;cookie_name = grafana_session
+
+# How many days an session can be unused before we inactivate it
+;login_remember_days = 7
+
+# How often should the login token be rotated. default to '10'
+;rotate_token_minutes = 10
+
+# How long should Grafana keep expired tokens before deleting them
+;delete_expired_token_after_days = 30
+
 #################################### Session ####################################
 [session]
 # Either "memory", "file", "redis", "mysql", "postgres", default is "file"
@@ -130,6 +146,9 @@ log_queries =
 # This enables data proxy logging, default is false
 ;logging = false
 
+# How long the data proxy should wait before timing out default is 30 (seconds)
+;timeout = 30
+
 #################################### Analytics ####################################
 [analytics]
 # Server reporting, sends usage counters to stats.grafana.org every 24 hours.
@@ -162,11 +181,6 @@ log_queries =
 # used for signing
 ;secret_key = SW2YcwTIb9zpOOhoPsMm
 
-# Auto-login remember days
-;login_remember_days = 7
-;cookie_username = grafana_user
-;cookie_remember_name = grafana_remember
-
 # disable gravatar profile images
 ;disable_gravatar = false
 
@@ -176,6 +190,9 @@ log_queries =
 # disable protection against brute force login attempts
 ;disable_brute_force_login_protection = false
 
+# set cookies as https only. default is false
+;https_flag_cookies = false
+
 #################################### Snapshots ###########################
 [snapshots]
 # snapshot sharing options
@@ -415,7 +432,7 @@ log_queries =
 #################################### Explore #############################
 [explore]
 # Enable the Explore section
-;enabled = false
+;enabled = true
 
 #################################### Internal Grafana Metrics ##########################
 # Metrics available at HTTP API Url /metrics
@@ -495,3 +512,8 @@ log_queries =
 # Path to a valid Grafana Enterprise license.jwt file
 ;license_path =
 
+[panels]
+;enable_alpha = false
+# If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities.
+;disable_sanitize_html = false
+

+ 1 - 1
devenv/dashboards.yaml

@@ -4,6 +4,6 @@ providers:
  - name: 'gdev dashboards'
    folder: 'gdev dashboards'
    type: file
+   updateIntervalSeconds: 15
    options:
      path: devenv/dev-dashboards
-

+ 1674 - 0
devenv/dev-dashboards-without-uid/panel_tests_graph.json

@@ -0,0 +1,1674 @@
+{
+  "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",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 0,
+        "y": 0
+      },
+      "id": 1,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 2,
+      "links": [],
+      "nullPointMode": "connected",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenario": "random_walk",
+          "scenarioId": "no_data_points",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "No Data Points Warning",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "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",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 8,
+        "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": "connected",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenario": "random_walk",
+          "scenarioId": "datapoints_outside_range",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Datapoints Outside Range Warning",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "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",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 16,
+        "y": 0
+      },
+      "id": 3,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 2,
+      "links": [],
+      "nullPointMode": "connected",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenario": "random_walk",
+          "scenarioId": "random_walk",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Random walk series",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "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",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 16,
+        "x": 0,
+        "y": 7
+      },
+      "id": 4,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 2,
+      "links": [],
+      "nullPointMode": "connected",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenario": "random_walk",
+          "scenarioId": "random_walk",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": "2s",
+      "timeShift": null,
+      "title": "Millisecond res x-axis and tooltip",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "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
+      }
+    },
+    {
+      "content": "Just verify that the tooltip time has millisecond resolution ",
+      "editable": true,
+      "error": false,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 16,
+        "y": 7
+      },
+      "id": 6,
+      "links": [],
+      "mode": "markdown",
+      "title": "",
+      "type": "text"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "gdev-testdata",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 9,
+        "w": 16,
+        "x": 0,
+        "y": 14
+      },
+      "id": 5,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 2,
+      "links": [],
+      "nullPointMode": "connected",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [
+        {
+          "alias": "B-series",
+          "yaxis": 2
+        }
+      ],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "B",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "2000,3000,4000,1000,3000,10000",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "2 yaxis and axis labels",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "percent",
+          "label": "Perecent",
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": "Pressure",
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "content": "Verify that axis labels look ok",
+      "editable": true,
+      "error": false,
+      "gridPos": {
+        "h": 9,
+        "w": 8,
+        "x": 16,
+        "y": 14
+      },
+      "id": 7,
+      "links": [],
+      "mode": "markdown",
+      "title": "",
+      "type": "text"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "gdev-testdata",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 0,
+        "y": 23
+      },
+      "id": 8,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 2,
+      "links": [],
+      "nullPointMode": "connected",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "B",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,null,null,null,null,null,null,100,10,10,20,30,40,10",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "null value connected",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "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",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 8,
+        "y": 23
+      },
+      "id": 10,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 2,
+      "links": [],
+      "nullPointMode": "null as zero",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "B",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,null,null,null,null,null,null,100,10,10,20,30,40,10",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "null value null as zero",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "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
+      }
+    },
+    {
+      "content": "Should be a long line connecting the null region in the `connected`  mode, and in zero it should just be a line with zero value at the null points. ",
+      "editable": true,
+      "error": false,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 16,
+        "y": 23
+      },
+      "id": 13,
+      "links": [],
+      "mode": "markdown",
+      "title": "",
+      "type": "text"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "gdev-testdata",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 16,
+        "x": 0,
+        "y": 30
+      },
+      "id": 9,
+      "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": [
+        {
+          "alias": "B-series",
+          "zindex": -3
+        }
+      ],
+      "spaceLength": 10,
+      "stack": true,
+      "steppedLine": false,
+      "targets": [
+        {
+          "hide": false,
+          "refId": "B",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,null,null,null,null,null,null,100,10,10,20,30,40,10",
+          "target": ""
+        },
+        {
+          "alias": "",
+          "hide": false,
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,20,30,40,40,40,100,10,20,20",
+          "target": ""
+        },
+        {
+          "alias": "",
+          "hide": false,
+          "refId": "C",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,20,30,40,40,40,100,10,20,20",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Stacking value ontop of nulls",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "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
+      }
+    },
+    {
+      "content": "Stacking values on top of nulls, should treat the null values as zero. ",
+      "editable": true,
+      "error": false,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 16,
+        "y": 30
+      },
+      "id": 14,
+      "links": [],
+      "mode": "markdown",
+      "title": "",
+      "type": "text"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "gdev-testdata",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 16,
+        "x": 0,
+        "y": 37
+      },
+      "id": 12,
+      "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": [
+        {
+          "alias": "B-series",
+          "zindex": -3
+        }
+      ],
+      "spaceLength": 10,
+      "stack": true,
+      "steppedLine": false,
+      "targets": [
+        {
+          "alias": "",
+          "hide": false,
+          "refId": "B",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
+          "target": ""
+        },
+        {
+          "alias": "",
+          "hide": false,
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
+          "target": ""
+        },
+        {
+          "alias": "",
+          "hide": false,
+          "refId": "C",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Stacking all series null segment",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "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
+      }
+    },
+    {
+      "content": "Stacking when all values are null should leave a gap in the graph",
+      "editable": true,
+      "error": false,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 16,
+        "y": 37
+      },
+      "id": 15,
+      "links": [],
+      "mode": "markdown",
+      "title": "",
+      "type": "text"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "gdev-testdata",
+      "editable": true,
+      "error": false,
+      "fill": 0,
+      "gridPos": {
+        "h": 7,
+        "w": 16,
+        "x": 0,
+        "y": 44
+      },
+      "id": 21,
+      "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": [
+        {
+          "alias": "C-series",
+          "steppedLine": true
+        }
+      ],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "alias": "",
+          "hide": false,
+          "refId": "B",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,null,40,null,90,null,null,100,null,null,100,null,null,80,null",
+          "target": ""
+        },
+        {
+          "alias": "",
+          "hide": false,
+          "refId": "C",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "20,null40,null,null,50,null,70,null,100,null,10,null,30,null",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Null between points",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "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
+      }
+    },
+    {
+      "content": "Left is showing null between values for a normal line graph and staircase graph. Orphaned data points should be rendered as points",
+      "editable": true,
+      "error": false,
+      "gridPos": {
+        "h": 7,
+        "w": 8,
+        "x": 16,
+        "y": 44
+      },
+      "id": 22,
+      "links": [],
+      "mode": "markdown",
+      "title": "",
+      "type": "text"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "gdev-testdata",
+      "decimals": 3,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 24,
+        "x": 0,
+        "y": 51
+      },
+      "id": 20,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": true,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Legend Table Single Series Should Take Minimum Height",
+      "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",
+      "decimals": 3,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 0,
+        "y": 58
+      },
+      "id": 16,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": true,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "B",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "C",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "D",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Legend Table No Scroll Visible",
+      "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",
+      "decimals": 3,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 12,
+        "y": 58
+      },
+      "id": 17,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "show": true,
+        "total": true,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "B",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "C",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "D",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "E",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "F",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "G",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "H",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "I",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "J",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Legend Table Should Scroll",
+      "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",
+      "decimals": 3,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 0,
+        "y": 65
+      },
+      "id": 18,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "rightSide": true,
+        "show": true,
+        "total": true,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "B",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "C",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "D",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Legend Table No Scroll Visible",
+      "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",
+      "decimals": 3,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 12,
+        "y": 65
+      },
+      "id": 19,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "rightSide": true,
+        "show": true,
+        "total": true,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "B",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "C",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "D",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "E",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "F",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "G",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "H",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "I",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "J",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "K",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        },
+        {
+          "refId": "L",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Legend Table No Scroll Visible",
+      "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,
+  "revision": 8,
+  "schemaVersion": 16,
+  "style": "dark",
+  "tags": [
+    "gdev",
+    "panel-tests"
+  ],
+  "templating": {
+    "list": []
+  },
+  "time": {
+    "from": "now-1h",
+    "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",
+  "version": 1
+}

+ 510 - 0
devenv/dev-dashboards-without-uid/panel_tests_graph_time_regions.json

@@ -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 - 0
devenv/dev-dashboards-without-uid/panel_tests_polystat.json

@@ -0,0 +1,3342 @@
+{
+  "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": [
+    {
+      "animationModes": [
+        {
+          "text": "Show All",
+          "value": "all"
+        },
+        {
+          "text": "Show Triggered",
+          "value": "triggered"
+        }
+      ],
+      "colors": [
+        "#299c46",
+        "rgba(237, 129, 40, 0.89)",
+        "#d44a3a"
+      ],
+      "d3DivId": "d3_svg_4",
+      "datasource": "gdev-testdata",
+      "decimals": 2,
+      "displayModes": [
+        {
+          "text": "Show All",
+          "value": "all"
+        },
+        {
+          "text": "Show Triggered",
+          "value": "triggered"
+        }
+      ],
+      "fontSizes": [
+        4,
+        5,
+        6,
+        7,
+        8,
+        9,
+        10,
+        11,
+        12,
+        13,
+        14,
+        15,
+        16,
+        17,
+        18,
+        19,
+        20,
+        22,
+        24,
+        26,
+        28,
+        30,
+        32,
+        34,
+        36,
+        38,
+        40,
+        42,
+        44,
+        46,
+        48,
+        50,
+        52,
+        54,
+        56,
+        58,
+        60,
+        62,
+        64,
+        66,
+        68,
+        70
+      ],
+      "fontTypes": [
+        "Open Sans",
+        "Arial",
+        "Avant Garde",
+        "Bookman",
+        "Consolas",
+        "Courier",
+        "Courier New",
+        "Futura",
+        "Garamond",
+        "Helvetica",
+        "Palatino",
+        "Times",
+        "Times New Roman",
+        "Verdana"
+      ],
+      "format": "none",
+      "gridPos": {
+        "h": 9,
+        "w": 12,
+        "x": 0,
+        "y": 0
+      },
+      "id": 4,
+      "links": [],
+      "notcolors": [
+        "rgba(245, 54, 54, 0.9)",
+        "rgba(237, 129, 40, 0.89)",
+        "rgba(50, 172, 45, 0.97)"
+      ],
+      "operatorName": "avg",
+      "operatorOptions": [
+        {
+          "text": "Average",
+          "value": "avg"
+        },
+        {
+          "text": "Count",
+          "value": "count"
+        },
+        {
+          "text": "Current",
+          "value": "current"
+        },
+        {
+          "text": "Delta",
+          "value": "delta"
+        },
+        {
+          "text": "Difference",
+          "value": "diff"
+        },
+        {
+          "text": "First",
+          "value": "first"
+        },
+        {
+          "text": "Log Min",
+          "value": "logmin"
+        },
+        {
+          "text": "Max",
+          "value": "max"
+        },
+        {
+          "text": "Min",
+          "value": "min"
+        },
+        {
+          "text": "Name",
+          "value": "name"
+        },
+        {
+          "text": "Time of Last Point",
+          "value": "last_time"
+        },
+        {
+          "text": "Time Step",
+          "value": "time_step"
+        },
+        {
+          "text": "Total",
+          "value": "total"
+        }
+      ],
+      "polystat": {
+        "animationSpeed": 2500,
+        "columnAutoSize": true,
+        "columns": "",
+        "defaultClickThrough": "",
+        "defaultClickThroughSanitize": true,
+        "displayLimit": 100,
+        "fontAutoScale": true,
+        "fontSize": 12,
+        "globalDisplayMode": "all",
+        "globalOperatorName": "avg",
+        "gradientEnabled": true,
+        "hexagonSortByDirection": "asc",
+        "hexagonSortByField": "name",
+        "maxMetrics": 0,
+        "polygonBorderColor": "black",
+        "polygonBorderSize": 2,
+        "radius": "",
+        "radiusAutoSize": true,
+        "rowAutoSize": true,
+        "rows": "",
+        "shape": "hexagon_pointed_top",
+        "tooltipDisplayMode": "all",
+        "tooltipDisplayTextTriggeredEmpty": "OK",
+        "tooltipFontSize": 12,
+        "tooltipFontType": "Open Sans",
+        "tooltipPrimarySortDirection": "desc",
+        "tooltipPrimarySortField": "thresholdLevel",
+        "tooltipSecondarySortDirection": "desc",
+        "tooltipSecondarySortField": "value",
+        "tooltipTimestampEnabled": true
+      },
+      "savedComposites": [],
+      "savedOverrides": [],
+      "shapes": [
+        {
+          "text": "Hexagon Pointed Top",
+          "value": "hexagon_pointed_top"
+        },
+        {
+          "text": "Hexagon Flat Top",
+          "value": "hexagon_flat_top"
+        },
+        {
+          "text": "Circle",
+          "value": "circle"
+        },
+        {
+          "text": "Cross",
+          "value": "cross"
+        },
+        {
+          "text": "Diamond",
+          "value": "diamond"
+        },
+        {
+          "text": "Square",
+          "value": "square"
+        },
+        {
+          "text": "Star",
+          "value": "star"
+        },
+        {
+          "text": "Triangle",
+          "value": "triangle"
+        },
+        {
+          "text": "Wye",
+          "value": "wye"
+        }
+      ],
+      "sortDirections": [
+        {
+          "text": "Ascending",
+          "value": "asc"
+        },
+        {
+          "text": "Descending",
+          "value": "desc"
+        }
+      ],
+      "sortFields": [
+        {
+          "text": "Name",
+          "value": "name"
+        },
+        {
+          "text": "Threshold Level",
+          "value": "thresholdLevel"
+        },
+        {
+          "text": "Value",
+          "value": "value"
+        }
+      ],
+      "svgContainer": {},
+      "targets": [
+        {
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "A",
+          "scenarioId": "random_walk"
+        },
+        {
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "B",
+          "scenarioId": "random_walk"
+        },
+        {
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "C",
+          "scenarioId": "random_walk"
+        },
+        {
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "D",
+          "scenarioId": "random_walk"
+        },
+        {
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "E",
+          "scenarioId": "random_walk"
+        }
+      ],
+      "thresholdStates": [
+        {
+          "text": "ok",
+          "value": 0
+        },
+        {
+          "text": "warning",
+          "value": 1
+        },
+        {
+          "text": "critical",
+          "value": 2
+        },
+        {
+          "text": "custom",
+          "value": 3
+        }
+      ],
+      "title": "Poor use of space",
+      "type": "grafana-polystat-panel",
+      "unitFormats": [
+        {
+          "submenu": [
+            {
+              "text": "none",
+              "value": "none"
+            },
+            {
+              "text": "short",
+              "value": "short"
+            },
+            {
+              "text": "percent (0-100)",
+              "value": "percent"
+            },
+            {
+              "text": "percent (0.0-1.0)",
+              "value": "percentunit"
+            },
+            {
+              "text": "Humidity (%H)",
+              "value": "humidity"
+            },
+            {
+              "text": "decibel",
+              "value": "dB"
+            },
+            {
+              "text": "hexadecimal (0x)",
+              "value": "hex0x"
+            },
+            {
+              "text": "hexadecimal",
+              "value": "hex"
+            },
+            {
+              "text": "scientific notation",
+              "value": "sci"
+            },
+            {
+              "text": "locale format",
+              "value": "locale"
+            }
+          ],
+          "text": "none"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Dollars ($)",
+              "value": "currencyUSD"
+            },
+            {
+              "text": "Pounds (£)",
+              "value": "currencyGBP"
+            },
+            {
+              "text": "Euro (€)",
+              "value": "currencyEUR"
+            },
+            {
+              "text": "Yen (¥)",
+              "value": "currencyJPY"
+            },
+            {
+              "text": "Rubles (₽)",
+              "value": "currencyRUB"
+            },
+            {
+              "text": "Hryvnias (₴)",
+              "value": "currencyUAH"
+            },
+            {
+              "text": "Real (R$)",
+              "value": "currencyBRL"
+            },
+            {
+              "text": "Danish Krone (kr)",
+              "value": "currencyDKK"
+            },
+            {
+              "text": "Icelandic Króna (kr)",
+              "value": "currencyISK"
+            },
+            {
+              "text": "Norwegian Krone (kr)",
+              "value": "currencyNOK"
+            },
+            {
+              "text": "Swedish Krona (kr)",
+              "value": "currencySEK"
+            },
+            {
+              "text": "Czech koruna (czk)",
+              "value": "currencyCZK"
+            },
+            {
+              "text": "Swiss franc (CHF)",
+              "value": "currencyCHF"
+            },
+            {
+              "text": "Polish Złoty (PLN)",
+              "value": "currencyPLN"
+            },
+            {
+              "text": "Bitcoin (฿)",
+              "value": "currencyBTC"
+            }
+          ],
+          "text": "currency"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Hertz (1/s)",
+              "value": "hertz"
+            },
+            {
+              "text": "nanoseconds (ns)",
+              "value": "ns"
+            },
+            {
+              "text": "microseconds (µs)",
+              "value": "µs"
+            },
+            {
+              "text": "milliseconds (ms)",
+              "value": "ms"
+            },
+            {
+              "text": "seconds (s)",
+              "value": "s"
+            },
+            {
+              "text": "minutes (m)",
+              "value": "m"
+            },
+            {
+              "text": "hours (h)",
+              "value": "h"
+            },
+            {
+              "text": "days (d)",
+              "value": "d"
+            },
+            {
+              "text": "duration (ms)",
+              "value": "dtdurationms"
+            },
+            {
+              "text": "duration (s)",
+              "value": "dtdurations"
+            },
+            {
+              "text": "duration (hh:mm:ss)",
+              "value": "dthms"
+            },
+            {
+              "text": "Timeticks (s/100)",
+              "value": "timeticks"
+            }
+          ],
+          "text": "time"
+        },
+        {
+          "submenu": [
+            {
+              "text": "YYYY-MM-DD HH:mm:ss",
+              "value": "dateTimeAsIso"
+            },
+            {
+              "text": "DD/MM/YYYY h:mm:ss a",
+              "value": "dateTimeAsUS"
+            },
+            {
+              "text": "From Now",
+              "value": "dateTimeFromNow"
+            }
+          ],
+          "text": "date & time"
+        },
+        {
+          "submenu": [
+            {
+              "text": "bits",
+              "value": "bits"
+            },
+            {
+              "text": "bytes",
+              "value": "bytes"
+            },
+            {
+              "text": "kibibytes",
+              "value": "kbytes"
+            },
+            {
+              "text": "mebibytes",
+              "value": "mbytes"
+            },
+            {
+              "text": "gibibytes",
+              "value": "gbytes"
+            }
+          ],
+          "text": "data (IEC)"
+        },
+        {
+          "submenu": [
+            {
+              "text": "bits",
+              "value": "decbits"
+            },
+            {
+              "text": "bytes",
+              "value": "decbytes"
+            },
+            {
+              "text": "kilobytes",
+              "value": "deckbytes"
+            },
+            {
+              "text": "megabytes",
+              "value": "decmbytes"
+            },
+            {
+              "text": "gigabytes",
+              "value": "decgbytes"
+            }
+          ],
+          "text": "data (Metric)"
+        },
+        {
+          "submenu": [
+            {
+              "text": "packets/sec",
+              "value": "pps"
+            },
+            {
+              "text": "bits/sec",
+              "value": "bps"
+            },
+            {
+              "text": "bytes/sec",
+              "value": "Bps"
+            },
+            {
+              "text": "kilobits/sec",
+              "value": "Kbits"
+            },
+            {
+              "text": "kilobytes/sec",
+              "value": "KBs"
+            },
+            {
+              "text": "megabits/sec",
+              "value": "Mbits"
+            },
+            {
+              "text": "megabytes/sec",
+              "value": "MBs"
+            },
+            {
+              "text": "gigabytes/sec",
+              "value": "GBs"
+            },
+            {
+              "text": "gigabits/sec",
+              "value": "Gbits"
+            }
+          ],
+          "text": "data rate"
+        },
+        {
+          "submenu": [
+            {
+              "text": "hashes/sec",
+              "value": "Hs"
+            },
+            {
+              "text": "kilohashes/sec",
+              "value": "KHs"
+            },
+            {
+              "text": "megahashes/sec",
+              "value": "MHs"
+            },
+            {
+              "text": "gigahashes/sec",
+              "value": "GHs"
+            },
+            {
+              "text": "terahashes/sec",
+              "value": "THs"
+            },
+            {
+              "text": "petahashes/sec",
+              "value": "PHs"
+            },
+            {
+              "text": "exahashes/sec",
+              "value": "EHs"
+            }
+          ],
+          "text": "hash rate"
+        },
+        {
+          "submenu": [
+            {
+              "text": "ops/sec (ops)",
+              "value": "ops"
+            },
+            {
+              "text": "requests/sec (rps)",
+              "value": "reqps"
+            },
+            {
+              "text": "reads/sec (rps)",
+              "value": "rps"
+            },
+            {
+              "text": "writes/sec (wps)",
+              "value": "wps"
+            },
+            {
+              "text": "I/O ops/sec (iops)",
+              "value": "iops"
+            },
+            {
+              "text": "ops/min (opm)",
+              "value": "opm"
+            },
+            {
+              "text": "reads/min (rpm)",
+              "value": "rpm"
+            },
+            {
+              "text": "writes/min (wpm)",
+              "value": "wpm"
+            }
+          ],
+          "text": "throughput"
+        },
+        {
+          "submenu": [
+            {
+              "text": "millimetre (mm)",
+              "value": "lengthmm"
+            },
+            {
+              "text": "meter (m)",
+              "value": "lengthm"
+            },
+            {
+              "text": "feet (ft)",
+              "value": "lengthft"
+            },
+            {
+              "text": "kilometer (km)",
+              "value": "lengthkm"
+            },
+            {
+              "text": "mile (mi)",
+              "value": "lengthmi"
+            }
+          ],
+          "text": "length"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Square Meters (m²)",
+              "value": "areaM2"
+            },
+            {
+              "text": "Square Feet (ft²)",
+              "value": "areaF2"
+            },
+            {
+              "text": "Square Miles (mi²)",
+              "value": "areaMI2"
+            }
+          ],
+          "text": "area"
+        },
+        {
+          "submenu": [
+            {
+              "text": "milligram (mg)",
+              "value": "massmg"
+            },
+            {
+              "text": "gram (g)",
+              "value": "massg"
+            },
+            {
+              "text": "kilogram (kg)",
+              "value": "masskg"
+            },
+            {
+              "text": "metric ton (t)",
+              "value": "masst"
+            }
+          ],
+          "text": "mass"
+        },
+        {
+          "submenu": [
+            {
+              "text": "metres/second (m/s)",
+              "value": "velocityms"
+            },
+            {
+              "text": "kilometers/hour (km/h)",
+              "value": "velocitykmh"
+            },
+            {
+              "text": "miles/hour (mph)",
+              "value": "velocitymph"
+            },
+            {
+              "text": "knot (kn)",
+              "value": "velocityknot"
+            }
+          ],
+          "text": "velocity"
+        },
+        {
+          "submenu": [
+            {
+              "text": "millilitre (mL)",
+              "value": "mlitre"
+            },
+            {
+              "text": "litre (L)",
+              "value": "litre"
+            },
+            {
+              "text": "cubic metre",
+              "value": "m3"
+            },
+            {
+              "text": "Normal cubic metre",
+              "value": "Nm3"
+            },
+            {
+              "text": "cubic decimetre",
+              "value": "dm3"
+            },
+            {
+              "text": "gallons",
+              "value": "gallons"
+            }
+          ],
+          "text": "volume"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Watt (W)",
+              "value": "watt"
+            },
+            {
+              "text": "Kilowatt (kW)",
+              "value": "kwatt"
+            },
+            {
+              "text": "Milliwatt (mW)",
+              "value": "mwatt"
+            },
+            {
+              "text": "Watt per square metre (W/m²)",
+              "value": "Wm2"
+            },
+            {
+              "text": "Volt-ampere (VA)",
+              "value": "voltamp"
+            },
+            {
+              "text": "Kilovolt-ampere (kVA)",
+              "value": "kvoltamp"
+            },
+            {
+              "text": "Volt-ampere reactive (var)",
+              "value": "voltampreact"
+            },
+            {
+              "text": "Kilovolt-ampere reactive (kvar)",
+              "value": "kvoltampreact"
+            },
+            {
+              "text": "Watt-hour (Wh)",
+              "value": "watth"
+            },
+            {
+              "text": "Kilowatt-hour (kWh)",
+              "value": "kwatth"
+            },
+            {
+              "text": "Kilowatt-min (kWm)",
+              "value": "kwattm"
+            },
+            {
+              "text": "Joule (J)",
+              "value": "joule"
+            },
+            {
+              "text": "Electron volt (eV)",
+              "value": "ev"
+            },
+            {
+              "text": "Ampere (A)",
+              "value": "amp"
+            },
+            {
+              "text": "Kiloampere (kA)",
+              "value": "kamp"
+            },
+            {
+              "text": "Milliampere (mA)",
+              "value": "mamp"
+            },
+            {
+              "text": "Volt (V)",
+              "value": "volt"
+            },
+            {
+              "text": "Kilovolt (kV)",
+              "value": "kvolt"
+            },
+            {
+              "text": "Millivolt (mV)",
+              "value": "mvolt"
+            },
+            {
+              "text": "Decibel-milliwatt (dBm)",
+              "value": "dBm"
+            },
+            {
+              "text": "Ohm (Ω)",
+              "value": "ohm"
+            },
+            {
+              "text": "Lumens (Lm)",
+              "value": "lumens"
+            }
+          ],
+          "text": "energy"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Celsius (°C)",
+              "value": "celsius"
+            },
+            {
+              "text": "Farenheit (°F)",
+              "value": "farenheit"
+            },
+            {
+              "text": "Kelvin (K)",
+              "value": "kelvin"
+            }
+          ],
+          "text": "temperature"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Millibars",
+              "value": "pressurembar"
+            },
+            {
+              "text": "Bars",
+              "value": "pressurebar"
+            },
+            {
+              "text": "Kilobars",
+              "value": "pressurekbar"
+            },
+            {
+              "text": "Hectopascals",
+              "value": "pressurehpa"
+            },
+            {
+              "text": "Kilopascals",
+              "value": "pressurekpa"
+            },
+            {
+              "text": "Inches of mercury",
+              "value": "pressurehg"
+            },
+            {
+              "text": "PSI",
+              "value": "pressurepsi"
+            }
+          ],
+          "text": "pressure"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Newton-meters (Nm)",
+              "value": "forceNm"
+            },
+            {
+              "text": "Kilonewton-meters (kNm)",
+              "value": "forcekNm"
+            },
+            {
+              "text": "Newtons (N)",
+              "value": "forceN"
+            },
+            {
+              "text": "Kilonewtons (kN)",
+              "value": "forcekN"
+            }
+          ],
+          "text": "force"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Gallons/min (gpm)",
+              "value": "flowgpm"
+            },
+            {
+              "text": "Cubic meters/sec (cms)",
+              "value": "flowcms"
+            },
+            {
+              "text": "Cubic feet/sec (cfs)",
+              "value": "flowcfs"
+            },
+            {
+              "text": "Cubic feet/min (cfm)",
+              "value": "flowcfm"
+            },
+            {
+              "text": "Litre/hour",
+              "value": "litreh"
+            },
+            {
+              "text": "Litre/min (l/min)",
+              "value": "flowlpm"
+            },
+            {
+              "text": "milliLitre/min (mL/min)",
+              "value": "flowmlpm"
+            }
+          ],
+          "text": "flow"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Degrees (°)",
+              "value": "degree"
+            },
+            {
+              "text": "Radians",
+              "value": "radian"
+            },
+            {
+              "text": "Gradian",
+              "value": "grad"
+            }
+          ],
+          "text": "angle"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Meters/sec²",
+              "value": "accMS2"
+            },
+            {
+              "text": "Feet/sec²",
+              "value": "accFS2"
+            },
+            {
+              "text": "G unit",
+              "value": "accG"
+            }
+          ],
+          "text": "acceleration"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Becquerel (Bq)",
+              "value": "radbq"
+            },
+            {
+              "text": "curie (Ci)",
+              "value": "radci"
+            },
+            {
+              "text": "Gray (Gy)",
+              "value": "radgy"
+            },
+            {
+              "text": "rad",
+              "value": "radrad"
+            },
+            {
+              "text": "Sievert (Sv)",
+              "value": "radsv"
+            },
+            {
+              "text": "rem",
+              "value": "radrem"
+            },
+            {
+              "text": "Exposure (C/kg)",
+              "value": "radexpckg"
+            },
+            {
+              "text": "roentgen (R)",
+              "value": "radr"
+            },
+            {
+              "text": "Sievert/hour (Sv/h)",
+              "value": "radsvh"
+            }
+          ],
+          "text": "radiation"
+        },
+        {
+          "submenu": [
+            {
+              "text": "parts-per-million (ppm)",
+              "value": "ppm"
+            },
+            {
+              "text": "parts-per-billion (ppb)",
+              "value": "conppb"
+            },
+            {
+              "text": "nanogram per cubic metre (ng/m³)",
+              "value": "conngm3"
+            },
+            {
+              "text": "nanogram per normal cubic metre (ng/Nm³)",
+              "value": "conngNm3"
+            },
+            {
+              "text": "microgram per cubic metre (μg/m³)",
+              "value": "conμgm3"
+            },
+            {
+              "text": "microgram per normal cubic metre (μg/Nm³)",
+              "value": "conμgNm3"
+            },
+            {
+              "text": "milligram per cubic metre (mg/m³)",
+              "value": "conmgm3"
+            },
+            {
+              "text": "milligram per normal cubic metre (mg/Nm³)",
+              "value": "conmgNm3"
+            },
+            {
+              "text": "gram per cubic metre (g/m³)",
+              "value": "congm3"
+            },
+            {
+              "text": "gram per normal cubic metre (g/Nm³)",
+              "value": "congNm3"
+            }
+          ],
+          "text": "concentration"
+        }
+      ]
+    },
+    {
+      "animationModes": [
+        {
+          "text": "Show All",
+          "value": "all"
+        },
+        {
+          "text": "Show Triggered",
+          "value": "triggered"
+        }
+      ],
+      "colors": [
+        "#299c46",
+        "rgba(237, 129, 40, 0.89)",
+        "#d44a3a"
+      ],
+      "d3DivId": "d3_svg_5",
+      "datasource": "gdev-testdata",
+      "decimals": 2,
+      "displayModes": [
+        {
+          "text": "Show All",
+          "value": "all"
+        },
+        {
+          "text": "Show Triggered",
+          "value": "triggered"
+        }
+      ],
+      "fontSizes": [
+        4,
+        5,
+        6,
+        7,
+        8,
+        9,
+        10,
+        11,
+        12,
+        13,
+        14,
+        15,
+        16,
+        17,
+        18,
+        19,
+        20,
+        22,
+        24,
+        26,
+        28,
+        30,
+        32,
+        34,
+        36,
+        38,
+        40,
+        42,
+        44,
+        46,
+        48,
+        50,
+        52,
+        54,
+        56,
+        58,
+        60,
+        62,
+        64,
+        66,
+        68,
+        70
+      ],
+      "fontTypes": [
+        "Open Sans",
+        "Arial",
+        "Avant Garde",
+        "Bookman",
+        "Consolas",
+        "Courier",
+        "Courier New",
+        "Futura",
+        "Garamond",
+        "Helvetica",
+        "Palatino",
+        "Times",
+        "Times New Roman",
+        "Verdana"
+      ],
+      "format": "none",
+      "gridPos": {
+        "h": 9,
+        "w": 12,
+        "x": 12,
+        "y": 0
+      },
+      "id": 5,
+      "links": [],
+      "notcolors": [
+        "rgba(245, 54, 54, 0.9)",
+        "rgba(237, 129, 40, 0.89)",
+        "rgba(50, 172, 45, 0.97)"
+      ],
+      "operatorName": "avg",
+      "operatorOptions": [
+        {
+          "text": "Average",
+          "value": "avg"
+        },
+        {
+          "text": "Count",
+          "value": "count"
+        },
+        {
+          "text": "Current",
+          "value": "current"
+        },
+        {
+          "text": "Delta",
+          "value": "delta"
+        },
+        {
+          "text": "Difference",
+          "value": "diff"
+        },
+        {
+          "text": "First",
+          "value": "first"
+        },
+        {
+          "text": "Log Min",
+          "value": "logmin"
+        },
+        {
+          "text": "Max",
+          "value": "max"
+        },
+        {
+          "text": "Min",
+          "value": "min"
+        },
+        {
+          "text": "Name",
+          "value": "name"
+        },
+        {
+          "text": "Time of Last Point",
+          "value": "last_time"
+        },
+        {
+          "text": "Time Step",
+          "value": "time_step"
+        },
+        {
+          "text": "Total",
+          "value": "total"
+        }
+      ],
+      "polystat": {
+        "animationSpeed": 2500,
+        "columnAutoSize": true,
+        "columns": "",
+        "defaultClickThrough": "",
+        "defaultClickThroughSanitize": true,
+        "displayLimit": 100,
+        "fontAutoScale": true,
+        "fontSize": 12,
+        "globalDisplayMode": "all",
+        "globalOperatorName": "avg",
+        "gradientEnabled": true,
+        "hexagonSortByDirection": "asc",
+        "hexagonSortByField": "name",
+        "maxMetrics": 0,
+        "polygonBorderColor": "black",
+        "polygonBorderSize": 2,
+        "radius": "",
+        "radiusAutoSize": true,
+        "rowAutoSize": true,
+        "rows": "",
+        "shape": "hexagon_pointed_top",
+        "tooltipDisplayMode": "all",
+        "tooltipDisplayTextTriggeredEmpty": "OK",
+        "tooltipFontSize": 12,
+        "tooltipFontType": "Open Sans",
+        "tooltipPrimarySortDirection": "desc",
+        "tooltipPrimarySortField": "thresholdLevel",
+        "tooltipSecondarySortDirection": "desc",
+        "tooltipSecondarySortField": "value",
+        "tooltipTimestampEnabled": true
+      },
+      "savedComposites": [
+        {
+          "compositeName": "comp",
+          "members": [
+            {
+              "seriesName": "A-series"
+            },
+            {
+              "seriesName": "B-series"
+            }
+          ],
+          "enabled": true,
+          "clickThrough": "",
+          "hideMembers": true,
+          "showName": true,
+          "showValue": true,
+          "animateMode": "all",
+          "thresholdLevel": 0,
+          "sanitizeURLEnabled": true,
+          "sanitizedURL": ""
+        }
+      ],
+      "savedOverrides": [],
+      "shapes": [
+        {
+          "text": "Hexagon Pointed Top",
+          "value": "hexagon_pointed_top"
+        },
+        {
+          "text": "Hexagon Flat Top",
+          "value": "hexagon_flat_top"
+        },
+        {
+          "text": "Circle",
+          "value": "circle"
+        },
+        {
+          "text": "Cross",
+          "value": "cross"
+        },
+        {
+          "text": "Diamond",
+          "value": "diamond"
+        },
+        {
+          "text": "Square",
+          "value": "square"
+        },
+        {
+          "text": "Star",
+          "value": "star"
+        },
+        {
+          "text": "Triangle",
+          "value": "triangle"
+        },
+        {
+          "text": "Wye",
+          "value": "wye"
+        }
+      ],
+      "sortDirections": [
+        {
+          "text": "Ascending",
+          "value": "asc"
+        },
+        {
+          "text": "Descending",
+          "value": "desc"
+        }
+      ],
+      "sortFields": [
+        {
+          "text": "Name",
+          "value": "name"
+        },
+        {
+          "text": "Threshold Level",
+          "value": "thresholdLevel"
+        },
+        {
+          "text": "Value",
+          "value": "value"
+        }
+      ],
+      "svgContainer": {},
+      "targets": [
+        {
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "A",
+          "scenarioId": "random_walk"
+        },
+        {
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "B",
+          "scenarioId": "random_walk"
+        },
+        {
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "C",
+          "scenarioId": "random_walk"
+        },
+        {
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "D",
+          "scenarioId": "random_walk"
+        },
+        {
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "E",
+          "scenarioId": "random_walk"
+        }
+      ],
+      "thresholdStates": [
+        {
+          "text": "ok",
+          "value": 0
+        },
+        {
+          "text": "warning",
+          "value": 1
+        },
+        {
+          "text": "critical",
+          "value": 2
+        },
+        {
+          "text": "custom",
+          "value": 3
+        }
+      ],
+      "title": "Composite crash",
+      "type": "grafana-polystat-panel",
+      "unitFormats": [
+        {
+          "submenu": [
+            {
+              "text": "none",
+              "value": "none"
+            },
+            {
+              "text": "short",
+              "value": "short"
+            },
+            {
+              "text": "percent (0-100)",
+              "value": "percent"
+            },
+            {
+              "text": "percent (0.0-1.0)",
+              "value": "percentunit"
+            },
+            {
+              "text": "Humidity (%H)",
+              "value": "humidity"
+            },
+            {
+              "text": "decibel",
+              "value": "dB"
+            },
+            {
+              "text": "hexadecimal (0x)",
+              "value": "hex0x"
+            },
+            {
+              "text": "hexadecimal",
+              "value": "hex"
+            },
+            {
+              "text": "scientific notation",
+              "value": "sci"
+            },
+            {
+              "text": "locale format",
+              "value": "locale"
+            }
+          ],
+          "text": "none"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Dollars ($)",
+              "value": "currencyUSD"
+            },
+            {
+              "text": "Pounds (£)",
+              "value": "currencyGBP"
+            },
+            {
+              "text": "Euro (€)",
+              "value": "currencyEUR"
+            },
+            {
+              "text": "Yen (¥)",
+              "value": "currencyJPY"
+            },
+            {
+              "text": "Rubles (₽)",
+              "value": "currencyRUB"
+            },
+            {
+              "text": "Hryvnias (₴)",
+              "value": "currencyUAH"
+            },
+            {
+              "text": "Real (R$)",
+              "value": "currencyBRL"
+            },
+            {
+              "text": "Danish Krone (kr)",
+              "value": "currencyDKK"
+            },
+            {
+              "text": "Icelandic Króna (kr)",
+              "value": "currencyISK"
+            },
+            {
+              "text": "Norwegian Krone (kr)",
+              "value": "currencyNOK"
+            },
+            {
+              "text": "Swedish Krona (kr)",
+              "value": "currencySEK"
+            },
+            {
+              "text": "Czech koruna (czk)",
+              "value": "currencyCZK"
+            },
+            {
+              "text": "Swiss franc (CHF)",
+              "value": "currencyCHF"
+            },
+            {
+              "text": "Polish Złoty (PLN)",
+              "value": "currencyPLN"
+            },
+            {
+              "text": "Bitcoin (฿)",
+              "value": "currencyBTC"
+            }
+          ],
+          "text": "currency"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Hertz (1/s)",
+              "value": "hertz"
+            },
+            {
+              "text": "nanoseconds (ns)",
+              "value": "ns"
+            },
+            {
+              "text": "microseconds (µs)",
+              "value": "µs"
+            },
+            {
+              "text": "milliseconds (ms)",
+              "value": "ms"
+            },
+            {
+              "text": "seconds (s)",
+              "value": "s"
+            },
+            {
+              "text": "minutes (m)",
+              "value": "m"
+            },
+            {
+              "text": "hours (h)",
+              "value": "h"
+            },
+            {
+              "text": "days (d)",
+              "value": "d"
+            },
+            {
+              "text": "duration (ms)",
+              "value": "dtdurationms"
+            },
+            {
+              "text": "duration (s)",
+              "value": "dtdurations"
+            },
+            {
+              "text": "duration (hh:mm:ss)",
+              "value": "dthms"
+            },
+            {
+              "text": "Timeticks (s/100)",
+              "value": "timeticks"
+            }
+          ],
+          "text": "time"
+        },
+        {
+          "submenu": [
+            {
+              "text": "YYYY-MM-DD HH:mm:ss",
+              "value": "dateTimeAsIso"
+            },
+            {
+              "text": "DD/MM/YYYY h:mm:ss a",
+              "value": "dateTimeAsUS"
+            },
+            {
+              "text": "From Now",
+              "value": "dateTimeFromNow"
+            }
+          ],
+          "text": "date & time"
+        },
+        {
+          "submenu": [
+            {
+              "text": "bits",
+              "value": "bits"
+            },
+            {
+              "text": "bytes",
+              "value": "bytes"
+            },
+            {
+              "text": "kibibytes",
+              "value": "kbytes"
+            },
+            {
+              "text": "mebibytes",
+              "value": "mbytes"
+            },
+            {
+              "text": "gibibytes",
+              "value": "gbytes"
+            }
+          ],
+          "text": "data (IEC)"
+        },
+        {
+          "submenu": [
+            {
+              "text": "bits",
+              "value": "decbits"
+            },
+            {
+              "text": "bytes",
+              "value": "decbytes"
+            },
+            {
+              "text": "kilobytes",
+              "value": "deckbytes"
+            },
+            {
+              "text": "megabytes",
+              "value": "decmbytes"
+            },
+            {
+              "text": "gigabytes",
+              "value": "decgbytes"
+            }
+          ],
+          "text": "data (Metric)"
+        },
+        {
+          "submenu": [
+            {
+              "text": "packets/sec",
+              "value": "pps"
+            },
+            {
+              "text": "bits/sec",
+              "value": "bps"
+            },
+            {
+              "text": "bytes/sec",
+              "value": "Bps"
+            },
+            {
+              "text": "kilobits/sec",
+              "value": "Kbits"
+            },
+            {
+              "text": "kilobytes/sec",
+              "value": "KBs"
+            },
+            {
+              "text": "megabits/sec",
+              "value": "Mbits"
+            },
+            {
+              "text": "megabytes/sec",
+              "value": "MBs"
+            },
+            {
+              "text": "gigabytes/sec",
+              "value": "GBs"
+            },
+            {
+              "text": "gigabits/sec",
+              "value": "Gbits"
+            }
+          ],
+          "text": "data rate"
+        },
+        {
+          "submenu": [
+            {
+              "text": "hashes/sec",
+              "value": "Hs"
+            },
+            {
+              "text": "kilohashes/sec",
+              "value": "KHs"
+            },
+            {
+              "text": "megahashes/sec",
+              "value": "MHs"
+            },
+            {
+              "text": "gigahashes/sec",
+              "value": "GHs"
+            },
+            {
+              "text": "terahashes/sec",
+              "value": "THs"
+            },
+            {
+              "text": "petahashes/sec",
+              "value": "PHs"
+            },
+            {
+              "text": "exahashes/sec",
+              "value": "EHs"
+            }
+          ],
+          "text": "hash rate"
+        },
+        {
+          "submenu": [
+            {
+              "text": "ops/sec (ops)",
+              "value": "ops"
+            },
+            {
+              "text": "requests/sec (rps)",
+              "value": "reqps"
+            },
+            {
+              "text": "reads/sec (rps)",
+              "value": "rps"
+            },
+            {
+              "text": "writes/sec (wps)",
+              "value": "wps"
+            },
+            {
+              "text": "I/O ops/sec (iops)",
+              "value": "iops"
+            },
+            {
+              "text": "ops/min (opm)",
+              "value": "opm"
+            },
+            {
+              "text": "reads/min (rpm)",
+              "value": "rpm"
+            },
+            {
+              "text": "writes/min (wpm)",
+              "value": "wpm"
+            }
+          ],
+          "text": "throughput"
+        },
+        {
+          "submenu": [
+            {
+              "text": "millimetre (mm)",
+              "value": "lengthmm"
+            },
+            {
+              "text": "meter (m)",
+              "value": "lengthm"
+            },
+            {
+              "text": "feet (ft)",
+              "value": "lengthft"
+            },
+            {
+              "text": "kilometer (km)",
+              "value": "lengthkm"
+            },
+            {
+              "text": "mile (mi)",
+              "value": "lengthmi"
+            }
+          ],
+          "text": "length"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Square Meters (m²)",
+              "value": "areaM2"
+            },
+            {
+              "text": "Square Feet (ft²)",
+              "value": "areaF2"
+            },
+            {
+              "text": "Square Miles (mi²)",
+              "value": "areaMI2"
+            }
+          ],
+          "text": "area"
+        },
+        {
+          "submenu": [
+            {
+              "text": "milligram (mg)",
+              "value": "massmg"
+            },
+            {
+              "text": "gram (g)",
+              "value": "massg"
+            },
+            {
+              "text": "kilogram (kg)",
+              "value": "masskg"
+            },
+            {
+              "text": "metric ton (t)",
+              "value": "masst"
+            }
+          ],
+          "text": "mass"
+        },
+        {
+          "submenu": [
+            {
+              "text": "metres/second (m/s)",
+              "value": "velocityms"
+            },
+            {
+              "text": "kilometers/hour (km/h)",
+              "value": "velocitykmh"
+            },
+            {
+              "text": "miles/hour (mph)",
+              "value": "velocitymph"
+            },
+            {
+              "text": "knot (kn)",
+              "value": "velocityknot"
+            }
+          ],
+          "text": "velocity"
+        },
+        {
+          "submenu": [
+            {
+              "text": "millilitre (mL)",
+              "value": "mlitre"
+            },
+            {
+              "text": "litre (L)",
+              "value": "litre"
+            },
+            {
+              "text": "cubic metre",
+              "value": "m3"
+            },
+            {
+              "text": "Normal cubic metre",
+              "value": "Nm3"
+            },
+            {
+              "text": "cubic decimetre",
+              "value": "dm3"
+            },
+            {
+              "text": "gallons",
+              "value": "gallons"
+            }
+          ],
+          "text": "volume"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Watt (W)",
+              "value": "watt"
+            },
+            {
+              "text": "Kilowatt (kW)",
+              "value": "kwatt"
+            },
+            {
+              "text": "Milliwatt (mW)",
+              "value": "mwatt"
+            },
+            {
+              "text": "Watt per square metre (W/m²)",
+              "value": "Wm2"
+            },
+            {
+              "text": "Volt-ampere (VA)",
+              "value": "voltamp"
+            },
+            {
+              "text": "Kilovolt-ampere (kVA)",
+              "value": "kvoltamp"
+            },
+            {
+              "text": "Volt-ampere reactive (var)",
+              "value": "voltampreact"
+            },
+            {
+              "text": "Kilovolt-ampere reactive (kvar)",
+              "value": "kvoltampreact"
+            },
+            {
+              "text": "Watt-hour (Wh)",
+              "value": "watth"
+            },
+            {
+              "text": "Kilowatt-hour (kWh)",
+              "value": "kwatth"
+            },
+            {
+              "text": "Kilowatt-min (kWm)",
+              "value": "kwattm"
+            },
+            {
+              "text": "Joule (J)",
+              "value": "joule"
+            },
+            {
+              "text": "Electron volt (eV)",
+              "value": "ev"
+            },
+            {
+              "text": "Ampere (A)",
+              "value": "amp"
+            },
+            {
+              "text": "Kiloampere (kA)",
+              "value": "kamp"
+            },
+            {
+              "text": "Milliampere (mA)",
+              "value": "mamp"
+            },
+            {
+              "text": "Volt (V)",
+              "value": "volt"
+            },
+            {
+              "text": "Kilovolt (kV)",
+              "value": "kvolt"
+            },
+            {
+              "text": "Millivolt (mV)",
+              "value": "mvolt"
+            },
+            {
+              "text": "Decibel-milliwatt (dBm)",
+              "value": "dBm"
+            },
+            {
+              "text": "Ohm (Ω)",
+              "value": "ohm"
+            },
+            {
+              "text": "Lumens (Lm)",
+              "value": "lumens"
+            }
+          ],
+          "text": "energy"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Celsius (°C)",
+              "value": "celsius"
+            },
+            {
+              "text": "Farenheit (°F)",
+              "value": "farenheit"
+            },
+            {
+              "text": "Kelvin (K)",
+              "value": "kelvin"
+            }
+          ],
+          "text": "temperature"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Millibars",
+              "value": "pressurembar"
+            },
+            {
+              "text": "Bars",
+              "value": "pressurebar"
+            },
+            {
+              "text": "Kilobars",
+              "value": "pressurekbar"
+            },
+            {
+              "text": "Hectopascals",
+              "value": "pressurehpa"
+            },
+            {
+              "text": "Kilopascals",
+              "value": "pressurekpa"
+            },
+            {
+              "text": "Inches of mercury",
+              "value": "pressurehg"
+            },
+            {
+              "text": "PSI",
+              "value": "pressurepsi"
+            }
+          ],
+          "text": "pressure"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Newton-meters (Nm)",
+              "value": "forceNm"
+            },
+            {
+              "text": "Kilonewton-meters (kNm)",
+              "value": "forcekNm"
+            },
+            {
+              "text": "Newtons (N)",
+              "value": "forceN"
+            },
+            {
+              "text": "Kilonewtons (kN)",
+              "value": "forcekN"
+            }
+          ],
+          "text": "force"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Gallons/min (gpm)",
+              "value": "flowgpm"
+            },
+            {
+              "text": "Cubic meters/sec (cms)",
+              "value": "flowcms"
+            },
+            {
+              "text": "Cubic feet/sec (cfs)",
+              "value": "flowcfs"
+            },
+            {
+              "text": "Cubic feet/min (cfm)",
+              "value": "flowcfm"
+            },
+            {
+              "text": "Litre/hour",
+              "value": "litreh"
+            },
+            {
+              "text": "Litre/min (l/min)",
+              "value": "flowlpm"
+            },
+            {
+              "text": "milliLitre/min (mL/min)",
+              "value": "flowmlpm"
+            }
+          ],
+          "text": "flow"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Degrees (°)",
+              "value": "degree"
+            },
+            {
+              "text": "Radians",
+              "value": "radian"
+            },
+            {
+              "text": "Gradian",
+              "value": "grad"
+            }
+          ],
+          "text": "angle"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Meters/sec²",
+              "value": "accMS2"
+            },
+            {
+              "text": "Feet/sec²",
+              "value": "accFS2"
+            },
+            {
+              "text": "G unit",
+              "value": "accG"
+            }
+          ],
+          "text": "acceleration"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Becquerel (Bq)",
+              "value": "radbq"
+            },
+            {
+              "text": "curie (Ci)",
+              "value": "radci"
+            },
+            {
+              "text": "Gray (Gy)",
+              "value": "radgy"
+            },
+            {
+              "text": "rad",
+              "value": "radrad"
+            },
+            {
+              "text": "Sievert (Sv)",
+              "value": "radsv"
+            },
+            {
+              "text": "rem",
+              "value": "radrem"
+            },
+            {
+              "text": "Exposure (C/kg)",
+              "value": "radexpckg"
+            },
+            {
+              "text": "roentgen (R)",
+              "value": "radr"
+            },
+            {
+              "text": "Sievert/hour (Sv/h)",
+              "value": "radsvh"
+            }
+          ],
+          "text": "radiation"
+        },
+        {
+          "submenu": [
+            {
+              "text": "parts-per-million (ppm)",
+              "value": "ppm"
+            },
+            {
+              "text": "parts-per-billion (ppb)",
+              "value": "conppb"
+            },
+            {
+              "text": "nanogram per cubic metre (ng/m³)",
+              "value": "conngm3"
+            },
+            {
+              "text": "nanogram per normal cubic metre (ng/Nm³)",
+              "value": "conngNm3"
+            },
+            {
+              "text": "microgram per cubic metre (μg/m³)",
+              "value": "conμgm3"
+            },
+            {
+              "text": "microgram per normal cubic metre (μg/Nm³)",
+              "value": "conμgNm3"
+            },
+            {
+              "text": "milligram per cubic metre (mg/m³)",
+              "value": "conmgm3"
+            },
+            {
+              "text": "milligram per normal cubic metre (mg/Nm³)",
+              "value": "conmgNm3"
+            },
+            {
+              "text": "gram per cubic metre (g/m³)",
+              "value": "congm3"
+            },
+            {
+              "text": "gram per normal cubic metre (g/Nm³)",
+              "value": "congNm3"
+            }
+          ],
+          "text": "concentration"
+        }
+      ]
+    },
+    {
+      "animationModes": [
+        {
+          "text": "Show All",
+          "value": "all"
+        },
+        {
+          "text": "Show Triggered",
+          "value": "triggered"
+        }
+      ],
+      "colors": [
+        "#299c46",
+        "rgba(237, 129, 40, 0.89)",
+        "#d44a3a"
+      ],
+      "d3DivId": "d3_svg_2",
+      "datasource": "gdev-testdata",
+      "decimals": 2,
+      "displayModes": [
+        {
+          "text": "Show All",
+          "value": "all"
+        },
+        {
+          "text": "Show Triggered",
+          "value": "triggered"
+        }
+      ],
+      "fontSizes": [
+        4,
+        5,
+        6,
+        7,
+        8,
+        9,
+        10,
+        11,
+        12,
+        13,
+        14,
+        15,
+        16,
+        17,
+        18,
+        19,
+        20,
+        22,
+        24,
+        26,
+        28,
+        30,
+        32,
+        34,
+        36,
+        38,
+        40,
+        42,
+        44,
+        46,
+        48,
+        50,
+        52,
+        54,
+        56,
+        58,
+        60,
+        62,
+        64,
+        66,
+        68,
+        70
+      ],
+      "fontTypes": [
+        "Open Sans",
+        "Arial",
+        "Avant Garde",
+        "Bookman",
+        "Consolas",
+        "Courier",
+        "Courier New",
+        "Futura",
+        "Garamond",
+        "Helvetica",
+        "Palatino",
+        "Times",
+        "Times New Roman",
+        "Verdana"
+      ],
+      "format": "none",
+      "gridPos": {
+        "h": 10,
+        "w": 12,
+        "x": 0,
+        "y": 9
+      },
+      "id": 2,
+      "links": [],
+      "notcolors": [
+        "rgba(245, 54, 54, 0.9)",
+        "rgba(237, 129, 40, 0.89)",
+        "rgba(50, 172, 45, 0.97)"
+      ],
+      "operatorName": "avg",
+      "operatorOptions": [
+        {
+          "text": "Average",
+          "value": "avg"
+        },
+        {
+          "text": "Count",
+          "value": "count"
+        },
+        {
+          "text": "Current",
+          "value": "current"
+        },
+        {
+          "text": "Delta",
+          "value": "delta"
+        },
+        {
+          "text": "Difference",
+          "value": "diff"
+        },
+        {
+          "text": "First",
+          "value": "first"
+        },
+        {
+          "text": "Log Min",
+          "value": "logmin"
+        },
+        {
+          "text": "Max",
+          "value": "max"
+        },
+        {
+          "text": "Min",
+          "value": "min"
+        },
+        {
+          "text": "Name",
+          "value": "name"
+        },
+        {
+          "text": "Time of Last Point",
+          "value": "last_time"
+        },
+        {
+          "text": "Time Step",
+          "value": "time_step"
+        },
+        {
+          "text": "Total",
+          "value": "total"
+        }
+      ],
+      "polystat": {
+        "animationSpeed": 2500,
+        "columnAutoSize": true,
+        "columns": 1,
+        "defaultClickThrough": "",
+        "defaultClickThroughSanitize": true,
+        "displayLimit": 100,
+        "fontAutoScale": true,
+        "fontSize": 12,
+        "globalDisplayMode": "all",
+        "globalOperatorName": "avg",
+        "gradientEnabled": true,
+        "hexagonSortByDirection": "asc",
+        "hexagonSortByField": "name",
+        "maxMetrics": 0,
+        "polygonBorderColor": "black",
+        "polygonBorderSize": 2,
+        "radius": "",
+        "radiusAutoSize": true,
+        "rowAutoSize": true,
+        "rows": 1,
+        "shape": "hexagon_pointed_top",
+        "tooltipDisplayMode": "all",
+        "tooltipDisplayTextTriggeredEmpty": "OK",
+        "tooltipFontSize": 12,
+        "tooltipFontType": "Open Sans",
+        "tooltipPrimarySortDirection": "desc",
+        "tooltipPrimarySortField": "thresholdLevel",
+        "tooltipSecondarySortDirection": "desc",
+        "tooltipSecondarySortField": "value",
+        "tooltipTimestampEnabled": true
+      },
+      "savedComposites": [],
+      "savedOverrides": [],
+      "shapes": [
+        {
+          "text": "Hexagon Pointed Top",
+          "value": "hexagon_pointed_top"
+        },
+        {
+          "text": "Hexagon Flat Top",
+          "value": "hexagon_flat_top"
+        },
+        {
+          "text": "Circle",
+          "value": "circle"
+        },
+        {
+          "text": "Cross",
+          "value": "cross"
+        },
+        {
+          "text": "Diamond",
+          "value": "diamond"
+        },
+        {
+          "text": "Square",
+          "value": "square"
+        },
+        {
+          "text": "Star",
+          "value": "star"
+        },
+        {
+          "text": "Triangle",
+          "value": "triangle"
+        },
+        {
+          "text": "Wye",
+          "value": "wye"
+        }
+      ],
+      "sortDirections": [
+        {
+          "text": "Ascending",
+          "value": "asc"
+        },
+        {
+          "text": "Descending",
+          "value": "desc"
+        }
+      ],
+      "sortFields": [
+        {
+          "text": "Name",
+          "value": "name"
+        },
+        {
+          "text": "Threshold Level",
+          "value": "thresholdLevel"
+        },
+        {
+          "text": "Value",
+          "value": "value"
+        }
+      ],
+      "svgContainer": {},
+      "targets": [
+        {
+          "alias": "Sensor-A",
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        },
+        {
+          "alias": "Sensor-B",
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "B",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "3433,23432,55"
+        },
+        {
+          "alias": "Sensor-C",
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "C",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,2,3,4,5,6"
+        },
+        {
+          "alias": "Sensor-E",
+          "expr": "",
+          "format": "time_series",
+          "intervalFactor": 1,
+          "refId": "D",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "thresholdStates": [
+        {
+          "text": "ok",
+          "value": 0
+        },
+        {
+          "text": "warning",
+          "value": 1
+        },
+        {
+          "text": "critical",
+          "value": 2
+        },
+        {
+          "text": "custom",
+          "value": 3
+        }
+      ],
+      "title": "No Value in Sensor-C Bug",
+      "type": "grafana-polystat-panel",
+      "unitFormats": [
+        {
+          "submenu": [
+            {
+              "text": "none",
+              "value": "none"
+            },
+            {
+              "text": "short",
+              "value": "short"
+            },
+            {
+              "text": "percent (0-100)",
+              "value": "percent"
+            },
+            {
+              "text": "percent (0.0-1.0)",
+              "value": "percentunit"
+            },
+            {
+              "text": "Humidity (%H)",
+              "value": "humidity"
+            },
+            {
+              "text": "decibel",
+              "value": "dB"
+            },
+            {
+              "text": "hexadecimal (0x)",
+              "value": "hex0x"
+            },
+            {
+              "text": "hexadecimal",
+              "value": "hex"
+            },
+            {
+              "text": "scientific notation",
+              "value": "sci"
+            },
+            {
+              "text": "locale format",
+              "value": "locale"
+            }
+          ],
+          "text": "none"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Dollars ($)",
+              "value": "currencyUSD"
+            },
+            {
+              "text": "Pounds (£)",
+              "value": "currencyGBP"
+            },
+            {
+              "text": "Euro (€)",
+              "value": "currencyEUR"
+            },
+            {
+              "text": "Yen (¥)",
+              "value": "currencyJPY"
+            },
+            {
+              "text": "Rubles (₽)",
+              "value": "currencyRUB"
+            },
+            {
+              "text": "Hryvnias (₴)",
+              "value": "currencyUAH"
+            },
+            {
+              "text": "Real (R$)",
+              "value": "currencyBRL"
+            },
+            {
+              "text": "Danish Krone (kr)",
+              "value": "currencyDKK"
+            },
+            {
+              "text": "Icelandic Króna (kr)",
+              "value": "currencyISK"
+            },
+            {
+              "text": "Norwegian Krone (kr)",
+              "value": "currencyNOK"
+            },
+            {
+              "text": "Swedish Krona (kr)",
+              "value": "currencySEK"
+            },
+            {
+              "text": "Czech koruna (czk)",
+              "value": "currencyCZK"
+            },
+            {
+              "text": "Swiss franc (CHF)",
+              "value": "currencyCHF"
+            },
+            {
+              "text": "Polish Złoty (PLN)",
+              "value": "currencyPLN"
+            },
+            {
+              "text": "Bitcoin (฿)",
+              "value": "currencyBTC"
+            }
+          ],
+          "text": "currency"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Hertz (1/s)",
+              "value": "hertz"
+            },
+            {
+              "text": "nanoseconds (ns)",
+              "value": "ns"
+            },
+            {
+              "text": "microseconds (µs)",
+              "value": "µs"
+            },
+            {
+              "text": "milliseconds (ms)",
+              "value": "ms"
+            },
+            {
+              "text": "seconds (s)",
+              "value": "s"
+            },
+            {
+              "text": "minutes (m)",
+              "value": "m"
+            },
+            {
+              "text": "hours (h)",
+              "value": "h"
+            },
+            {
+              "text": "days (d)",
+              "value": "d"
+            },
+            {
+              "text": "duration (ms)",
+              "value": "dtdurationms"
+            },
+            {
+              "text": "duration (s)",
+              "value": "dtdurations"
+            },
+            {
+              "text": "duration (hh:mm:ss)",
+              "value": "dthms"
+            },
+            {
+              "text": "Timeticks (s/100)",
+              "value": "timeticks"
+            }
+          ],
+          "text": "time"
+        },
+        {
+          "submenu": [
+            {
+              "text": "YYYY-MM-DD HH:mm:ss",
+              "value": "dateTimeAsIso"
+            },
+            {
+              "text": "DD/MM/YYYY h:mm:ss a",
+              "value": "dateTimeAsUS"
+            },
+            {
+              "text": "From Now",
+              "value": "dateTimeFromNow"
+            }
+          ],
+          "text": "date & time"
+        },
+        {
+          "submenu": [
+            {
+              "text": "bits",
+              "value": "bits"
+            },
+            {
+              "text": "bytes",
+              "value": "bytes"
+            },
+            {
+              "text": "kibibytes",
+              "value": "kbytes"
+            },
+            {
+              "text": "mebibytes",
+              "value": "mbytes"
+            },
+            {
+              "text": "gibibytes",
+              "value": "gbytes"
+            }
+          ],
+          "text": "data (IEC)"
+        },
+        {
+          "submenu": [
+            {
+              "text": "bits",
+              "value": "decbits"
+            },
+            {
+              "text": "bytes",
+              "value": "decbytes"
+            },
+            {
+              "text": "kilobytes",
+              "value": "deckbytes"
+            },
+            {
+              "text": "megabytes",
+              "value": "decmbytes"
+            },
+            {
+              "text": "gigabytes",
+              "value": "decgbytes"
+            }
+          ],
+          "text": "data (Metric)"
+        },
+        {
+          "submenu": [
+            {
+              "text": "packets/sec",
+              "value": "pps"
+            },
+            {
+              "text": "bits/sec",
+              "value": "bps"
+            },
+            {
+              "text": "bytes/sec",
+              "value": "Bps"
+            },
+            {
+              "text": "kilobits/sec",
+              "value": "Kbits"
+            },
+            {
+              "text": "kilobytes/sec",
+              "value": "KBs"
+            },
+            {
+              "text": "megabits/sec",
+              "value": "Mbits"
+            },
+            {
+              "text": "megabytes/sec",
+              "value": "MBs"
+            },
+            {
+              "text": "gigabytes/sec",
+              "value": "GBs"
+            },
+            {
+              "text": "gigabits/sec",
+              "value": "Gbits"
+            }
+          ],
+          "text": "data rate"
+        },
+        {
+          "submenu": [
+            {
+              "text": "hashes/sec",
+              "value": "Hs"
+            },
+            {
+              "text": "kilohashes/sec",
+              "value": "KHs"
+            },
+            {
+              "text": "megahashes/sec",
+              "value": "MHs"
+            },
+            {
+              "text": "gigahashes/sec",
+              "value": "GHs"
+            },
+            {
+              "text": "terahashes/sec",
+              "value": "THs"
+            },
+            {
+              "text": "petahashes/sec",
+              "value": "PHs"
+            },
+            {
+              "text": "exahashes/sec",
+              "value": "EHs"
+            }
+          ],
+          "text": "hash rate"
+        },
+        {
+          "submenu": [
+            {
+              "text": "ops/sec (ops)",
+              "value": "ops"
+            },
+            {
+              "text": "requests/sec (rps)",
+              "value": "reqps"
+            },
+            {
+              "text": "reads/sec (rps)",
+              "value": "rps"
+            },
+            {
+              "text": "writes/sec (wps)",
+              "value": "wps"
+            },
+            {
+              "text": "I/O ops/sec (iops)",
+              "value": "iops"
+            },
+            {
+              "text": "ops/min (opm)",
+              "value": "opm"
+            },
+            {
+              "text": "reads/min (rpm)",
+              "value": "rpm"
+            },
+            {
+              "text": "writes/min (wpm)",
+              "value": "wpm"
+            }
+          ],
+          "text": "throughput"
+        },
+        {
+          "submenu": [
+            {
+              "text": "millimetre (mm)",
+              "value": "lengthmm"
+            },
+            {
+              "text": "meter (m)",
+              "value": "lengthm"
+            },
+            {
+              "text": "feet (ft)",
+              "value": "lengthft"
+            },
+            {
+              "text": "kilometer (km)",
+              "value": "lengthkm"
+            },
+            {
+              "text": "mile (mi)",
+              "value": "lengthmi"
+            }
+          ],
+          "text": "length"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Square Meters (m²)",
+              "value": "areaM2"
+            },
+            {
+              "text": "Square Feet (ft²)",
+              "value": "areaF2"
+            },
+            {
+              "text": "Square Miles (mi²)",
+              "value": "areaMI2"
+            }
+          ],
+          "text": "area"
+        },
+        {
+          "submenu": [
+            {
+              "text": "milligram (mg)",
+              "value": "massmg"
+            },
+            {
+              "text": "gram (g)",
+              "value": "massg"
+            },
+            {
+              "text": "kilogram (kg)",
+              "value": "masskg"
+            },
+            {
+              "text": "metric ton (t)",
+              "value": "masst"
+            }
+          ],
+          "text": "mass"
+        },
+        {
+          "submenu": [
+            {
+              "text": "metres/second (m/s)",
+              "value": "velocityms"
+            },
+            {
+              "text": "kilometers/hour (km/h)",
+              "value": "velocitykmh"
+            },
+            {
+              "text": "miles/hour (mph)",
+              "value": "velocitymph"
+            },
+            {
+              "text": "knot (kn)",
+              "value": "velocityknot"
+            }
+          ],
+          "text": "velocity"
+        },
+        {
+          "submenu": [
+            {
+              "text": "millilitre (mL)",
+              "value": "mlitre"
+            },
+            {
+              "text": "litre (L)",
+              "value": "litre"
+            },
+            {
+              "text": "cubic metre",
+              "value": "m3"
+            },
+            {
+              "text": "Normal cubic metre",
+              "value": "Nm3"
+            },
+            {
+              "text": "cubic decimetre",
+              "value": "dm3"
+            },
+            {
+              "text": "gallons",
+              "value": "gallons"
+            }
+          ],
+          "text": "volume"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Watt (W)",
+              "value": "watt"
+            },
+            {
+              "text": "Kilowatt (kW)",
+              "value": "kwatt"
+            },
+            {
+              "text": "Milliwatt (mW)",
+              "value": "mwatt"
+            },
+            {
+              "text": "Watt per square metre (W/m²)",
+              "value": "Wm2"
+            },
+            {
+              "text": "Volt-ampere (VA)",
+              "value": "voltamp"
+            },
+            {
+              "text": "Kilovolt-ampere (kVA)",
+              "value": "kvoltamp"
+            },
+            {
+              "text": "Volt-ampere reactive (var)",
+              "value": "voltampreact"
+            },
+            {
+              "text": "Kilovolt-ampere reactive (kvar)",
+              "value": "kvoltampreact"
+            },
+            {
+              "text": "Watt-hour (Wh)",
+              "value": "watth"
+            },
+            {
+              "text": "Kilowatt-hour (kWh)",
+              "value": "kwatth"
+            },
+            {
+              "text": "Kilowatt-min (kWm)",
+              "value": "kwattm"
+            },
+            {
+              "text": "Joule (J)",
+              "value": "joule"
+            },
+            {
+              "text": "Electron volt (eV)",
+              "value": "ev"
+            },
+            {
+              "text": "Ampere (A)",
+              "value": "amp"
+            },
+            {
+              "text": "Kiloampere (kA)",
+              "value": "kamp"
+            },
+            {
+              "text": "Milliampere (mA)",
+              "value": "mamp"
+            },
+            {
+              "text": "Volt (V)",
+              "value": "volt"
+            },
+            {
+              "text": "Kilovolt (kV)",
+              "value": "kvolt"
+            },
+            {
+              "text": "Millivolt (mV)",
+              "value": "mvolt"
+            },
+            {
+              "text": "Decibel-milliwatt (dBm)",
+              "value": "dBm"
+            },
+            {
+              "text": "Ohm (Ω)",
+              "value": "ohm"
+            },
+            {
+              "text": "Lumens (Lm)",
+              "value": "lumens"
+            }
+          ],
+          "text": "energy"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Celsius (°C)",
+              "value": "celsius"
+            },
+            {
+              "text": "Farenheit (°F)",
+              "value": "farenheit"
+            },
+            {
+              "text": "Kelvin (K)",
+              "value": "kelvin"
+            }
+          ],
+          "text": "temperature"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Millibars",
+              "value": "pressurembar"
+            },
+            {
+              "text": "Bars",
+              "value": "pressurebar"
+            },
+            {
+              "text": "Kilobars",
+              "value": "pressurekbar"
+            },
+            {
+              "text": "Hectopascals",
+              "value": "pressurehpa"
+            },
+            {
+              "text": "Kilopascals",
+              "value": "pressurekpa"
+            },
+            {
+              "text": "Inches of mercury",
+              "value": "pressurehg"
+            },
+            {
+              "text": "PSI",
+              "value": "pressurepsi"
+            }
+          ],
+          "text": "pressure"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Newton-meters (Nm)",
+              "value": "forceNm"
+            },
+            {
+              "text": "Kilonewton-meters (kNm)",
+              "value": "forcekNm"
+            },
+            {
+              "text": "Newtons (N)",
+              "value": "forceN"
+            },
+            {
+              "text": "Kilonewtons (kN)",
+              "value": "forcekN"
+            }
+          ],
+          "text": "force"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Gallons/min (gpm)",
+              "value": "flowgpm"
+            },
+            {
+              "text": "Cubic meters/sec (cms)",
+              "value": "flowcms"
+            },
+            {
+              "text": "Cubic feet/sec (cfs)",
+              "value": "flowcfs"
+            },
+            {
+              "text": "Cubic feet/min (cfm)",
+              "value": "flowcfm"
+            },
+            {
+              "text": "Litre/hour",
+              "value": "litreh"
+            },
+            {
+              "text": "Litre/min (l/min)",
+              "value": "flowlpm"
+            },
+            {
+              "text": "milliLitre/min (mL/min)",
+              "value": "flowmlpm"
+            }
+          ],
+          "text": "flow"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Degrees (°)",
+              "value": "degree"
+            },
+            {
+              "text": "Radians",
+              "value": "radian"
+            },
+            {
+              "text": "Gradian",
+              "value": "grad"
+            }
+          ],
+          "text": "angle"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Meters/sec²",
+              "value": "accMS2"
+            },
+            {
+              "text": "Feet/sec²",
+              "value": "accFS2"
+            },
+            {
+              "text": "G unit",
+              "value": "accG"
+            }
+          ],
+          "text": "acceleration"
+        },
+        {
+          "submenu": [
+            {
+              "text": "Becquerel (Bq)",
+              "value": "radbq"
+            },
+            {
+              "text": "curie (Ci)",
+              "value": "radci"
+            },
+            {
+              "text": "Gray (Gy)",
+              "value": "radgy"
+            },
+            {
+              "text": "rad",
+              "value": "radrad"
+            },
+            {
+              "text": "Sievert (Sv)",
+              "value": "radsv"
+            },
+            {
+              "text": "rem",
+              "value": "radrem"
+            },
+            {
+              "text": "Exposure (C/kg)",
+              "value": "radexpckg"
+            },
+            {
+              "text": "roentgen (R)",
+              "value": "radr"
+            },
+            {
+              "text": "Sievert/hour (Sv/h)",
+              "value": "radsvh"
+            }
+          ],
+          "text": "radiation"
+        },
+        {
+          "submenu": [
+            {
+              "text": "parts-per-million (ppm)",
+              "value": "ppm"
+            },
+            {
+              "text": "parts-per-billion (ppb)",
+              "value": "conppb"
+            },
+            {
+              "text": "nanogram per cubic metre (ng/m³)",
+              "value": "conngm3"
+            },
+            {
+              "text": "nanogram per normal cubic metre (ng/Nm³)",
+              "value": "conngNm3"
+            },
+            {
+              "text": "microgram per cubic metre (μg/m³)",
+              "value": "conμgm3"
+            },
+            {
+              "text": "microgram per normal cubic metre (μg/Nm³)",
+              "value": "conμgNm3"
+            },
+            {
+              "text": "milligram per cubic metre (mg/m³)",
+              "value": "conmgm3"
+            },
+            {
+              "text": "milligram per normal cubic metre (mg/Nm³)",
+              "value": "conmgNm3"
+            },
+            {
+              "text": "gram per cubic metre (g/m³)",
+              "value": "congm3"
+            },
+            {
+              "text": "gram per normal cubic metre (g/Nm³)",
+              "value": "congNm3"
+            }
+          ],
+          "text": "concentration"
+        }
+      ]
+    }
+  ],
+  "schemaVersion": 16,
+  "style": "dark",
+  "tags": [
+    "panel-test",
+    "gdev"
+  ],
+  "templating": {
+    "list": []
+  },
+  "time": {
+    "from": "now-6h",
+    "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": "",
+  "title": "Panel Tests - Polystat",
+  "version": 5
+}

+ 545 - 7
devenv/dev-dashboards/datasource_tests_elasticsearch_compare.json

@@ -17,7 +17,7 @@
   "editable": true,
   "gnetId": null,
   "graphTooltip": 0,
-  "iteration": 1542304484522,
+  "iteration": 1545263815779,
   "links": [
     {
       "icon": "external link",
@@ -66,6 +66,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -168,6 +169,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -270,6 +272,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -372,6 +375,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -474,6 +478,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -576,6 +581,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -2249,6 +2255,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -2366,6 +2373,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -2483,6 +2491,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -2600,6 +2609,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -2717,6 +2727,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -2834,6 +2845,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -2951,6 +2963,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -3068,6 +3081,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -3185,6 +3199,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -3302,6 +3317,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -3419,6 +3435,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -3536,6 +3553,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -3667,6 +3685,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -3780,6 +3799,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -3893,6 +3913,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -4006,6 +4027,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -4119,6 +4141,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -4232,6 +4255,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -4345,6 +4369,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -4458,6 +4483,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -4571,6 +4597,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -4684,6 +4711,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -4797,6 +4825,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -4910,6 +4939,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -5008,6 +5038,512 @@
         "x": 0,
         "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,
       "panels": [
         {
@@ -5042,6 +5578,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -5193,6 +5730,7 @@
           "linewidth": 1,
           "links": [],
           "nullPointMode": "null",
+          "paceLength": 10,
           "percentage": false,
           "pointradius": 5,
           "points": false,
@@ -5328,8 +5866,8 @@
     "list": [
       {
         "current": {
-          "text": "gdev-elasticsearch-v2-metrics",
-          "value": "gdev-elasticsearch-v2-metrics"
+          "text": "gdev-elasticsearch-v5-metrics",
+          "value": "gdev-elasticsearch-v5-metrics"
         },
         "hide": 0,
         "label": "Version One",
@@ -5343,8 +5881,8 @@
       },
       {
         "current": {
-          "text": "gdev-elasticsearch-v5-metrics",
-          "value": "gdev-elasticsearch-v5-metrics"
+          "text": "gdev-elasticsearch-v6-metrics",
+          "value": "gdev-elasticsearch-v6-metrics"
         },
         "hide": 0,
         "label": "Version Two",
@@ -5359,7 +5897,7 @@
     ]
   },
   "time": {
-    "from": "now-3h",
+    "from": "now-1h",
     "to": "now"
   },
   "timepicker": {
@@ -5390,5 +5928,5 @@
   "timezone": "",
   "title": "Datasource tests - Elasticsearch comparison",
   "uid": "fuFWehBmk",
-  "version": 10
+  "version": 4
 }

+ 1250 - 0
devenv/dev-dashboards/panel_tests_gauge.json

@@ -0,0 +1,1250 @@
+{
+  "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,
+  "iteration": 1547810606599,
+  "links": [],
+  "panels": [
+    {
+      "collapsed": false,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 0
+      },
+      "id": 11,
+      "panels": [],
+      "title": "Value options tests",
+      "type": "row"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 0,
+        "y": 1
+      },
+      "id": 2,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "2",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "avg",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Average, 2 decimals, ms unit",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 5,
+        "y": 1
+      },
+      "id": 5,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "max",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Max (90 ms), no decimals",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 11,
+        "y": 1
+      },
+      "id": 6,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "p",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "s",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Current (10 ms), no unit, prefix (p), suffix (s)",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 4,
+        "w": 3,
+        "x": 16,
+        "y": 1
+      },
+      "id": 16,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 4,
+        "w": 5,
+        "x": 19,
+        "y": 1
+      },
+      "id": 18,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,91"
+        }
+      ],
+      "timeFrom": "1h",
+      "timeShift": null,
+      "title": "",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 4,
+        "w": 3,
+        "x": 16,
+        "y": 5
+      },
+      "id": 17,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 4,
+        "w": 5,
+        "x": 19,
+        "y": 5
+      },
+      "id": 19,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,81"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "",
+      "type": "gauge"
+    },
+    {
+      "collapsed": false,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 9
+      },
+      "id": 15,
+      "panels": [],
+      "title": "Value Mappings",
+      "type": "row"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 4,
+        "x": 0,
+        "y": 10
+      },
+      "id": 12,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": [
+          {
+            "from": "",
+            "id": 1,
+            "operator": "",
+            "text": "TEN",
+            "to": "",
+            "type": 1,
+            "value": "10"
+          }
+        ]
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "value mapping 10 -> TEN",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "description": "should read N/A",
+      "gridPos": {
+        "h": 8,
+        "w": 4,
+        "x": 4,
+        "y": 10
+      },
+      "id": 13,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": [
+          {
+            "from": "",
+            "id": 1,
+            "operator": "",
+            "text": "N/A",
+            "to": "",
+            "type": 1,
+            "value": "null"
+          }
+        ]
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,null,null,null,null"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "value mapping null -> N/A",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "description": "should read N/A",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 8,
+        "y": 10
+      },
+      "id": 20,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": [
+          {
+            "from": "0",
+            "id": 1,
+            "operator": "",
+            "text": "OK",
+            "to": "10",
+            "type": 2,
+            "value": "null"
+          }
+        ]
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,null,null,null,null,10"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "value mapping range, 0-10 -> OK, value 10",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "description": "should read N/A",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 14,
+        "y": 10
+      },
+      "id": 21,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": [
+          {
+            "from": "0",
+            "id": 1,
+            "operator": "",
+            "text": "OK",
+            "to": "90",
+            "type": 2,
+            "value": "null"
+          },
+          {
+            "from": "90",
+            "id": 2,
+            "operator": "",
+            "text": "BAD",
+            "to": "100",
+            "type": 2,
+            "value": ""
+          }
+        ]
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,null,null,null,null,10,95"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "value mapping range, 90-100 -> BAD, value 90",
+      "type": "gauge"
+    },
+    {
+      "collapsed": false,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 18
+      },
+      "id": 9,
+      "panels": [],
+      "title": "Templating & Repeat",
+      "type": "row"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 0,
+        "y": 19
+      },
+      "id": 7,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "2",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "$Servers",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "avg",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "repeat": "Servers",
+      "repeatDirection": "h",
+      "scopedVars": {
+        "Servers": {
+          "selected": false,
+          "text": "server1",
+          "value": "server1"
+        }
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "repeat $Servers",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 6,
+        "y": 19
+      },
+      "id": 22,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "2",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "$Servers",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "avg",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "repeat": null,
+      "repeatDirection": "h",
+      "repeatIteration": 1547810606599,
+      "repeatPanelId": 7,
+      "scopedVars": {
+        "Servers": {
+          "selected": false,
+          "text": "server2",
+          "value": "server2"
+        }
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "repeat $Servers",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 12,
+        "y": 19
+      },
+      "id": 23,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "2",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "$Servers",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "avg",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "repeat": null,
+      "repeatDirection": "h",
+      "repeatIteration": 1547810606599,
+      "repeatPanelId": 7,
+      "scopedVars": {
+        "Servers": {
+          "selected": false,
+          "text": "server3",
+          "value": "server3"
+        }
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "repeat $Servers",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 18,
+        "y": 19
+      },
+      "id": 24,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "2",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "$Servers",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "avg",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "repeat": null,
+      "repeatDirection": "h",
+      "repeatIteration": 1547810606599,
+      "repeatPanelId": 7,
+      "scopedVars": {
+        "Servers": {
+          "selected": false,
+          "text": "server4",
+          "value": "server4"
+        }
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "repeat $Servers",
+      "type": "gauge"
+    }
+  ],
+  "refresh": false,
+  "schemaVersion": 17,
+  "style": "dark",
+  "tags": [
+    "gdev",
+    "panel-tests"
+  ],
+  "templating": {
+    "list": [
+      {
+        "allValue": null,
+        "current": {
+          "selected": true,
+          "tags": [],
+          "text": "All",
+          "value": [
+            "$__all"
+          ]
+        },
+        "hide": 0,
+        "includeAll": true,
+        "label": null,
+        "multi": true,
+        "name": "Servers",
+        "options": [
+          {
+            "selected": true,
+            "text": "All",
+            "value": "$__all"
+          },
+          {
+            "selected": false,
+            "text": "server1",
+            "value": "server1"
+          },
+          {
+            "selected": false,
+            "text": "server2",
+            "value": "server2"
+          },
+          {
+            "selected": false,
+            "text": "server3",
+            "value": "server3"
+          },
+          {
+            "selected": false,
+            "text": "server4",
+            "value": "server4"
+          }
+        ],
+        "query": "server1,server2,server3,server4",
+        "skipUrlSync": false,
+        "type": "custom"
+      }
+    ]
+  },
+  "time": {
+    "from": "now-1h",
+    "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": "",
+  "title": "Panel Tests - Gauge",
+  "uid": "_5rDmaQiz",
+  "version": 5
+}

+ 1 - 0
devenv/docker/blocks/influxdb/influxdb.conf

@@ -69,6 +69,7 @@ reporting-disabled = false
 
   unix-socket-enabled = false # enable http service over unix domain socket
   # bind-socket = "/var/run/influxdb.sock"
+  flux-enabled = true
 
 [subscriber]
   enabled = true

+ 2 - 1
devenv/docker/ha_test/docker-compose.yaml

@@ -54,7 +54,8 @@ services:
       # - GF_DATABASE_SSL_MODE=disable
       # - GF_SESSION_PROVIDER=postgres
       # - GF_SESSION_PROVIDER_CONFIG=user=grafana password=password host=db port=5432 dbname=grafana sslmode=disable
-      - GF_LOG_FILTERS=alerting.notifier:debug,alerting.notifier.slack:debug
+      - GF_LOG_FILTERS=alerting.notifier:debug,alerting.notifier.slack:debug,auth:debug
+      - GF_LOGIN_ROTATE_TOKEN_MINUTES=2
     ports:
       - 3000
     depends_on:

+ 69 - 0
devenv/docker/loadtest/README.md

@@ -0,0 +1,69 @@
+# Grafana load test
+
+Runs load tests and checks using [k6](https://k6.io/).
+
+## Prerequisites
+
+Docker
+
+## Run
+
+Run load test for 15 minutes:
+
+```bash
+$ ./run.sh
+```
+
+Run load test for custom duration:
+
+```bash
+$ ./run.sh -d 10s
+```
+
+Example output:
+
+```bash
+
+          /\      |‾‾|  /‾‾/  /‾/
+     /\  /  \     |  |_/  /  / /
+    /  \/    \    |      |  /  ‾‾\
+   /          \   |  |‾\  \ | (_) |
+  / __________ \  |__|  \__\ \___/ .io
+
+  execution: local
+     output: -
+     script: src/auth_token_test.js
+
+    duration: 15m0s, iterations: -
+         vus: 2,     max: 2
+
+    done [==========================================================] 15m0s / 15m0s
+
+    █ user auth token test
+
+      █ user authenticates thru ui with username and password
+
+        ✓ response status is 200
+        ✓ response has cookie 'grafana_session' with 32 characters
+
+      █ batch tsdb requests
+
+        ✓ response status is 200
+
+    checks.....................: 100.00% ✓ 32844 ✗ 0
+    data_received..............: 411 MB  457 kB/s
+    data_sent..................: 12 MB   14 kB/s
+    group_duration.............: avg=95.64ms  min=16.42ms  med=94.35ms  max=307.52ms p(90)=137.78ms p(95)=146.75ms
+    http_req_blocked...........: avg=1.27ms   min=942ns    med=610.08µs max=48.32ms  p(90)=2.92ms   p(95)=4.25ms
+    http_req_connecting........: avg=1.06ms   min=0s       med=456.79µs max=47.19ms  p(90)=2.55ms   p(95)=3.78ms
+    http_req_duration..........: avg=58.16ms  min=1ms      med=52.59ms  max=293.35ms p(90)=109.53ms p(95)=120.19ms
+    http_req_receiving.........: avg=38.98µs  min=6.43µs   med=32.55µs  max=16.2ms   p(90)=64.63µs  p(95)=78.8µs
+    http_req_sending...........: avg=328.66µs min=8.09µs   med=110.77µs max=44.13ms  p(90)=552.65µs p(95)=1.09ms
+    http_req_tls_handshaking...: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s
+    http_req_waiting...........: avg=57.79ms  min=935.02µs med=52.15ms  max=293.06ms p(90)=109.04ms p(95)=119.71ms
+    http_reqs..................: 34486   38.317775/s
+    iteration_duration.........: avg=1.09s    min=1.81µs   med=1.09s    max=1.3s     p(90)=1.13s    p(95)=1.14s
+    iterations.................: 1642    1.824444/s
+    vus........................: 2       min=2   max=2
+    vus_max....................: 2       min=2   max=2
+```

+ 71 - 0
devenv/docker/loadtest/auth_token_test.js

@@ -0,0 +1,71 @@
+import { sleep, check, group } from 'k6';
+import { createClient, createBasicAuthClient } from './modules/client.js';
+import { createTestOrgIfNotExists, createTestdataDatasourceIfNotExists } from './modules/util.js';
+
+export let options = {
+  noCookiesReset: true
+};
+
+let endpoint = __ENV.URL || 'http://localhost:3000';
+const client = createClient(endpoint);
+
+export const setup = () => {
+  const basicAuthClient = createBasicAuthClient(endpoint, 'admin', 'admin');
+  const orgId = createTestOrgIfNotExists(basicAuthClient);
+  const datasourceId = createTestdataDatasourceIfNotExists(basicAuthClient);
+  client.withOrgId(orgId);
+  return {
+    orgId: orgId,
+    datasourceId: datasourceId,
+  };
+}
+
+export default (data) => {
+  group("user auth token test", () => {
+    if (__ITER === 0) {
+      group("user authenticates thru ui with username and password", () => {
+        let res = client.ui.login('admin', 'admin');
+
+        check(res, {
+          'response status is 200': (r) => r.status === 200,
+          'response has cookie \'grafana_session\' with 32 characters': (r) => r.cookies.grafana_session[0].value.length === 32,
+        });
+      });
+    }
+
+    if (__ITER !== 0) {
+      group("batch tsdb requests", () => {
+        const batchCount = 20;
+        const requests = [];
+        const payload = {
+          from: '1547765247624',
+          to: '1547768847624',
+          queries: [{
+            refId: 'A',
+            scenarioId: 'random_walk',
+            intervalMs: 10000,
+            maxDataPoints: 433,
+            datasourceId: data.datasourceId,
+          }]
+        };
+
+        requests.push({ method: 'GET', url: '/api/annotations?dashboardId=2074&from=1548078832772&to=1548082432772' });
+
+        for (let n = 0; n < batchCount; n++) {
+          requests.push({ method: 'POST', url: '/api/tsdb/query', body: payload });
+        }
+
+        let responses = client.batch(requests);
+        for (let n = 0; n < batchCount; n++) {
+          check(responses[n], {
+            'response status is 200': (r) => r.status === 200,
+          });
+        }
+      });
+    }
+  });
+
+  sleep(1)
+}
+
+export const teardown = (data) => {}

+ 187 - 0
devenv/docker/loadtest/modules/client.js

@@ -0,0 +1,187 @@
+import http from "k6/http";
+import encoding from 'k6/encoding';
+
+export const UIEndpoint = class UIEndpoint {
+  constructor(httpClient) {
+    this.httpClient = httpClient;
+  }
+
+  login(username, pwd) {
+    const payload = { user: username, password: pwd };
+    return this.httpClient.formPost('/login', payload);
+  }
+}
+
+export const DatasourcesEndpoint = class DatasourcesEndpoint {
+  constructor(httpClient) {
+    this.httpClient = httpClient;
+  }
+
+  getById(id) {
+    return this.httpClient.get(`/datasources/${id}`);
+  }
+
+  getByName(name) {
+    return this.httpClient.get(`/datasources/name/${name}`);
+  }
+
+  create(payload) {
+    return this.httpClient.post(`/datasources`, JSON.stringify(payload));
+  }
+
+  delete(id) {
+    return this.httpClient.delete(`/datasources/${id}`);
+  }
+}
+
+export const OrganizationsEndpoint = class OrganizationsEndpoint {
+  constructor(httpClient) {
+    this.httpClient = httpClient;
+  }
+
+  getById(id) {
+    return this.httpClient.get(`/orgs/${id}`);
+  }
+
+  getByName(name) {
+    return this.httpClient.get(`/orgs/name/${name}`);
+  }
+
+  create(name) {
+    let payload = {
+      name: name,
+    };
+    return this.httpClient.post(`/orgs`, JSON.stringify(payload));
+  }
+
+  delete(id) {
+    return this.httpClient.delete(`/orgs/${id}`);
+  }
+}
+
+export const GrafanaClient = class GrafanaClient {
+  constructor(httpClient) {
+    httpClient.onBeforeRequest = this.onBeforeRequest;
+    this.raw = httpClient;
+    this.ui = new UIEndpoint(httpClient);
+    this.orgs = new OrganizationsEndpoint(httpClient.withUrl('/api'));
+    this.datasources = new DatasourcesEndpoint(httpClient.withUrl('/api'));
+  }
+
+  batch(requests) {
+    return this.raw.batch(requests);
+  }
+
+  withOrgId(orgId) {
+    this.orgId = orgId;
+  }
+
+  onBeforeRequest(params) {
+    if (this.orgId && this.orgId > 0) {
+      params = params.headers || {};
+      params.headers["X-Grafana-Org-Id"] = this.orgId;
+    }
+  }
+}
+
+export const BaseClient = class BaseClient {
+  constructor(url, subUrl) {
+    if (url.endsWith('/')) {
+      url = url.substring(0, url.length - 1);
+    }
+
+    if (subUrl.endsWith('/')) {
+      subUrl = subUrl.substring(0, subUrl.length - 1);
+    }
+
+    this.url = url + subUrl;
+    this.onBeforeRequest = () => {};
+  }
+
+  withUrl(subUrl) {
+    let c = new BaseClient(this.url,  subUrl);
+    c.onBeforeRequest = this.onBeforeRequest;
+    return c;
+  }
+
+  beforeRequest(params) {
+
+  }
+
+  get(url, params) {
+    params = params || {};
+    this.beforeRequest(params);
+    this.onBeforeRequest(params);
+    return http.get(this.url + url, params);
+  }
+
+  formPost(url, body, params) {
+    params = params || {};
+    this.beforeRequest(params);
+    this.onBeforeRequest(params);
+    return http.post(this.url + url, body, params);
+  }
+
+  post(url, body, params) {
+    params = params || {};
+    params.headers = params.headers || {};
+    params.headers['Content-Type'] = 'application/json';
+
+    this.beforeRequest(params);
+    this.onBeforeRequest(params);
+    return http.post(this.url + url, body, params);
+  }
+
+  delete(url, params) {
+    params = params || {};
+    this.beforeRequest(params);
+    this.onBeforeRequest(params);
+    return http.del(this.url + url, null, params);
+  }
+
+  batch(requests) {
+    for (let n = 0; n < requests.length; n++) {
+      let params = requests[n].params || {};
+      params.headers = params.headers || {};
+      params.headers['Content-Type'] = 'application/json';
+      this.beforeRequest(params);
+      this.onBeforeRequest(params);
+      requests[n].params = params;
+      requests[n].url = this.url + requests[n].url;
+      if (requests[n].body) {
+        requests[n].body = JSON.stringify(requests[n].body);
+      }
+    }
+
+    return http.batch(requests);
+  }
+}
+
+export class BasicAuthClient extends BaseClient {
+  constructor(url, subUrl, username, password) {
+    super(url, subUrl);
+    this.username = username;
+    this.password = password;
+  }
+
+  withUrl(subUrl) {
+    let c = new BasicAuthClient(this.url,  subUrl, this.username, this.password);
+    c.onBeforeRequest = this.onBeforeRequest;
+    return c;
+  }
+
+  beforeRequest(params) {
+    params = params || {};
+    params.headers = params.headers || {};
+    let token = `${this.username}:${this.password}`;
+    params.headers['Authorization'] = `Basic ${encoding.b64encode(token)}`;
+  }
+}
+
+export const createClient = (url) => {
+  return new GrafanaClient(new BaseClient(url, ''));
+}
+
+export const createBasicAuthClient = (url, username, password) => {
+  return new GrafanaClient(new BasicAuthClient(url, '', username, password));
+}

+ 35 - 0
devenv/docker/loadtest/modules/util.js

@@ -0,0 +1,35 @@
+export const createTestOrgIfNotExists = (client) => {
+  let orgId = 0;
+  let res = client.orgs.getByName('k6');
+  if (res.status === 404) {
+    res = client.orgs.create('k6');
+    if (res.status !== 200) {
+      throw new Error('Expected 200 response status when creating org');
+    }
+    orgId = res.json().orgId;
+  } else {
+    orgId = res.json().id;
+  }
+
+  client.withOrgId(orgId);
+  return orgId;
+}
+
+export const createTestdataDatasourceIfNotExists = (client) => {
+  const payload = {
+    access: 'proxy',
+    isDefault: false,
+    name: 'k6-testdata',
+    type: 'testdata',
+  };
+
+  let res = client.datasources.getByName(payload.name);
+  if (res.status === 404) {
+    res = client.datasources.create(payload);
+    if (res.status !== 200) {
+      throw new Error('Expected 200 response status when creating datasource');
+    }
+  }
+
+  return res.json().id;
+}

+ 24 - 0
devenv/docker/loadtest/run.sh

@@ -0,0 +1,24 @@
+#/bin/bash
+
+PWD=$(pwd)
+
+run() {
+  duration='15m'
+  url='http://localhost:3000'
+
+  while getopts ":d:u:" o; do
+    case "${o}" in
+				d)
+            duration=${OPTARG}
+            ;;
+        u)
+            url=${OPTARG}
+            ;;
+    esac
+	done
+	shift $((OPTIND-1))
+
+  docker run -t --network=host -v $PWD:/src -e URL=$url --rm -i loadimpact/k6:master run --vus 2 --duration $duration src/auth_token_test.js
+}
+
+run "$@"

+ 1 - 1
docs/sources/auth/gitlab.md

@@ -47,7 +47,7 @@ authentication:
 
 ```bash
 [auth.gitlab]
-enabled = false
+enabled = true
 allow_sign_up = false
 client_id = GITLAB_APPLICATION_ID
 client_secret = GITLAB_SECRET

+ 1 - 1
docs/sources/features/datasources/cloudwatch.md

@@ -38,7 +38,7 @@ Name | Description
 
 ### IAM Roles
 
-Currently all access to CloudWatch is done server side by the Grafana backend using the official AWS SDK. If you grafana
+Currently all access to CloudWatch is done server side by the Grafana backend using the official AWS SDK. If your Grafana
 server is running on AWS you can use IAM Roles and authentication will be handled automatically.
 
 Checkout AWS docs on [IAM Roles](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)

+ 6 - 1
docs/sources/features/explore/index.md

@@ -1,5 +1,6 @@
 +++
 title = "Explore"
+keywords = ["explore", "loki", "logs"]
 type = "docs"
 [menu.docs]
 name = "Explore"
@@ -8,7 +9,11 @@ parent = "features"
 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.
 

+ 3 - 3
docs/sources/http_api/admin.md

@@ -285,7 +285,7 @@ Content-Type: application/json
 HTTP/1.1 200
 Content-Type: application/json
 
-{message: "User permissions updated"}
+{"message": "User permissions updated"}
 ```
 
 ## Delete global User
@@ -308,7 +308,7 @@ Content-Type: application/json
 HTTP/1.1 200
 Content-Type: application/json
 
-{message: "User deleted"}
+{"message": "User deleted"}
 ```
 
 ## Pause all alerts
@@ -339,5 +339,5 @@ JSON Body schema:
 HTTP/1.1 200
 Content-Type: application/json
 
-{state: "new state", message: "alerts pause/un paused", "alertsAffected": 100}
+{"state": "new state", "message": "alerts pause/un paused", "alertsAffected": 100}
 ```

+ 2 - 2
docs/sources/http_api/data_source.md

@@ -188,8 +188,8 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
     "defaultRegion": "us-west-1"
   },
   "secureJsonData": {
-    "accessKey": "Ol4pIDpeKSA6XikgOl4p", //should not be encoded
-    "secretKey": "dGVzdCBrZXkgYmxlYXNlIGRvbid0IHN0ZWFs" //should be Base-64 encoded
+    "accessKey": "Ol4pIDpeKSA6XikgOl4p",
+    "secretKey": "dGVzdCBrZXkgYmxlYXNlIGRvbid0IHN0ZWFs"
   }
 }
 ```

+ 1 - 1
docs/sources/http_api/folder_permissions.md

@@ -105,7 +105,7 @@ POST /api/folders/nErXDvCkzz/permissions
 Accept: application/json
 Content-Type: application/json
 Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
-
+{
   "items": [
     {
       "role": "Viewer",

+ 26 - 1
docs/sources/http_api/other.md

@@ -82,4 +82,29 @@ HTTP/1.1 200
 Content-Type: application/json
 
 {"message": "Logged in"}
-```
+```
+
+# Health API
+
+## Returns health information about Grafana
+
+`GET /api/health`
+
+**Example Request**
+
+```http
+GET /api/health
+Accept: application/json
+```
+
+**Example Response**:
+
+```http
+HTTP/1.1 200 OK
+
+{
+  "commit": "087143285",
+  "database": "ok",
+  "version": "5.1.3"
+}
+```

+ 17 - 0
docs/sources/installation/configuration.md

@@ -391,6 +391,12 @@ value is `true`.
 If you want to track Grafana usage via Google analytics specify *your* Universal
 Analytics ID here. By default this feature is disabled.
 
+### check_for_updates
+
+Set to false to disable all checks to https://grafana.com for new versions of Grafana and installed plugins. Check is used
+in some UI views to notify that a Grafana or plugin update exists. This option does not cause any auto updates, nor
+send any sensitive information.
+
 <hr />
 
 ## [dashboards]
@@ -589,3 +595,14 @@ Default setting for how Grafana handles nodata or null values in alerting. (aler
 Alert notifications can include images, but rendering many images at the same time can overload the server.
 This limit will protect the server from render overloading and make sure notifications are sent out quickly. Default
 value is `5`.
+
+## [panels]
+
+### enable_alpha
+Set to true if you want to test panels that are not yet ready for general usage.
+
+### disable_sanitize_html
+If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities. Default
+is false. This settings was introduced in Grafana v6.0.
+
+

+ 8 - 11
docs/sources/installation/debian.md

@@ -34,32 +34,29 @@ sudo dpkg -i grafana_<version>_amd64.deb
 Example:
 
 ```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 dpkg -i grafana_5.1.4_amd64.deb
+sudo dpkg -i grafana_5.4.2_amd64.deb
 ```
 
 ## 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
-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 also a testing repository if you want beta or release
-candidates.
+There is a separate repository if you want beta releases.
 
 ```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
-allows you to install signed packages.
+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.
 
 ```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

+ 15 - 7
docs/sources/installation/rpm.md

@@ -32,7 +32,7 @@ $ sudo yum install <rpm package url>
 Example:
 
 ```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
@@ -44,7 +44,7 @@ $ wget <rpm package url>
 Example:
 
 ```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:
@@ -67,19 +67,27 @@ Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
 ```bash
 [grafana]
 name=grafana
-baseurl=https://packagecloud.io/grafana/stable/el/7/$basearch
+baseurl=https://packages.grafana.com/oss/rpm
 repo_gpgcheck=1
 enabled=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
 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
-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.
@@ -91,7 +99,7 @@ $ sudo yum install grafana
 ### RPM GPG Key
 
 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
 

+ 1 - 1
docs/sources/reference/dashboard.md

@@ -51,7 +51,7 @@ When a user creates a new dashboard, a new dashboard JSON object is initialized
     "list": []
   },
   "refresh": "5s",
-  "schemaVersion": 16,
+  "schemaVersion": 17,
   "version": 0,
   "links": []
 }

+ 5 - 2
docs/sources/reference/templating.md

@@ -52,6 +52,7 @@ Filter Option | Example | Raw | Interpolated | Description
 `csv`| ${servers:csv} |  `'test1', 'test2'` | `test1,test2` | Formats multi-value variable as a comma-separated string
 `distributed`| ${servers:distributed} | `'test1', 'test2'` | `test1,servers=test2` | Formats multi-value variable in custom format for OpenTSDB.
 `lucene`| ${servers:lucene} | `'test', 'test2'` | `("test" OR "test2")` | Formats multi-value variable as a lucene expression.
+`percentencode` | ${servers:percentencode} |  `'foo()bar BAZ', 'test2'` | `{foo%28%29bar%20BAZ%2Ctest2}` | Formats multi-value variable into a glob, percent-encoded.
 
 Test the formatting options on the [Grafana Play site](http://play.grafana.org/d/cJtIfcWiz/template-variable-formatting-options?orgId=1).
 
@@ -292,9 +293,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
 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.
 You can do this by either changing the variable value (that is the basis for the repeat) or reload the dashboard.

+ 2 - 2
latest.json

@@ -1,4 +1,4 @@
 {
-  "stable": "5.4.2",
-  "testing": "5.4.2"
+  "stable": "5.4.3",
+  "testing": "5.4.3"
 }

+ 5 - 5
package.json

@@ -5,7 +5,7 @@
     "company": "Grafana Labs"
   },
   "name": "grafana",
-  "version": "5.5.0-pre1",
+  "version": "6.0.0-pre1",
   "repository": {
     "type": "git",
     "url": "http://github.com/grafana/grafana.git"
@@ -24,7 +24,6 @@
     "@types/jquery": "^1.10.35",
     "@types/node": "^8.0.31",
     "@types/react": "^16.7.6",
-    "@types/react-custom-scrollbars": "^4.0.5",
     "@types/react-dom": "^16.0.9",
     "@types/react-select": "^2.0.4",
     "angular-mocks": "1.6.6",
@@ -65,6 +64,7 @@
     "html-webpack-plugin": "^3.2.0",
     "husky": "^0.14.3",
     "jest": "^23.6.0",
+    "jest-date-mock": "^1.0.6",
     "lint-staged": "^6.0.0",
     "load-grunt-tasks": "3.5.2",
     "mini-css-extract-plugin": "^0.4.0",
@@ -72,8 +72,8 @@
     "ng-annotate-loader": "^0.6.1",
     "ng-annotate-webpack-plugin": "^0.3.0",
     "ngtemplate-loader": "^2.0.1",
-    "npm": "^5.4.2",
     "node-sass": "^4.11.0",
+    "npm": "^5.4.2",
     "optimize-css-assets-webpack-plugin": "^4.0.2",
     "phantomjs-prebuilt": "^2.1.15",
     "postcss-browser-reporter": "^0.5.0",
@@ -167,7 +167,6 @@
     "prop-types": "^15.6.2",
     "rc-cascader": "^0.14.0",
     "react": "^16.6.3",
-    "react-custom-scrollbars": "^4.2.1",
     "react-dom": "^16.6.3",
     "react-grid-layout": "0.16.6",
     "react-highlight-words": "0.11.0",
@@ -189,7 +188,8 @@
     "slate-react": "^0.12.4",
     "tether": "^1.4.0",
     "tether-drop": "https://github.com/torkelo/drop/tarball/master",
-    "tinycolor2": "^1.4.1"
+    "tinycolor2": "^1.4.1",
+    "xss": "^1.0.3"
   },
   "resolutions": {
     "caniuse-db": "1.0.30000772",

+ 14 - 3
packages/grafana-ui/package.json

@@ -11,23 +11,34 @@
   "license": "ISC",
   "dependencies": {
     "@torkelo/react-select": "2.1.1",
+    "@types/react-test-renderer": "^16.0.3",
+    "@types/react-transition-group": "^2.0.15",
     "classnames": "^2.2.5",
     "jquery": "^3.2.1",
     "lodash": "^4.17.10",
     "moment": "^2.22.2",
     "react": "^16.6.3",
+    "react-custom-scrollbars": "^4.2.1",
     "react-dom": "^16.6.3",
     "react-highlight-words": "0.11.0",
     "react-popper": "^1.3.0",
     "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": {
+    "@types/classnames": "^2.2.6",
     "@types/jest": "^23.3.2",
+    "@types/jquery": "^1.10.35",
     "@types/lodash": "^4.14.119",
     "@types/react": "^16.7.6",
-    "@types/classnames": "^2.2.6",
-    "@types/jquery": "^1.10.35",
+    "@types/react-custom-scrollbars": "^4.0.5",
+    "@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"
   }
 }

+ 1 - 1
public/app/core/specs/ColorPalette.test.tsx → packages/grafana-ui/src/components/ColorPicker/ColorPalette.test.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import renderer from 'react-test-renderer';
-import { ColorPalette } from '../components/colorpicker/ColorPalette';
+import { ColorPalette } from './ColorPalette';
 
 describe('CollorPalette', () => {
   it('renders correctly', () => {

+ 3 - 3
public/app/core/components/colorpicker/ColorPalette.tsx → packages/grafana-ui/src/components/ColorPicker/ColorPalette.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import { sortedColors } from 'app/core/utils/colors';
+import { sortedColors } from '../../utils';
 
 export interface Props {
   color: string;
@@ -9,13 +9,13 @@ export interface Props {
 export class ColorPalette extends React.Component<Props, any> {
   paletteColors: string[];
 
-  constructor(props) {
+  constructor(props: Props) {
     super(props);
     this.paletteColors = sortedColors;
     this.onColorSelect = this.onColorSelect.bind(this);
   }
 
-  onColorSelect(color) {
+  onColorSelect(color: string) {
     return () => {
       this.props.onColorSelect(color);
     };

+ 4 - 9
public/app/core/components/colorpicker/ColorPicker.tsx → packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx

@@ -2,7 +2,6 @@ import React from 'react';
 import ReactDOM from 'react-dom';
 import Drop from 'tether-drop';
 import { ColorPickerPopover } from './ColorPickerPopover';
-import { react2AngularDirective } from 'app/core/utils/react2angular';
 
 export interface Props {
   color: string;
@@ -10,7 +9,7 @@ export interface Props {
 }
 
 export class ColorPicker extends React.Component<Props, any> {
-  pickerElem: HTMLElement;
+  pickerElem: HTMLElement | null;
   colorPickerDrop: any;
 
   openColorPicker = () => {
@@ -20,7 +19,7 @@ export class ColorPicker extends React.Component<Props, any> {
     ReactDOM.render(dropContent, dropContentElem);
 
     const drop = new Drop({
-      target: this.pickerElem,
+      target: this.pickerElem as Element,
       content: dropContentElem,
       position: 'top center',
       classes: 'drop-popover',
@@ -28,6 +27,7 @@ export class ColorPicker extends React.Component<Props, any> {
       hoverCloseDelay: 200,
       tetherOptions: {
         constraints: [{ to: 'scrollParent', attachment: 'none both' }],
+        attachment: 'bottom center',
       },
     });
 
@@ -45,7 +45,7 @@ export class ColorPicker extends React.Component<Props, any> {
     }, 100);
   };
 
-  onColorSelect = color => {
+  onColorSelect = (color: string) => {
     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 }],
-]);

+ 12 - 21
public/app/core/components/colorpicker/ColorPickerPopover.tsx → packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx

@@ -14,7 +14,7 @@ export interface Props {
 export class ColorPickerPopover extends React.Component<Props, any> {
   pickerNavElem: any;
 
-  constructor(props) {
+  constructor(props: Props) {
     super(props);
     this.state = {
       tab: 'palette',
@@ -23,60 +23,51 @@ export class ColorPickerPopover extends React.Component<Props, any> {
     };
   }
 
-  setPickerNavElem(elem) {
+  setPickerNavElem(elem: any) {
     this.pickerNavElem = $(elem);
   }
 
-  setColor(color) {
+  setColor(color: string) {
     const newColor = tinycolor(color);
     if (newColor.isValid()) {
-      this.setState({
-        color: newColor.toString(),
-        colorString: newColor.toString(),
-      });
+      this.setState({ color: newColor.toString(), colorString: newColor.toString() });
       this.props.onColorSelect(color);
     }
   }
 
-  sampleColorSelected(color) {
+  sampleColorSelected(color: string) {
     this.setColor(color);
   }
 
-  spectrumColorSelected(color) {
+  spectrumColorSelected(color: any) {
     const rgbColor = color.toRgbString();
     this.setColor(rgbColor);
   }
 
-  onColorStringChange(e) {
+  onColorStringChange(e: any) {
     const colorString = e.target.value;
-    this.setState({
-      colorString: colorString,
-    });
+    this.setState({ colorString: colorString });
 
     const newColor = tinycolor(colorString);
     if (newColor.isValid()) {
       // Update only color state
       const newColorString = newColor.toString();
-      this.setState({
-        color: newColorString,
-      });
+      this.setState({ color: newColorString });
       this.props.onColorSelect(newColorString);
     }
   }
 
-  onColorStringBlur(e) {
+  onColorStringBlur(e: any) {
     const colorString = e.target.value;
     this.setColor(colorString);
   }
 
   componentDidMount() {
     this.pickerNavElem.find('li:first').addClass('active');
-    this.pickerNavElem.on('show', e => {
+    this.pickerNavElem.on('show', (e: any) => {
       // use href attr (#name => name)
       const tab = e.target.hash.slice(1);
-      this.setState({
-        tab: tab,
-      });
+      this.setState({ tab: tab });
     });
   }
 

+ 2 - 1
public/app/core/components/colorpicker/SeriesColorPicker.tsx → packages/grafana-ui/src/components/ColorPicker/SeriesColorPicker.tsx

@@ -21,7 +21,7 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
     onToggleAxis: () => {},
   };
 
-  constructor(props) {
+  constructor(props: SeriesColorPickerProps) {
     super(props);
   }
 
@@ -51,6 +51,7 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
       remove: true,
       tetherOptions: {
         constraints: [{ to: 'scrollParent', attachment: 'none both' }],
+        attachment: 'bottom center',
       },
     });
 

+ 6 - 10
public/app/core/components/colorpicker/SeriesColorPickerPopover.tsx → packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx

@@ -1,6 +1,5 @@
 import React from 'react';
 import { ColorPickerPopover } from './ColorPickerPopover';
-import { react2AngularDirective } from 'app/core/utils/react2angular';
 
 export interface SeriesColorPickerPopoverProps {
   color: string;
@@ -22,7 +21,7 @@ export class SeriesColorPickerPopover extends React.PureComponent<SeriesColorPic
 
 interface AxisSelectorProps {
   yaxis: number;
-  onToggleAxis: () => void;
+  onToggleAxis?: () => void;
 }
 
 interface AxisSelectorState {
@@ -30,7 +29,7 @@ interface AxisSelectorState {
 }
 
 export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSelectorState> {
-  constructor(props) {
+  constructor(props: AxisSelectorProps) {
     super(props);
     this.state = {
       yaxis: this.props.yaxis,
@@ -42,7 +41,10 @@ export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSel
     this.setState({
       yaxis: this.state.yaxis === 2 ? 1 : 2,
     });
-    this.props.onToggleAxis();
+
+    if (this.props.onToggleAxis) {
+      this.props.onToggleAxis();
+    }
   }
 
   render() {
@@ -62,9 +64,3 @@ export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSel
     );
   }
 }
-
-react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopover, [
-  'series',
-  'onColorChange',
-  'onToggleAxis',
-]);

+ 5 - 5
public/app/core/components/colorpicker/SpectrumPicker.tsx → packages/grafana-ui/src/components/ColorPicker/SpectrumPicker.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 import _ from 'lodash';
 import $ from 'jquery';
-import 'vendor/spectrum';
+import '../../vendor/spectrum';
 
 export interface Props {
   color: string;
@@ -13,17 +13,17 @@ export class SpectrumPicker extends React.Component<Props, any> {
   elem: any;
   isMoving: boolean;
 
-  constructor(props) {
+  constructor(props: Props) {
     super(props);
     this.onSpectrumMove = this.onSpectrumMove.bind(this);
     this.setComponentElem = this.setComponentElem.bind(this);
   }
 
-  setComponentElem(elem) {
+  setComponentElem(elem: any) {
     this.elem = $(elem);
   }
 
-  onSpectrumMove(color) {
+  onSpectrumMove(color: any) {
     this.isMoving = true;
     this.props.onColorSelect(color);
   }
@@ -46,7 +46,7 @@ export class SpectrumPicker extends React.Component<Props, any> {
     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
     // 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

+ 0 - 0
public/sass/components/_color_picker.scss → packages/grafana-ui/src/components/ColorPicker/_ColorPicker.scss


+ 0 - 0
public/app/core/specs/__snapshots__/ColorPalette.test.tsx.snap → packages/grafana-ui/src/components/ColorPicker/__snapshots__/ColorPalette.test.tsx.snap


+ 0 - 0
public/app/core/components/CustomScrollbar/CustomScrollbar.test.tsx → packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.test.tsx


+ 96 - 0
packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx

@@ -0,0 +1,96 @@
+import React, { PureComponent } from 'react';
+import _ from 'lodash';
+import Scrollbars from 'react-custom-scrollbars';
+
+interface Props {
+  customClassName?: string;
+  autoHide?: boolean;
+  autoHideTimeout?: number;
+  autoHideDuration?: number;
+  autoHeightMax?: string;
+  hideTracksWhenNotNeeded?: boolean;
+  scrollTop?: number;
+  setScrollTop: (event: any) => void;
+  autoHeightMin?: number | string;
+}
+
+/**
+ * Wraps component into <Scrollbars> component from `react-custom-scrollbars`
+ */
+export class CustomScrollbar extends PureComponent<Props> {
+  static defaultProps: Partial<Props> = {
+    customClassName: 'custom-scrollbars',
+    autoHide: false,
+    autoHideTimeout: 200,
+    autoHideDuration: 200,
+    setScrollTop: () => {},
+    hideTracksWhenNotNeeded: false,
+    autoHeightMin: '0',
+    autoHeightMax: '100%',
+  };
+
+  private ref: React.RefObject<Scrollbars>;
+
+  constructor(props: Props) {
+    super(props);
+    this.ref = React.createRef<Scrollbars>();
+  }
+
+  updateScroll() {
+    const ref = this.ref.current;
+
+    if (ref && !_.isNil(this.props.scrollTop)) {
+      if (this.props.scrollTop > 10000) {
+        ref.scrollToBottom();
+      } else {
+        ref.scrollTop(this.props.scrollTop);
+      }
+    }
+  }
+
+  componentDidMount() {
+    this.updateScroll();
+  }
+
+  componentDidUpdate() {
+    this.updateScroll();
+  }
+
+  render() {
+    const {
+      customClassName,
+      children,
+      autoHeightMax,
+      autoHeightMin,
+      setScrollTop,
+      autoHide,
+      autoHideTimeout,
+      hideTracksWhenNotNeeded,
+    } = this.props;
+
+    return (
+      <Scrollbars
+        ref={this.ref}
+        className={customClassName}
+        onScroll={setScrollTop}
+        autoHeight={true}
+        autoHide={autoHide}
+        autoHideTimeout={autoHideTimeout}
+        hideTracksWhenNotNeeded={hideTracksWhenNotNeeded}
+        // These autoHeightMin & autoHeightMax options affect firefox and chrome differently.
+        // Before these where set to inhert but that caused problems with cut of legends in firefox
+        autoHeightMax={autoHeightMax}
+        autoHeightMin={autoHeightMin}
+        renderTrackHorizontal={props => <div {...props} className="track-horizontal" />}
+        renderTrackVertical={props => <div {...props} className="track-vertical" />}
+        renderThumbHorizontal={props => <div {...props} className="thumb-horizontal" />}
+        renderThumbVertical={props => <div {...props} className="thumb-vertical" />}
+        renderView={props => <div {...props} className="view" />}
+      >
+        {children}
+      </Scrollbars>
+    );
+  }
+}
+
+export default CustomScrollbar;

+ 40 - 0
packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss

@@ -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;
+  }
+}

+ 4 - 8
public/app/core/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap → packages/grafana-ui/src/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap

@@ -6,8 +6,8 @@ exports[`CustomScrollbar renders correctly 1`] = `
   style={
     Object {
       "height": "auto",
-      "maxHeight": "inherit",
-      "minHeight": "inherit",
+      "maxHeight": "100%",
+      "minHeight": "0",
       "overflow": "hidden",
       "position": "relative",
       "width": "100%",
@@ -23,8 +23,8 @@ exports[`CustomScrollbar renders correctly 1`] = `
         "left": undefined,
         "marginBottom": 0,
         "marginRight": 0,
-        "maxHeight": "calc(inherit + 0px)",
-        "minHeight": "calc(inherit + 0px)",
+        "maxHeight": "calc(100% + 0px)",
+        "minHeight": "calc(0 + 0px)",
         "overflow": "scroll",
         "position": "relative",
         "right": undefined,
@@ -42,9 +42,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
       Object {
         "display": "none",
         "height": 6,
-        "opacity": 0,
         "position": "absolute",
-        "transition": "opacity 200ms",
       }
     }
   >
@@ -64,9 +62,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
     style={
       Object {
         "display": "none",
-        "opacity": 0,
         "position": "absolute",
-        "transition": "opacity 200ms",
         "width": 6,
       }
     }

+ 24 - 0
packages/grafana-ui/src/components/FormField/FormField.test.tsx

@@ -0,0 +1,24 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import { FormField, Props } from './FormField';
+
+const setup = (propOverrides?: object) => {
+  const props: Props = {
+    label: 'Test',
+    labelWidth: 11,
+    value: 10,
+    onChange: jest.fn(),
+  };
+
+  Object.assign(props, propOverrides);
+
+  return shallow(<FormField {...props} />);
+};
+
+describe('Render', () => {
+  it('should render component', () => {
+    const wrapper = setup();
+
+    expect(wrapper).toMatchSnapshot();
+  });
+});

+ 25 - 0
packages/grafana-ui/src/components/FormField/FormField.tsx

@@ -0,0 +1,25 @@
+import React, { InputHTMLAttributes, FunctionComponent } from 'react';
+import { FormLabel } from '..';
+
+export interface Props extends InputHTMLAttributes<HTMLInputElement> {
+  label: string;
+  labelWidth?: number;
+  inputWidth?: number;
+}
+
+const defaultProps = {
+  labelWidth: 6,
+  inputWidth: 12,
+};
+
+const FormField: FunctionComponent<Props> = ({ label, labelWidth, inputWidth, ...inputProps }) => {
+  return (
+    <div className="form-field">
+      <FormLabel width={labelWidth}>{label}</FormLabel>
+      <input type="text" className={`gf-form-input width-${inputWidth}`} {...inputProps} />
+    </div>
+  );
+};
+
+FormField.defaultProps = defaultProps;
+export { FormField };

+ 12 - 0
packages/grafana-ui/src/components/FormField/_FormField.scss

@@ -0,0 +1,12 @@
+.form-field {
+  margin-bottom: $gf-form-margin;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  text-align: left;
+  position: relative;
+
+  &--grow {
+    flex-grow: 1;
+  }
+}

+ 19 - 0
packages/grafana-ui/src/components/FormField/__snapshots__/FormField.test.tsx.snap

@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Render should render component 1`] = `
+<div
+  className="form-field"
+>
+  <Component
+    width={11}
+  >
+    Test
+  </Component>
+  <input
+    className="gf-form-input width-12"
+    onChange={[MockFunction]}
+    type="text"
+    value={10}
+  />
+</div>
+`;

+ 42 - 0
packages/grafana-ui/src/components/FormLabel/FormLabel.tsx

@@ -0,0 +1,42 @@
+import React, { FunctionComponent, ReactNode } from 'react';
+import classNames from 'classnames';
+import { Tooltip } from '..';
+
+interface Props {
+  children: ReactNode;
+  className?: string;
+  htmlFor?: string;
+  isFocused?: boolean;
+  isInvalid?: boolean;
+  tooltip?: string;
+  width?: number;
+}
+
+export const FormLabel: FunctionComponent<Props> = ({
+  children,
+  isFocused,
+  isInvalid,
+  className,
+  htmlFor,
+  tooltip,
+  width,
+  ...rest
+}) => {
+  const classes = classNames(`gf-form-label width-${width ? width : '10'}`, className, {
+    'gf-form-label--is-focused': isFocused,
+    'gf-form-label--is-invalid': isInvalid,
+  });
+
+  return (
+    <label className={classes} {...rest} htmlFor={htmlFor}>
+      {children}
+      {tooltip && (
+        <Tooltip placement="auto" content={tooltip}>
+          <div className="gf-form-help-icon--right-normal">
+            <i className="gicon gicon-question gicon--has-hover" />
+          </div>
+        </Tooltip>
+      )}
+    </label>
+  );
+};

+ 147 - 0
packages/grafana-ui/src/components/Gauge/Gauge.test.tsx

@@ -0,0 +1,147 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { Gauge, Props } from './Gauge';
+import { TimeSeriesVMs } from '../../types/series';
+import { ValueMapping, MappingType } from '../../types';
+
+jest.mock('jquery', () => ({
+  plot: jest.fn(),
+}));
+
+const setup = (propOverrides?: object) => {
+  const props: Props = {
+    maxValue: 100,
+    valueMappings: [],
+    minValue: 0,
+    prefix: '',
+    showThresholdMarkers: true,
+    showThresholdLabels: false,
+    suffix: '',
+    thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }],
+    unit: 'none',
+    stat: 'avg',
+    height: 300,
+    width: 300,
+    timeSeries: {} as TimeSeriesVMs,
+    decimals: 0,
+  };
+
+  Object.assign(props, propOverrides);
+
+  const wrapper = shallow(<Gauge {...props} />);
+  const instance = wrapper.instance() as Gauge;
+
+  return {
+    instance,
+    wrapper,
+  };
+};
+
+describe('Get font color', () => {
+  it('should get first threshold color when only one threshold', () => {
+    const { instance } = setup({ thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }] });
+
+    expect(instance.getFontColor(49)).toEqual('#7EB26D');
+  });
+
+  it('should get the threshold color if value is same as a threshold', () => {
+    const { instance } = setup({
+      thresholds: [
+        { index: 2, value: 75, color: '#6ED0E0' },
+        { index: 1, value: 50, color: '#EAB839' },
+        { index: 0, value: -Infinity, color: '#7EB26D' },
+      ],
+    });
+
+    expect(instance.getFontColor(50)).toEqual('#EAB839');
+  });
+
+  it('should get the nearest threshold color between thresholds', () => {
+    const { instance } = setup({
+      thresholds: [
+        { index: 2, value: 75, color: '#6ED0E0' },
+        { index: 1, value: 50, color: '#EAB839' },
+        { index: 0, value: -Infinity, color: '#7EB26D' },
+      ],
+    });
+
+    expect(instance.getFontColor(55)).toEqual('#EAB839');
+  });
+});
+
+describe('Get thresholds formatted', () => {
+  it('should return first thresholds color for min and max', () => {
+    const { instance } = setup({ thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }] });
+
+    expect(instance.getFormattedThresholds()).toEqual([
+      { value: 0, color: '#7EB26D' },
+      { value: 100, color: '#7EB26D' },
+    ]);
+  });
+
+  it('should get the correct formatted values when thresholds are added', () => {
+    const { instance } = setup({
+      thresholds: [
+        { index: 2, value: 75, color: '#6ED0E0' },
+        { index: 1, value: 50, color: '#EAB839' },
+        { index: 0, value: -Infinity, color: '#7EB26D' },
+      ],
+    });
+
+    expect(instance.getFormattedThresholds()).toEqual([
+      { value: 0, color: '#7EB26D' },
+      { value: 50, color: '#7EB26D' },
+      { value: 75, color: '#EAB839' },
+      { value: 100, color: '#6ED0E0' },
+    ]);
+  });
+});
+
+describe('Format value', () => {
+  it('should return if value isNaN', () => {
+    const valueMappings: ValueMapping[] = [];
+    const value = 'N/A';
+    const { instance } = setup({ valueMappings });
+
+    const result = instance.formatValue(value);
+
+    expect(result).toEqual('N/A');
+  });
+
+  it('should return formatted value if there are no value mappings', () => {
+    const valueMappings: ValueMapping[] = [];
+    const value = '6';
+    const { instance } = setup({ valueMappings, decimals: 1 });
+
+    const result = instance.formatValue(value);
+
+    expect(result).toEqual(' 6.0 ');
+  });
+
+  it('should return formatted value if there are no matching value mappings', () => {
+    const valueMappings: ValueMapping[] = [
+      { id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+      { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
+    ];
+    const value = '10';
+    const { instance } = setup({ valueMappings, decimals: 1 });
+
+    const result = instance.formatValue(value);
+
+    expect(result).toEqual(' 10.0 ');
+  });
+
+  it('should return mapped value if there are matching value mappings', () => {
+    const valueMappings: ValueMapping[] = [
+      { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
+      { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+    ];
+    const value = '11';
+    const { instance } = setup({ valueMappings, decimals: 1 });
+
+    const result = instance.formatValue(value);
+
+    expect(result).toEqual(' 1-20 ');
+  });
+});

+ 62 - 76
public/app/viz/Gauge.tsx → packages/grafana-ui/src/components/Gauge/Gauge.tsx

@@ -1,15 +1,15 @@
 import React, { PureComponent } from 'react';
 import $ from 'jquery';
-import { BasicGaugeColor, MappingType, RangeMap, Threshold, ValueMap } from 'app/types';
-import { TimeSeriesVMs } from '@grafana/ui';
-import config from '../core/config';
-import kbn from '../core/utils/kbn';
 
-interface Props {
-  baseColor: string;
+import { ValueMapping, Threshold, ThemeName, BasicGaugeColor, ThemeNames } from '../../types/panel';
+import { TimeSeriesVMs } from '../../types/series';
+import { getValueFormat } from '../../utils/valueFormats/valueFormats';
+import { TimeSeriesValue, getMappedValue } from '../../utils/valueMappings';
+
+export interface Props {
   decimals: number;
   height: number;
-  mappings: Array<RangeMap | ValueMap>;
+  valueMappings: ValueMapping[];
   maxValue: number;
   minValue: number;
   prefix: string;
@@ -21,15 +21,15 @@ interface Props {
   suffix: string;
   unit: string;
   width: number;
+  theme?: ThemeName;
 }
 
 export class Gauge extends PureComponent<Props> {
   canvasElement: any;
 
   static defaultProps = {
-    baseColor: BasicGaugeColor.Green,
     maxValue: 100,
-    mappings: [],
+    valueMappings: [],
     minValue: 0,
     prefix: '',
     showThresholdMarkers: true,
@@ -38,6 +38,7 @@ export class Gauge extends PureComponent<Props> {
     thresholds: [],
     unit: 'none',
     stat: 'avg',
+    theme: ThemeNames.Dark,
   };
 
   componentDidMount() {
@@ -48,89 +49,93 @@ export class Gauge extends PureComponent<Props> {
     this.draw();
   }
 
-  formatWithMappings(mappings, value) {
-    const valueMaps = mappings.filter(m => m.type === MappingType.ValueToText);
-    const rangeMaps = mappings.filter(m => m.type === MappingType.RangeToText);
+  formatValue(value: TimeSeriesValue) {
+    const { decimals, valueMappings, prefix, suffix, unit } = this.props;
 
-    const valueMap = valueMaps.map(mapping => {
-      if (mapping.value && value === mapping.value) {
-        return mapping.text;
-      }
-    })[0];
+    if (isNaN(value as number)) {
+      return value;
+    }
 
-    const rangeMap = rangeMaps.map(mapping => {
-      if (mapping.from && mapping.to && value > mapping.from && value < mapping.to) {
-        return mapping.text;
+    if (valueMappings.length > 0) {
+      const valueMappedValue = getMappedValue(valueMappings, value);
+      if (valueMappedValue) {
+        return `${prefix} ${valueMappedValue.text} ${suffix}`;
       }
-    })[0];
+    }
 
-    return {
-      rangeMap,
-      valueMap,
-    };
-  }
+    const formatFunc = getValueFormat(unit);
+    const formattedValue = formatFunc(value as number, decimals);
+    const handleNoValueValue = formattedValue || 'no value';
 
-  formatValue(value) {
-    const { decimals, mappings, prefix, suffix, unit } = this.props;
+    return `${prefix} ${handleNoValueValue} ${suffix}`;
+  }
 
-    const formatFunc = kbn.valueFormats[unit];
-    const formattedValue = formatFunc(value, decimals);
+  getFontColor(value: TimeSeriesValue) {
+    const { thresholds } = this.props;
 
-    if (mappings.length > 0) {
-      const { rangeMap, valueMap } = this.formatWithMappings(mappings, formattedValue);
+    if (thresholds.length === 1) {
+      return thresholds[0].color;
+    }
 
-      if (valueMap) {
-        return valueMap;
-      } else if (rangeMap) {
-        return rangeMap;
-      }
+    const atThreshold = thresholds.filter(threshold => (value as number) === threshold.value)[0];
+    if (atThreshold) {
+      return atThreshold.color;
     }
 
-    if (isNaN(value)) {
-      return '-';
+    const belowThreshold = thresholds.filter(threshold => (value as number) > threshold.value);
+
+    if (belowThreshold.length > 0) {
+      const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0];
+      return nearestThreshold.color;
     }
 
-    return `${prefix} ${formattedValue} ${suffix}`;
+    return BasicGaugeColor.Red;
   }
 
-  getFontColor(value) {
-    const { baseColor, maxValue, thresholds } = this.props;
+  getFormattedThresholds() {
+    const { maxValue, minValue, thresholds } = this.props;
 
-    const atThreshold = thresholds.filter(threshold => value <= threshold.value);
+    const thresholdsSortedByIndex = [...thresholds].sort((t1, t2) => t1.index - t2.index);
+    const lastThreshold = thresholdsSortedByIndex[thresholdsSortedByIndex.length - 1];
 
-    if (atThreshold.length > 0) {
-      return atThreshold[0].color;
-    } else if (value <= maxValue) {
-      return BasicGaugeColor.Red;
-    }
+    const formattedThresholds = [
+      ...thresholdsSortedByIndex.map(threshold => {
+        if (threshold.index === 0) {
+          return { value: minValue, color: threshold.color };
+        }
+
+        const previousThreshold = thresholdsSortedByIndex[threshold.index - 1];
+        return { value: threshold.value, color: previousThreshold.color };
+      }),
+      { value: maxValue, color: lastThreshold.color },
+    ];
 
-    return baseColor;
+    return formattedThresholds;
   }
 
   draw() {
     const {
-      baseColor,
       maxValue,
       minValue,
       timeSeries,
       showThresholdLabels,
       showThresholdMarkers,
-      thresholds,
       width,
       height,
       stat,
+      theme,
     } = this.props;
 
-    let value: string | number = '';
+    let value: TimeSeriesValue = '';
 
     if (timeSeries[0]) {
       value = timeSeries[0].stats[stat];
     } else {
-      value = 'N/A';
+      value = null;
     }
 
     const dimension = Math.min(width, height * 1.3);
-    const backgroundColor = config.bootData.user.lightTheme ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
+    const backgroundColor = theme === ThemeNames.Light ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
     const fontScale = parseInt('80', 10) / 100;
     const fontSize = Math.min(dimension / 5, 100) * fontScale;
     const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;
@@ -138,20 +143,6 @@ export class Gauge extends PureComponent<Props> {
     const thresholdMarkersWidth = gaugeWidth / 5;
     const thresholdLabelFontSize = fontSize / 2.5;
 
-    const formattedThresholds = [
-      { value: minValue, color: BasicGaugeColor.Green },
-      ...thresholds.map((threshold, index) => {
-        return {
-          value: threshold.value,
-          color: index === 0 ? threshold.color : thresholds[index].color,
-        };
-      }),
-      {
-        value: maxValue,
-        color: thresholds.length > 0 ? BasicGaugeColor.Red : baseColor,
-      },
-    ];
-
     const options = {
       series: {
         gauges: {
@@ -168,7 +159,7 @@ export class Gauge extends PureComponent<Props> {
           layout: { margin: 0, thresholdWidth: 0 },
           cell: { border: { width: 0 } },
           threshold: {
-            values: formattedThresholds,
+            values: this.getFormattedThresholds(),
             label: {
               show: showThresholdLabels,
               margin: thresholdMarkersWidth + 1,
@@ -182,19 +173,14 @@ export class Gauge extends PureComponent<Props> {
             formatter: () => {
               return this.formatValue(value);
             },
-            font: {
-              size: fontSize,
-              family: '"Helvetica Neue", Helvetica, Arial, sans-serif',
-            },
+            font: { size: fontSize, family: '"Helvetica Neue", Helvetica, Arial, sans-serif' },
           },
           show: true,
         },
       },
     };
 
-    const plotSeries = {
-      data: [[0, value]],
-    };
+    const plotSeries = { data: [[0, value]] };
 
     try {
       $.plot(this.canvasElement, [plotSeries], options);

+ 1 - 0
packages/grafana-ui/src/visualizations/Graph/Graph.tsx → packages/grafana-ui/src/components/Graph/Graph.tsx

@@ -98,6 +98,7 @@ export class Graph extends PureComponent<GraphProps> {
       $.plot(this.element, timeSeries, flotOptions);
     } catch (err) {
       console.log('Graph rendering error', err, flotOptions, timeSeries);
+      throw new Error('Error rendering panel');
     }
   }
 

+ 11 - 0
packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx

@@ -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>
+);

+ 15 - 0
packages/grafana-ui/src/components/PanelOptionsGrid/PanelOptionsGrid.tsx

@@ -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>
+  );
+};

+ 10 - 0
packages/grafana-ui/src/components/PanelOptionsGrid/_PanelOptionsGrid.scss

@@ -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);
+  }
+}

+ 4 - 4
public/app/features/dashboard/dashgrid/PanelOptionSection.tsx → packages/grafana-ui/src/components/PanelOptionsGroup/PanelOptionsGroup.tsx

@@ -7,11 +7,11 @@ interface Props {
   children: JSX.Element | JSX.Element[];
 }
 
-export const PanelOptionSection: SFC<Props> = props => {
+export const PanelOptionsGroup: SFC<Props> = props => {
   return (
-    <div className="panel-option-section">
+    <div className="panel-options-group">
       {props.title && (
-        <div className="panel-option-section__header">
+        <div className="panel-options-group__header">
           {props.title}
           {props.onClose && (
             <button className="btn btn-link" onClick={props.onClose}>
@@ -20,7 +20,7 @@ export const PanelOptionSection: SFC<Props> = props => {
           )}
         </div>
       )}
-      <div className="panel-option-section__body">{props.children}</div>
+      <div className="panel-options-group__body">{props.children}</div>
     </div>
   );
 };

+ 27 - 0
packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss

@@ -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 8px;
+  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;
+  }
+}

+ 3 - 6
public/app/core/components/Portal/Portal.tsx → packages/grafana-ui/src/components/Portal/Portal.tsx

@@ -6,16 +6,13 @@ interface Props {
   root?: HTMLElement;
 }
 
-export default class BodyPortal extends PureComponent<Props> {
+export class Portal extends PureComponent<Props> {
   node: HTMLElement = document.createElement('div');
   portalRoot: HTMLElement;
 
-  constructor(props) {
+  constructor(props: Props) {
     super(props);
-    const {
-      className,
-      root = document.body
-    } = this.props;
+    const { className, root = document.body } = this.props;
 
     if (className) {
       this.node.classList.add(className);

+ 4 - 1
public/app/core/components/Select/IndicatorsContainer.tsx → packages/grafana-ui/src/components/Select/IndicatorsContainer.tsx

@@ -1,7 +1,10 @@
 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';
 
-export const IndicatorsContainer = props => {
+export const IndicatorsContainer = (props: any) => {
   const isOpen = props.selectProps.menuIsOpen;
   return (
     <components.IndicatorsContainer {...props}>

+ 4 - 0
public/app/core/components/Select/NoOptionsMessage.tsx → packages/grafana-ui/src/components/Select/NoOptionsMessage.tsx

@@ -1,5 +1,9 @@
 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';
+// @ts-ignore
 import { OptionProps } from '@torkelo/react-select/lib/components/Option';
 
 export interface Props {

+ 16 - 11
public/app/core/components/Select/Select.tsx → packages/grafana-ui/src/components/Select/Select.tsx

@@ -1,17 +1,22 @@
 // Libraries
 import classNames from 'classnames';
 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';
+// @ts-ignore
 import { default as ReactAsyncSelect } from '@torkelo/react-select/lib/Async';
+// @ts-ignore
 import { components } from '@torkelo/react-select';
 
 // Components
-import { Option, SingleValue } from './PickerOption';
-import OptionGroup from './OptionGroup';
+import { SelectOption, SingleValue } from './SelectOption';
+import SelectOptionGroup from './SelectOptionGroup';
 import IndicatorsContainer from './IndicatorsContainer';
 import NoOptionsMessage from './NoOptionsMessage';
-import ResetStyles from './ResetStyles';
-import CustomScrollbar from '../CustomScrollbar/CustomScrollbar';
+import resetSelectStyles from './resetSelectStyles';
+import { CustomScrollbar } from '..';
 
 export interface SelectOptionItem {
   label?: string;
@@ -53,10 +58,10 @@ interface AsyncProps {
   loadingMessage?: () => string;
 }
 
-export const MenuList = props => {
+export const MenuList = (props: any) => {
   return (
     <components.MenuList {...props}>
-      <CustomScrollbar autoHide={false}>{props.children}</CustomScrollbar>
+      <CustomScrollbar autoHide={false} autoHeightMax="inherit">{props.children}</CustomScrollbar>
     </components.MenuList>
   );
 };
@@ -112,11 +117,11 @@ export class Select extends PureComponent<CommonProps & SelectProps> {
         classNamePrefix="gf-form-select-box"
         className={selectClassNames}
         components={{
-          Option,
+          Option: SelectOption,
           SingleValue,
           IndicatorsContainer,
           MenuList,
-          Group: OptionGroup,
+          Group: SelectOptionGroup,
         }}
         defaultValue={defaultValue}
         value={value}
@@ -127,7 +132,7 @@ export class Select extends PureComponent<CommonProps & SelectProps> {
         onChange={onChange}
         options={options}
         placeholder={placeholder || 'Choose'}
-        styles={ResetStyles}
+        styles={resetSelectStyles()}
         isDisabled={isDisabled}
         isLoading={isLoading}
         isClearable={isClearable}
@@ -197,7 +202,7 @@ export class AsyncSelect extends PureComponent<CommonProps & AsyncProps> {
         classNamePrefix="gf-form-select-box"
         className={selectClassNames}
         components={{
-          Option,
+          Option: SelectOption,
           SingleValue,
           IndicatorsContainer,
           NoOptionsMessage,
@@ -212,7 +217,7 @@ export class AsyncSelect extends PureComponent<CommonProps & AsyncProps> {
         isLoading={isLoading}
         defaultOptions={defaultOptions}
         placeholder={placeholder || 'Choose'}
-        styles={ResetStyles}
+        styles={resetSelectStyles()}
         loadingMessage={loadingMessage}
         noOptionsMessage={noOptionsMessage}
         isDisabled={isDisabled}

+ 23 - 12
public/app/core/components/Select/PickerOption.test.tsx → packages/grafana-ui/src/components/Select/SelectOption.test.tsx

@@ -1,11 +1,12 @@
 import React from 'react';
 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(),
   clearValue: jest.fn(),
-  onSelect: jest.fn(),
   getStyles: jest.fn(),
   getValue: jest.fn(),
   hasValue: true,
@@ -18,21 +19,31 @@ const model = {
   isFocused: false,
   isSelected: false,
   innerRef: null,
-  innerProps: null,
+  innerProps: {
+    id: '',
+    key: '',
+    onClick: jest.fn(),
+    onMouseOver: jest.fn(),
+    tabIndex: 1,
+  },
   label: 'Option label',
-  type: null,
+  type: 'option',
   children: 'Model title',
-  data: {
-    title: 'Model title',
-    imgUrl: 'url/to/avatar',
-    label: 'User picker label',
-  },
   className: 'class-for-user-picker',
 };
 
-describe('PickerOption', () => {
+describe('SelectOption', () => {
   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();
   });
 });

+ 6 - 3
public/app/core/components/Select/PickerOption.tsx → packages/grafana-ui/src/components/Select/SelectOption.tsx

@@ -1,4 +1,7 @@
 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 { 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;
 
   return (
@@ -28,7 +31,7 @@ export const Option = (props: ExtendedOptionProps) => {
 };
 
 // was not able to type this without typescript error
-export const SingleValue = props => {
+export const SingleValue = (props: any) => {
   const { children, data } = props;
 
   return (
@@ -41,4 +44,4 @@ export const SingleValue = props => {
   );
 };
 
-export default Option;
+export default SelectOption;

+ 11 - 5
public/app/core/components/Select/OptionGroup.tsx → packages/grafana-ui/src/components/Select/SelectOptionGroup.tsx

@@ -2,21 +2,27 @@ import React, { PureComponent } from 'react';
 import { GroupProps } from 'react-select/lib/components/Group';
 
 interface ExtendedGroupProps extends GroupProps<any> {
-  data: any;
+  data: {
+    label: string;
+    expanded: boolean;
+    options: any[];
+  };
 }
 
 interface State {
   expanded: boolean;
 }
 
-export default class OptionGroup extends PureComponent<ExtendedGroupProps, State> {
+export default class SelectOptionGroup extends PureComponent<ExtendedGroupProps, State> {
   state = {
     expanded: false,
   };
 
   componentDidMount() {
-    if (this.props.selectProps) {
-      const value = this.props.selectProps.value[this.props.selectProps.value.length - 1];
+    if (this.props.data.expanded) {
+      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)) {
         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 !== '') {
       this.setState({ expanded: true });
     }

+ 2 - 0
public/sass/components/_form_select_box.scss → packages/grafana-ui/src/components/Select/_Select.scss

@@ -63,6 +63,7 @@ $select-input-bg-disabled: $input-bg-disabled;
 .gf-form-select-box__menu-list {
   overflow-y: auto;
   max-height: 300px;
+  max-width: 600px;
 }
 
 .tag-filter .gf-form-select-box__menu {
@@ -101,6 +102,7 @@ $select-input-bg-disabled: $input-bg-disabled;
 .gf-form-select-box__value-container {
   display: table-cell;
   padding: 6px 10px;
+  vertical-align: middle;
   > div {
     display: inline-block;
   }

+ 7 - 2
public/app/core/components/Select/__snapshots__/PickerOption.test.tsx.snap → packages/grafana-ui/src/components/Select/__snapshots__/SelectOption.test.tsx.snap

@@ -1,7 +1,12 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`PickerOption renders correctly 1`] = `
-<div>
+exports[`SelectOption renders correctly 1`] = `
+<div
+  id=""
+  onClick={[MockFunction]}
+  onMouseOver={[MockFunction]}
+  tabIndex={1}
+>
   <div
     className="gf-form-select-box__desc-option"
   >

+ 27 - 0
packages/grafana-ui/src/components/Select/resetSelectStyles.ts

@@ -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: () => ({}),
+  };
+}

+ 173 - 0
packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx

@@ -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' },
+    ]);
+  });
+});

+ 211 - 0
packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx

@@ -0,0 +1,211 @@
+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 addDefaultThreshold = this.props.thresholds.length === 0;
+    const thresholds: Threshold[] = addDefaultThreshold
+      ? [{ index: 0, value: -Infinity, color: colors[0] }]
+      : props.thresholds;
+    this.state = { thresholds };
+
+    if (addDefaultThreshold) {
+      this.onChange();
+    }
+  }
+
+  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.onChange()
+    );
+  };
+
+  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.onChange()
+    );
+  };
+
+  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.index !== 0) {
+        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.onChange()
+    );
+  };
+
+  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.onChange();
+  };
+
+  onChange = () => {
+    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>
+    );
+  }
+}

+ 105 - 0
packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss

@@ -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;
+}

+ 21 - 16
public/app/core/components/Tooltip/Popper.tsx → packages/grafana-ui/src/components/Tooltip/Popper.tsx

@@ -1,49 +1,54 @@
 import React, { PureComponent } from 'react';
-import Portal from 'app/core/components/Portal/Portal';
-import { Manager, Popper as ReactPopper, Reference } from 'react-popper';
+import * as PopperJS from 'popper.js';
+import { Manager, Popper as ReactPopper } from 'react-popper';
+import { Portal } from '@grafana/ui';
 import Transition from 'react-transition-group/Transition';
 
+export enum Themes {
+  Default = 'popper__background--default',
+  Error = 'popper__background--error',
+  Brand = 'popper__background--brand',
+}
+
 const defaultTransitionStyles = {
   transition: 'opacity 200ms linear',
   opacity: 0,
 };
 
-const transitionStyles = {
+const transitionStyles: {[key: string]: object} = {
   exited: { opacity: 0 },
   entering: { opacity: 0 },
   entered: { opacity: 1 },
   exiting: { opacity: 0 },
 };
 
-interface Props {
+interface Props extends React.DOMAttributes<HTMLDivElement> {
   renderContent: (content: any) => any;
   show: boolean;
-  placement?: any;
+  placement?: PopperJS.Placement;
   content: string | ((props: any) => JSX.Element);
-  refClassName?: string;
+  referenceElement: PopperJS.ReferenceObject;
+  theme?: Themes;
 }
 
 class Popper extends PureComponent<Props> {
   render() {
-    const { children, renderContent, show, placement, refClassName } = this.props;
+    const { renderContent, show, placement, onMouseEnter, onMouseLeave, theme } = this.props;
     const { content } = this.props;
 
+    const popperBackgroundClassName = 'popper__background' + (theme ? ' ' + theme : '');
+
     return (
       <Manager>
-        <Reference>
-          {({ ref }) => (
-            <div className={`popper_ref ${refClassName || ''}`} ref={ref}>
-              {children}
-            </div>
-          )}
-        </Reference>
         <Transition in={show} timeout={100} mountOnEnter={true} unmountOnExit={true}>
           {transitionState => (
             <Portal>
-              <ReactPopper placement={placement}>
+              <ReactPopper placement={placement} referenceElement={this.props.referenceElement}>
                 {({ ref, style, placement, arrowProps }) => {
                   return (
                     <div
+                      onMouseEnter={onMouseEnter}
+                      onMouseLeave={onMouseLeave}
                       ref={ref}
                       style={{
                         ...style,
@@ -53,7 +58,7 @@ class Popper extends PureComponent<Props> {
                       data-placement={placement}
                       className="popper"
                     >
-                      <div className="popper__background">
+                      <div className={popperBackgroundClassName}>
                         {renderContent(content)}
                         <div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" />
                       </div>

+ 99 - 0
packages/grafana-ui/src/components/Tooltip/PopperController.tsx

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

+ 5 - 3
public/app/core/components/Tooltip/Tooltip.test.tsx → packages/grafana-ui/src/components/Tooltip/Tooltip.test.tsx

@@ -1,13 +1,15 @@
 import React from 'react';
 import renderer from 'react-test-renderer';
-import Tooltip from './Tooltip';
+import { Tooltip } from './Tooltip';
 
 describe('Tooltip', () => {
   it('renders correctly', () => {
     const tree = renderer
       .create(
-        <Tooltip className="test-class" placement="auto" content="Tooltip text">
-          <a href="http://www.grafana.com">Link with tooltip</a>
+        <Tooltip placement="auto" content="Tooltip text">
+          <a className="test-class" href="http://www.grafana.com">
+            Link with tooltip
+          </a>
         </Tooltip>
       )
       .toJSON();

+ 32 - 0
packages/grafana-ui/src/components/Tooltip/Tooltip.tsx

@@ -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>
+  );
+};

+ 27 - 9
public/sass/components/_popper.scss → packages/grafana-ui/src/components/Tooltip/_Tooltip.scss

@@ -1,5 +1,13 @@
 $popper-margin-from-ref: 5px;
 
+
+@mixin popper-theme($backgroundColor, $arrowColor) {
+  background: $backgroundColor;
+  .popper__arrow {
+    border-color: $arrowColor;
+  }
+}
+
 .popper {
   position: absolute;
   z-index: $zindex-tooltip;
@@ -8,7 +16,24 @@ $popper-margin-from-ref: 5px;
   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;
   height: 0;
   border-style: solid;
@@ -16,17 +41,10 @@ $popper-margin-from-ref: 5px;
   margin: 0px;
 }
 
-.popper .popper__arrow {
+.popper__arrow {
   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
 .popper[data-placement^='top'] {
   padding-bottom: $popper-margin-from-ref;

+ 12 - 0
packages/grafana-ui/src/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap

@@ -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>
+`;

+ 50 - 55
public/app/plugins/panel/gauge/MappingRow.tsx → packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx

@@ -1,22 +1,22 @@
-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 {
-  mapping: ValueMap | RangeMap;
-  updateMapping: (mapping) => void;
-  removeMapping: () => void;
+import React, { ChangeEvent, PureComponent } from 'react';
+
+import { MappingType, ValueMapping } from '../../types';
+import { FormField, FormLabel, Select } from '..';
+
+export interface Props {
+  valueMapping: ValueMapping;
+  updateValueMapping: (valueMapping: ValueMapping) => void;
+  removeValueMapping: () => void;
 }
 
 interface State {
-  from: string;
+  from?: string;
   id: number;
   operator: string;
   text: string;
-  to: string;
+  to?: string;
   type: MappingType;
-  value: string;
+  value?: string;
 }
 
 const mappingOptions = [
@@ -25,36 +25,34 @@ const mappingOptions = [
 ];
 
 export default class MappingRow extends PureComponent<Props, State> {
-  constructor(props) {
+  constructor(props: Props) {
     super(props);
 
-    this.state = {
-      ...props.mapping,
-    };
+    this.state = { ...props.valueMapping };
   }
 
-  onMappingValueChange = event => {
+  onMappingValueChange = (event: ChangeEvent<HTMLInputElement>) => {
     this.setState({ value: event.target.value });
   };
 
-  onMappingFromChange = event => {
+  onMappingFromChange = (event: ChangeEvent<HTMLInputElement>) => {
     this.setState({ from: event.target.value });
   };
 
-  onMappingToChange = event => {
+  onMappingToChange = (event: ChangeEvent<HTMLInputElement>) => {
     this.setState({ to: event.target.value });
   };
 
-  onMappingTextChange = event => {
+  onMappingTextChange = (event: ChangeEvent<HTMLInputElement>) => {
     this.setState({ text: event.target.value });
   };
 
-  onMappingTypeChange = mappingType => {
+  onMappingTypeChange = (mappingType: MappingType) => {
     this.setState({ type: mappingType });
   };
 
   updateMapping = () => {
-    this.props.updateMapping({ ...this.state });
+    this.props.updateValueMapping({ ...this.state } as ValueMapping);
   };
 
   renderRow() {
@@ -63,30 +61,28 @@ export default class MappingRow extends PureComponent<Props, State> {
     if (type === MappingType.RangeToText) {
       return (
         <>
-          <div className="gf-form">
-            <Label width={4}>From</Label>
-            <input
-              className="gf-form-input width-8"
-              value={from}
-              onBlur={this.updateMapping}
-              onChange={this.onMappingFromChange}
-            />
-          </div>
-          <div className="gf-form">
-            <Label width={4}>To</Label>
+          <FormField
+            label="From"
+            labelWidth={4}
+            inputWidth={8}
+            onBlur={this.updateMapping}
+            onChange={this.onMappingFromChange}
+            value={from}
+          />
+          <FormField
+            label="To"
+            labelWidth={4}
+            inputWidth={8}
+            onBlur={this.updateMapping}
+            onChange={this.onMappingToChange}
+            value={to}
+          />
+          <div className="gf-form gf-form--grow">
+            <FormLabel width={4}>Text</FormLabel>
             <input
-              className="gf-form-input width-8"
-              value={to}
+              className="gf-form-input"
               onBlur={this.updateMapping}
-              onChange={this.onMappingToChange}
-            />
-          </div>
-          <div className="gf-form">
-            <Label width={4}>Text</Label>
-            <input
-              className="gf-form-input width-10"
               value={text}
-              onBlur={this.updateMapping}
               onChange={this.onMappingTextChange}
             />
           </div>
@@ -96,17 +92,16 @@ export default class MappingRow extends PureComponent<Props, State> {
 
     return (
       <>
-        <div className="gf-form">
-          <Label width={4}>Value</Label>
-          <input
-            className="gf-form-input width-8"
-            onBlur={this.updateMapping}
-            onChange={this.onMappingValueChange}
-            value={value}
-          />
-        </div>
+        <FormField
+          label="Value"
+          labelWidth={4}
+          onBlur={this.updateMapping}
+          onChange={this.onMappingValueChange}
+          value={value}
+          inputWidth={8}
+        />
         <div className="gf-form gf-form--grow">
-          <Label width={4}>Text</Label>
+          <FormLabel width={4}>Text</FormLabel>
           <input
             className="gf-form-input"
             onBlur={this.updateMapping}
@@ -124,7 +119,7 @@ export default class MappingRow extends PureComponent<Props, State> {
     return (
       <div className="gf-form-inline">
         <div className="gf-form">
-          <Label width={5}>Type</Label>
+          <FormLabel width={5}>Type</FormLabel>
           <Select
             placeholder="Choose type"
             isSearchable={false}
@@ -136,7 +131,7 @@ export default class MappingRow extends PureComponent<Props, State> {
         </div>
         {this.renderRow()}
         <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" />
           </button>
         </div>

+ 15 - 16
public/app/plugins/panel/gauge/ValueMappings.test.tsx → packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.test.tsx

@@ -1,26 +1,23 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import ValueMappings from './ValueMappings';
-import { defaultProps, OptionModuleProps } from './module';
-import { MappingType } from 'app/types';
+
+import { ValueMappingsEditor, Props } from './ValueMappingsEditor';
+import { MappingType } from '../../types/panel';
 
 const setup = (propOverrides?: object) => {
-  const props: OptionModuleProps = {
+  const props: Props = {
     onChange: jest.fn(),
-    options: {
-      ...defaultProps.options,
-      mappings: [
-        { id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
-        { id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
-      ],
-    },
+    valueMappings: [
+      { 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);
 
-  const wrapper = shallow(<ValueMappings {...props} />);
+  const wrapper = shallow(<ValueMappingsEditor {...props} />);
 
-  const instance = wrapper.instance() as ValueMappings;
+  const instance = wrapper.instance() as ValueMappingsEditor;
 
   return {
     instance,
@@ -39,18 +36,20 @@ describe('Render', () => {
 describe('On remove mapping', () => {
   it('Should remove mapping with id 0', () => {
     const { instance } = setup();
+
     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' },
     ]);
   });
 
   it('should remove mapping with id 1', () => {
     const { instance } = setup();
+
     instance.onRemoveMapping(2);
 
-    expect(instance.state.mappings).toEqual([
+    expect(instance.state.valueMappings).toEqual([
       { id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
     ]);
   });
@@ -66,7 +65,7 @@ describe('Next id to add', () => {
   });
 
   it('should default to 1', () => {
-    const { instance } = setup({ options: { ...defaultProps.options } });
+    const { instance } = setup({ valueMappings: [] });
 
     expect(instance.state.nextIdToAdd).toEqual(1);
   });

+ 105 - 0
packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx

@@ -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>
+    );
+  }
+}

+ 0 - 0
public/sass/components/_value-mappings.scss → packages/grafana-ui/src/components/ValueMappingsEditor/_ValueMappingsEditor.scss


+ 9 - 14
public/app/plugins/panel/gauge/__snapshots__/ValueMappings.test.tsx.snap → packages/grafana-ui/src/components/ValueMappingsEditor/__snapshots__/ValueMappingsEditor.test.tsx.snap

@@ -1,18 +1,15 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`Render should render component 1`] = `
-<div
-  className="section gf-form-group"
+<Component
+  title="Value Mappings"
 >
-  <h5
-    className="section-heading"
-  >
-    Value mappings
-  </h5>
   <div>
     <MappingRow
       key="Ok-0"
-      mapping={
+      removeValueMapping={[Function]}
+      updateValueMapping={[Function]}
+      valueMapping={
         Object {
           "id": 1,
           "operator": "",
@@ -21,12 +18,12 @@ exports[`Render should render component 1`] = `
           "value": "20",
         }
       }
-      removeMapping={[Function]}
-      updateMapping={[Function]}
     />
     <MappingRow
       key="Meh-1"
-      mapping={
+      removeValueMapping={[Function]}
+      updateValueMapping={[Function]}
+      valueMapping={
         Object {
           "from": "21",
           "id": 2,
@@ -36,8 +33,6 @@ exports[`Render should render component 1`] = `
           "type": 2,
         }
       }
-      removeMapping={[Function]}
-      updateMapping={[Function]}
     />
   </div>
   <div
@@ -57,5 +52,5 @@ exports[`Render should render component 1`] = `
       Add mapping
     </div>
   </div>
-</div>
+</Component>
 `;

+ 9 - 0
packages/grafana-ui/src/components/index.scss

@@ -1 +1,10 @@
+@import 'CustomScrollbar/CustomScrollbar';
 @import 'DeleteButton/DeleteButton';
+@import 'ThresholdsEditor/ThresholdsEditor';
+@import 'Tooltip/Tooltip';
+@import 'Select/Select';
+@import 'PanelOptionsGroup/PanelOptionsGroup';
+@import 'PanelOptionsGrid/PanelOptionsGrid';
+@import 'ColorPicker/ColorPicker';
+@import 'ValueMappingsEditor/ValueMappingsEditor';
+@import "FormField/FormField";

+ 24 - 0
packages/grafana-ui/src/components/index.ts

@@ -1 +1,25 @@
 export { DeleteButton } from './DeleteButton/DeleteButton';
+export { Tooltip } from './Tooltip/Tooltip';
+export { Portal } from './Portal/Portal';
+export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
+
+// 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';
+
+// Forms
+export { FormLabel } from './FormLabel/FormLabel';
+export { FormField } from './FormField/FormField';
+
+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 { Graph } from './Graph/Graph';
+export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup';
+export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid';
+export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor';
+export { Gauge } from './Gauge/Gauge';

+ 0 - 23
packages/grafana-ui/src/forms/GfFormLabel/GfFormLabel.tsx

@@ -1,23 +0,0 @@
-import React, { SFC, ReactNode } from 'react';
-import classNames from 'classnames';
-
-interface Props {
-  children: ReactNode;
-  htmlFor?: string;
-  className?: string;
-  isFocused?: boolean;
-  isInvalid?: boolean;
-}
-
-export const GfFormLabel: SFC<Props> = ({ children, isFocused, isInvalid, className, htmlFor, ...rest }) => {
-  const classes = classNames('gf-form-label', className, {
-    'gf-form-label--is-focused': isFocused,
-    'gf-form-label--is-invalid': isInvalid,
-  });
-
-  return (
-    <label className={classes} {...rest} htmlFor={htmlFor}>
-      {children}
-    </label>
-  );
-};

+ 0 - 1
packages/grafana-ui/src/forms/index.ts

@@ -1 +0,0 @@
-export { GfFormLabel } from './GfFormLabel/GfFormLabel';

+ 2 - 0
packages/grafana-ui/src/index.scss

@@ -1 +1,3 @@
+@import 'vendor/spectrum';
 @import 'components/index';
+

+ 0 - 2
packages/grafana-ui/src/index.ts

@@ -1,5 +1,3 @@
 export * from './components';
-export * from './visualizations';
 export * from './types';
 export * from './utils';
-export * from './forms';

+ 89 - 0
packages/grafana-ui/src/types/datasource.ts

@@ -0,0 +1,89 @@
+import { TimeRange, RawTimeRange } from './time';
+import { TimeSeries } from './series';
+import { PluginMeta } from './plugin';
+
+export interface DataQueryResponse {
+  data: TimeSeries[];
+}
+
+export interface DataQuery {
+  /**
+   * A - Z
+   */
+  refId: string;
+
+  /**
+   * true if query is disabled (ie not executed / sent to TSDB)
+   */
+  hide?: boolean;
+
+  /**
+   * Unique, guid like, string used in explore mode
+   */
+  key?: string;
+
+  /**
+   * For mixed data sources the selected datasource is on the query level.
+   * For non mixed scenarios this is undefined.
+   */
+  datasource?: string | null;
+}
+
+export interface DataQueryOptions<TQuery extends DataQuery = DataQuery> {
+  timezone: string;
+  range: TimeRange;
+  rangeRaw: RawTimeRange;
+  targets: TQuery[];
+  panelId: number;
+  dashboardId: number;
+  cacheTimeout?: string;
+  interval: string;
+  intervalMs: number;
+  maxDataPoints: number;
+  scopedVars: object;
+}
+
+export interface QueryFix {
+  type: string;
+  label: string;
+  action?: QueryFixAction;
+}
+
+export interface QueryFixAction {
+  type: string;
+  query?: string;
+  preventSubmit?: boolean;
+}
+
+export interface QueryHint {
+  type: string;
+  label: string;
+  fix?: QueryFix;
+}
+
+export interface DataSourceSettings {
+  id: number;
+  orgId: number;
+  name: string;
+  typeLogoUrl: string;
+  type: string;
+  access: string;
+  url: string;
+  password: string;
+  user: string;
+  database: string;
+  basicAuth: boolean;
+  basicAuthPassword: string;
+  basicAuthUser: string;
+  isDefault: boolean;
+  jsonData: { authType: string; defaultRegion: string };
+  readOnly: boolean;
+  withCredentials: boolean;
+}
+
+export interface DataSourceSelectItem {
+  name: string;
+  value: string | null;
+  meta: PluginMeta;
+  sort: string;
+}

+ 2 - 0
packages/grafana-ui/src/types/index.ts

@@ -1,3 +1,5 @@
 export * from './series';
 export * from './time';
 export * from './panel';
+export * from './plugin';
+export * from './datasource';

+ 44 - 0
packages/grafana-ui/src/types/panel.ts

@@ -1,6 +1,8 @@
 import { TimeSeries, LoadingState } from './series';
 import { TimeRange } from './time';
 
+export type InterpolateFunction = (value: string, format?: string | Function) => string;
+
 export interface PanelProps<T = any> {
   timeSeries: TimeSeries[];
   timeRange: TimeRange;
@@ -9,6 +11,7 @@ export interface PanelProps<T = any> {
   renderCounter: number;
   width: number;
   height: number;
+  onInterpolate: InterpolateFunction;
 }
 
 export interface PanelOptionsProps<T = any> {
@@ -29,3 +32,44 @@ export interface PanelMenuItem {
   shortcut?: string;
   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;
+}
+
+export type ThemeName = 'dark' | 'light';
+
+export enum ThemeNames {
+  Dark = 'dark',
+  Light = 'light',
+}

+ 118 - 0
packages/grafana-ui/src/types/plugin.ts

@@ -0,0 +1,118 @@
+import { ComponentClass } from 'react';
+import { PanelProps, PanelOptionsProps } from './panel';
+import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint } from './datasource';
+
+export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
+  /**
+   *  min interval range
+   */
+  interval?: string;
+
+  /**
+   * Imports queries from a different datasource
+   */
+  importQueries?(queries: TQuery[], originMeta: PluginMeta): Promise<TQuery[]>;
+
+  /**
+   * Initializes a datasource after instantiation
+   */
+  init?: () => void;
+
+  /**
+   * Main metrics / data query action
+   */
+  query(options: DataQueryOptions<TQuery>): Promise<DataQueryResponse>;
+
+  /**
+   * Test & verify datasource settings & connection details
+   */
+  testDatasource(): Promise<any>;
+
+  /**
+   *  Get hints for query improvements
+   */
+  getQueryHints?(query: TQuery, results: any[], ...rest: any): QueryHint[];
+
+  /**
+   *  Set after constructor is called by Grafana
+   */
+  name?: string;
+  meta?: PluginMeta;
+  pluginExports?: PluginExports;
+}
+
+export interface QueryEditorProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
+  datasource: DSType;
+  query: TQuery;
+  onExecuteQuery?: () => void;
+  onQueryChange?: (value: TQuery) => void;
+}
+
+export interface PluginExports {
+  Datasource?: DataSourceApi;
+  QueryCtrl?: any;
+  QueryEditor?: ComponentClass<QueryEditorProps<DataSourceApi,DataQuery>>;
+  ConfigCtrl?: any;
+  AnnotationsQueryCtrl?: any;
+  VariableQueryEditor?: any;
+  ExploreQueryField?: any;
+  ExploreStartPage?: any;
+
+  // Panel plugin
+  PanelCtrl?: any;
+  Panel?: ComponentClass<PanelProps>;
+  PanelOptions?: ComponentClass<PanelOptionsProps>;
+  PanelDefaults?: any;
+}
+
+export interface PluginMeta {
+  id: string;
+  name: string;
+  info: PluginMetaInfo;
+  includes: PluginInclude[];
+
+  // Datasource-specific
+  metrics?: boolean;
+  tables?: boolean;
+  logs?: boolean;
+  explore?: boolean;
+  annotations?: boolean;
+  mixed?: boolean;
+  hasQueryHelp?: boolean;
+  queryOptions?: PluginMetaQueryOptions;
+}
+
+interface PluginMetaQueryOptions {
+  cacheTimeout?: boolean;
+  maxDataPoints?: boolean;
+  minInterval?: boolean;
+}
+
+export interface PluginInclude {
+  type: string;
+  name: string;
+  path: string;
+}
+
+interface PluginMetaInfoLink {
+  name: string;
+  url: string;
+}
+
+export interface PluginMetaInfo {
+  author: {
+    name: string;
+    url?: string;
+  };
+  description: string;
+  links: PluginMetaInfoLink[];
+  logos: {
+    large: string;
+    small: string;
+  };
+  screenshots: any[];
+  updated: string;
+  version: string;
+}
+
+

+ 3 - 2
packages/grafana-ui/src/types/series.ts

@@ -21,9 +21,12 @@ export interface TimeSeriesVM {
   color: string;
   data: TimeSeriesValue[][];
   stats: TimeSeriesStats;
+  allIsNull: boolean;
+  allIsZero: boolean;
 }
 
 export interface TimeSeriesStats {
+  [key: string]: number | null;
   total: number | null;
   max: number | null;
   min: number | null;
@@ -36,8 +39,6 @@ export interface TimeSeriesStats {
   range: number | null;
   timeStep: number;
   count: number;
-  allIsNull: boolean;
-  allIsZero: boolean;
 }
 
 export enum NullValueMode {

Some files were not shown because too many files changed in this diff