diff --git a/.circleci/config.yml b/.circleci/config.yml index 075105dd0d180..0cd47bfa6d1d3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -69,6 +69,28 @@ jobs: - run: name: cache server tests command: './scripts/circle-test-cache-servers.sh' + + end-to-end-test: + docker: + - image: circleci/node:8-browsers + - image: grafana/grafana:master + steps: + - run: dockerize -wait tcp://127.0.0.1:3000 -timeout 120s + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "yarn.lock" }} + - run: + name: yarn install + command: 'yarn install --pure-lockfile --no-progress' + no_output_timeout: 5m + - save_cache: + key: dependency-cache-{{ checksum "yarn.lock" }} + paths: + - node_modules + - run: + name: run end-to-end tests + command: 'env BASE_URL=http://127.0.0.1:3000 yarn e2e-tests' + no_output_timeout: 5m codespell: docker: diff --git a/.gitignore b/.gitignore index a9547973fc839..50bfceb36861d 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,5 @@ debug.test /packages/**/dist /packages/**/compiled /packages/**/.rpt2_cache + +theOutput/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b920883ffffb0..7183196d91f40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,56 @@ # 6.2.0 (unreleased) +# 6.2.0-beta1 (2019-05-07) + +### Features / Enhancements + * **Admin**: Add more stats about roles. [#16667](https://github.com/grafana/grafana/pull/16667), [@bergquist](https://github.com/bergquist) + * **Alert list panel**: Support variables in filters. [#16892](https://github.com/grafana/grafana/pull/16892), [@psschand](https://github.com/psschand) + * **Alerting**: Adjust label for send on all alerts to default . [#16554](https://github.com/grafana/grafana/pull/16554), [@simPod](https://github.com/simPod) + * **Alerting**: Makes timeouts and retries configurable. [#16259](https://github.com/grafana/grafana/pull/16259), [@kobehaha](https://github.com/kobehaha) + * **Alerting**: No notification when going from no data to pending. [#16905](https://github.com/grafana/grafana/pull/16905), [@bergquist](https://github.com/bergquist) + * **Alerting**: Pushover alert, support for different sound for OK. [#16525](https://github.com/grafana/grafana/pull/16525), [@Hofls](https://github.com/Hofls) + * **Auth**: Enable retries and transaction for some db calls for auth tokens . [#16785](https://github.com/grafana/grafana/pull/16785), [@bergquist](https://github.com/bergquist) + * **AzureMonitor**: Adds support for multiple subscriptions per datasource. [#16922](https://github.com/grafana/grafana/pull/16922), [@daniellee](https://github.com/daniellee) + * **Bar Gauge**: New multi series enabled gauge like panel with horizontal and vertical layouts and 3 display modes. [#16918](https://github.com/grafana/grafana/pull/16918), [@torkelo](https://github.com/torkelo) + * **Build**: Upgrades to golang 1.12.4. [#16545](https://github.com/grafana/grafana/pull/16545), [@bergquist](https://github.com/bergquist) + * **CloudWatch**: Update AWS/IoT metric and dimensions. [#16337](https://github.com/grafana/grafana/pull/16337), [@nonamef](https://github.com/nonamef) + * **Config**: Show user-friendly error message instead of stack trace. [#16564](https://github.com/grafana/grafana/pull/16564), [@Hofls](https://github.com/Hofls) + * **Dashboard**: Enable filtering dashboards in search by current folder. [#16790](https://github.com/grafana/grafana/pull/16790), [@dprokop](https://github.com/dprokop) + * **Dashboard**: Lazy load out of view panels . [#15554](https://github.com/grafana/grafana/pull/15554), [@ryantxu](https://github.com/ryantxu) + * **DataProxy**: Restore Set-Cookie header after proxy request. [#16838](https://github.com/grafana/grafana/pull/16838), [@marefr](https://github.com/marefr) + * **Datasources**: Add pattern validation for time input on datasource config pages. [#16837](https://github.com/grafana/grafana/pull/16837), [@aocenas](https://github.com/aocenas) + * **Elasticsearch**: Add 7.x version support. [#16646](https://github.com/grafana/grafana/pull/16646), [@alcidesv](https://github.com/alcidesv) + * **Explore**: Adds reconnect for failing datasource. [#16226](https://github.com/grafana/grafana/pull/16226), [@hugohaggmark](https://github.com/hugohaggmark) + * **Explore**: Support user timezone. [#16469](https://github.com/grafana/grafana/pull/16469), [@marefr](https://github.com/marefr) + * **InfluxDB**: Add support for POST HTTP verb. [#16690](https://github.com/grafana/grafana/pull/16690), [@StephenSorriaux](https://github.com/StephenSorriaux) + * **Loki**: Search is now case insensitive. [#15948](https://github.com/grafana/grafana/pull/15948), [@steven-sheehy](https://github.com/steven-sheehy) + * **OAuth**: Update jwt regexp to include `=`. [#16521](https://github.com/grafana/grafana/pull/16521), [@DanCech](https://github.com/DanCech) + * **Panels**: No title will no longer make panel header take up space. [#16884](https://github.com/grafana/grafana/pull/16884), [@torkelo](https://github.com/torkelo) + * **Prometheus**: Adds tracing headers for Prometheus datasource. [#16724](https://github.com/grafana/grafana/pull/16724), [@svagner](https://github.com/svagner) + * **Provisioning**: Add API endpoint to reload provisioning configs. [#16579](https://github.com/grafana/grafana/pull/16579), [@aocenas](https://github.com/aocenas) + * **Provisioning**: Do not allow deletion of provisioned dashboards. [#16211](https://github.com/grafana/grafana/pull/16211), [@aocenas](https://github.com/aocenas) + * **Provisioning**: Interpolate env vars in provisioning files. [#16499](https://github.com/grafana/grafana/pull/16499), [@aocenas](https://github.com/aocenas) + * **Security**: Add new setting allow_embedding. [#16853](https://github.com/grafana/grafana/pull/16853), [@marefr](https://github.com/marefr) + * **Security**: Store datasource passwords encrypted in secureJsonData. [#16175](https://github.com/grafana/grafana/pull/16175), [@aocenas](https://github.com/aocenas) + * **UX**: Improve Grafana usage for smaller screens. [#16783](https://github.com/grafana/grafana/pull/16783), [@torkelo](https://github.com/torkelo) + * **Units**: Add angle units, Arc Minutes and Seconds. [#16271](https://github.com/grafana/grafana/pull/16271), [@Dripoul](https://github.com/Dripoul) + +### Bug Fixes + * **Build**: Fix bug where grafana didn't start after mysql on rpm packages. [#16917](https://github.com/grafana/grafana/pull/16917), [@bergquist](https://github.com/bergquist) + * **CloudWatch**: Fixes query order not affecting series ordering & color. [#16408](https://github.com/grafana/grafana/pull/16408), [@mtanda](https://github.com/mtanda) + * **CloudWatch**: Use default alias if there is no alias for metrics. [#16732](https://github.com/grafana/grafana/pull/16732), [@utkarshcmu](https://github.com/utkarshcmu) + * **Config**: Fixes bug where timeouts for alerting was not parsed correctly. [#16784](https://github.com/grafana/grafana/pull/16784), [@aocenas](https://github.com/aocenas) + * **Elasticsearch**: Fix view percentiles metric in table without date histogram. [#15686](https://github.com/grafana/grafana/pull/15686), [@Igor-Ratsuk](https://github.com/Igor-Ratsuk) + * **Explore**: Prevents histogram loading from killing Prometheus instance. [#16768](https://github.com/grafana/grafana/pull/16768), [@hugohaggmark](https://github.com/hugohaggmark) + * **Graph**: Allow override decimals to fully override. [#16414](https://github.com/grafana/grafana/pull/16414), [@torkelo](https://github.com/torkelo) + * **Mixed Datasource**: Fix error when one query is disabled. [#16409](https://github.com/grafana/grafana/pull/16409), [@marefr](https://github.com/marefr) + * **Search**: Fixes search limits and adds a page parameter. [#16458](https://github.com/grafana/grafana/pull/16458), [@torkelo](https://github.com/torkelo) + * **Security**: Responses from backend should not be cached. [#16848](https://github.com/grafana/grafana/pull/16848), [@marefr](https://github.com/marefr) + ### Breaking changes -* **Gauge Panel**: The suffix / prefix options have been removed from the new Guage Panel (introduced in v6.0). [#16870](https://github.com/grafana/grafana/issues/16870). +* **Gauge Panel**: The suffix / prefix options have been removed from the new Gauge Panel (introduced in v6.0). [#16870](https://github.com/grafana/grafana/issues/16870). + # 6.1.6 (2019-04-29) ### Features / Enhancements @@ -258,6 +306,10 @@ * **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.4 (2019-04-29) + +* **Security**: Urgent security patch release. Please read more in our [blog](https://grafana.com/blog/2019/04/29/grafana-5.4.4-and-6.1.6-released-with-important-security-fix/) + # 5.4.3 (2019-01-14) ### Tech diff --git a/Dockerfile b/Dockerfile index 9e5551bd4a4a0..537aaca840cbf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -66,8 +66,8 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \ "$GF_PATHS_DATA" && \ cp "$GF_PATHS_HOME/conf/sample.ini" "$GF_PATHS_CONFIG" && \ cp "$GF_PATHS_HOME/conf/ldap.toml" /etc/grafana/ldap.toml && \ - chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" && \ - chmod 777 "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" + chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING" && \ + chmod 777 -R "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING" COPY --from=0 /go/src/github.com/grafana/grafana/bin/linux-amd64/grafana-server /go/src/github.com/grafana/grafana/bin/linux-amd64/grafana-cli ./bin/ COPY --from=1 /usr/src/app/public ./public diff --git a/README.md b/README.md index 84af184bda80b..075b1dfd7e2d9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[Grafana](https://grafana.com) [![Circle CI](https://circleci.com/gh/grafana/grafana.svg?style=svg)](https://circleci.com/gh/grafana/grafana) [![Go Report Card](https://goreportcard.com/badge/github.com/grafana/grafana)](https://goreportcard.com/report/github.com/grafana/grafana) [![codecov](https://codecov.io/gh/grafana/grafana/branch/master/graph/badge.svg)](https://codecov.io/gh/grafana/grafana) -================ +# [Grafana](https://grafana.com) [![Circle CI](https://circleci.com/gh/grafana/grafana.svg?style=svg)](https://circleci.com/gh/grafana/grafana) [![Go Report Card](https://goreportcard.com/badge/github.com/grafana/grafana)](https://goreportcard.com/report/github.com/grafana/grafana) [![codecov](https://codecov.io/gh/grafana/grafana/branch/master/graph/badge.svg)](https://codecov.io/gh/grafana/grafana) + [Website](https://grafana.com) | [Twitter](https://twitter.com/grafana) | [Community & Forum](https://community.grafana.com) @@ -12,12 +12,15 @@ Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB. --> ## Installation + Head to [docs.grafana.org](http://docs.grafana.org/installation/) for documentation or [download](https://grafana.com/get) to get the latest release. ## Documentation & Support + 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 the latest master builds [here](https://grafana.com/grafana/download) @@ -48,7 +51,7 @@ go run build.go build #### Frontend assets -*For this you need Node.js (LTS version).* +_For this you need Node.js (LTS version)._ ```bash yarn install --pure-lockfile @@ -80,7 +83,7 @@ yarn start:hot env GRAFANA_THEME=light yarn start:hot ``` -*Note: HMR for Angular is not supported. If you edit files in the Angular part of the app, the whole page will reload.* +_Note: HMR for Angular is not supported. If you edit files in the Angular part of the app, the whole page will reload._ Run tests and rebuild on source change: @@ -128,7 +131,9 @@ In your custom.ini uncomment (remove the leading `;`) sign. And set `app_mode = ### Running tests #### Frontend + Execute all frontend tests + ```bash yarn test ``` @@ -139,6 +144,7 @@ Writing & watching frontend tests - Jest will run all test files that end with the name ".test.ts" #### Backend + ```bash # Run Golang tests using sqlite3 as database (default) go test ./pkg/... @@ -150,6 +156,26 @@ GRAFANA_TEST_DB=mysql go test ./pkg/... GRAFANA_TEST_DB=postgres go test ./pkg/... ``` +#### End-to-end + +Execute all end-to-end tests + +```bash +yarn e2e-tests +``` + +Execute all end-to-end tests using using a specific url + +```bash +ENV BASE_URL=http://localhost:3333 yarn e2e-tests +``` + +Debugging all end-to-end tests (BROWSER=1 will start the browser and SLOWMO=1 will delay each puppeteer operation by 100ms) + +```bash +ENV BROWSER=1 SLOWMO=1 yarn e2e-tests +``` + ### Datasource and dashboard provisioning [Here](https://github.com/grafana/grafana/tree/master/devenv) you can find helpful scripts and docker-compose setup @@ -171,4 +197,3 @@ plugin development. ## License Grafana is distributed under [Apache 2.0 License](https://github.com/grafana/grafana/blob/master/LICENSE). - diff --git a/devenv/datasources.yaml b/devenv/datasources.yaml index af673d96c5a04..e0f63bef29995 100644 --- a/devenv/datasources.yaml +++ b/devenv/datasources.yaml @@ -13,6 +13,11 @@ datasources: access: proxy url: http://localhost:9090 + - name: gdev-slow-prometheus + type: prometheus + access: proxy + url: http://localhost:3011 + - name: gdev-testdata type: testdata isDefault: true diff --git a/devenv/dev-dashboards/panel-bargauge/animated_demo.json b/devenv/dev-dashboards/panel-bargauge/animated_demo.json index 1a215d2767798..a061f5766d1f7 100644 --- a/devenv/dev-dashboards/panel-bargauge/animated_demo.json +++ b/devenv/dev-dashboards/panel-bargauge/animated_demo.json @@ -28,35 +28,38 @@ "links": [], "options": { "displayMode": "gradient", - "maxValue": 100, - "minValue": 0, - "orientation": "vertical", - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "watt" }, - { - "color": "orange", - "index": 1, - "value": 40 - }, - { - "color": "red", - "index": 2, - "value": 80 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "watt" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "orange", + "index": 1, + "value": 40 + }, + { + "color": "red", + "index": 2, + "value": 80 + } + ], + "values": false + }, + "orientation": "vertical" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "refId": "A", @@ -145,35 +148,38 @@ "links": [], "options": { "displayMode": "gradient", - "maxValue": 100, - "minValue": 0, - "orientation": "horizontal", - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "orange", - "index": 1, - "value": 55 + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "watt" }, - { - "color": "red", - "index": 2, - "value": 95 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "watt" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "orange", + "index": 1, + "value": 55 + }, + { + "color": "red", + "index": 2, + "value": 95 + } + ], + "values": false + }, + "orientation": "horizontal" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "refId": "E", @@ -269,40 +275,43 @@ "links": [], "options": { "displayMode": "gradient", - "maxValue": 100, - "minValue": 0, - "orientation": "horizontal", - "thresholds": [ - { - "color": "blue", - "index": 0, - "value": null - }, - { - "color": "green", - "index": 1, - "value": 20 + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "celsius" }, - { - "color": "orange", - "index": 2, - "value": 40 - }, - { - "color": "red", - "index": 3, - "value": 80 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "celsius" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "blue", + "index": 0, + "value": null + }, + { + "color": "green", + "index": 1, + "value": 20 + }, + { + "color": "orange", + "index": 2, + "value": 40 + }, + { + "color": "red", + "index": 3, + "value": 80 + } + ], + "values": false + }, + "orientation": "horizontal" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "alias": "Inside", @@ -356,40 +365,43 @@ "links": [], "options": { "displayMode": "basic", - "maxValue": 100, - "minValue": 0, - "orientation": "horizontal", - "thresholds": [ - { - "color": "blue", - "index": 0, - "value": null - }, - { - "color": "green", - "index": 1, - "value": 20 + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "celsius" }, - { - "color": "orange", - "index": 2, - "value": 40 - }, - { - "color": "red", - "index": 3, - "value": 80 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "celsius" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "blue", + "index": 0, + "value": null + }, + { + "color": "green", + "index": 1, + "value": 20 + }, + { + "color": "orange", + "index": 2, + "value": 40 + }, + { + "color": "red", + "index": 3, + "value": 80 + } + ], + "values": false + }, + "orientation": "horizontal" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "alias": "Inside", @@ -471,5 +483,5 @@ "timezone": "", "title": "Bar Gauge Animated Demo", "uid": "k5IUwQeikaa", - "version": 4 + "version": 1 } diff --git a/devenv/dev-dashboards/panel-bargauge/gradient_demo.json b/devenv/dev-dashboards/panel-bargauge/gradient_demo.json index 3e5c224db4527..ffe68eb7a863b 100644 --- a/devenv/dev-dashboards/panel-bargauge/gradient_demo.json +++ b/devenv/dev-dashboards/panel-bargauge/gradient_demo.json @@ -28,35 +28,38 @@ "links": [], "options": { "displayMode": "gradient", - "maxValue": 100, - "minValue": 0, - "orientation": "vertical", - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "watt" }, - { - "color": "orange", - "index": 1, - "value": 40 - }, - { - "color": "red", - "index": 2, - "value": 80 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "watt" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "orange", + "index": 1, + "value": 40 + }, + { + "color": "red", + "index": 2, + "value": 80 + } + ], + "values": false + }, + "orientation": "vertical" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "refId": "A", @@ -145,35 +148,38 @@ "links": [], "options": { "displayMode": "gradient", - "maxValue": 100, - "minValue": 0, - "orientation": "horizontal", - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "watt" }, - { - "color": "orange", - "index": 1, - "value": 65 - }, - { - "color": "red", - "index": 2, - "value": 95 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "watt" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "orange", + "index": 1, + "value": 65 + }, + { + "color": "red", + "index": 2, + "value": 95 + } + ], + "values": false + }, + "orientation": "horizontal" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "refId": "E", @@ -269,40 +275,43 @@ "links": [], "options": { "displayMode": "gradient", - "maxValue": 100, - "minValue": 0, - "orientation": "horizontal", - "thresholds": [ - { - "color": "blue", - "index": 0, - "value": null + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "celsius" }, - { - "color": "green", - "index": 1, - "value": 20 - }, - { - "color": "orange", - "index": 2, - "value": 40 - }, - { - "color": "red", - "index": 3, - "value": 80 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "celsius" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "blue", + "index": 0, + "value": null + }, + { + "color": "green", + "index": 1, + "value": 20 + }, + { + "color": "orange", + "index": 2, + "value": 40 + }, + { + "color": "red", + "index": 3, + "value": 80 + } + ], + "values": false + }, + "orientation": "horizontal" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "alias": "Inside", @@ -363,5 +372,5 @@ "timezone": "", "title": "Bar Gauge Gradient Demo", "uid": "RndRQw6mz", - "version": 3 + "version": 1 } diff --git a/devenv/dev-dashboards/panel-bargauge/many_modes_demo.json b/devenv/dev-dashboards/panel-bargauge/many_modes_demo.json index fb08950538f00..8b498e78ef156 100644 --- a/devenv/dev-dashboards/panel-bargauge/many_modes_demo.json +++ b/devenv/dev-dashboards/panel-bargauge/many_modes_demo.json @@ -28,35 +28,38 @@ "links": [], "options": { "displayMode": "lcd", - "maxValue": 100, - "minValue": 0, - "orientation": "vertical", - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "watt" }, - { - "color": "orange", - "index": 1, - "value": 40 - }, - { - "color": "red", - "index": 2, - "value": 80 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "watt" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "orange", + "index": 1, + "value": 40 + }, + { + "color": "red", + "index": 2, + "value": 80 + } + ], + "values": false + }, + "orientation": "vertical" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "refId": "A", @@ -145,30 +148,33 @@ "links": [], "options": { "displayMode": "lcd", - "maxValue": 100, - "minValue": 0, - "orientation": "vertical", - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "percent" }, - { - "color": "red", - "index": 1, - "value": 80 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "percent" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "red", + "index": 1, + "value": 80 + } + ], + "values": false + }, + "orientation": "vertical" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "refId": "A", @@ -191,40 +197,43 @@ "links": [], "options": { "displayMode": "gradient", - "maxValue": 100, - "minValue": 0, - "orientation": "horizontal", - "thresholds": [ - { - "color": "blue", - "index": 0, - "value": null - }, - { - "color": "green", - "index": 1, - "value": 20 + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "celsius" }, - { - "color": "orange", - "index": 2, - "value": 40 - }, - { - "color": "red", - "index": 3, - "value": 80 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "celsius" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "blue", + "index": 0, + "value": null + }, + { + "color": "green", + "index": 1, + "value": 20 + }, + { + "color": "orange", + "index": 2, + "value": 40 + }, + { + "color": "red", + "index": 3, + "value": 80 + } + ], + "values": false + }, + "orientation": "horizontal" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "alias": "Inside", @@ -278,35 +287,38 @@ "links": [], "options": { "displayMode": "basic", - "maxValue": 100, - "minValue": 0, - "orientation": "horizontal", - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "watt" }, - { - "color": "purple", - "index": 1, - "value": 50 - }, - { - "color": "blue", - "index": 2, - "value": 70 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "watt" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "purple", + "index": 1, + "value": 50 + }, + { + "color": "blue", + "index": 2, + "value": 70 + } + ], + "values": false + }, + "orientation": "horizontal" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "refId": "H", diff --git a/devenv/dev-dashboards/panel-bargauge/retro_led_demo.json b/devenv/dev-dashboards/panel-bargauge/retro_led_demo.json index 4f2e22f73304d..3fe8272c4dfe2 100644 --- a/devenv/dev-dashboards/panel-bargauge/retro_led_demo.json +++ b/devenv/dev-dashboards/panel-bargauge/retro_led_demo.json @@ -28,35 +28,38 @@ "links": [], "options": { "displayMode": "lcd", - "maxValue": 100, - "minValue": 0, - "orientation": "vertical", - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "watt" }, - { - "color": "orange", - "index": 1, - "value": 40 - }, - { - "color": "red", - "index": 2, - "value": 80 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "watt" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "orange", + "index": 1, + "value": 40 + }, + { + "color": "red", + "index": 2, + "value": 80 + } + ], + "values": false + }, + "orientation": "vertical" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "refId": "A", @@ -145,30 +148,33 @@ "links": [], "options": { "displayMode": "lcd", - "maxValue": 100, - "minValue": 0, - "orientation": "vertical", - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "percent" }, - { - "color": "red", - "index": 1, - "value": 80 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "percent" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "red", + "index": 1, + "value": 80 + } + ], + "values": false + }, + "orientation": "vertical" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "refId": "A", @@ -191,35 +197,38 @@ "links": [], "options": { "displayMode": "lcd", - "maxValue": 100, - "minValue": 0, - "orientation": "horizontal", - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "orange", - "index": 1, - "value": 40 + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "celsius" }, - { - "color": "red", - "index": 2, - "value": 80 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "celsius" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "orange", + "index": 1, + "value": 40 + }, + { + "color": "red", + "index": 2, + "value": 80 + } + ], + "values": false + }, + "orientation": "horizontal" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "alias": "Inside", @@ -273,35 +282,38 @@ "links": [], "options": { "displayMode": "lcd", - "maxValue": 100, - "minValue": 0, - "orientation": "horizontal", - "thresholds": [ - { - "color": "green", - "index": 0, - "value": null - }, - { - "color": "orange", - "index": 1, - "value": 85 + "fieldOptions": { + "calcs": ["mean"], + "defaults": { + "decimals": null, + "max": 100, + "min": 0, + "unit": "watt" }, - { - "color": "red", - "index": 2, - "value": 95 - } - ], - "valueMappings": [], - "valueOptions": { - "decimals": null, - "prefix": "", - "stat": "mean", - "suffix": "", - "unit": "watt" - } + "mappings": [], + "override": {}, + "thresholds": [ + { + "color": "green", + "index": 0, + "value": null + }, + { + "color": "orange", + "index": 1, + "value": 85 + }, + { + "color": "red", + "index": 2, + "value": 95 + } + ], + "values": false + }, + "orientation": "horizontal" }, + "pluginVersion": "6.2.0-pre", "targets": [ { "refId": "H", @@ -384,5 +396,5 @@ "timezone": "", "title": "Bar Gauge LED Demo", "uid": "0G3rbkqmkaa", - "version": 42 + "version": 1 } diff --git a/devenv/docker/blocks/prometheus_basic_auth_proxy/Dockerfile b/devenv/docker/blocks/prometheus_basic_auth_proxy/Dockerfile new file mode 100644 index 0000000000000..04de507499d1b --- /dev/null +++ b/devenv/docker/blocks/prometheus_basic_auth_proxy/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx:alpine + +COPY nginx.conf /etc/nginx/nginx.conf +COPY htpasswd /etc/nginx/htpasswd diff --git a/devenv/docker/blocks/prometheus_basic_auth_proxy/docker-compose.yaml b/devenv/docker/blocks/prometheus_basic_auth_proxy/docker-compose.yaml new file mode 100644 index 0000000000000..d7b1d8f866f04 --- /dev/null +++ b/devenv/docker/blocks/prometheus_basic_auth_proxy/docker-compose.yaml @@ -0,0 +1,6 @@ +# This will proxy all requests for http://localhost:10090 to +# http://prometheus:9090 (Prometheus inside the docker compose) + + nginxproxy: + build: docker/blocks/nginx_proxy + network_mode: host diff --git a/devenv/docker/blocks/prometheus_basic_auth_proxy/htpasswd b/devenv/docker/blocks/prometheus_basic_auth_proxy/htpasswd new file mode 100755 index 0000000000000..c04a5ab0c9f6b --- /dev/null +++ b/devenv/docker/blocks/prometheus_basic_auth_proxy/htpasswd @@ -0,0 +1 @@ +prom:$apr1$bfu32njz$HHDDTjaeWHDzQs2UMXP.C1 diff --git a/devenv/docker/blocks/prometheus_basic_auth_proxy/nginx.conf b/devenv/docker/blocks/prometheus_basic_auth_proxy/nginx.conf new file mode 100644 index 0000000000000..fd968a9c96954 --- /dev/null +++ b/devenv/docker/blocks/prometheus_basic_auth_proxy/nginx.conf @@ -0,0 +1,34 @@ +events { } + +http { + server { + + listen 10090; + + location / { + + # Removes any Access-Control-Allow-Origin from Prometheus itself. When accessing from browser, having * or + # multiple values is not allowed in some cases + proxy_hide_header Access-Control-Allow-Origin; + + # Allow the origin access. This is kinda wildcard but for browser it seems more strict and is needed for + # withCredentials requests. + add_header Access-Control-Allow-Origin $http_origin; + + # When using withCredentials requests this must be true. + add_header Access-Control-Allow-Credentials true; + + # Ask for basic auth except for pre flight OPTIONS request. + limit_except OPTIONS { + ################################################################ + # The htpasswd file contains user: + # prom: test + ################################################################ + auth_basic "prom"; + auth_basic_user_file /etc/nginx/htpasswd; + } + + proxy_pass http://prometheus:9090/; + } + } +} diff --git a/devenv/docker/blocks/slow_proxy/Dockerfile b/devenv/docker/blocks/slow_proxy/Dockerfile new file mode 100644 index 0000000000000..e553cb6727c6d --- /dev/null +++ b/devenv/docker/blocks/slow_proxy/Dockerfile @@ -0,0 +1,7 @@ + +FROM golang:latest +ADD main.go / +WORKDIR / +RUN go build -o main . +EXPOSE 3011 +ENTRYPOINT ["/main"] diff --git a/devenv/docker/blocks/slow_proxy/docker-compose.yaml b/devenv/docker/blocks/slow_proxy/docker-compose.yaml new file mode 100644 index 0000000000000..4af9396a510aa --- /dev/null +++ b/devenv/docker/blocks/slow_proxy/docker-compose.yaml @@ -0,0 +1,7 @@ + slow_proxy: + build: docker/blocks/slow_proxy + network_mode: host + ports: + - "3011:3011" + environment: + ORIGIN_SERVER: "http://localhost:9090/" \ No newline at end of file diff --git a/devenv/docker/blocks/slow_proxy/main.go b/devenv/docker/blocks/slow_proxy/main.go new file mode 100644 index 0000000000000..0db800849130f --- /dev/null +++ b/devenv/docker/blocks/slow_proxy/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "net/http/httputil" + "net/url" + "os" + "time" +) + +func main() { + origin := os.Getenv("ORIGIN_SERVER") + if origin == "" { + origin = "http://localhost:9090/" + } + + sleep := time.Minute + + originURL, _ := url.Parse(origin) + proxy := httputil.NewSingleHostReverseProxy(originURL) + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Printf("sleeping for %s then proxying request: %s", sleep.String(), r.RequestURI) + <-time.After(sleep) + proxy.ServeHTTP(w, r) + }) + + log.Fatal(http.ListenAndServe(":3011", nil)) +} diff --git a/docs/sources/guides/whats-new-in-v6-2.md b/docs/sources/guides/whats-new-in-v6-2.md new file mode 100644 index 0000000000000..00337c8ef2a28 --- /dev/null +++ b/docs/sources/guides/whats-new-in-v6-2.md @@ -0,0 +1,93 @@ ++++ +title = "What's New in Grafana v6.2" +description = "Feature & improvement highlights for Grafana v6.2" +keywords = ["grafana", "new", "documentation", "6.2"] +type = "docs" +[menu.docs] +name = "Version 6.2" +identifier = "v6.2" +parent = "whatsnew" +weight = -13 ++++ + +# What's New in Grafana v6.2 + +> More content will be added to this guide before the stable release. + +Grafana v6.2 Beta is now [available for download](https://grafana.com/grafana/download/beta). + +For all details please read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) + +If you use a password for your datasources please read the [upgrade notes](/installation/upgrading/#upgrading-to-v6-2). + +## Improved security + +- Ensure encryption of datasource secrets +- Embedding Grafana not allowed per default +- Disable browser caching for full page requests + +## Provisioning + +- Environment variables support, see [Using environment variables](/administration/provisioning/#using-environment-variables) for more information. +- Reload provisioning configs, see [Admin HTTP API](/http_api/admin/#reload-provisioning-configurations) for more information. +- Do not allow deletion of provisioned dashboards +- When trying to delete or save provisioned dashboard, relative file path to the file is shown in the dialog. + +## Official support for Elasticsearch 7 + +Grafana v6.2 ships with official support for Elasticsearch v7, see [Using Elasticsearch in Grafana](/features/datasources/elasticsearch/#elasticsearch-version) for more information. + +## Bar Gauge Panel + +Grafana v6.2 ships with a new exciting panel! This new panel, named Bar Gauge, is very similar to the current +Gauge panel and shares almost all it's options. The main difference is that the Bar Gauge uses both horizontal and +vertical space much better and can be more efficiently stacked both vertically and horizontally. The Bar Gauge also +comes with 3 unique display modes, Basic, Gradient, and Retro LED. Read the +[preview article](https://grafana.com/blog/2019/04/11/sneak-preview-of-new-visualizations-coming-to-grafana/) to learn +more about the design & features of this new panel. + +Retro LED display mode +{{< docs-imagebox img="/assets/img/blog/bargauge/bar_gauge_retro_led.jpg" max-width="800px" caption="Bar Gauge LED mode" >}} + +Gradient mode +{{< docs-imagebox img="/assets/img/blog/bargauge/gradient.jpg" max-width="800px" caption="Bar Gauge Gradient mode" >}} + +## Improved table data support + +We have been working on improving table support in our new react panels (Gauge & Bar Gauge) and this is ongoing work +that will eventually come to the new Graph & Singlestat & Table panels we are working on. But you can see it already in +the Gauge and Bar Gauge panels. Without any config, you can visualize any number of columns or choose to visualize each +row as its own gauge. + +## Lazy loading of panels out of view + +This has been one of the most requested features for many years and is now finally here! Lazy loading of panels means +Grafana will not issue any data queries for panels that are not visible. This will greatly reduce the load +on your data source backends when loading dashboards with many panels. + +## Panels without title + +Sometimes your panels do not need a title and having that panel header still take up space makes singlestats and +other panels look strange and have bad vertical centering. In v6.2 Grafana will allow panel content (visualizations) +to use the full panel height in case there is no panel title. + +{{< docs-imagebox img="/img/docs/v62/panels_with_no_title.jpg" max-width="800px" caption="Bar Gauge Gradient mode" >}} + +## Minor Features and Fixes + +This release contains a lot of small features and fixes: + +- Explore - Adds user time zone support, reconnect for failing datasources and a fix that prevents killing Prometheus instances when Histogram metrics are loaded. +- Alerting - Adds support for configuring timeout durations and retries, see [configuration](/installation/configuration/#evaluation-timeout-seconds) for more information. +- Elasticsearch - A small bug fix to properly display percentiles metrics in table panel. +- InfluxDB - Support for POST HTTP verb. +- CloudWatch - Important fix for default alias disappearing in v6.1. +- Search - Works in a scope of dashboard's folder by default when viewing dashboard + +Checkout the [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) file for a complete list of new features, changes, and bug fixes. + +A huge thanks to our community for all the reported issues, bug fixes and feedback. + +## Upgrading + +Read important [upgrade notes](/installation/upgrading/#upgrading-to-v6-2). diff --git a/docs/sources/index.md b/docs/sources/index.md index 744b04203e432..17f99d4e659f1 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -60,9 +60,9 @@ aliases = ["v1.1", "guides/reference/admin", "v3.1"]

Provisioning

A guide to help you automate your Grafana setup & configuration.

- }}" class="nav-cards__item nav-cards__item--guide"> -

What's new in v6.0

-

Article on all the new cool features and enhancements in v6.0

+
}}" class="nav-cards__item nav-cards__item--guide"> +

What's new in v6.2

+

Article on all the new cool features and enhancements in v6.2

}}" class="nav-cards__item nav-cards__item--guide">

Screencasts

diff --git a/docs/sources/installation/upgrading.md b/docs/sources/installation/upgrading.md index 6cedf46afa147..bd2a5434b25fd 100644 --- a/docs/sources/installation/upgrading.md +++ b/docs/sources/installation/upgrading.md @@ -157,6 +157,7 @@ The default cookie name for storing the auth token is `grafana_session`. you can Datasources store passwords and basic auth passwords in secureJsonData encrypted by default. Existing datasource will keep working with unencrypted passwords. If you want to migrate to encrypted storage for your existing datasources you can do that by: + - For datasources created through UI, you need to go to datasource config, re enter the password or basic auth password and save the datasource. - For datasources created by provisioning, you need to update your config file and use secureJsonData.password or diff --git a/docs/sources/plugins/developing/auth-for-datasources.md b/docs/sources/plugins/developing/auth-for-datasources.md index c03793e745ff2..49cd747e2b640 100644 --- a/docs/sources/plugins/developing/auth-for-datasources.md +++ b/docs/sources/plugins/developing/auth-for-datasources.md @@ -51,6 +51,36 @@ then the Grafana proxy will transform it into "https://management.azure.com/foo/ The `method` parameter is optional. It can be set to any HTTP verb to provide more fine-grained control. +### Dynamic Routes + +When using routes, you can also reference a variable stored in JsonData or SecureJsonData which will be interpolated when connecting to the datasource. + +With JsonData: +```json +"routes": [ + { + "path": "custom/api/v5/*", + "method": "*", + "url": "{{.JsonData.dynamicUrl}}", + ... + }, +] +``` + +With SecureJsonData: +```json +"routes": [{ + "path": "custom/api/v5/*", + "method": "*", + "url": "{{.SecureJsonData.dynamicUrl}}", + ... +}] +``` + +In the above example, the app is able to set the value for `dynamicUrl` in JsonData or SecureJsonData and it will be replaced on-demand. + +An app using this feature can be found [here](https://github.com/grafana/kentik-app). + ## Encrypting Sensitive Data When a user saves a password or secret with your datasource plugin's Config page, then you can save data to a column in the datasource table called `secureJsonData` that is an encrypted blob. Any data saved in the blob is encrypted by Grafana and can only be decrypted by the Grafana server on the backend. This means once a password is saved, no sensitive data is sent to the browser. If the password is saved in the `jsonData` blob or the `password` field then it is unencrypted and anyone with Admin access (with the help of Chrome Developer Tools) can read it. diff --git a/jest.config.e2e.js b/jest.config.e2e.js new file mode 100644 index 0000000000000..feb6f7d4f3a34 --- /dev/null +++ b/jest.config.e2e.js @@ -0,0 +1,15 @@ +require('module-alias/register'); + +module.exports = { + verbose: false, + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + }, + moduleDirectories: ['node_modules', 'public'], + roots: ['/public/e2e-test'], + testRegex: '(\\.|/)(test)\\.(jsx?|tsx?)$', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], + setupFiles: [], + globals: { 'ts-jest': { isolatedModules: true } }, + setupFilesAfterEnv: ['expect-puppeteer', '/public/e2e-test/install/install.ts'], +}; diff --git a/latest.json b/latest.json index 47c25593c18d6..ff5fac8e6d550 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { "stable": "6.1.6", - "testing": "6.1.6" + "testing": "6.2.0-beta1" } diff --git a/package.json b/package.json index 6b5f01db47b0d..69309e2e341b6 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "company": "Grafana Labs" }, "name": "grafana", - "version": "6.2.0-pre", + "version": "6.3.0-pre", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git" @@ -25,13 +25,17 @@ "@types/commander": "2.12.2", "@types/d3": "4.13.1", "@types/enzyme": "3.9.0", + "@types/expect-puppeteer": "3.3.1", "@types/inquirer": "0.0.43", "@types/jest": "24.0.11", "@types/jquery": "1.10.35", "@types/lodash": "4.14.123", "@types/node": "11.13.4", "@types/papaparse": "4.5.9", - "@types/react": "16.8.13", + "@types/pixelmatch": "4.0.0", + "@types/pngjs": "3.3.2", + "@types/puppeteer-core": "1.9.0", + "@types/react": "16.8.16", "@types/react-dom": "16.8.4", "@types/react-grid-layout": "0.16.7", "@types/react-select": "2.0.15", @@ -55,6 +59,7 @@ "es6-promise": "3.3.1", "es6-shim": "0.35.5", "execa": "1.0.0", + "expect-puppeteer": "4.1.1", "expect.js": "0.2.0", "expose-loader": "0.7.5", "file-loader": "3.0.1", @@ -85,6 +90,7 @@ "load-grunt-tasks": "3.5.2", "mini-css-extract-plugin": "0.5.0", "mocha": "4.1.0", + "module-alias": "2.2.0", "monaco-editor": "0.15.6", "ng-annotate-loader": "0.6.1", "ng-annotate-webpack-plugin": "0.3.0", @@ -94,10 +100,13 @@ "optimize-css-assets-webpack-plugin": "5.0.1", "ora": "3.2.0", "phantomjs-prebuilt": "2.1.16", + "pixelmatch": "4.0.2", + "pngjs": "3.4.0", "postcss-browser-reporter": "0.5.0", "postcss-loader": "3.0.0", "postcss-reporter": "6.0.1", "prettier": "1.16.4", + "puppeteer-core": "1.15.0", "react-hooks-testing-library": "0.3.7", "react-hot-loader": "4.8.0", "react-test-renderer": "16.8.4", @@ -107,6 +116,7 @@ "sass-lint": "1.12.1", "sass-loader": "7.1.0", "semver": "5.7.0", + "simple-git": "^1.112.0", "sinon": "1.17.6", "style-loader": "0.23.1", "systemjs": "0.20.19", @@ -139,20 +149,23 @@ "tslint": "tslint -c tslint.json --project tsconfig.json", "typecheck": "tsc --noEmit", "jest": "jest --notify --watch", + "e2e-tests": "jest --runInBand --config=jest.config.e2e.js", "api-tests": "jest --notify --watch --config=tests/api/jest.js", "storybook": "cd packages/grafana-ui && yarn storybook", "storybook:build": "cd packages/grafana-ui && yarn storybook:build", "themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts", "prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\"", + "gui:tslint": "tslint -c ./packages/grafana-ui/tslint.json --project ./packages/grafana-ui/tsconfig.json", "gui:build": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:build", "gui:releasePrepare": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:release", "gui:publish": "cd packages/grafana-ui/dist && npm publish --access public", "gui:release": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:release -p --createVersionCommit", + "precommit": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts precommit", "cli": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts" }, "husky": { "hooks": { - "pre-commit": "lint-staged && grunt precommit" + "pre-commit": "lint-staged && npm run precommit" } }, "lint-staged": { @@ -189,6 +202,7 @@ "d3-scale-chromatic": "1.3.3", "eventemitter3": "2.0.3", "file-saver": "1.3.8", + "fast-text-encoding": "^1.0.0", "immutable": "3.8.2", "jquery": "3.4.0", "lodash": "4.17.11", @@ -239,5 +253,8 @@ "**/@types/*", "**/@types/*/**" ] + }, + "_moduleAliases": { + "puppeteer": "node_modules/puppeteer-core" } } diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 425f1bb4977db..7db50f75168fe 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -55,7 +55,7 @@ "@types/node": "10.14.1", "@types/papaparse": "4.5.9", "@types/pretty-format": "20.0.1", - "@types/react": "16.8.13", + "@types/react": "16.8.16", "@types/react-custom-scrollbars": "4.0.5", "@types/react-test-renderer": "16.8.1", "@types/react-transition-group": "2.0.16", diff --git a/packages/grafana-ui/rollup.config.ts b/packages/grafana-ui/rollup.config.ts index 28f949d6e4e79..7dc70ffc648e0 100644 --- a/packages/grafana-ui/rollup.config.ts +++ b/packages/grafana-ui/rollup.config.ts @@ -41,6 +41,7 @@ const buildCjsPackage = ({ env }) => { 'sortBy', 'uniqueId', 'zip', + 'omit', ], '../../node_modules/react-color/lib/components/common': ['Saturation', 'Hue', 'Alpha'], }, diff --git a/packages/grafana-ui/src/components/Graph/mockGraphWithLegendData.ts b/packages/grafana-ui/src/components/Graph/mockGraphWithLegendData.ts index 2e150eb04ea53..54d3a354b27bf 100644 --- a/packages/grafana-ui/src/components/Graph/mockGraphWithLegendData.ts +++ b/packages/grafana-ui/src/components/Graph/mockGraphWithLegendData.ts @@ -1,6 +1,6 @@ import { GraphWithLegendProps } from './GraphWithLegend'; -import moment from 'moment'; import { LegendDisplayMode } from '../Legend/Legend'; +import { dateTime } from '../../utils/moment_wrapper'; // import { LegendList } from '../Legend/LegendList'; export const mockGraphWithLegendData = ({ @@ -3287,8 +3287,8 @@ export const mockGraphWithLegendData = ({ }, ], timeRange: { - from: moment('2019-04-09T07:01:14.247Z'), - to: moment('2019-04-09T13:01:14.247Z'), + from: dateTime('2019-04-09T07:01:14.247Z'), + to: dateTime('2019-04-09T13:01:14.247Z'), raw: { from: 'now-6h', to: 'now', diff --git a/packages/grafana-ui/src/components/Input/Input.story.tsx b/packages/grafana-ui/src/components/Input/Input.story.tsx new file mode 100644 index 0000000000000..bed23c10bc712 --- /dev/null +++ b/packages/grafana-ui/src/components/Input/Input.story.tsx @@ -0,0 +1,40 @@ +import React, { useState } from 'react'; +import { zip, fromPairs } from 'lodash'; + +import { storiesOf } from '@storybook/react'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { Input } from './Input'; +import { text, select } from '@storybook/addon-knobs'; +import { EventsWithValidation } from '../../utils'; + +const getKnobs = () => { + return { + validation: text('Validation regex (will do a partial match if you do not anchor it)', ''), + validationErrorMessage: text('Validation error message', 'Input not valid'), + validationEvent: select( + 'Validation event', + fromPairs(zip(Object.keys(EventsWithValidation), Object.values(EventsWithValidation))), + EventsWithValidation.onBlur + ), + }; +}; + +const Wrapper = () => { + const { validation, validationErrorMessage, validationEvent } = getKnobs(); + const [value, setValue] = useState(''); + const validations = { + [validationEvent]: [ + { + rule: (value: string) => { + return !!value.match(validation); + }, + errorMessage: validationErrorMessage, + }, + ], + }; + return setValue(e.currentTarget.value)} validationEvents={validations} />; +}; + +const story = storiesOf('UI/Input', module); +story.addDecorator(withCenteredStory); +story.add('input', () => ); diff --git a/packages/grafana-ui/src/components/Input/Input.tsx b/packages/grafana-ui/src/components/Input/Input.tsx index b75a8c1776058..b8165c48dca6f 100644 --- a/packages/grafana-ui/src/components/Input/Input.tsx +++ b/packages/grafana-ui/src/components/Input/Input.tsx @@ -18,7 +18,11 @@ interface Props extends React.HTMLProps { onChange?: (event: React.ChangeEvent, status?: InputStatus) => void; } -export class Input extends PureComponent { +interface State { + error: string | null; +} + +export class Input extends PureComponent { static defaultProps = { className: '', }; diff --git a/packages/grafana-ui/src/components/PieChart/PieChart.tsx b/packages/grafana-ui/src/components/PieChart/PieChart.tsx index f23349b7746bb..4c53959467de5 100644 --- a/packages/grafana-ui/src/components/PieChart/PieChart.tsx +++ b/packages/grafana-ui/src/components/PieChart/PieChart.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import { select, pie, arc, event } from 'd3'; -import { sum } from 'lodash'; +import sum from 'lodash/sum'; import { GrafanaThemeType, DisplayValue } from '../../types'; import { Themeable } from '../../index'; diff --git a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx index b53355e01783f..2046a5a50cee4 100644 --- a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx +++ b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx @@ -1,7 +1,8 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; - -import { SelectOptionItem, ButtonSelect, Tooltip } from '@grafana/ui'; +import { SelectOptionItem } from '../Select/Select'; +import { Tooltip } from '../Tooltip/Tooltip'; +import { ButtonSelect } from '../Select/ButtonSelect'; export const offOption = { label: 'Off', value: '' }; export const defaultIntervals = ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d']; diff --git a/packages/grafana-ui/src/components/SecretFormFied/SecretFormField.tsx b/packages/grafana-ui/src/components/SecretFormFied/SecretFormField.tsx index 234b36c2a43d3..0ed46ce2486ed 100644 --- a/packages/grafana-ui/src/components/SecretFormFied/SecretFormField.tsx +++ b/packages/grafana-ui/src/components/SecretFormFied/SecretFormField.tsx @@ -1,4 +1,4 @@ -import { omit } from 'lodash'; +import omit from 'lodash/omit'; import React, { InputHTMLAttributes, FunctionComponent } from 'react'; import { FormField } from '..'; diff --git a/packages/grafana-ui/src/components/Select/ButtonSelect.tsx b/packages/grafana-ui/src/components/Select/ButtonSelect.tsx index 5b079d0195f6e..7ebfb07c0da00 100644 --- a/packages/grafana-ui/src/components/Select/ButtonSelect.tsx +++ b/packages/grafana-ui/src/components/Select/ButtonSelect.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import Select, { SelectOptionItem } from './Select'; -import { PopperContent } from '@grafana/ui/src/components/Tooltip/PopperController'; +import { PopperContent } from '../Tooltip/PopperController'; interface ButtonComponentProps { label: string | undefined; diff --git a/packages/grafana-ui/src/components/Select/Select.tsx b/packages/grafana-ui/src/components/Select/Select.tsx index e578b707db66a..f36f78a50feae 100644 --- a/packages/grafana-ui/src/components/Select/Select.tsx +++ b/packages/grafana-ui/src/components/Select/Select.tsx @@ -17,8 +17,8 @@ import IndicatorsContainer from './IndicatorsContainer'; import NoOptionsMessage from './NoOptionsMessage'; import resetSelectStyles from './resetSelectStyles'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; -import { PopperContent } from '@grafana/ui/src/components/Tooltip/PopperController'; -import { Tooltip } from '@grafana/ui'; +import { PopperContent } from '../Tooltip/PopperController'; +import { Tooltip } from '../Tooltip/Tooltip'; export interface SelectOptionItem { label?: string; diff --git a/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx b/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx index 37afe2c62d8f4..b44a49f9603ec 100644 --- a/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx +++ b/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx @@ -1,47 +1,48 @@ import { PureComponent } from 'react'; +import { interval, Subscription, empty, Subject } from 'rxjs'; +import { tap, switchMap } from 'rxjs/operators'; + import { stringToMs } from '../../utils/string'; interface Props { func: () => any; // TODO + loading: boolean; interval: string; } export class SetInterval extends PureComponent { - private intervalId = 0; + private propsSubject: Subject; + private subscription: Subscription | null; - componentDidMount() { - this.addInterval(); + constructor(props: Props) { + super(props); + this.propsSubject = new Subject(); + this.subscription = null; } - componentDidUpdate(prevProps: Props) { - const { interval } = this.props; - if (interval !== prevProps.interval) { - this.clearInterval(); - this.addInterval(); - } + componentDidMount() { + this.subscription = this.propsSubject + .pipe( + switchMap(props => { + return props.loading ? empty() : interval(stringToMs(props.interval)); + }), + tap(() => this.props.func()) + ) + .subscribe(); + this.propsSubject.next(this.props); } - componentWillUnmount() { - this.clearInterval(); + componentDidUpdate() { + this.propsSubject.next(this.props); } - addInterval = () => { - const { func, interval } = this.props; - - if (interval) { - func().then(() => { - if (interval) { - this.intervalId = window.setTimeout(() => { - this.addInterval(); - }, stringToMs(interval)); - } - }); + componentWillUnmount() { + if (this.subscription) { + this.subscription.unsubscribe(); } - }; - clearInterval = () => { - window.clearTimeout(this.intervalId); - }; + this.propsSubject.unsubscribe(); + } render() { return null; diff --git a/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx b/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx index fccf211004dfb..85a255656363d 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx +++ b/packages/grafana-ui/src/components/SingleStatShared/FieldDisplayEditor.tsx @@ -2,13 +2,16 @@ import React, { PureComponent, ChangeEvent } from 'react'; // Components -import { FormField, FormLabel, PanelOptionsGroup, StatsPicker, ReducerID } from '@grafana/ui'; +import { FormLabel } from '../FormLabel/FormLabel'; +import { FormField } from '../FormField/FormField'; +import { StatsPicker } from '../StatsPicker/StatsPicker'; // Types import { FieldDisplayOptions, DEFAULT_FIELD_DISPLAY_VALUES_LIMIT } from '../../utils/fieldDisplay'; import { Field } from '../../types/data'; import Select, { SelectOptionItem } from '../Select/Select'; import { toNumberString, toIntegerOrUndefined } from '../../utils'; +import { ReducerID } from '../../utils/fieldReducer'; const showOptions: Array> = [ { @@ -24,75 +27,71 @@ const showOptions: Array> = [ ]; export interface Props { - options: FieldDisplayOptions; - onChange: (valueOptions: FieldDisplayOptions) => void; labelWidth?: number; - children?: JSX.Element[]; + value: FieldDisplayOptions; + onChange: (value: FieldDisplayOptions, event?: React.SyntheticEvent) => void; } export class FieldDisplayEditor extends PureComponent { onShowValuesChange = (item: SelectOptionItem) => { const val = item.value === true; - this.props.onChange({ ...this.props.options, values: val }); + this.props.onChange({ ...this.props.value, values: val }); }; onCalcsChange = (calcs: string[]) => { - this.props.onChange({ ...this.props.options, calcs }); + this.props.onChange({ ...this.props.value, calcs }); }; onDefaultsChange = (value: Partial) => { - this.props.onChange({ ...this.props.options, defaults: value }); + this.props.onChange({ ...this.props.value, defaults: value }); }; onLimitChange = (event: ChangeEvent) => { this.props.onChange({ - ...this.props.options, + ...this.props.value, limit: toIntegerOrUndefined(event.target.value), }); }; render() { - const { options, children } = this.props; - const { calcs, values, limit } = options; + const { value } = this.props; + const { calcs, values, limit } = value; const labelWidth = this.props.labelWidth || 5; return ( - - <> + <> +
+ Show + Calc +
- {values ? ( - - ) : ( -
- Calc - -
- )} - {children} - -
+ )} + ); } } diff --git a/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx b/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx index 290791d492315..0c7858aef69da 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx +++ b/packages/grafana-ui/src/components/SingleStatShared/FieldPropertiesEditor.tsx @@ -2,54 +2,56 @@ import React, { PureComponent, ChangeEvent } from 'react'; // Components -import { FormField, FormLabel, PanelOptionsGroup, UnitPicker, SelectOptionItem } from '@grafana/ui'; +import { FormField } from '../FormField/FormField'; +import { FormLabel } from '../FormLabel/FormLabel'; +import { UnitPicker } from '../UnitPicker/UnitPicker'; // Types import { Field } from '../../types/data'; import { toNumberString, toIntegerOrUndefined } from '../../utils'; +import { SelectOptionItem } from '../Select/Select'; import { VAR_SERIES_NAME, VAR_FIELD_NAME, VAR_CALC, VAR_CELL_PREFIX } from '../../utils/fieldDisplay'; const labelWidth = 6; export interface Props { - title: string; - options: Partial; - onChange: (fieldProperties: Partial) => void; showMinMax: boolean; + value: Partial; + onChange: (value: Partial, event?: React.SyntheticEvent) => void; } export class FieldPropertiesEditor extends PureComponent { onTitleChange = (event: ChangeEvent) => - this.props.onChange({ ...this.props.options, title: event.target.value }); + this.props.onChange({ ...this.props.value, title: event.target.value }); // @ts-ignore onUnitChange = (unit: SelectOptionItem) => this.props.onChange({ ...this.props.value, unit: unit.value }); onDecimalChange = (event: ChangeEvent) => { this.props.onChange({ - ...this.props.options, + ...this.props.value, decimals: toIntegerOrUndefined(event.target.value), }); }; onMinChange = (event: ChangeEvent) => { this.props.onChange({ - ...this.props.options, + ...this.props.value, min: toIntegerOrUndefined(event.target.value), }); }; onMaxChange = (event: ChangeEvent) => { this.props.onChange({ - ...this.props.options, + ...this.props.value, max: toIntegerOrUndefined(event.target.value), }); }; render() { - const { showMinMax, title } = this.props; - const { unit, decimals, min, max } = this.props.options; + const { showMinMax } = this.props; + const { unit, decimals, min, max } = this.props.value; const titleTooltip = (
@@ -64,49 +66,47 @@ export class FieldPropertiesEditor extends PureComponent { ); return ( - - <> - + <> + -
- Unit - -
- {showMinMax && ( - <> - - - - )} - - -
+
+ Unit + +
+ {showMinMax && ( + <> + + + + )} + + ); } } diff --git a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts index 84dc4ad63c886..6bcf3d712c29f 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts +++ b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts @@ -11,7 +11,7 @@ export interface SingleStatBaseOptions { orientation: VizOrientation; } -const optionsToKeep = ['valueOptions', 'stat', 'maxValue', 'maxValue', 'thresholds', 'valueMappings']; +const optionsToKeep = ['fieldOptions', 'orientation']; export const sharedSingleStatOptionsCheck = ( options: Partial | any, @@ -53,10 +53,13 @@ export const sharedSingleStatMigrationCheck = (panel: PanelModel s.id); } } + field.min = old.minValue; field.max = old.maxValue; - return omit(old, 'valueMappings', 'thresholds', 'valueOptions'); + // remove old props + return omit(old, 'valueMappings', 'thresholds', 'valueOptions', 'minValue', 'maxValue'); } + return panel.options; }; diff --git a/packages/grafana-ui/src/components/Switch/Switch.story.tsx b/packages/grafana-ui/src/components/Switch/Switch.story.tsx new file mode 100644 index 0000000000000..c3cb6fef76d48 --- /dev/null +++ b/packages/grafana-ui/src/components/Switch/Switch.story.tsx @@ -0,0 +1,22 @@ +import React, { useState } from 'react'; + +import { storiesOf } from '@storybook/react'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { Switch } from './Switch'; +import { text } from '@storybook/addon-knobs'; + +const getKnobs = () => { + return { + label: text('Label Text', 'Label'), + }; +}; + +const SwitchWrapper = () => { + const { label } = getKnobs(); + const [checked, setChecked] = useState(false); + return setChecked(!checked)} />; +}; + +const story = storiesOf('UI/Switch', module); +story.addDecorator(withCenteredStory); +story.add('switch', () => ); diff --git a/packages/grafana-ui/src/components/Switch/Switch.tsx b/packages/grafana-ui/src/components/Switch/Switch.tsx index e2a256825192e..470ca3f118df9 100644 --- a/packages/grafana-ui/src/components/Switch/Switch.tsx +++ b/packages/grafana-ui/src/components/Switch/Switch.tsx @@ -12,7 +12,7 @@ export interface Props { } export interface State { - id: any; + id: string; } export class Switch extends PureComponent { diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index 3d759ec1ae521..72b7d6392b938 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -14,7 +14,6 @@ import { Themeable } from '../../types/theme'; import { sortSeriesData } from '../../utils/processSeriesData'; -import { SeriesData, InterpolateFunction } from '@grafana/ui'; import { TableCellBuilder, ColumnStyle, @@ -23,6 +22,8 @@ import { simpleCellBuilder, } from './TableCellBuilder'; import { stringToJsRegex } from '../../utils/index'; +import { SeriesData } from '../../types/data'; +import { InterpolateFunction } from '../../types/panel'; export interface Props extends Themeable { data: SeriesData; diff --git a/packages/grafana-ui/src/components/Table/TableCellBuilder.tsx b/packages/grafana-ui/src/components/Table/TableCellBuilder.tsx index 2d11f10c8ce2a..f7e54d7bb5d7e 100644 --- a/packages/grafana-ui/src/components/Table/TableCellBuilder.tsx +++ b/packages/grafana-ui/src/components/Table/TableCellBuilder.tsx @@ -3,11 +3,11 @@ import _ from 'lodash'; import React, { ReactElement } from 'react'; import { GridCellProps } from 'react-virtualized'; import { Table, Props } from './Table'; -import moment from 'moment'; -import { ValueFormatter } from '../../utils/index'; +import { ValueFormatter, getValueFormat, getColorFromHexRgbOrName } from '../../utils/index'; import { GrafanaTheme } from '../../types/theme'; -import { getValueFormat, getColorFromHexRgbOrName, Field } from '@grafana/ui'; import { InterpolateFunction } from '../../types/panel'; +import { Field } from '../../types/data'; +import { dateTime } from '../../utils/moment_wrapper'; export interface TableCellBuilderOptions { value: any; @@ -96,7 +96,7 @@ export function getCellBuilder(schema: Field, style: ColumnStyle | null, props: if (_.isArray(v)) { v = v[0]; } - let date = moment(v); + let date = dateTime(v); if (false) { // TODO?????? this.props.isUTC) { date = date.utc(); diff --git a/packages/grafana-ui/src/components/Table/examples.ts b/packages/grafana-ui/src/components/Table/examples.ts index efeac94969d1e..9fce97f49d479 100644 --- a/packages/grafana-ui/src/components/Table/examples.ts +++ b/packages/grafana-ui/src/components/Table/examples.ts @@ -1,7 +1,6 @@ import { SeriesData } from '../../types/data'; import { ColumnStyle } from './TableCellBuilder'; - -import { getColorDefinitionByName } from '@grafana/ui'; +import { getColorDefinitionByName } from '../../utils/namedColorsPalette'; const SemiDarkOrange = getColorDefinitionByName('semi-dark-orange'); diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx index 12206b6f8bfa4..123c9085a88e4 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx +++ b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx @@ -1,10 +1,11 @@ import React, { PureComponent, ChangeEvent } from 'react'; import { Threshold } from '../../types'; -import { ColorPicker } from '..'; -import { Input, PanelOptionsGroup } from '..'; import { colors } from '../../utils'; import { ThemeContext } from '../../themes'; import { getColorFromHexRgbOrName } from '../../utils'; +import { Input } from '../Input/Input'; +import { ColorPicker } from '../ColorPicker/ColorPicker'; +import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup'; export interface Props { thresholds: Threshold[]; diff --git a/packages/grafana-ui/src/components/TimePicker/TimePicker.story.tsx b/packages/grafana-ui/src/components/TimePicker/TimePicker.story.tsx index 18cdf82258356..36c6eaf3f1116 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimePicker.story.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimePicker.story.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import moment, { Moment } from 'moment'; import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { TimePicker } from './TimePicker'; import { UseState } from '../../utils/storybook/UseState'; import { withRighAlignedStory } from '../../utils/storybook/withRightAlignedStory'; +import { TimeFragment } from '../../types/time'; +import { dateTime } from '../../utils/moment_wrapper'; const TimePickerStories = storiesOf('UI/TimePicker', module); export const popoverOptions = { @@ -177,9 +178,9 @@ TimePickerStories.add('default', () => { return ( {(value, updateValue) => { diff --git a/packages/grafana-ui/src/components/TimePicker/TimePicker.tsx b/packages/grafana-ui/src/components/TimePicker/TimePicker.tsx index 54d9cce30fd69..5fb625df25716 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimePicker.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimePicker.tsx @@ -1,12 +1,13 @@ import React, { PureComponent } from 'react'; -import moment from 'moment'; -import { TimeRange, TimeOptions, TimeOption, SelectOptionItem } from '@grafana/ui'; import { ButtonSelect } from '../Select/ButtonSelect'; import { mapTimeOptionToTimeRange, mapTimeRangeToRangeString } from './time'; import { Props as TimePickerPopoverProps } from './TimePickerPopover'; import { TimePickerOptionGroup } from './TimePickerOptionGroup'; import { PopperContent } from '../Tooltip/PopperController'; import { Timezone } from '../../utils/datemath'; +import { TimeRange, TimeOption, TimeOptions } from '../../types/time'; +import { SelectOptionItem } from '../Select/Select'; +import { isDateTime } from '../../utils/moment_wrapper'; export interface Props { value: TimeRange; @@ -265,7 +266,7 @@ export class TimePicker extends PureComponent { } = this.props; const options = this.mapTimeOptionsToSelectOptionItems(selectTimeOptions); const rangeString = mapTimeRangeToRangeString(value); - const isAbsolute = moment.isMoment(value.raw.to); + const isAbsolute = isDateTime(value.raw.to); return (
diff --git a/packages/grafana-ui/src/components/TimePicker/TimePickerCalendar.story.tsx b/packages/grafana-ui/src/components/TimePicker/TimePickerCalendar.story.tsx index 3cc044430b151..5a6687d17fc50 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimePickerCalendar.story.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimePickerCalendar.story.tsx @@ -1,18 +1,18 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; -import { Moment } from 'moment'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { TimePickerCalendar } from './TimePickerCalendar'; import { UseState } from '../../utils/storybook/UseState'; +import { TimeFragment } from '../../types/time'; const TimePickerCalendarStories = storiesOf('UI/TimePicker/TimePickerCalendar', module); TimePickerCalendarStories.addDecorator(withCenteredStory); TimePickerCalendarStories.add('default', () => ( - + {(value, updateValue) => { return ( void; + onChange: (value: DateTime) => void; } export class TimePickerCalendar extends PureComponent { @@ -22,15 +22,15 @@ export class TimePickerCalendar extends PureComponent { return; } - onChange(moment(date)); + onChange(dateTime(date)); }; render() { const { value, isTimezoneUtc, roundup, timezone } = this.props; - const dateValue = moment.isMoment(value) + const dateValue = isDateTime(value) ? value.toDate() - : stringToMoment(value, isTimezoneUtc, roundup, timezone).toDate(); - const calendarValue = dateValue instanceof Date && !isNaN(dateValue.getTime()) ? dateValue : moment().toDate(); + : stringToDateTimeType(value, isTimezoneUtc, roundup, timezone).toDate(); + const calendarValue = dateValue instanceof Date && !isNaN(dateValue.getTime()) ? dateValue : dateTime().toDate(); return ( { return isValid; } - const parsed = stringToMoment(value, isTimezoneUtc); + const parsed = stringToDateTimeType(value, isTimezoneUtc); const isValid = parsed.isValid(); return isValid; }; @@ -35,7 +34,7 @@ export class TimePickerInput extends PureComponent { }; valueToString = (value: TimeFragment) => { - if (moment.isMoment(value)) { + if (isDateTime(value)) { return value.format(TIME_FORMAT); } else { return value; diff --git a/packages/grafana-ui/src/components/TimePicker/TimePickerOptionGroup.story.tsx b/packages/grafana-ui/src/components/TimePicker/TimePickerOptionGroup.story.tsx index e5d104d9921a3..795c536ece25b 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimePickerOptionGroup.story.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimePickerOptionGroup.story.tsx @@ -1,12 +1,12 @@ import React, { ComponentType } from 'react'; import { storiesOf } from '@storybook/react'; -import moment from 'moment'; import { action } from '@storybook/addon-actions'; import { TimePickerOptionGroup } from './TimePickerOptionGroup'; import { TimeRange } from '../../types/time'; import { withRighAlignedStory } from '../../utils/storybook/withRightAlignedStory'; import { popoverOptions } from './TimePicker.story'; +import { dateTime } from '../../utils/moment_wrapper'; const TimePickerOptionGroupStories = storiesOf('UI/TimePicker/TimePickerOptionGroup', module); @@ -21,7 +21,7 @@ const data = { action('onPopoverClose fired')(timeRange); }, popoverProps: { - value: { from: moment(), to: moment(), raw: { from: 'now/d', to: 'now/d' } }, + value: { from: dateTime(), to: dateTime(), raw: { from: 'now/d', to: 'now/d' } }, options: popoverOptions, isTimezoneUtc: false, onChange: (timeRange: TimeRange) => { diff --git a/packages/grafana-ui/src/components/TimePicker/TimePickerOptionGroup.tsx b/packages/grafana-ui/src/components/TimePicker/TimePickerOptionGroup.tsx index a7ff0c2587bc2..017668b40569e 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimePickerOptionGroup.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimePickerOptionGroup.tsx @@ -1,8 +1,8 @@ import React, { PureComponent, createRef } from 'react'; import { GroupProps } from 'react-select/lib/components/Group'; -import { Popper } from '@grafana/ui/src/components/Tooltip/Popper'; import { Props as TimePickerProps, TimePickerPopover } from './TimePickerPopover'; -import { TimeRange } from '@grafana/ui'; +import { TimeRange } from '../../types/time'; +import { Popper } from '../Tooltip/Popper'; export interface DataProps { onPopoverOpen: () => void; diff --git a/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.story.tsx b/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.story.tsx index 38f5e48bbe0a5..03dfaa1fb63f1 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.story.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.story.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; -import moment, { Moment } from 'moment'; import { storiesOf } from '@storybook/react'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { TimePickerPopover } from './TimePickerPopover'; import { UseState } from '../../utils/storybook/UseState'; import { popoverOptions } from './TimePicker.story'; +import { dateTime, DateTime } from '../../utils/moment_wrapper'; const TimePickerPopoverStories = storiesOf('UI/TimePicker/TimePickerPopover', module); @@ -15,9 +15,9 @@ TimePickerPopoverStories.addDecorator(withCenteredStory); TimePickerPopoverStories.add('default', () => ( {(value, updateValue) => { diff --git a/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.tsx b/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.tsx index 0367659808968..37c05863a03a2 100644 --- a/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.tsx +++ b/packages/grafana-ui/src/components/TimePicker/TimePickerPopover.tsx @@ -1,11 +1,11 @@ import React, { Component, SyntheticEvent } from 'react'; import { TimeRange, TimeOptions, TimeOption } from '../../types/time'; -import { Moment } from 'moment'; import { TimePickerCalendar } from './TimePickerCalendar'; import { TimePickerInput } from './TimePickerInput'; import { mapTimeOptionToTimeRange } from './time'; import { Timezone } from '../../utils/datemath'; +import { DateTime } from '../../utils/moment_wrapper'; export interface Props { value: TimeRange; @@ -42,13 +42,13 @@ export class TimePickerPopover extends Component { }); }; - onFromCalendarChanged = (value: Moment) => { + onFromCalendarChanged = (value: DateTime) => { this.setState({ value: { ...this.state.value, raw: { ...this.state.value.raw, from: value } }, }); }; - onToCalendarChanged = (value: Moment) => { + onToCalendarChanged = (value: DateTime) => { this.setState({ value: { ...this.state.value, raw: { ...this.state.value.raw, to: value } }, }); diff --git a/packages/grafana-ui/src/components/TimePicker/time.ts b/packages/grafana-ui/src/components/TimePicker/time.ts index 77a554b38de4f..f697456eeb378 100644 --- a/packages/grafana-ui/src/components/TimePicker/time.ts +++ b/packages/grafana-ui/src/components/TimePicker/time.ts @@ -1,40 +1,39 @@ -import moment, { Moment } from 'moment'; -import { TimeOption, TimeRange, TIME_FORMAT } from '@grafana/ui'; - -import * as dateMath from '@grafana/ui/src/utils/datemath'; -import { describeTimeRange } from '@grafana/ui/src/utils/rangeutil'; +import { TimeOption, TimeRange, TIME_FORMAT } from '../../types/time'; +import { describeTimeRange } from '../../utils/rangeutil'; +import * as dateMath from '../../utils/datemath'; +import { dateTime, DateTime, toUtc } from '../../utils/moment_wrapper'; export const mapTimeOptionToTimeRange = ( timeOption: TimeOption, isTimezoneUtc: boolean, timezone?: dateMath.Timezone ): TimeRange => { - const fromMoment = stringToMoment(timeOption.from, isTimezoneUtc, false, timezone); - const toMoment = stringToMoment(timeOption.to, isTimezoneUtc, true, timezone); + const fromMoment = stringToDateTimeType(timeOption.from, isTimezoneUtc, false, timezone); + const toMoment = stringToDateTimeType(timeOption.to, isTimezoneUtc, true, timezone); return { from: fromMoment, to: toMoment, raw: { from: timeOption.from, to: timeOption.to } }; }; -export const stringToMoment = ( +export const stringToDateTimeType = ( value: string, isTimezoneUtc: boolean, roundUp?: boolean, timezone?: dateMath.Timezone -): Moment => { +): DateTime => { if (value.indexOf('now') !== -1) { if (!dateMath.isValid(value)) { - return moment(); + return dateTime(); } const parsed = dateMath.parse(value, roundUp, timezone); - return parsed || moment(); + return parsed || dateTime(); } if (isTimezoneUtc) { - return moment.utc(value, TIME_FORMAT); + return toUtc(value, TIME_FORMAT); } - return moment(value, TIME_FORMAT); + return dateTime(value, TIME_FORMAT); }; export const mapTimeRangeToRangeString = (timeRange: TimeRange): string => { diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index fee2c2bff0aee..5a4f58626c6a7 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -33,6 +33,7 @@ export { UnitPicker } from './UnitPicker/UnitPicker'; export { StatsPicker } from './StatsPicker/StatsPicker'; export { Input, InputStatus } from './Input/Input'; export { RefreshPicker } from './RefreshPicker/RefreshPicker'; +export { List } from './List/List'; // Renderless export { SetInterval } from './SetInterval/SetInterval'; diff --git a/packages/grafana-ui/src/types/data.ts b/packages/grafana-ui/src/types/data.ts index 9eb75ea403060..678efc40f0074 100644 --- a/packages/grafana-ui/src/types/data.ts +++ b/packages/grafana-ui/src/types/data.ts @@ -70,6 +70,7 @@ export interface Column { } export interface TableData extends QueryResultBase { + name?: string; columns: Column[]; rows: any[][]; } diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index f918063d06bcf..2622efd3ed50c 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -1,4 +1,4 @@ -import { ComponentClass } from 'react'; +import { ComponentType, ComponentClass } from 'react'; import { TimeRange } from './time'; import { PluginMeta, GrafanaPlugin } from './plugin'; import { TableData, TimeSeries, SeriesData, LoadingState } from './data'; @@ -11,19 +11,20 @@ export interface DataSourcePluginOptionsEditorProps { } export class DataSourcePlugin< - TOptions extends DataSourceJsonData = DataSourceJsonData, - TQuery extends DataQuery = DataQuery + DSType extends DataSourceApi, + TQuery extends DataQuery = DataQuery, + TOptions extends DataSourceJsonData = DataSourceJsonData > extends GrafanaPlugin { - DataSourceClass: DataSourceConstructor; - components: DataSourcePluginComponents; + DataSourceClass: DataSourceConstructor; + components: DataSourcePluginComponents; - constructor(DataSourceClass: DataSourceConstructor) { + constructor(DataSourceClass: DataSourceConstructor) { super(); this.DataSourceClass = DataSourceClass; this.components = {}; } - setConfigEditor(editor: React.ComponentType>>) { + setConfigEditor(editor: ComponentType>>) { this.components.ConfigEditor = editor; return this; } @@ -38,12 +39,12 @@ export class DataSourcePlugin< return this; } - setQueryEditor(QueryEditor: ComponentClass>) { + setQueryEditor(QueryEditor: ComponentType>) { this.components.QueryEditor = QueryEditor; return this; } - setExploreQueryField(ExploreQueryField: ComponentClass>) { + setExploreQueryField(ExploreQueryField: ComponentClass>) { this.components.ExploreQueryField = ExploreQueryField; return this; } @@ -79,7 +80,9 @@ export interface DataSourcePluginMeta extends PluginMeta { annotations?: boolean; mixed?: boolean; hasQueryHelp?: boolean; + category?: string; queryOptions?: PluginMetaQueryOptions; + sort?: number; } interface PluginMetaQueryOptions { @@ -89,31 +92,55 @@ interface PluginMetaQueryOptions { } export interface DataSourcePluginComponents< - TOptions extends DataSourceJsonData = DataSourceJsonData, - TQuery extends DataQuery = DataQuery + DSType extends DataSourceApi, + TQuery extends DataQuery = DataQuery, + TOptions extends DataSourceJsonData = DataSourceJsonData > { QueryCtrl?: any; AnnotationsQueryCtrl?: any; VariableQueryEditor?: any; - QueryEditor?: ComponentClass>; - ExploreQueryField?: ComponentClass>; + QueryEditor?: ComponentType>; + ExploreQueryField?: ComponentClass>; ExploreStartPage?: ComponentClass; - ConfigEditor?: React.ComponentType>>; + ConfigEditor?: ComponentType>>; } -export interface DataSourceConstructor { - new (instanceSettings: DataSourceInstanceSettings, ...args: any[]): DataSourceApi; +// Only exported for tests +export interface DataSourceConstructor< + DSType extends DataSourceApi, + TQuery extends DataQuery = DataQuery, + TOptions extends DataSourceJsonData = DataSourceJsonData +> { + new (instanceSettings: DataSourceInstanceSettings, ...args: any[]): DSType; } /** * The main data source abstraction interface, represents an instance of a data source */ -export interface DataSourceApi { +export abstract class DataSourceApi< + TQuery extends DataQuery = DataQuery, + TOptions extends DataSourceJsonData = DataSourceJsonData +> { + /** + * Set in constructor + */ + readonly name: string; + + /** + * Set in constructor + */ + readonly id: number; + /** * min interval range */ interval?: string; + constructor(instanceSettings: DataSourceInstanceSettings) { + this.name = instanceSettings.name; + this.id = instanceSettings.id; + } + /** * Imports queries from a different datasource */ @@ -127,12 +154,12 @@ export interface DataSourceApi { /** * Main metrics / data query action */ - query(options: DataQueryRequest, observer?: DataStreamObserver): Promise; + abstract query(options: DataQueryRequest, observer?: DataStreamObserver): Promise; /** * Test & verify datasource settings & connection details */ - testDatasource(): Promise; + abstract testDatasource(): Promise; /** * Get hints for query improvements @@ -144,21 +171,11 @@ export interface DataSourceApi { */ getQueryDisplayText?(query: TQuery): string; - /** - * Set after constructor is called by Grafana - */ - name?: string; - - /** - * Set after constructor is called by Grafana - */ - id?: number; - /** * Set after constructor call, as the data source instance is the most common thing to pass around * we attach the components to this instance for easy access */ - components?: DataSourcePluginComponents; + components?: DataSourcePluginComponents, TQuery, TOptions>; /** * static information about the datasource @@ -166,13 +183,20 @@ export interface DataSourceApi { meta?: DataSourcePluginMeta; } -export interface ExploreDataSourceApi extends DataSourceApi { +export abstract class ExploreDataSourceApi< + TQuery extends DataQuery = DataQuery, + TOptions extends DataSourceJsonData = DataSourceJsonData +> extends DataSourceApi { modifyQuery?(query: TQuery, action: QueryFixAction): TQuery; getHighlighterExpression?(query: TQuery): string; languageProvider?: any; } -export interface QueryEditorProps { +export interface QueryEditorProps< + DSType extends DataSourceApi, + TQuery extends DataQuery = DataQuery, + TOptions extends DataSourceJsonData = DataSourceJsonData +> { datasource: DSType; query: TQuery; onRunQuery: () => void; @@ -186,16 +210,14 @@ export enum DataSourceStatus { Disconnected, } -export interface ExploreQueryFieldProps { - datasource: DSType; +export interface ExploreQueryFieldProps< + DSType extends DataSourceApi, + TQuery extends DataQuery = DataQuery, + TOptions extends DataSourceJsonData = DataSourceJsonData +> extends QueryEditorProps { datasourceStatus: DataSourceStatus; - query: TQuery; - error?: string | JSX.Element; - hint?: QueryHint; history: any[]; - onExecuteQuery?: () => void; - onQueryChange?: (value: TQuery) => void; - onExecuteHint?: (action: QueryFixAction) => void; + onHint?: (action: QueryFixAction) => void; } export interface ExploreStartPageProps { diff --git a/packages/grafana-ui/src/types/input.ts b/packages/grafana-ui/src/types/input.ts index 95026c30be9ad..d3522e77b3592 100644 --- a/packages/grafana-ui/src/types/input.ts +++ b/packages/grafana-ui/src/types/input.ts @@ -4,5 +4,6 @@ } export interface ValidationEvents { + // Event name should be one of EventsWithValidation enum [eventName: string]: ValidationRule[]; } diff --git a/packages/grafana-ui/src/types/panel.ts b/packages/grafana-ui/src/types/panel.ts index ea28ca2fb47da..a1756ff02fea9 100644 --- a/packages/grafana-ui/src/types/panel.ts +++ b/packages/grafana-ui/src/types/panel.ts @@ -7,18 +7,9 @@ import { PluginMeta, GrafanaPlugin } from './plugin'; export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string; export interface PanelPluginMeta extends PluginMeta { + skipDataQuery?: boolean; hideFromList?: boolean; sort: number; - - // if length>0 the query tab will show up - // Before 6.2 this could be table and/or series, but 6.2+ supports both transparently - // so it will be deprecated soon - dataFormats?: PanelDataFormat[]; -} - -export enum PanelDataFormat { - Table = 'table', - TimeSeries = 'time_series', } export interface PanelData { @@ -32,6 +23,7 @@ export interface PanelData { } export interface PanelProps { + id: number; // ID within the current dashboard data: PanelData; // TODO: annotation?: PanelData; diff --git a/packages/grafana-ui/src/types/time.ts b/packages/grafana-ui/src/types/time.ts index d6c524ac19a9d..c7120776facf6 100644 --- a/packages/grafana-ui/src/types/time.ts +++ b/packages/grafana-ui/src/types/time.ts @@ -1,13 +1,13 @@ -import { Moment } from 'moment'; +import { DateTime } from '../utils/moment_wrapper'; export interface RawTimeRange { - from: Moment | string; - to: Moment | string; + from: DateTime | string; + to: DateTime | string; } export interface TimeRange { - from: Moment; - to: Moment; + from: DateTime; + to: DateTime; raw: RawTimeRange; } @@ -47,6 +47,6 @@ export interface TimeOptions { [key: string]: TimeOption[]; } -export type TimeFragment = string | Moment; +export type TimeFragment = string | DateTime; export const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; diff --git a/packages/grafana-ui/src/utils/datemath.test.ts b/packages/grafana-ui/src/utils/datemath.test.ts index 73407d558217f..802fb0e0785b8 100644 --- a/packages/grafana-ui/src/utils/datemath.test.ts +++ b/packages/grafana-ui/src/utils/datemath.test.ts @@ -1,13 +1,13 @@ import sinon, { SinonFakeTimers } from 'sinon'; +import each from 'lodash/each'; import * as dateMath from './datemath'; -import moment, { Moment, unitOfTime } from 'moment'; -import each from 'lodash/each'; +import { dateTime, DurationUnit, DateTime } from '../utils/moment_wrapper'; describe('DateMath', () => { - const spans: unitOfTime.Base[] = ['s', 'm', 'h', 'd', 'w', 'M', 'y']; + const spans: DurationUnit[] = ['s', 'm', 'h', 'd', 'w', 'M', 'y']; const anchor = '2014-01-01T06:06:06.666Z'; - const unix = moment(anchor).valueOf(); + const unix = dateTime(anchor).valueOf(); const format = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; let clock: SinonFakeTimers; @@ -55,13 +55,13 @@ describe('DateMath', () => { }); describe('subtraction', () => { - let now: Moment; - let anchored: Moment; + let now: DateTime; + let anchored: DateTime; beforeEach(() => { clock = sinon.useFakeTimers(unix); - now = moment(); - anchored = moment(anchor); + now = dateTime(); + anchored = dateTime(anchor); }); each(spans, span => { @@ -83,11 +83,11 @@ describe('DateMath', () => { }); describe('rounding', () => { - let now: Moment; + let now: DateTime; beforeEach(() => { clock = sinon.useFakeTimers(unix); - now = moment(); + now = dateTime(); }); each(spans, span => { @@ -116,17 +116,17 @@ describe('DateMath', () => { describe('relative time to date parsing', () => { it('should handle negative time', () => { - const date = dateMath.parseDateMath('-2d', moment([2014, 1, 5])); - expect(date!.valueOf()).toEqual(moment([2014, 1, 3]).valueOf()); + const date = dateMath.parseDateMath('-2d', dateTime([2014, 1, 5])); + expect(date!.valueOf()).toEqual(dateTime([2014, 1, 3]).valueOf()); }); it('should handle multiple math expressions', () => { - const date = dateMath.parseDateMath('-2d-6h', moment([2014, 1, 5])); - expect(date!.valueOf()).toEqual(moment([2014, 1, 2, 18]).valueOf()); + const date = dateMath.parseDateMath('-2d-6h', dateTime([2014, 1, 5])); + expect(date!.valueOf()).toEqual(dateTime([2014, 1, 2, 18]).valueOf()); }); it('should return false when invalid expression', () => { - const date = dateMath.parseDateMath('2', moment([2014, 1, 5])); + const date = dateMath.parseDateMath('2', dateTime([2014, 1, 5])); expect(date).toEqual(undefined); }); }); diff --git a/packages/grafana-ui/src/utils/datemath.ts b/packages/grafana-ui/src/utils/datemath.ts index af9719a73a3e0..6ab7b61ec6629 100644 --- a/packages/grafana-ui/src/utils/datemath.ts +++ b/packages/grafana-ui/src/utils/datemath.ts @@ -1,8 +1,8 @@ import includes from 'lodash/includes'; import isDate from 'lodash/isDate'; -import moment, { unitOfTime } from 'moment'; +import { DateTime, dateTime, toUtc, ISO_8601, isDateTime, DurationUnit } from '../utils/moment_wrapper'; -const units: unitOfTime.Base[] = ['y', 'M', 'w', 'd', 'h', 'm', 's']; +const units: DurationUnit[] = ['y', 'M', 'w', 'd', 'h', 'm', 's']; export type Timezone = 'utc'; @@ -13,21 +13,17 @@ export type Timezone = 'utc'; * @param roundUp See parseDateMath function. * @param timezone Only string 'utc' is acceptable here, for anything else, local timezone is used. */ -export function parse( - text: string | moment.Moment | Date, - roundUp?: boolean, - timezone?: Timezone -): moment.Moment | undefined { +export function parse(text: string | DateTime | Date, roundUp?: boolean, timezone?: Timezone): DateTime | undefined { if (!text) { return undefined; } if (typeof text !== 'string') { - if (moment.isMoment(text)) { + if (isDateTime(text)) { return text; } if (isDate(text)) { - return moment(text); + return dateTime(text); } // We got some non string which is not a moment nor Date. TS should be able to check for that but not always. return undefined; @@ -39,9 +35,9 @@ export function parse( if (text.substring(0, 3) === 'now') { if (timezone === 'utc') { - time = moment.utc(); + time = toUtc(); } else { - time = moment(); + time = dateTime(); } mathString = text.substring('now'.length); } else { @@ -54,7 +50,7 @@ export function parse( mathString = text.substring(index + 2); } // We're going to just require ISO8601 timestamps, k? - time = moment(parseString, moment.ISO_8601); + time = dateTime(parseString, ISO_8601); } if (!mathString.length) { @@ -70,13 +66,13 @@ export function parse( * by parse function. See parse function to see what is considered acceptable. * @param text */ -export function isValid(text: string | moment.Moment): boolean { +export function isValid(text: string | DateTime): boolean { const date = parse(text); if (!date) { return false; } - if (moment.isMoment(date)) { + if (isDateTime(date)) { return date.isValid(); } @@ -90,7 +86,7 @@ export function isValid(text: string | moment.Moment): boolean { * @param roundUp If true it will round the time to endOf time unit, otherwise to startOf time unit. */ // TODO: Had to revert Andrejs `time: moment.Moment` to `time: any` -export function parseDateMath(mathString: string, time: any, roundUp?: boolean): moment.Moment | undefined { +export function parseDateMath(mathString: string, time: any, roundUp?: boolean): DateTime | undefined { const dateTime = time; let i = 0; const len = mathString.length; diff --git a/packages/grafana-ui/src/utils/displayValue.ts b/packages/grafana-ui/src/utils/displayValue.ts index 7c3a6e07034ab..59a04fefc2b93 100644 --- a/packages/grafana-ui/src/utils/displayValue.ts +++ b/packages/grafana-ui/src/utils/displayValue.ts @@ -1,6 +1,5 @@ // Libraries import _ from 'lodash'; -import moment from 'moment'; // Utils import { getValueFormat } from './valueFormats/valueFormats'; @@ -18,6 +17,7 @@ import { DecimalCount, Field, } from '../types'; +import { DateTime, dateTime } from './moment_wrapper'; export type DisplayProcessor = (value: any) => DisplayValue; @@ -91,18 +91,18 @@ export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProce return toStringProcessor; } -function toMoment(value: any, numeric: number, format: string): moment.Moment { +function toMoment(value: any, numeric: number, format: string): DateTime { if (!isNaN(numeric)) { - const v = moment(numeric); + const v = dateTime(numeric); if (v.isValid()) { return v; } } - const v = moment(value, format); + const v = dateTime(value, format); if (v.isValid) { return v; } - return moment(value); // moment will try to parse the format + return dateTime(value); // moment will try to parse the format } /** Will return any value as a number or NaN */ diff --git a/packages/grafana-ui/src/utils/index.ts b/packages/grafana-ui/src/utils/index.ts index d3b11d3e20796..8a8857364682a 100644 --- a/packages/grafana-ui/src/utils/index.ts +++ b/packages/grafana-ui/src/utils/index.ts @@ -16,6 +16,7 @@ export * from './validate'; export { getFlotPairs } from './flotPairs'; export * from './object'; export * from './fieldCache'; +export * from './moment_wrapper'; // Names are too general to export // rangeutils, datemath diff --git a/packages/grafana-ui/src/utils/moment_wrapper.ts b/packages/grafana-ui/src/utils/moment_wrapper.ts new file mode 100644 index 0000000000000..063c427372b6d --- /dev/null +++ b/packages/grafana-ui/src/utils/moment_wrapper.ts @@ -0,0 +1,90 @@ +/* tslint:disable:import-blacklist ban ban-types */ +import moment, { MomentInput, DurationInputArg1 } from 'moment'; + +export interface DateTimeBuiltinFormat { + __momentBuiltinFormatBrand: any; +} +export const ISO_8601: DateTimeBuiltinFormat = moment.ISO_8601; +export type DateTimeInput = Date | string | number | Array | DateTime; // null | undefined +export type FormatInput = string | DateTimeBuiltinFormat | undefined; +export type DurationInput = string | number | DateTimeDuration; +export type DurationUnit = + | 'year' + | 'years' + | 'y' + | 'month' + | 'months' + | 'M' + | 'week' + | 'weeks' + | 'w' + | 'day' + | 'days' + | 'd' + | 'hour' + | 'hours' + | 'h' + | 'minute' + | 'minutes' + | 'm' + | 'second' + | 'seconds' + | 's' + | 'millisecond' + | 'milliseconds' + | 'ms' + | 'quarter' + | 'quarters' + | 'Q'; + +export interface DateTimeLocale { + firstDayOfWeek: () => number; +} + +export interface DateTimeDuration { + asHours: () => number; +} + +export interface DateTime extends Object { + add: (amount?: DateTimeInput, unit?: DurationUnit) => DateTime; + diff: (amount: DateTimeInput, unit?: DurationUnit, truncate?: boolean) => number; + endOf: (unitOfTime: DurationUnit) => DateTime; + format: (formatInput?: FormatInput) => string; + fromNow: (withoutSuffix?: boolean) => string; + from: (formaInput: DateTimeInput) => string; + isSame: (input?: DateTimeInput, granularity?: DurationUnit) => boolean; + isValid: () => boolean; + local: () => DateTime; + locale: (locale: string) => DateTime; + startOf: (unitOfTime: DurationUnit) => DateTime; + subtract: (amount?: DateTimeInput, unit?: DurationUnit) => DateTime; + toDate: () => Date; + toISOString: () => string; + valueOf: () => number; + unix: () => number; + utc: () => DateTime; +} + +export const setLocale = (language: string) => { + moment.locale(language); +}; + +export const getLocaleData = (): DateTimeLocale => { + return moment.localeData(); +}; + +export const isDateTime = (value: any): value is DateTime => { + return moment.isMoment(value); +}; + +export const toUtc = (input?: DateTimeInput, formatInput?: FormatInput): DateTime => { + return moment.utc(input as MomentInput, formatInput) as DateTime; +}; + +export const toDuration = (input?: DurationInput, unit?: DurationUnit): DateTimeDuration => { + return moment.duration(input as DurationInputArg1, unit) as DateTimeDuration; +}; + +export const dateTime = (input?: DateTimeInput, formatInput?: FormatInput): DateTime => { + return moment(input as MomentInput, formatInput) as DateTime; +}; diff --git a/packages/grafana-ui/src/utils/processSeriesData.test.ts b/packages/grafana-ui/src/utils/processSeriesData.test.ts index 03b1a889db255..96afa79aa8c00 100644 --- a/packages/grafana-ui/src/utils/processSeriesData.test.ts +++ b/packages/grafana-ui/src/utils/processSeriesData.test.ts @@ -7,7 +7,7 @@ import { guessFieldTypeFromValue, } from './processSeriesData'; import { FieldType, TimeSeries } from '../types/data'; -import moment from 'moment'; +import { dateTime } from './moment_wrapper'; describe('toSeriesData', () => { it('converts timeseries to series', () => { @@ -45,7 +45,7 @@ describe('toSeriesData', () => { expect(guessFieldTypeFromValue(true)).toBe(FieldType.boolean); expect(guessFieldTypeFromValue(false)).toBe(FieldType.boolean); expect(guessFieldTypeFromValue(new Date())).toBe(FieldType.time); - expect(guessFieldTypeFromValue(moment())).toBe(FieldType.time); + expect(guessFieldTypeFromValue(dateTime())).toBe(FieldType.time); }); it('Guess Colum Types from strings', () => { diff --git a/packages/grafana-ui/src/utils/processSeriesData.ts b/packages/grafana-ui/src/utils/processSeriesData.ts index 0a8d420fdaa13..1ba31e3bbdee1 100644 --- a/packages/grafana-ui/src/utils/processSeriesData.ts +++ b/packages/grafana-ui/src/utils/processSeriesData.ts @@ -2,10 +2,10 @@ import isNumber from 'lodash/isNumber'; import isString from 'lodash/isString'; import isBoolean from 'lodash/isBoolean'; -import moment from 'moment'; // Types import { SeriesData, Field, TimeSeries, FieldType, TableData } from '../types/index'; +import { isDateTime } from './moment_wrapper'; function convertTableToSeriesData(table: TableData): SeriesData { return { @@ -19,6 +19,7 @@ function convertTableToSeriesData(table: TableData): SeriesData { rows: table.rows, refId: table.refId, meta: table.meta, + name: table.name, }; } @@ -73,7 +74,7 @@ export function guessFieldTypeFromValue(v: any): FieldType { return FieldType.boolean; } - if (v instanceof Date || v instanceof moment) { + if (v instanceof Date || isDateTime(v)) { return FieldType.time; } diff --git a/packages/grafana-ui/src/utils/rangeutil.ts b/packages/grafana-ui/src/utils/rangeutil.ts index 75f2b81443335..bcc27ecfd8eae 100644 --- a/packages/grafana-ui/src/utils/rangeutil.ts +++ b/packages/grafana-ui/src/utils/rangeutil.ts @@ -1,10 +1,10 @@ // @ts-ignore import _ from 'lodash'; -import moment from 'moment'; -import { RawTimeRange } from '@grafana/ui'; +import { RawTimeRange } from '../types/time'; import * as dateMath from './datemath'; +import { isDateTime, DateTime } from '../utils/moment_wrapper'; const spans: { [key: string]: { display: string; section?: number } } = { s: { display: 'second' }, @@ -85,7 +85,7 @@ export function getRelativeTimesList(timepickerSettings: any, currentDisplay: an return groups; } -function formatDate(date: any) { +function formatDate(date: DateTime) { return date.format(absoluteFormat); } @@ -139,16 +139,16 @@ export function describeTimeRange(range: RawTimeRange): string { return option.display; } - if (moment.isMoment(range.from) && moment.isMoment(range.to)) { + if (isDateTime(range.from) && isDateTime(range.to)) { return formatDate(range.from) + ' to ' + formatDate(range.to); } - if (moment.isMoment(range.from)) { + if (isDateTime(range.from)) { const toMoment = dateMath.parse(range.to, true); return toMoment ? formatDate(range.from) + ' to ' + toMoment.fromNow() : ''; } - if (moment.isMoment(range.to)) { + if (isDateTime(range.to)) { const from = dateMath.parse(range.from, false); return from ? from.fromNow() + ' to ' + formatDate(range.to) : ''; } diff --git a/packages/grafana-ui/src/utils/string.test.ts b/packages/grafana-ui/src/utils/string.test.ts index b1a2859986084..b99614c040686 100644 --- a/packages/grafana-ui/src/utils/string.test.ts +++ b/packages/grafana-ui/src/utils/string.test.ts @@ -1,4 +1,4 @@ -import { stringToJsRegex, stringToMs } from '@grafana/ui'; +import { stringToJsRegex, stringToMs } from './string'; describe('stringToJsRegex', () => { it('should parse the valid regex value', () => { diff --git a/packages/grafana-ui/src/utils/valueFormats/dateTimeFormatters.test.ts b/packages/grafana-ui/src/utils/valueFormats/dateTimeFormatters.test.ts index cf69a1d433ad6..f800d2b75fd6f 100644 --- a/packages/grafana-ui/src/utils/valueFormats/dateTimeFormatters.test.ts +++ b/packages/grafana-ui/src/utils/valueFormats/dateTimeFormatters.test.ts @@ -1,4 +1,3 @@ -import moment from 'moment'; import { dateTimeAsIso, dateTimeAsUS, @@ -9,11 +8,12 @@ import { toDurationInMilliseconds, toDurationInSeconds, } from './dateTimeFormatters'; +import { toUtc, dateTime } from '../moment_wrapper'; describe('date time formats', () => { const epoch = 1505634997920; - const utcTime = moment.utc(epoch); - const browserTime = moment(epoch); + const utcTime = toUtc(epoch); + const browserTime = dateTime(epoch); it('should format as iso date', () => { const expected = browserTime.format('YYYY-MM-DD HH:mm:ss'); @@ -28,14 +28,14 @@ describe('date time formats', () => { }); it('should format as iso date and skip date when today', () => { - const now = moment(); + const now = dateTime(); const expected = now.format('HH:mm:ss'); const actual = dateTimeAsIso(now.valueOf(), 0, 0, false); expect(actual).toBe(expected); }); it('should format as iso date (in UTC) and skip date when today', () => { - const now = moment.utc(); + const now = toUtc(); const expected = now.format('HH:mm:ss'); const actual = dateTimeAsIso(now.valueOf(), 0, 0, true); expect(actual).toBe(expected); @@ -54,42 +54,42 @@ describe('date time formats', () => { }); it('should format as US date and skip date when today', () => { - const now = moment(); + const now = dateTime(); const expected = now.format('h:mm:ss a'); const actual = dateTimeAsUS(now.valueOf(), 0, 0, false); expect(actual).toBe(expected); }); it('should format as US date (in UTC) and skip date when today', () => { - const now = moment.utc(); + const now = toUtc(); const expected = now.format('h:mm:ss a'); const actual = dateTimeAsUS(now.valueOf(), 0, 0, true); expect(actual).toBe(expected); }); it('should format as from now with days', () => { - const daysAgo = moment().add(-7, 'd'); + const daysAgo = dateTime().add(-7, 'd'); const expected = '7 days ago'; const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, false); expect(actual).toBe(expected); }); it('should format as from now with days (in UTC)', () => { - const daysAgo = moment.utc().add(-7, 'd'); + const daysAgo = toUtc().add(-7, 'd'); const expected = '7 days ago'; const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, true); expect(actual).toBe(expected); }); it('should format as from now with minutes', () => { - const daysAgo = moment().add(-2, 'm'); + const daysAgo = dateTime().add(-2, 'm'); const expected = '2 minutes ago'; const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, false); expect(actual).toBe(expected); }); it('should format as from now with minutes (in UTC)', () => { - const daysAgo = moment.utc().add(-2, 'm'); + const daysAgo = toUtc().add(-2, 'm'); const expected = '2 minutes ago'; const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, true); expect(actual).toBe(expected); diff --git a/packages/grafana-ui/src/utils/valueFormats/dateTimeFormatters.ts b/packages/grafana-ui/src/utils/valueFormats/dateTimeFormatters.ts index bad77191fb5e0..2c2ab34681608 100644 --- a/packages/grafana-ui/src/utils/valueFormats/dateTimeFormatters.ts +++ b/packages/grafana-ui/src/utils/valueFormats/dateTimeFormatters.ts @@ -1,6 +1,6 @@ import { toFixed, toFixedScaled } from './valueFormats'; import { DecimalCount } from '../../types'; -import moment from 'moment'; +import { toUtc, toDuration as duration, dateTime } from '../moment_wrapper'; interface IntervalsInSeconds { [interval: string]: number; @@ -237,7 +237,7 @@ export function toClock(size: number, decimals?: DecimalCount) { // < 1 second if (size < 1000) { - return moment.utc(size).format('SSS\\m\\s'); + return toUtc(size).format('SSS\\m\\s'); } // < 1 minute @@ -246,7 +246,7 @@ export function toClock(size: number, decimals?: DecimalCount) { if (decimals === 0) { format = 'ss\\s'; } - return moment.utc(size).format(format); + return toUtc(size).format(format); } // < 1 hour @@ -257,12 +257,12 @@ export function toClock(size: number, decimals?: DecimalCount) { } else if (decimals === 1) { format = 'mm\\m:ss\\s'; } - return moment.utc(size).format(format); + return toUtc(size).format(format); } let format = 'mm\\m:ss\\s:SSS\\m\\s'; - const hours = `${('0' + Math.floor(moment.duration(size, 'milliseconds').asHours())).slice(-2)}h`; + const hours = `${('0' + Math.floor(duration(size, 'milliseconds').asHours())).slice(-2)}h`; if (decimals === 0) { format = ''; @@ -272,7 +272,7 @@ export function toClock(size: number, decimals?: DecimalCount) { format = 'mm\\m:ss\\s'; } - return format ? `${hours}:${moment.utc(size).format(format)}` : hours; + return format ? `${hours}:${toUtc(size).format(format)}` : hours; } export function toDurationInMilliseconds(size: number, decimals: DecimalCount) { @@ -307,24 +307,24 @@ export function toClockSeconds(size: number, decimals: DecimalCount) { } export function dateTimeAsIso(value: number, decimals: DecimalCount, scaledDecimals: DecimalCount, isUtc?: boolean) { - const time = isUtc ? moment.utc(value) : moment(value); + const time = isUtc ? toUtc(value) : dateTime(value); - if (moment().isSame(value, 'day')) { + if (dateTime().isSame(value, 'day')) { return time.format('HH:mm:ss'); } return time.format('YYYY-MM-DD HH:mm:ss'); } export function dateTimeAsUS(value: number, decimals: DecimalCount, scaledDecimals: DecimalCount, isUtc?: boolean) { - const time = isUtc ? moment.utc(value) : moment(value); + const time = isUtc ? toUtc(value) : dateTime(value); - if (moment().isSame(value, 'day')) { + if (dateTime().isSame(value, 'day')) { return time.format('h:mm:ss a'); } return time.format('MM/DD/YYYY h:mm:ss a'); } export function dateTimeFromNow(value: number, decimals: DecimalCount, scaledDecimals: DecimalCount, isUtc?: boolean) { - const time = isUtc ? moment.utc(value) : moment(value); + const time = isUtc ? toUtc(value) : dateTime(value); return time.fromNow(); } diff --git a/packages/grafana-ui/tslint.json b/packages/grafana-ui/tslint.json index 0946f20963a3d..937aa29800e5e 100644 --- a/packages/grafana-ui/tslint.json +++ b/packages/grafana-ui/tslint.json @@ -1,3 +1,6 @@ { - "extends": "../../tslint.json" + "extends": "../../tslint.json", + "rules": { + "import-blacklist": [true, "moment", ["^@grafana/ui.*"]] + } } diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile index d72accb0cd924..2db48e095250f 100644 --- a/packaging/docker/Dockerfile +++ b/packaging/docker/Dockerfile @@ -47,8 +47,8 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \ "$GF_PATHS_DATA" && \ cp "$GF_PATHS_HOME/conf/sample.ini" "$GF_PATHS_CONFIG" && \ cp "$GF_PATHS_HOME/conf/ldap.toml" /etc/grafana/ldap.toml && \ - chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" && \ - chmod 777 "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" + chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING" && \ + chmod -R 777 "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING" EXPOSE 3000 diff --git a/packaging/rpm/systemd/grafana-server.service b/packaging/rpm/systemd/grafana-server.service index ad5006d1d4ce3..f16fda7f54a65 100644 --- a/packaging/rpm/systemd/grafana-server.service +++ b/packaging/rpm/systemd/grafana-server.service @@ -3,7 +3,7 @@ Description=Grafana instance Documentation=http://docs.grafana.org Wants=network-online.target After=network-online.target -After=postgresql.service mariadb.service mysql.service +After=postgresql.service mariadb.service mysqld.service [Service] EnvironmentFile=/etc/sysconfig/grafana-server diff --git a/pkg/api/app_routes.go b/pkg/api/app_routes.go index 27fba4fe3c3ea..e85a19dbce817 100644 --- a/pkg/api/app_routes.go +++ b/pkg/api/app_routes.go @@ -27,7 +27,6 @@ func (hs *HTTPServer) initAppPluginRoutes(r *macaron.Macaron) { Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, - DualStack: true, }).Dial, TLSHandshakeTimeout: 10 * time.Second, } diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go index ba4a09f6f0123..a71169ac3767e 100644 --- a/pkg/api/dashboard_snapshot.go +++ b/pkg/api/dashboard_snapshot.go @@ -99,8 +99,14 @@ func CreateDashboardSnapshot(c *m.ReqContext, cmd m.CreateDashboardSnapshotComma metrics.M_Api_Dashboard_Snapshot_External.Inc() } else { - cmd.Key = util.GetRandomString(32) - cmd.DeleteKey = util.GetRandomString(32) + if cmd.Key == "" { + cmd.Key = util.GetRandomString(32) + } + + if cmd.DeleteKey == "" { + cmd.DeleteKey = util.GetRandomString(32) + } + url = setting.ToAbsUrl("dashboard/snapshot/" + cmd.Key) metrics.M_Api_Dashboard_Snapshot_Create.Inc() diff --git a/pkg/api/dtos/plugins.go b/pkg/api/dtos/plugins.go index edc9d96d1ac5a..8b32bea41dc1c 100644 --- a/pkg/api/dtos/plugins.go +++ b/pkg/api/dtos/plugins.go @@ -34,6 +34,7 @@ type PluginListItem struct { LatestVersion string `json:"latestVersion"` HasUpdate bool `json:"hasUpdate"` DefaultNavUrl string `json:"defaultNavUrl"` + Category string `json:"category"` State plugins.PluginState `json:"state"` } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 1fceca12bfa12..e4fb0ed34c01a 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -1,9 +1,11 @@ package api import ( - "github.com/grafana/grafana/pkg/util" "strconv" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" @@ -85,12 +87,13 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf defaultDatasource = ds.Name } - if ds.JsonData != nil { - dsMap["jsonData"] = ds.JsonData - } else { - dsMap["jsonData"] = make(map[string]string) + jsonData := ds.JsonData + if jsonData == nil { + jsonData = simplejson.New() } + dsMap["jsonData"] = jsonData + if ds.Access == m.DS_ACCESS_DIRECT { if ds.BasicAuth { dsMap["basicAuth"] = util.GetBasicAuthHeader(ds.BasicAuthUser, ds.DecryptedBasicAuthPassword()) @@ -122,7 +125,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf if ds.Type == m.DS_PROMETHEUS { // add unproxied server URL for link to Prometheus web UI - dsMap["directUrl"] = ds.Url + jsonData.Set("directUrl", ds.Url) } datasources[ds.Name] = dsMap @@ -154,15 +157,15 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf } panels[panel.Id] = map[string]interface{}{ - "module": panel.Module, - "baseUrl": panel.BaseUrl, - "name": panel.Name, - "id": panel.Id, - "info": panel.Info, - "hideFromList": panel.HideFromList, - "sort": getPanelSort(panel.Id), - "dataFormats": panel.DataFormats, - "state": panel.State, + "module": panel.Module, + "baseUrl": panel.BaseUrl, + "name": panel.Name, + "id": panel.Id, + "info": panel.Info, + "hideFromList": panel.HideFromList, + "sort": getPanelSort(panel.Id), + "skipDataQuery": panel.SkipDataQuery, + "state": panel.State, } } diff --git a/pkg/api/grafana_com_proxy.go b/pkg/api/grafana_com_proxy.go index 541d66b0b35db..2a7a5427c7a1b 100644 --- a/pkg/api/grafana_com_proxy.go +++ b/pkg/api/grafana_com_proxy.go @@ -17,7 +17,6 @@ var grafanaComProxyTransport = &http.Transport{ Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, - DualStack: true, }).Dial, TLSHandshakeTimeout: 10 * time.Second, } diff --git a/pkg/api/index.go b/pkg/api/index.go index e7555e1462111..d4103b0aa5263 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -233,7 +233,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er if len(appLink.Children) > 0 && c.OrgRole == m.ROLE_ADMIN { appLink.Children = append(appLink.Children, &dtos.NavLink{Divider: true}) - appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "gicon gicon-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/edit"}) + appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "gicon gicon-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/"}) } if len(appLink.Children) > 0 { diff --git a/pkg/api/pluginproxy/access_token_provider.go b/pkg/api/pluginproxy/access_token_provider.go index 22407823ff99a..fb07bea00200e 100644 --- a/pkg/api/pluginproxy/access_token_provider.go +++ b/pkg/api/pluginproxy/access_token_provider.go @@ -67,14 +67,14 @@ func (provider *accessTokenProvider) getAccessToken(data templateData) (string, } } - urlInterpolated, err := interpolateString(provider.route.TokenAuth.Url, data) + urlInterpolated, err := InterpolateString(provider.route.TokenAuth.Url, data) if err != nil { return "", err } params := make(url.Values) for key, value := range provider.route.TokenAuth.Params { - interpolatedParam, err := interpolateString(value, data) + interpolatedParam, err := InterpolateString(value, data) if err != nil { return "", err } @@ -119,7 +119,7 @@ func (provider *accessTokenProvider) getJwtAccessToken(ctx context.Context, data conf := &jwt.Config{} if val, ok := provider.route.JwtTokenAuth.Params["client_email"]; ok { - interpolatedVal, err := interpolateString(val, data) + interpolatedVal, err := InterpolateString(val, data) if err != nil { return "", err } @@ -127,7 +127,7 @@ func (provider *accessTokenProvider) getJwtAccessToken(ctx context.Context, data } if val, ok := provider.route.JwtTokenAuth.Params["private_key"]; ok { - interpolatedVal, err := interpolateString(val, data) + interpolatedVal, err := InterpolateString(val, data) if err != nil { return "", err } @@ -135,7 +135,7 @@ func (provider *accessTokenProvider) getJwtAccessToken(ctx context.Context, data } if val, ok := provider.route.JwtTokenAuth.Params["token_uri"]; ok { - interpolatedVal, err := interpolateString(val, data) + interpolatedVal, err := InterpolateString(val, data) if err != nil { return "", err } diff --git a/pkg/api/pluginproxy/ds_auth_provider.go b/pkg/api/pluginproxy/ds_auth_provider.go index 7e0a88bae3d2d..e4147d494b9d3 100644 --- a/pkg/api/pluginproxy/ds_auth_provider.go +++ b/pkg/api/pluginproxy/ds_auth_provider.go @@ -1,13 +1,11 @@ package pluginproxy import ( - "bytes" "context" "fmt" "net/http" "net/url" "strings" - "text/template" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" @@ -24,7 +22,7 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route SecureJsonData: ds.SecureJsonData.Decrypt(), } - interpolatedURL, err := interpolateString(route.Url, data) + interpolatedURL, err := InterpolateString(route.Url, data) if err != nil { logger.Error("Error interpolating proxy url", "error", err) return @@ -81,24 +79,9 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route logger.Info("Requesting", "url", req.URL.String()) } -func interpolateString(text string, data templateData) (string, error) { - t, err := template.New("content").Parse(text) - if err != nil { - return "", fmt.Errorf("could not parse template %s", text) - } - - var contentBuf bytes.Buffer - err = t.Execute(&contentBuf, data) - if err != nil { - return "", fmt.Errorf("failed to execute template %s", text) - } - - return contentBuf.String(), nil -} - func addHeaders(reqHeaders *http.Header, route *plugins.AppPluginRoute, data templateData) error { for _, header := range route.Headers { - interpolated, err := interpolateString(header.Content, data) + interpolated, err := InterpolateString(header.Content, data) if err != nil { return err } diff --git a/pkg/api/pluginproxy/ds_auth_provider_test.go b/pkg/api/pluginproxy/ds_auth_provider_test.go index 9bd98a339e5eb..fa22cc0a168d4 100644 --- a/pkg/api/pluginproxy/ds_auth_provider_test.go +++ b/pkg/api/pluginproxy/ds_auth_provider_test.go @@ -14,7 +14,7 @@ func TestDsAuthProvider(t *testing.T) { }, } - interpolated, err := interpolateString("{{.SecureJsonData.Test}}", data) + interpolated, err := InterpolateString("{{.SecureJsonData.Test}}", data) So(err, ShouldBeNil) So(interpolated, ShouldEqual, "0asd+asd") }) diff --git a/pkg/api/pluginproxy/pluginproxy.go b/pkg/api/pluginproxy/pluginproxy.go index 5ee59017a825f..4ee4e5b8db3ab 100644 --- a/pkg/api/pluginproxy/pluginproxy.go +++ b/pkg/api/pluginproxy/pluginproxy.go @@ -2,12 +2,13 @@ package pluginproxy import ( "encoding/json" - "github.com/grafana/grafana/pkg/setting" "net" "net/http" "net/http/httputil" "net/url" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" @@ -38,6 +39,24 @@ func getHeaders(route *plugins.AppPluginRoute, orgId int64, appID string) (http. return result, err } +func updateURL(route *plugins.AppPluginRoute, orgId int64, appID string) (string, error) { + query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appID} + if err := bus.Dispatch(&query); err != nil { + return "", err + } + + data := templateData{ + JsonData: query.Result.JsonData, + SecureJsonData: query.Result.SecureJsonData.Decrypt(), + } + interpolated, err := InterpolateString(route.Url, data) + if err != nil { + return "", err + } + return interpolated, err +} + +// NewApiPluginProxy create a plugin proxy func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPluginRoute, appID string, cfg *setting.Cfg) *httputil.ReverseProxy { targetURL, _ := url.Parse(route.Url) @@ -48,7 +67,6 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl req.Host = targetURL.Host req.URL.Path = util.JoinURLFragments(targetURL.Path, proxyPath) - // clear cookie headers req.Header.Del("Cookie") req.Header.Del("Set-Cookie") @@ -72,13 +90,13 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl } // Create a HTTP header with the context in it. - ctxJson, err := json.Marshal(ctx.SignedInUser) + ctxJSON, err := json.Marshal(ctx.SignedInUser) if err != nil { ctx.JsonApiErr(500, "failed to marshal context to json.", err) return } - req.Header.Add("X-Grafana-Context", string(ctxJson)) + req.Header.Add("X-Grafana-Context", string(ctxJSON)) if cfg.SendUserHeader && !ctx.SignedInUser.IsAnonymous { req.Header.Add("X-Grafana-User", ctx.SignedInUser.Login) @@ -97,6 +115,27 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl } } + if len(route.Url) > 0 { + interpolatedURL, err := updateURL(route, ctx.OrgId, appID) + if err != nil { + ctx.JsonApiErr(500, "Could not interpolate plugin route url", err) + } + targetURL, err := url.Parse(interpolatedURL) + if err != nil { + ctx.JsonApiErr(500, "Could not parse custom url: %v", err) + return + } + req.URL.Scheme = targetURL.Scheme + req.URL.Host = targetURL.Host + req.Host = targetURL.Host + req.URL.Path = util.JoinURLFragments(targetURL.Path, proxyPath) + + if err != nil { + ctx.JsonApiErr(500, "Could not interpolate plugin route url", err) + return + } + } + // reqBytes, _ := httputil.DumpRequestOut(req, true); // log.Trace("Proxying plugin request: %s", string(reqBytes)) } diff --git a/pkg/api/pluginproxy/pluginproxy_test.go b/pkg/api/pluginproxy/pluginproxy_test.go index e4a4fdb25baa0..343823b00a277 100644 --- a/pkg/api/pluginproxy/pluginproxy_test.go +++ b/pkg/api/pluginproxy/pluginproxy_test.go @@ -53,6 +53,7 @@ func TestPluginProxy(t *testing.T) { }, }, &setting.Cfg{SendUserHeader: true}, + nil, ) Convey("Should add header with username", func() { @@ -69,6 +70,7 @@ func TestPluginProxy(t *testing.T) { }, }, &setting.Cfg{SendUserHeader: false}, + nil, ) Convey("Should not add header with username", func() { // Get will return empty string even if header is not set @@ -82,6 +84,7 @@ func TestPluginProxy(t *testing.T) { SignedInUser: &m.SignedInUser{IsAnonymous: true}, }, &setting.Cfg{SendUserHeader: true}, + nil, ) Convey("Should not add header with username", func() { @@ -89,14 +92,59 @@ func TestPluginProxy(t *testing.T) { So(req.Header.Get("X-Grafana-User"), ShouldEqual, "") }) }) + + Convey("When getting templated url", t, func() { + route := &plugins.AppPluginRoute{ + Url: "{{.JsonData.dynamicUrl}}", + Method: "GET", + } + + bus.AddHandler("test", func(query *m.GetPluginSettingByIdQuery) error { + query.Result = &m.PluginSetting{ + JsonData: map[string]interface{}{ + "dynamicUrl": "https://dynamic.grafana.com", + }, + } + return nil + }) + + req := getPluginProxiedRequest( + &m.ReqContext{ + SignedInUser: &m.SignedInUser{ + Login: "test_user", + }, + }, + &setting.Cfg{SendUserHeader: true}, + route, + ) + Convey("Headers should be updated", func() { + header, err := getHeaders(route, 1, "my-app") + So(err, ShouldBeNil) + So(header.Get("X-Grafana-User"), ShouldEqual, "") + }) + Convey("Should set req.URL to be interpolated value from jsonData", func() { + So(req.URL.String(), ShouldEqual, "https://dynamic.grafana.com") + }) + Convey("Route url should not be modified", func() { + So(route.Url, ShouldEqual, "{{.JsonData.dynamicUrl}}") + }) + }) + } // getPluginProxiedRequest is a helper for easier setup of tests based on global config and ReqContext. -func getPluginProxiedRequest(ctx *m.ReqContext, cfg *setting.Cfg) *http.Request { - route := &plugins.AppPluginRoute{} +func getPluginProxiedRequest(ctx *m.ReqContext, cfg *setting.Cfg, route *plugins.AppPluginRoute) *http.Request { + // insert dummy route if none is specified + if route == nil { + route = &plugins.AppPluginRoute{ + Path: "api/v4/", + Url: "https://www.google.com", + ReqRole: m.ROLE_EDITOR, + } + } proxy := NewApiPluginProxy(ctx, "", route, "", cfg) - req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) + req, err := http.NewRequest(http.MethodGet, route.Url, nil) So(err, ShouldBeNil) proxy.Director(req) return req diff --git a/pkg/api/pluginproxy/utils.go b/pkg/api/pluginproxy/utils.go new file mode 100644 index 0000000000000..f507a54d37608 --- /dev/null +++ b/pkg/api/pluginproxy/utils.go @@ -0,0 +1,49 @@ +package pluginproxy + +import ( + "bytes" + "fmt" + "net/url" + "text/template" + + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" +) + +// InterpolateString accepts template data and return a string with substitutions +func InterpolateString(text string, data templateData) (string, error) { + t, err := template.New("content").Parse(text) + if err != nil { + return "", fmt.Errorf("could not parse template %s", text) + } + + var contentBuf bytes.Buffer + err = t.Execute(&contentBuf, data) + if err != nil { + return "", fmt.Errorf("failed to execute template %s", text) + } + + return contentBuf.String(), nil +} + +// InterpolateURL accepts template data and return a string with substitutions +func InterpolateURL(anURL *url.URL, route *plugins.AppPluginRoute, orgID int64, appID string) (*url.URL, error) { + query := m.GetPluginSettingByIdQuery{OrgId: orgID, PluginId: appID} + result, err := url.Parse(anURL.String()) + if query.Result != nil { + if len(query.Result.JsonData) > 0 { + data := templateData{ + JsonData: query.Result.JsonData, + } + interpolatedResult, err := InterpolateString(anURL.String(), data) + if err == nil { + result, err = url.Parse(interpolatedResult) + if err != nil { + return nil, fmt.Errorf("Error parsing plugin route url %v", err) + } + } + } + } + + return result, err +} diff --git a/pkg/api/pluginproxy/utils_test.go b/pkg/api/pluginproxy/utils_test.go new file mode 100644 index 0000000000000..430ce34115c5f --- /dev/null +++ b/pkg/api/pluginproxy/utils_test.go @@ -0,0 +1,21 @@ +package pluginproxy + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestInterpolateString(t *testing.T) { + Convey("When interpolating string", t, func() { + data := templateData{ + SecureJsonData: map[string]string{ + "Test": "0asd+asd", + }, + } + + interpolated, err := InterpolateString("{{.SecureJsonData.Test}}", data) + So(err, ShouldBeNil) + So(interpolated, ShouldEqual, "0asd+asd") + }) +} diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index bec33313d0db9..92b5076de2eea 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -47,6 +47,7 @@ func (hs *HTTPServer) GetPluginList(c *m.ReqContext) Response { Id: pluginDef.Id, Name: pluginDef.Name, Type: pluginDef.Type, + Category: pluginDef.Category, Info: &pluginDef.Info, LatestVersion: pluginDef.GrafanaNetVersion, HasUpdate: pluginDef.GrafanaNetHasUpdate, diff --git a/pkg/cmd/grafana-cli/services/services.go b/pkg/cmd/grafana-cli/services/services.go index 8495aeea8112a..1bc1fb7fb1057 100644 --- a/pkg/cmd/grafana-cli/services/services.go +++ b/pkg/cmd/grafana-cli/services/services.go @@ -32,7 +32,6 @@ func Init(version string, skipTLSVerify bool) { DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, - DualStack: true, }).DialContext, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, diff --git a/pkg/components/imguploader/webdavuploader.go b/pkg/components/imguploader/webdavuploader.go index d7fa7f4889a5b..7fd513ed61e03 100644 --- a/pkg/components/imguploader/webdavuploader.go +++ b/pkg/components/imguploader/webdavuploader.go @@ -25,8 +25,7 @@ type WebdavUploader struct { var netTransport = &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ - Timeout: 60 * time.Second, - DualStack: true, + Timeout: 60 * time.Second, }).Dial, TLSHandshakeTimeout: 5 * time.Second, } diff --git a/pkg/models/datasource_cache.go b/pkg/models/datasource_cache.go index 864adb4a5a64d..c1c29c2c834b9 100644 --- a/pkg/models/datasource_cache.go +++ b/pkg/models/datasource_cache.go @@ -61,7 +61,6 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { Dial: (&net.Dialer{ Timeout: time.Duration(setting.DataProxyTimeout) * time.Second, KeepAlive: 30 * time.Second, - DualStack: true, }).Dial, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index feeae9366807d..132bb9e265859 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -45,6 +45,7 @@ type PluginBase struct { Includes []*PluginInclude `json:"includes"` Module string `json:"module"` BaseUrl string `json:"baseUrl"` + Category string `json:"category"` HideFromList bool `json:"hideFromList,omitempty"` Preload bool `json:"preload"` State PluginState `json:"state,omitempty"` diff --git a/pkg/plugins/panel_plugin.go b/pkg/plugins/panel_plugin.go index b90fd41e4650f..f44cd21bf00c8 100644 --- a/pkg/plugins/panel_plugin.go +++ b/pkg/plugins/panel_plugin.go @@ -4,7 +4,7 @@ import "encoding/json" type PanelPlugin struct { FrontendPluginBase - DataFormats []string `json:"dataFormats"` + SkipDataQuery bool `json:"skipDataQuery"` } func (p *PanelPlugin) Load(decoder *json.Decoder, pluginDir string) error { @@ -16,10 +16,6 @@ func (p *PanelPlugin) Load(decoder *json.Decoder, pluginDir string) error { return err } - if p.DataFormats == nil { - p.DataFormats = []string{"time_series", "table"} - } - Panels[p.Id] = p return nil } diff --git a/pkg/services/alerting/eval_context_test.go b/pkg/services/alerting/eval_context_test.go index af7e66b2f07e8..06d3ae16ed800 100644 --- a/pkg/services/alerting/eval_context_test.go +++ b/pkg/services/alerting/eval_context_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/grafana/grafana/pkg/models" ) @@ -195,12 +197,10 @@ func TestGetStateFromEvalContext(t *testing.T) { } for _, tc := range tcs { - ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}) + evalContext := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}) - tc.applyFn(ctx) - have := ctx.GetNewState() - if have != tc.expected { - t.Errorf("failed: %s \n expected '%s' have '%s'\n", tc.name, tc.expected, string(have)) - } + tc.applyFn(evalContext) + newState := evalContext.GetNewState() + assert.Equal(t, tc.expected, newState, "failed: %s \n expected '%s' have '%s'\n", tc.name, tc.expected, string(newState)) } } diff --git a/pkg/services/alerting/notifiers/base.go b/pkg/services/alerting/notifiers/base.go index 9616c2ab7cf6b..800505b63801b 100644 --- a/pkg/services/alerting/notifiers/base.go +++ b/pkg/services/alerting/notifiers/base.go @@ -48,12 +48,15 @@ func NewNotifierBase(model *models.AlertNotification) NotifierBase { // ShouldNotify checks this evaluation should send an alert notification func (n *NotifierBase) ShouldNotify(ctx context.Context, context *alerting.EvalContext, notiferState *models.AlertNotificationState) bool { + prevState := context.PrevAlertState + newState := context.Rule.State + // Only notify on state change. - if context.PrevAlertState == context.Rule.State && !n.SendReminder { + if prevState == newState && !n.SendReminder { return false } - if context.PrevAlertState == context.Rule.State && n.SendReminder { + if prevState == newState && n.SendReminder { // Do not notify if interval has not elapsed lastNotify := time.Unix(notiferState.UpdatedAt, 0) if notiferState.UpdatedAt != 0 && lastNotify.Add(n.Frequency).After(time.Now()) { @@ -61,32 +64,35 @@ func (n *NotifierBase) ShouldNotify(ctx context.Context, context *alerting.EvalC } // Do not notify if alert state is OK or pending even on repeated notify - if context.Rule.State == models.AlertStateOK || context.Rule.State == models.AlertStatePending { + if newState == models.AlertStateOK || newState == models.AlertStatePending { return false } } - // Do not notify when we become OK for the first time. - if context.PrevAlertState == models.AlertStateUnknown && context.Rule.State == models.AlertStateOK { + unknownOrNoData := prevState == models.AlertStateUnknown || prevState == models.AlertStateNoData + okOrPending := newState == models.AlertStatePending || newState == models.AlertStateOK + + // Do not notify when new state is ok/pending when previous is unknown or no_data + if unknownOrNoData && okOrPending { return false } - // Do not notify when we become OK for the first time. - if context.PrevAlertState == models.AlertStateUnknown && context.Rule.State == models.AlertStatePending { + // Do not notify when we become Pending for the first + if prevState == models.AlertStateNoData && newState == models.AlertStatePending { return false } // Do not notify when we become OK from pending - if context.PrevAlertState == models.AlertStatePending && context.Rule.State == models.AlertStateOK { + if prevState == models.AlertStatePending && newState == models.AlertStateOK { return false } // Do not notify when we OK -> Pending - if context.PrevAlertState == models.AlertStateOK && context.Rule.State == models.AlertStatePending { + if prevState == models.AlertStateOK && newState == models.AlertStatePending { return false } - // Do not notifu if state pending and it have been updated last minute + // Do not notify if state pending and it have been updated last minute if notiferState.State == models.AlertNotificationStatePending { lastUpdated := time.Unix(notiferState.UpdatedAt, 0) if lastUpdated.Add(1 * time.Minute).After(time.Now()) { @@ -95,7 +101,7 @@ func (n *NotifierBase) ShouldNotify(ctx context.Context, context *alerting.EvalC } // Do not notify when state is OK if DisableResolveMessage is set to true - if context.Rule.State == models.AlertStateOK && n.DisableResolveMessage { + if newState == models.AlertStateOK && n.DisableResolveMessage { return false } diff --git a/pkg/services/alerting/notifiers/base_test.go b/pkg/services/alerting/notifiers/base_test.go index bf09ecab0cd68..84294bfb29d58 100644 --- a/pkg/services/alerting/notifiers/base_test.go +++ b/pkg/services/alerting/notifiers/base_test.go @@ -5,8 +5,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/grafana/grafana/pkg/components/simplejson" - m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" . "github.com/smartystreets/goconvey/convey" ) @@ -16,76 +18,76 @@ func TestShouldSendAlertNotification(t *testing.T) { tcs := []struct { name string - prevState m.AlertStateType - newState m.AlertStateType + prevState models.AlertStateType + newState models.AlertStateType sendReminder bool frequency time.Duration - state *m.AlertNotificationState + state *models.AlertNotificationState expect bool }{ { name: "pending -> ok should not trigger an notification", - newState: m.AlertStateOK, - prevState: m.AlertStatePending, + newState: models.AlertStateOK, + prevState: models.AlertStatePending, sendReminder: false, expect: false, }, { name: "ok -> alerting should trigger an notification", - newState: m.AlertStateAlerting, - prevState: m.AlertStateOK, + newState: models.AlertStateAlerting, + prevState: models.AlertStateOK, sendReminder: false, expect: true, }, { name: "ok -> pending should not trigger an notification", - newState: m.AlertStatePending, - prevState: m.AlertStateOK, + newState: models.AlertStatePending, + prevState: models.AlertStateOK, sendReminder: false, expect: false, }, { name: "ok -> ok should not trigger an notification", - newState: m.AlertStateOK, - prevState: m.AlertStateOK, + newState: models.AlertStateOK, + prevState: models.AlertStateOK, sendReminder: false, expect: false, }, { name: "ok -> ok with reminder should not trigger an notification", - newState: m.AlertStateOK, - prevState: m.AlertStateOK, + newState: models.AlertStateOK, + prevState: models.AlertStateOK, sendReminder: true, expect: false, }, { name: "alerting -> ok should trigger an notification", - newState: m.AlertStateOK, - prevState: m.AlertStateAlerting, + newState: models.AlertStateOK, + prevState: models.AlertStateAlerting, sendReminder: false, expect: true, }, { name: "alerting -> ok should trigger an notification when reminders enabled", - newState: m.AlertStateOK, - prevState: m.AlertStateAlerting, + newState: models.AlertStateOK, + prevState: models.AlertStateAlerting, frequency: time.Minute * 10, sendReminder: true, - state: &m.AlertNotificationState{UpdatedAt: tnow.Add(-time.Minute).Unix()}, + state: &models.AlertNotificationState{UpdatedAt: tnow.Add(-time.Minute).Unix()}, expect: true, }, { name: "alerting -> alerting with reminder and no state should trigger", - newState: m.AlertStateAlerting, - prevState: m.AlertStateAlerting, + newState: models.AlertStateAlerting, + prevState: models.AlertStateAlerting, frequency: time.Minute * 10, sendReminder: true, @@ -93,78 +95,84 @@ func TestShouldSendAlertNotification(t *testing.T) { }, { name: "alerting -> alerting with reminder and last notification sent 1 minute ago should not trigger", - newState: m.AlertStateAlerting, - prevState: m.AlertStateAlerting, + newState: models.AlertStateAlerting, + prevState: models.AlertStateAlerting, frequency: time.Minute * 10, sendReminder: true, - state: &m.AlertNotificationState{UpdatedAt: tnow.Add(-time.Minute).Unix()}, + state: &models.AlertNotificationState{UpdatedAt: tnow.Add(-time.Minute).Unix()}, expect: false, }, { name: "alerting -> alerting with reminder and last notifciation sent 11 minutes ago should trigger", - newState: m.AlertStateAlerting, - prevState: m.AlertStateAlerting, + newState: models.AlertStateAlerting, + prevState: models.AlertStateAlerting, frequency: time.Minute * 10, sendReminder: true, - state: &m.AlertNotificationState{UpdatedAt: tnow.Add(-11 * time.Minute).Unix()}, + state: &models.AlertNotificationState{UpdatedAt: tnow.Add(-11 * time.Minute).Unix()}, expect: true, }, { name: "OK -> alerting with notifciation state pending and updated 30 seconds ago should not trigger", - newState: m.AlertStateAlerting, - prevState: m.AlertStateOK, - state: &m.AlertNotificationState{State: m.AlertNotificationStatePending, UpdatedAt: tnow.Add(-30 * time.Second).Unix()}, + newState: models.AlertStateAlerting, + prevState: models.AlertStateOK, + state: &models.AlertNotificationState{State: models.AlertNotificationStatePending, UpdatedAt: tnow.Add(-30 * time.Second).Unix()}, expect: false, }, { name: "OK -> alerting with notifciation state pending and updated 2 minutes ago should trigger", - newState: m.AlertStateAlerting, - prevState: m.AlertStateOK, - state: &m.AlertNotificationState{State: m.AlertNotificationStatePending, UpdatedAt: tnow.Add(-2 * time.Minute).Unix()}, + newState: models.AlertStateAlerting, + prevState: models.AlertStateOK, + state: &models.AlertNotificationState{State: models.AlertNotificationStatePending, UpdatedAt: tnow.Add(-2 * time.Minute).Unix()}, expect: true, }, { name: "unknown -> ok", - prevState: m.AlertStateUnknown, - newState: m.AlertStateOK, + prevState: models.AlertStateUnknown, + newState: models.AlertStateOK, expect: false, }, { name: "unknown -> pending", - prevState: m.AlertStateUnknown, - newState: m.AlertStatePending, + prevState: models.AlertStateUnknown, + newState: models.AlertStatePending, expect: false, }, { name: "unknown -> alerting", - prevState: m.AlertStateUnknown, - newState: m.AlertStateAlerting, + prevState: models.AlertStateUnknown, + newState: models.AlertStateAlerting, expect: true, }, + { + name: "no_data -> pending", + prevState: models.AlertStateNoData, + newState: models.AlertStatePending, + + expect: false, + }, } for _, tc := range tcs { - evalContext := alerting.NewEvalContext(context.TODO(), &alerting.Rule{ + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ State: tc.prevState, }) if tc.state == nil { - tc.state = &m.AlertNotificationState{} + tc.state = &models.AlertNotificationState{} } evalContext.Rule.State = tc.newState nb := &NotifierBase{SendReminder: tc.sendReminder, Frequency: tc.frequency} - if nb.ShouldNotify(evalContext.Ctx, evalContext, tc.state) != tc.expect { - t.Errorf("failed test %s.\n expected \n%+v \nto return: %v", tc.name, tc, tc.expect) - } + r := nb.ShouldNotify(evalContext.Ctx, evalContext, tc.state) + assert.Equal(t, r, tc.expect, "failed test %s. expected %+v to return: %v", tc.name, tc, tc.expect) } } @@ -172,7 +180,7 @@ func TestBaseNotifier(t *testing.T) { Convey("default constructor for notifiers", t, func() { bJson := simplejson.New() - model := &m.AlertNotification{ + model := &models.AlertNotification{ Uid: "1", Name: "name", Type: "email", diff --git a/pkg/services/notifications/mailer.go b/pkg/services/notifications/mailer.go index 4730ef7f0f113..f88deb2bf9608 100644 --- a/pkg/services/notifications/mailer.go +++ b/pkg/services/notifications/mailer.go @@ -14,6 +14,7 @@ import ( m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" + "github.com/pkg/errors" gomail "gopkg.in/mail.v2" ) @@ -23,6 +24,7 @@ func (ns *NotificationService) send(msg *Message) (int, error) { return 0, err } + var num int for _, address := range msg.To { m := gomail.NewMessage() m.SetHeader("From", msg.From) @@ -34,12 +36,16 @@ func (ns *NotificationService) send(msg *Message) (int, error) { m.SetBody("text/html", msg.Body) - if err := dialer.DialAndSend(m); err != nil { - return 0, err + e := dialer.DialAndSend(m) + if e != nil { + err = errors.Wrapf(e, "Failed to send notification to email address: %s", address) + continue } + + num += 1 } - return len(msg.To), nil + return num, err } func (ns *NotificationService) createDialer() (*gomail.Dialer, error) { diff --git a/pkg/services/notifications/webhook.go b/pkg/services/notifications/webhook.go index 2be3b14537299..3e2d097847f6f 100644 --- a/pkg/services/notifications/webhook.go +++ b/pkg/services/notifications/webhook.go @@ -32,8 +32,7 @@ var netTransport = &http.Transport{ }, Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ - Timeout: 30 * time.Second, - DualStack: true, + Timeout: 30 * time.Second, }).Dial, TLSHandshakeTimeout: 5 * time.Second, } diff --git a/pkg/services/rendering/http_mode.go b/pkg/services/rendering/http_mode.go index 40259c44746f6..a4d509f95659d 100644 --- a/pkg/services/rendering/http_mode.go +++ b/pkg/services/rendering/http_mode.go @@ -15,8 +15,7 @@ import ( var netTransport = &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ - Timeout: 30 * time.Second, - DualStack: true, + Timeout: 30 * time.Second, }).Dial, TLSHandshakeTimeout: 5 * time.Second, } diff --git a/public/app/app.ts b/public/app/app.ts index c72d17bbf5a24..9f95634330d57 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -19,7 +19,6 @@ import angular from 'angular'; import config from 'app/core/config'; // @ts-ignore ignoring this for now, otherwise we would have to extend _ interface with move import _ from 'lodash'; -import moment from 'moment'; import { addClassIfNoOverlayScrollbar } from 'app/core/utils/scrollbar'; import { importPluginModule } from 'app/features/plugins/plugin_loader'; @@ -36,6 +35,7 @@ import { setupAngularRoutes } from 'app/routes/routes'; import 'app/routes/GrafanaCtrl'; import 'app/features/all'; +import { setLocale } from '@grafana/ui/src/utils/moment_wrapper'; // import symlinked extensions const extensionsIndex = (require as any).context('.', true, /extensions\/index.ts/); @@ -68,7 +68,7 @@ export class GrafanaApp { init() { const app = angular.module('grafana', []); - moment.locale(config.bootData.user.locale); + setLocale(config.bootData.user.locale); app.config( ( diff --git a/public/app/core/components/search/search_results.html b/public/app/core/components/search/search_results.html index 09d21ba80c03c..a2337cfbc19cd 100644 --- a/public/app/core/components/search/search_results.html +++ b/public/app/core/components/search/search_results.html @@ -20,7 +20,7 @@
- +
{ - option.selected = false; - }); + this.selectedValues = _.filter(this.options, { selected: true }); + if (this.selectedValues.length > 1) { + _.each(this.options, option => { + option.selected = false; + }); + } else { + _.each(this.search.options, option => { + option.selected = true; + }); + } this.selectionsChanged(false); } diff --git a/public/app/core/filters/filters.ts b/public/app/core/filters/filters.ts index 22e1f9a79837f..1176395d09afe 100644 --- a/public/app/core/filters/filters.ts +++ b/public/app/core/filters/filters.ts @@ -1,8 +1,8 @@ import _ from 'lodash'; import angular from 'angular'; -import moment from 'moment'; import coreModule from '../core_module'; import { TemplateSrv } from 'app/features/templating/template_srv'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; coreModule.filter('stringSort', () => { return (input: any) => { @@ -33,9 +33,9 @@ coreModule.filter('moment', () => { return (date: string, mode: string) => { switch (mode) { case 'ago': - return moment(date).fromNow(); + return dateTime(date).fromNow(); } - return moment(date).fromNow(); + return dateTime(date).fromNow(); }; }); diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index 03420f8029949..2ba60ce428e9f 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -1,5 +1,4 @@ import _ from 'lodash'; -import moment from 'moment'; import ansicolor from 'vendor/ansicolor/ansicolor'; import { @@ -17,6 +16,7 @@ import { } from '@grafana/ui'; import { getThemeColor } from 'app/core/utils/colors'; import { hasAnsiCodes } from 'app/core/utils/text'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; export const LogLevelColor = { [LogLevel.critical]: colors[7], @@ -356,7 +356,12 @@ export function seriesDataToLogsModel(seriesData: SeriesData[], intervalMs: numb return logsModel; } - return undefined; + return { + hasUniqueLabels: false, + rows: [], + meta: [], + series: [], + }; } export function logSeriesToLogsModel(logSeries: SeriesData[]): LogsModel { @@ -435,7 +440,7 @@ export function processLogSeriesRow( const ts = row[timeFieldIndex]; const stringFieldIndex = fieldCache.getFirstFieldOfType(FieldType.string).index; const message = row[stringFieldIndex]; - const time = moment(ts); + const time = dateTime(ts); const timeEpochMs = time.valueOf(); const timeFromNow = time.fromNow(); const timeLocal = time.format('YYYY-MM-DD HH:mm:ss'); diff --git a/public/app/core/specs/logs_model.test.ts b/public/app/core/specs/logs_model.test.ts index 807605965ef95..c30a3ebfa3910 100644 --- a/public/app/core/specs/logs_model.test.ts +++ b/public/app/core/specs/logs_model.test.ts @@ -333,22 +333,29 @@ describe('LogsParsers', () => { }); }); +const emptyLogsModel = { + hasUniqueLabels: false, + rows: [], + meta: [], + series: [], +}; + describe('seriesDataToLogsModel', () => { - it('given empty series should return undefined', () => { - expect(seriesDataToLogsModel([] as SeriesData[], 0)).toBeUndefined(); + it('given empty series should return empty logs model', () => { + expect(seriesDataToLogsModel([] as SeriesData[], 0)).toMatchObject(emptyLogsModel); }); - it('given series without correct series name should not be processed', () => { + it('given series without correct series name should return empty logs model', () => { const series: SeriesData[] = [ { fields: [], rows: [], }, ]; - expect(seriesDataToLogsModel(series, 0)).toBeUndefined(); + expect(seriesDataToLogsModel(series, 0)).toMatchObject(emptyLogsModel); }); - it('given series without a time field should not be processed', () => { + it('given series without a time field should return empty logs model', () => { const series: SeriesData[] = [ { fields: [ @@ -360,10 +367,10 @@ describe('seriesDataToLogsModel', () => { rows: [], }, ]; - expect(seriesDataToLogsModel(series, 0)).toBeUndefined(); + expect(seriesDataToLogsModel(series, 0)).toMatchObject(emptyLogsModel); }); - it('given series without a string field should not be processed', () => { + it('given series without a string field should return empty logs model', () => { const series: SeriesData[] = [ { fields: [ @@ -375,7 +382,7 @@ describe('seriesDataToLogsModel', () => { rows: [], }, ]; - expect(seriesDataToLogsModel(series, 0)).toBeUndefined(); + expect(seriesDataToLogsModel(series, 0)).toMatchObject(emptyLogsModel); }); it('given one series should return expected logs model', () => { diff --git a/public/app/core/specs/rangeutil.test.ts b/public/app/core/specs/rangeutil.test.ts index 0094b714cd9d3..d23bbf9f0eb24 100644 --- a/public/app/core/specs/rangeutil.test.ts +++ b/public/app/core/specs/rangeutil.test.ts @@ -1,6 +1,6 @@ import * as rangeUtil from '@grafana/ui/src/utils/rangeutil'; import _ from 'lodash'; -import moment from 'moment'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; describe('rangeUtil', () => { describe('Can get range grouped list of ranges', () => { @@ -69,7 +69,7 @@ describe('rangeUtil', () => { it('Date range with absolute to now', () => { const text = rangeUtil.describeTimeRange({ - from: moment([2014, 10, 10, 2, 3, 4]), + from: dateTime([2014, 10, 10, 2, 3, 4]), to: 'now', }); expect(text).toBe('Nov 10, 2014 02:03:04 to a few seconds ago'); @@ -77,7 +77,7 @@ describe('rangeUtil', () => { it('Date range with absolute to relative', () => { const text = rangeUtil.describeTimeRange({ - from: moment([2014, 10, 10, 2, 3, 4]), + from: dateTime([2014, 10, 10, 2, 3, 4]), to: 'now-1d', }); expect(text).toBe('Nov 10, 2014 02:03:04 to a day ago'); @@ -86,7 +86,7 @@ describe('rangeUtil', () => { it('Date range with relative to absolute', () => { const text = rangeUtil.describeTimeRange({ from: 'now-7d', - to: moment([2014, 10, 10, 2, 3, 4]), + to: dateTime([2014, 10, 10, 2, 3, 4]), }); expect(text).toBe('7 days ago to Nov 10, 2014 02:03:04'); }); diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 2f28e60fdbc20..3a0752d2a5ed0 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -5,10 +5,15 @@ import { updateHistory, clearHistory, hasNonEmptyQuery, + instanceOfDataQueryError, + getValueWithRefId, + getFirstQueryErrorWithoutRefId, + getRefIds, } from './explore'; import { ExploreUrlState } from 'app/types/explore'; import store from 'app/core/store'; import { LogsDedupStrategy } from 'app/core/logs_model'; +import { DataQueryError } from '@grafana/ui'; const DEFAULT_EXPLORE_STATE: ExploreUrlState = { datasource: null, @@ -188,3 +193,164 @@ describe('hasNonEmptyQuery', () => { expect(hasNonEmptyQuery([])).toBeFalsy(); }); }); + +describe('instanceOfDataQueryError', () => { + describe('when called with a DataQueryError', () => { + it('then it should return true', () => { + const error: DataQueryError = { + message: 'A message', + status: '200', + statusText: 'Ok', + }; + const result = instanceOfDataQueryError(error); + + expect(result).toBe(true); + }); + }); + + describe('when called with a non DataQueryError', () => { + it('then it should return false', () => { + const error = {}; + const result = instanceOfDataQueryError(error); + + expect(result).toBe(false); + }); + }); +}); + +describe('hasRefId', () => { + describe('when called with a null value', () => { + it('then it should return null', () => { + const input = null; + const result = getValueWithRefId(input); + + expect(result).toBeNull(); + }); + }); + + describe('when called with a non object value', () => { + it('then it should return null', () => { + const input = 123; + const result = getValueWithRefId(input); + + expect(result).toBeNull(); + }); + }); + + describe('when called with an object that has refId', () => { + it('then it should return the object', () => { + const input = { refId: 'A' }; + const result = getValueWithRefId(input); + + expect(result).toBe(input); + }); + }); + + describe('when called with an array that has refId', () => { + it('then it should return the object', () => { + const input = [123, null, {}, { refId: 'A' }]; + const result = getValueWithRefId(input); + + expect(result).toBe(input[3]); + }); + }); + + describe('when called with an object that has refId somewhere in the object tree', () => { + it('then it should return the object', () => { + const input: any = { data: [123, null, {}, { series: [123, null, {}, { refId: 'A' }] }] }; + const result = getValueWithRefId(input); + + expect(result).toBe(input.data[3].series[3]); + }); + }); +}); + +describe('getFirstQueryErrorWithoutRefId', () => { + describe('when called with a null value', () => { + it('then it should return null', () => { + const errors: DataQueryError[] = null; + const result = getFirstQueryErrorWithoutRefId(errors); + + expect(result).toBeNull(); + }); + }); + + describe('when called with an array with only refIds', () => { + it('then it should return undefined', () => { + const errors: DataQueryError[] = [{ refId: 'A' }, { refId: 'B' }]; + const result = getFirstQueryErrorWithoutRefId(errors); + + expect(result).toBeUndefined(); + }); + }); + + describe('when called with an array with and without refIds', () => { + it('then it should return undefined', () => { + const errors: DataQueryError[] = [ + { refId: 'A' }, + { message: 'A message' }, + { refId: 'B' }, + { message: 'B message' }, + ]; + const result = getFirstQueryErrorWithoutRefId(errors); + + expect(result).toBe(errors[1]); + }); + }); +}); + +describe('getRefIds', () => { + describe('when called with a null value', () => { + it('then it should return empty array', () => { + const input = null; + const result = getRefIds(input); + + expect(result).toEqual([]); + }); + }); + + describe('when called with a non object value', () => { + it('then it should return empty array', () => { + const input = 123; + const result = getRefIds(input); + + expect(result).toEqual([]); + }); + }); + + describe('when called with an object that has refId', () => { + it('then it should return an array with that refId', () => { + const input = { refId: 'A' }; + const result = getRefIds(input); + + expect(result).toEqual(['A']); + }); + }); + + describe('when called with an array that has refIds', () => { + it('then it should return an array with unique refIds', () => { + const input = [123, null, {}, { refId: 'A' }, { refId: 'A' }, { refId: 'B' }]; + const result = getRefIds(input); + + expect(result).toEqual(['A', 'B']); + }); + }); + + describe('when called with an object that has refIds somewhere in the object tree', () => { + it('then it should return return an array with unique refIds', () => { + const input: any = { + data: [ + 123, + null, + { refId: 'B', series: [{ refId: 'X' }] }, + { refId: 'B' }, + {}, + { series: [123, null, {}, { refId: 'A' }] }, + ], + }; + const result = getRefIds(input); + + expect(result).toEqual(['B', 'X', 'A']); + }); + }); +}); diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index 5df42ad92d912..7a19fd5a822da 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -1,6 +1,5 @@ // Libraries import _ from 'lodash'; -import moment, { Moment } from 'moment'; // Services & Utils import * as dateMath from '@grafana/ui/src/utils/datemath'; @@ -21,6 +20,8 @@ import { DataSourceApi, toSeriesData, guessFieldTypes, + TimeFragment, + DataQueryError, } from '@grafana/ui'; import TimeSeries from 'app/core/time_series2'; import { @@ -33,6 +34,7 @@ import { ResultGetter, } from 'app/types/explore'; import { LogsDedupStrategy, seriesDataToLogsModel } from 'app/core/logs_model'; +import { toUtc } from '@grafana/ui/src/utils/moment_wrapper'; export const DEFAULT_RANGE = { from: 'now-6h', @@ -109,8 +111,7 @@ export async function getExploreUrl( } export function buildQueryTransaction( - query: DataQuery, - rowIndex: number, + queries: DataQuery[], resultType: ResultType, queryOptions: QueryOptions, range: TimeRange, @@ -119,12 +120,11 @@ export function buildQueryTransaction( ): QueryTransaction { const { interval, intervalMs } = queryIntervals; - const configuredQueries = [ - { - ...query, - ...queryOptions, - }, - ]; + const configuredQueries = queries.map(query => ({ ...query, ...queryOptions })); + const key = queries.reduce((combinedKey, query) => { + combinedKey += query.key; + return combinedKey; + }, ''); // Clone range for query request // const queryRange: RawTimeRange = { ...range }; @@ -133,7 +133,7 @@ export function buildQueryTransaction( // Using `format` here because it relates to the view panel that the request is for. // However, some datasources don't use `panelId + query.refId`, but only `panelId`. // Therefore panel id has to be unique. - const panelId = `${queryOptions.format}-${query.key}`; + const panelId = `${queryOptions.format}-${key}`; const options = { interval, @@ -150,10 +150,9 @@ export function buildQueryTransaction( }; return { + queries, options, - query, resultType, - rowIndex, scanning, id: generateKey(), // reusing for unique ID done: false, @@ -194,6 +193,20 @@ export const safeParseJson = (text: string) => { } }; +export const safeStringifyValue = (value: any, space?: number) => { + if (!value) { + return ''; + } + + try { + return JSON.stringify(value, null, space); + } catch (error) { + console.error(error); + } + + return ''; +}; + export function parseUrlState(initial: string | undefined): ExploreUrlState { const parsed = safeParseJson(initial); const errorResult = { @@ -264,12 +277,34 @@ export function generateEmptyQuery(queries: DataQuery[], index = 0): DataQuery { return { refId: getNextRefIdChar(queries), key: generateKey(index) }; } +export const generateNewKeyAndAddRefIdIfMissing = (target: DataQuery, queries: DataQuery[], index = 0): DataQuery => { + const key = generateKey(index); + const refId = target.refId || getNextRefIdChar(queries); + + return { ...target, refId, key }; +}; + /** * Ensure at least one target exists and that targets have the necessary keys */ export function ensureQueries(queries?: DataQuery[]): DataQuery[] { if (queries && typeof queries === 'object' && queries.length > 0) { - return queries.map((query, i) => ({ ...query, ...generateEmptyQuery(queries, i) })); + const allQueries = []; + for (let index = 0; index < queries.length; index++) { + const query = queries[index]; + const key = generateKey(index); + let refId = query.refId; + if (!refId) { + refId = getNextRefIdChar(allQueries); + } + + allQueries.push({ + ...query, + refId, + key, + }); + } + return allQueries; } return [{ ...generateEmptyQuery(queries) }]; } @@ -289,26 +324,20 @@ export function hasNonEmptyQuery(queries: TQuery ); } -export function calculateResultsFromQueryTransactions( - queryTransactions: QueryTransaction[], - datasource: any, - graphInterval: number -) { - const graphResult = _.flatten( - queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result) - ); - const tableResult = mergeTablesIntoModel( - new TableModel(), - ...queryTransactions - .filter(qt => qt.resultType === 'Table' && qt.done && qt.result && qt.result.columns && qt.result.rows) - .map(qt => qt.result) - ); - const logsResult = seriesDataToLogsModel( - _.flatten( - queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result) - ).map(r => guessFieldTypes(toSeriesData(r))), - graphInterval - ); +export function calculateResultsFromQueryTransactions(result: any, resultType: ResultType, graphInterval: number) { + const flattenedResult: any[] = _.flatten(result); + const graphResult = resultType === 'Graph' && result ? result : null; + const tableResult = + resultType === 'Table' && result + ? mergeTablesIntoModel( + new TableModel(), + ...flattenedResult.filter((r: any) => r.columns && r.rows).map((r: any) => r as TableModel) + ) + : mergeTablesIntoModel(new TableModel()); + const logsResult = + resultType === 'Logs' && result + ? seriesDataToLogsModel(flattenedResult.map(r => guessFieldTypes(toSeriesData(r))), graphInterval) + : null; return { graphResult, @@ -401,7 +430,7 @@ export const getTimeRange = (timeZone: TimeZone, rawRange: RawTimeRange): TimeRa }; }; -const parseRawTime = (value): Moment | string => { +const parseRawTime = (value): TimeFragment => { if (value === null) { return null; } @@ -410,19 +439,19 @@ const parseRawTime = (value): Moment | string => { return value; } if (value.length === 8) { - return moment.utc(value, 'YYYYMMDD'); + return toUtc(value, 'YYYYMMDD'); } if (value.length === 15) { - return moment.utc(value, 'YYYYMMDDTHHmmss'); + return toUtc(value, 'YYYYMMDDTHHmmss'); } // Backward compatibility if (value.length === 19) { - return moment.utc(value, 'YYYY-MM-DD HH:mm:ss'); + return toUtc(value, 'YYYY-MM-DD HH:mm:ss'); } if (!isNaN(value)) { const epoch = parseInt(value, 10); - return moment.utc(epoch); + return toUtc(epoch); } return null; @@ -440,3 +469,63 @@ export const getTimeRangeFromUrl = (range: RawTimeRange, timeZone: TimeZone): Ti raw, }; }; + +export const instanceOfDataQueryError = (value: any): value is DataQueryError => { + return value.message !== undefined && value.status !== undefined && value.statusText !== undefined; +}; + +export const getValueWithRefId = (value: any): any | null => { + if (!value) { + return null; + } + + if (typeof value !== 'object') { + return null; + } + + if (value.refId) { + return value; + } + + const keys = Object.keys(value); + for (let index = 0; index < keys.length; index++) { + const key = keys[index]; + const refId = getValueWithRefId(value[key]); + if (refId) { + return refId; + } + } + + return null; +}; + +export const getFirstQueryErrorWithoutRefId = (errors: DataQueryError[]) => { + if (!errors) { + return null; + } + + return errors.filter(error => (error.refId ? false : true))[0]; +}; + +export const getRefIds = (value: any): string[] => { + if (!value) { + return []; + } + + if (typeof value !== 'object') { + return []; + } + + const keys = Object.keys(value); + const refIds = []; + for (let index = 0; index < keys.length; index++) { + const key = keys[index]; + if (key === 'refId') { + refIds.push(value[key]); + continue; + } + refIds.push(getRefIds(value[key])); + } + + return _.uniq(_.flatten(refIds)); +}; diff --git a/public/app/core/utils/file_export.ts b/public/app/core/utils/file_export.ts index c59d646839fed..6d341b5582e29 100644 --- a/public/app/core/utils/file_export.ts +++ b/public/app/core/utils/file_export.ts @@ -1,7 +1,7 @@ import { isBoolean, isNumber, sortedUniq, sortedIndexOf, unescape as htmlUnescaped } from 'lodash'; -import moment from 'moment'; import { saveAs } from 'file-saver'; import { isNullOrUndefined } from 'util'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; const DEFAULT_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ'; const POINT_TIME_INDEX = 1; @@ -61,7 +61,7 @@ export function convertSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATE text += formatRow( [ seriesList[seriesIndex].alias, - moment(seriesList[seriesIndex].datapoints[i][POINT_TIME_INDEX]).format(dateTimeFormat), + dateTime(seriesList[seriesIndex].datapoints[i][POINT_TIME_INDEX]).format(dateTimeFormat), seriesList[seriesIndex].datapoints[i][POINT_VALUE_INDEX], ], i < seriesList[seriesIndex].datapoints.length - 1 || seriesIndex < seriesList.length - 1 @@ -92,7 +92,7 @@ export function convertSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAU // make text for (let i = 0; i < extendedDatapointsList[0].length; i += 1) { - const timestamp = moment(extendedDatapointsList[0][i][POINT_TIME_INDEX]).format(dateTimeFormat); + const timestamp = dateTime(extendedDatapointsList[0][i][POINT_TIME_INDEX]).format(dateTimeFormat); text += formatRow( [timestamp].concat( extendedDatapointsList.map(datapoints => { diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index dcd31b71419c8..703feb141d6da 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -114,7 +114,7 @@ export class AlertRuleList extends PureComponent {
- How to add an alert + How to add an alert
diff --git a/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap b/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap index dbc8945a1e73b..3e255c5e70241 100644 --- a/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap +++ b/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap @@ -89,10 +89,7 @@ exports[`Render should render alert rules 1`] = ` className="btn btn-secondary" onClick={[Function]} > - - How to add an alert + How to add an alert
@@ -236,10 +233,7 @@ exports[`Render should render component 1`] = ` className="btn btn-secondary" onClick={[Function]} > - - How to add an alert + How to add an alert
diff --git a/public/app/features/alerting/state/reducers.ts b/public/app/features/alerting/state/reducers.ts index f956dc3400506..2400760093865 100644 --- a/public/app/features/alerting/state/reducers.ts +++ b/public/app/features/alerting/state/reducers.ts @@ -1,7 +1,7 @@ -import moment from 'moment'; import { AlertRuleDTO, AlertRule, AlertRulesState } from 'app/types'; import { Action, ActionTypes } from './actions'; import alertDef from './alertDef'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; export const initialState: AlertRulesState = { items: [], searchQuery: '', isLoading: false }; @@ -10,7 +10,7 @@ function convertToAlertRule(rule, state): AlertRule { rule.stateText = stateModel.text; rule.stateIcon = stateModel.iconClass; rule.stateClass = stateModel.stateClass; - rule.stateAge = moment(rule.newStateDate) + rule.stateAge = dateTime(rule.newStateDate) .fromNow() .replace(' ago', ''); diff --git a/public/app/features/annotations/event_editor.ts b/public/app/features/annotations/event_editor.ts index a6970790bb33d..3cec5f13d7a77 100644 --- a/public/app/features/annotations/event_editor.ts +++ b/public/app/features/annotations/event_editor.ts @@ -1,8 +1,8 @@ import _ from 'lodash'; -import moment from 'moment'; import { coreModule } from 'app/core/core'; import { MetricsPanelCtrl } from 'app/plugins/sdk'; import { AnnotationEvent } from '@grafana/ui'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; export class EventEditorCtrl { panelCtrl: MetricsPanelCtrl; @@ -86,7 +86,7 @@ export class EventEditorCtrl { function tryEpochToMoment(timestamp) { if (timestamp && _.isNumber(timestamp)) { const epoch = Number(timestamp); - return moment(epoch); + return dateTime(epoch); } else { return timestamp; } diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx index dcb71cadbe0df..a4a1adffc7d86 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx @@ -132,10 +132,15 @@ export class AddPanelWidget extends React.Component { dashboard.removePanel(this.props.panel); }; - renderOptionLink = (icon, text, onClick) => { + renderOptionLink = (icon: string, text: string, onClick) => { return (
- +
diff --git a/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap b/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap index 00faf48d8dfc1..1908aa5f7f146 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap +++ b/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap @@ -35,6 +35,7 @@ exports[`Render should render component 1`] = ` >
= ({ icon, tooltip, classSu if (onClick) { return ( - diff --git a/public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts b/public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts index 23ad97708b47a..748acb94831d8 100644 --- a/public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts +++ b/public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts @@ -17,7 +17,7 @@ const template = `
- +
diff --git a/public/app/features/dashboard/components/ShareModal/ShareModalCtrl.ts b/public/app/features/dashboard/components/ShareModal/ShareModalCtrl.ts index b4ce25485b859..9d3c36ee2bc80 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareModalCtrl.ts +++ b/public/app/features/dashboard/components/ShareModal/ShareModalCtrl.ts @@ -1,6 +1,6 @@ import angular from 'angular'; import config from 'app/core/config'; -import moment from 'moment'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; /** @ngInject */ export function ShareModalCtrl($scope, $rootScope, $location, $timeout, timeSrv, templateSrv, linkSrv) { @@ -93,7 +93,7 @@ export function ShareModalCtrl($scope, $rootScope, $location, $timeout, timeSrv, // This function will try to return the proper full name of the local timezone // Chrome does not handle the timezone offset (but phantomjs does) $scope.getLocalTimeZone = () => { - const utcOffset = '&tz=UTC' + encodeURIComponent(moment().format('Z')); + const utcOffset = '&tz=UTC' + encodeURIComponent(dateTime().format('Z')); // Older browser does not the internationalization API if (!(window as any).Intl) { diff --git a/public/app/features/dashboard/components/ShareModal/template.html b/public/app/features/dashboard/components/ShareModal/template.html index 86643043f4f37..63f317fb44272 100644 --- a/public/app/features/dashboard/components/ShareModal/template.html +++ b/public/app/features/dashboard/components/ShareModal/template.html @@ -92,7 +92,7 @@
diff --git a/public/app/features/dashboard/components/TimePicker/TimePickerCtrl.ts b/public/app/features/dashboard/components/TimePicker/TimePickerCtrl.ts index a710bded5935b..f1d626e9e3479 100644 --- a/public/app/features/dashboard/components/TimePicker/TimePickerCtrl.ts +++ b/public/app/features/dashboard/components/TimePicker/TimePickerCtrl.ts @@ -1,6 +1,5 @@ import _ from 'lodash'; import angular from 'angular'; -import moment from 'moment'; import * as rangeUtil from '@grafana/ui/src/utils/rangeutil'; @@ -38,7 +37,7 @@ export class TimePickerCtrl { // init options this.panel = this.dashboard.timepicker; _.defaults(this.panel, TimePickerCtrl.defaults); - this.firstDayOfWeek = moment.localeData().firstDayOfWeek(); + this.firstDayOfWeek = getLocaleData().firstDayOfWeek(); // init time stuff this.onRefresh(); @@ -51,10 +50,10 @@ export class TimePickerCtrl { if (!this.dashboard.isTimezoneUtc()) { time.from.local(); time.to.local(); - if (moment.isMoment(timeRaw.from)) { + if (isDateTime(timeRaw.from)) { timeRaw.from.local(); } - if (moment.isMoment(timeRaw.to)) { + if (isDateTime(timeRaw.to)) { timeRaw.to.local(); } this.isUtc = false; @@ -67,7 +66,7 @@ export class TimePickerCtrl { this.tooltip = this.dashboard.formatDate(time.from) + '
to
'; this.tooltip += this.dashboard.formatDate(time.to); this.timeRaw = timeRaw; - this.isAbsolute = moment.isMoment(this.timeRaw.to); + this.isAbsolute = isDateTime(this.timeRaw.to); } zoom(factor) { @@ -94,7 +93,7 @@ export class TimePickerCtrl { from = range.from.valueOf(); } - this.timeSrv.setTime({ from: moment.utc(from), to: moment.utc(to) }); + this.timeSrv.setTime({ from: toUtc(from), to: toUtc(to) }); } openDropdown() { @@ -141,7 +140,7 @@ export class TimePickerCtrl { } getAbsoluteMomentForTimezone(jsDate) { - return this.dashboard.isTimezoneUtc() ? moment(jsDate).utc() : moment(jsDate); + return this.dashboard.isTimezoneUtc() ? dateTime(jsDate).utc() : dateTime(jsDate); } setRelativeFilter(timespan) { @@ -186,4 +185,5 @@ angular.module('grafana.directives').directive('gfTimePickerSettings', settingsD angular.module('grafana.directives').directive('gfTimePicker', timePickerDirective); import { inputDateDirective } from './validation'; +import { toUtc, getLocaleData, isDateTime, dateTime } from '@grafana/ui/src/utils/moment_wrapper'; angular.module('grafana.directives').directive('inputDatetime', inputDateDirective); diff --git a/public/app/features/dashboard/components/TimePicker/validation.ts b/public/app/features/dashboard/components/TimePicker/validation.ts index 7bf229441ecab..a99409a0fb0c6 100644 --- a/public/app/features/dashboard/components/TimePicker/validation.ts +++ b/public/app/features/dashboard/components/TimePicker/validation.ts @@ -1,5 +1,5 @@ -import moment from 'moment'; import * as dateMath from '@grafana/ui/src/utils/datemath'; +import { toUtc, dateTime, isDateTime } from '@grafana/ui/src/utils/moment_wrapper'; export function inputDateDirective() { return { @@ -20,9 +20,9 @@ export function inputDateDirective() { let parsed; if ($scope.ctrl.isUtc) { - parsed = moment.utc(text, format); + parsed = toUtc(text, format); } else { - parsed = moment(text, format); + parsed = dateTime(text, format); } if (!parsed.isValid()) { @@ -35,7 +35,7 @@ export function inputDateDirective() { }; const toUser = currentValue => { - if (moment.isMoment(currentValue)) { + if (isDateTime(currentValue)) { return currentValue.format(format); } else { return currentValue; diff --git a/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.ts b/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.ts index 19795ffc56474..cc918330a5e96 100644 --- a/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.ts +++ b/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.ts @@ -1,10 +1,10 @@ import _ from 'lodash'; import angular from 'angular'; -import moment from 'moment'; import locationUtil from 'app/core/utils/location_util'; import { DashboardModel } from '../../state/DashboardModel'; import { HistoryListOpts, RevisionsModel, CalculateDiffOptions, HistorySrv } from './HistorySrv'; +import { dateTime, toUtc } from '@grafana/ui/src/utils/moment_wrapper'; export class HistoryListCtrl { appending: boolean; @@ -74,8 +74,8 @@ export class HistoryListCtrl { } formatBasicDate(date) { - const now = this.dashboard.timezone === 'browser' ? moment() : moment.utc(); - const then = this.dashboard.timezone === 'browser' ? moment(date) : moment.utc(date); + const now = this.dashboard.timezone === 'browser' ? dateTime() : toUtc(); + const then = this.dashboard.timezone === 'browser' ? dateTime(date) : toUtc(date); return then.from(now); } diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 5a6d35a4427c0..38291d787c36c 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -222,7 +222,7 @@ export class PanelChrome extends PureComponent { } get wantsQueryExecution() { - return this.props.plugin.meta.dataFormats.length > 0 && !this.hasPanelSnapshot; + return !(this.props.plugin.meta.skipDataQuery || this.hasPanelSnapshot); } renderPanel(width: number, height: number): JSX.Element { @@ -250,6 +250,7 @@ export class PanelChrome extends PureComponent { {loading === LoadingState.Loading && this.renderLoadingState()}
{ return ( <> -
-
+ +
diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx index 66a942f0afc64..a50af1eb56c43 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx @@ -14,7 +14,9 @@ export const PanelHeaderMenuItem: FC = props => {
  • {props.iconClassName && } - {props.text} + + {props.text} + {props.shortcut && {props.shortcut}} {props.children} diff --git a/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx b/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx index c71ebc4357757..80440c9631cb4 100644 --- a/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx +++ b/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx @@ -49,7 +49,6 @@ export function getPanelPluginNotFound(id: string): PanelPlugin { type: PluginType.panel, module: '', baseUrl: '', - dataFormats: [], info: { author: { name: '', diff --git a/public/app/features/dashboard/panel_editor/PanelEditor.tsx b/public/app/features/dashboard/panel_editor/PanelEditor.tsx index 5cf7ad78d1f89..722b211e4ef12 100644 --- a/public/app/features/dashboard/panel_editor/PanelEditor.tsx +++ b/public/app/features/dashboard/panel_editor/PanelEditor.tsx @@ -105,7 +105,7 @@ export class PanelEditor extends PureComponent { ]; // handle panels that do not have queries tab - if (plugin.meta.dataFormats.length === 0) { + if (plugin.meta.skipDataQuery) { // remove queries tab tabs.shift(); // switch tab @@ -145,7 +145,7 @@ function TabItem({ tab, activeTab, onClick }: TabItemParams) { return (
    onClick(tab)}> - + diff --git a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx index 0cfa96da2a48d..eb7bad6524243 100644 --- a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx +++ b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx @@ -185,11 +185,14 @@ export class VisualizationTab extends PureComponent { ); } else { return ( -
    - -
    {meta.name}
    - -
    + <> +
    + +
    {meta.name}
    + +
    + + ); } }; @@ -237,7 +240,6 @@ export class VisualizationTab extends PureComponent { onClose={this.onCloseVizPicker} /> - {this.renderPanelOptions()} diff --git a/public/app/features/dashboard/services/DashboardLoaderSrv.ts b/public/app/features/dashboard/services/DashboardLoaderSrv.ts index 7cde345ee9912..2d1c0b556b021 100644 --- a/public/app/features/dashboard/services/DashboardLoaderSrv.ts +++ b/public/app/features/dashboard/services/DashboardLoaderSrv.ts @@ -1,3 +1,4 @@ +/* tslint:disable:import-blacklist */ import angular from 'angular'; import moment from 'moment'; import _ from 'lodash'; diff --git a/public/app/features/dashboard/services/TimeSrv.test.ts b/public/app/features/dashboard/services/TimeSrv.test.ts index 8bb93fc3aec5f..c54bfddc1400f 100644 --- a/public/app/features/dashboard/services/TimeSrv.test.ts +++ b/public/app/features/dashboard/services/TimeSrv.test.ts @@ -1,6 +1,6 @@ -import moment from 'moment'; import { TimeSrv } from './TimeSrv'; import { ContextSrvStub } from 'test/specs/helpers'; +import { isDateTime, dateTime } from '@grafana/ui/src/utils/moment_wrapper'; describe('timeSrv', () => { const rootScope = { @@ -43,8 +43,8 @@ describe('timeSrv', () => { it('should return parsed when parse is true', () => { timeSrv.setTime({ from: 'now', to: 'now-1h' }); const time = timeSrv.timeRange(); - expect(moment.isMoment(time.from)).toBe(true); - expect(moment.isMoment(time.to)).toBe(true); + expect(isDateTime(time.from)).toBe(true); + expect(isDateTime(time.to)).toBe(true); }); }); @@ -164,8 +164,8 @@ describe('timeSrv', () => { it('should restore refresh after relative time range is set', () => { _dashboard.refresh = '10s'; timeSrv.setTime({ - from: moment([2011, 1, 1]), - to: moment([2015, 1, 1]), + from: dateTime([2011, 1, 1]), + to: dateTime([2015, 1, 1]), }); expect(_dashboard.refresh).toBe(false); timeSrv.setTime({ from: '2011-01-01', to: 'now' }); diff --git a/public/app/features/dashboard/services/TimeSrv.ts b/public/app/features/dashboard/services/TimeSrv.ts index d6f0e91940e3c..9c415b9aea589 100644 --- a/public/app/features/dashboard/services/TimeSrv.ts +++ b/public/app/features/dashboard/services/TimeSrv.ts @@ -1,5 +1,4 @@ // Libraries -import moment from 'moment'; import _ from 'lodash'; // Utils @@ -12,6 +11,7 @@ import { TimeRange, RawTimeRange } from '@grafana/ui'; import { ITimeoutService, ILocationService } from 'angular'; import { ContextSrv } from 'app/core/services/context_srv'; import { DashboardModel } from '../state/DashboardModel'; +import { toUtc, dateTime, isDateTime } from '@grafana/ui/src/utils/moment_wrapper'; export class TimeSrv { time: any; @@ -65,10 +65,10 @@ export class TimeSrv { private parseTime() { // when absolute time is saved in json it is turned to a string if (_.isString(this.time.from) && this.time.from.indexOf('Z') >= 0) { - this.time.from = moment(this.time.from).utc(); + this.time.from = dateTime(this.time.from).utc(); } if (_.isString(this.time.to) && this.time.to.indexOf('Z') >= 0) { - this.time.to = moment(this.time.to).utc(); + this.time.to = dateTime(this.time.to).utc(); } } @@ -77,15 +77,15 @@ export class TimeSrv { return value; } if (value.length === 8) { - return moment.utc(value, 'YYYYMMDD'); + return toUtc(value, 'YYYYMMDD'); } if (value.length === 15) { - return moment.utc(value, 'YYYYMMDDTHHmmss'); + return toUtc(value, 'YYYYMMDDTHHmmss'); } if (!isNaN(value)) { const epoch = parseInt(value, 10); - return moment.utc(epoch); + return toUtc(epoch); } return null; @@ -184,7 +184,7 @@ export class TimeSrv { _.extend(this.time, time); // disable refresh if zoom in or zoom out - if (moment.isMoment(time.to)) { + if (isDateTime(time.to)) { this.oldRefresh = this.dashboard.refresh || this.oldRefresh; this.setAutoRefresh(false); } else if (this.oldRefresh && this.oldRefresh !== this.dashboard.refresh) { @@ -207,10 +207,10 @@ export class TimeSrv { timeRangeForUrl() { const range = this.timeRange().raw; - if (moment.isMoment(range.from)) { + if (isDateTime(range.from)) { range.from = range.from.valueOf().toString(); } - if (moment.isMoment(range.to)) { + if (isDateTime(range.to)) { range.to = range.to.valueOf().toString(); } @@ -220,8 +220,8 @@ export class TimeSrv { timeRange(): TimeRange { // make copies if they are moment (do not want to return out internal moment, because they are mutable!) const raw = { - from: moment.isMoment(this.time.from) ? moment(this.time.from) : this.time.from, - to: moment.isMoment(this.time.to) ? moment(this.time.to) : this.time.to, + from: isDateTime(this.time.from) ? dateTime(this.time.from) : this.time.from, + to: isDateTime(this.time.to) ? dateTime(this.time.to) : this.time.to, }; const timezone = this.dashboard && this.dashboard.getTimezone(); @@ -242,7 +242,7 @@ export class TimeSrv { const to = center + (timespan * factor) / 2; const from = center - (timespan * factor) / 2; - this.setTime({ from: moment.utc(from), to: moment.utc(to) }); + this.setTime({ from: toUtc(from), to: toUtc(to) }); } } diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index cfe4f5363376b..d5301e98cea2a 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -1,5 +1,4 @@ // Libaries -import moment, { MomentInput } from 'moment'; import _ from 'lodash'; // Constants @@ -16,6 +15,7 @@ import { PanelModel, GridPos } from './PanelModel'; import { DashboardMigrator } from './DashboardMigrator'; import { TimeRange } from '@grafana/ui/src'; import { UrlQueryValue, KIOSK_MODE_TV, DashboardMeta } from 'app/types'; +import { toUtc, DateTimeInput, dateTime, isDateTime } from '@grafana/ui/src/utils/moment_wrapper'; export interface CloneOptions { saveVariables?: boolean; @@ -698,12 +698,12 @@ export class DashboardModel { return newPanel; } - formatDate(date: MomentInput, format?: string) { - date = moment.isMoment(date) ? date : moment(date); + formatDate(date: DateTimeInput, format?: string) { + date = isDateTime(date) ? date : dateTime(date); format = format || 'YYYY-MM-DD HH:mm:ss'; const timezone = this.getTimezone(); - return timezone === 'browser' ? moment(date).format(format) : moment.utc(date).format(format); + return timezone === 'browser' ? dateTime(date).format(format) : toUtc(date).format(format); } destroy() { @@ -817,10 +817,10 @@ export class DashboardModel { return this.graphTooltip === 1; } - getRelativeTime(date: MomentInput) { - date = moment.isMoment(date) ? date : moment(date); + getRelativeTime(date: DateTimeInput) { + date = isDateTime(date) ? date : dateTime(date); - return this.timezone === 'browser' ? moment(date).fromNow() : moment.utc(date).fromNow(); + return this.timezone === 'browser' ? dateTime(date).fromNow() : toUtc(date).fromNow(); } isTimezoneUtc() { diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 18a3086fe5719..f4d9e8d1a6672 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -14,18 +14,20 @@ describe('PanelModel', () => { showColumns: true, targets: [{ refId: 'A' }, { noRefId: true }], options: { - thresholds: [ - { - color: '#F2495C', - index: 1, - value: 50, - }, - { - color: '#73BF69', - index: 0, - value: null, - }, - ], + fieldOptions: { + thresholds: [ + { + color: '#F2495C', + index: 1, + value: 50, + }, + { + color: '#73BF69', + index: 0, + value: null, + }, + ], + }, }, }; model = new PanelModel(modelJson); @@ -72,7 +74,7 @@ describe('PanelModel', () => { }); it('should restore -Infinity value for base threshold', () => { - expect(model.options.thresholds).toEqual([ + expect(model.options.fieldOptions.thresholds).toEqual([ { color: '#F2495C', index: 1, @@ -142,7 +144,7 @@ describe('PanelModel', () => { it('should call react onPanelTypeChanged', () => { expect(onPanelTypeChanged.mock.calls.length).toBe(1); expect(onPanelTypeChanged.mock.calls[0][1]).toBe('table'); - expect(onPanelTypeChanged.mock.calls[0][2].thresholds).toBeDefined(); + expect(onPanelTypeChanged.mock.calls[0][2].fieldOptions.thresholds).toBeDefined(); }); it('getQueryRunner() should return same instance after changing to another react panel', () => { diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 65a5a595d1896..156ae6dfa7207 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -6,7 +6,7 @@ import { Emitter } from 'app/core/utils/emitter'; import { getNextRefIdChar } from 'app/core/utils/query'; // Types -import { DataQuery, Threshold, ScopedVars, DataQueryResponseData, PanelPlugin } from '@grafana/ui'; +import { DataQuery, ScopedVars, DataQueryResponseData, PanelPlugin } from '@grafana/ui'; import config from 'app/core/config'; import { PanelQueryRunner } from './PanelQueryRunner'; @@ -131,9 +131,9 @@ export class PanelModel { // defaults _.defaultsDeep(this, _.cloneDeep(defaults)); + // queries must have refId this.ensureQueryIds(); - this.restoreInfintyForThresholds(); } @@ -148,15 +148,12 @@ export class PanelModel { } restoreInfintyForThresholds() { - if (this.options && this.options.thresholds) { - this.options.thresholds = this.options.thresholds.map((threshold: Threshold) => { - // JSON serialization of -Infinity is 'null' so lets convert it back to -Infinity - if (threshold.index === 0 && threshold.value === null) { - return { ...threshold, value: -Infinity }; + if (this.options && this.options.fieldOptions) { + for (const threshold of this.options.fieldOptions.thresholds) { + if (threshold.value === null) { + threshold.value = -Infinity; } - - return threshold; - }); + } } } @@ -327,7 +324,7 @@ export class PanelModel { } hasTitle() { - return !!this.title.length; + return this.title && this.title.length > 0; } destroy() { diff --git a/public/app/features/dashboard/state/PanelQueryRunner.test.ts b/public/app/features/dashboard/state/PanelQueryRunner.test.ts index 16cd2bc349bfe..4e9cba5100ce5 100644 --- a/public/app/features/dashboard/state/PanelQueryRunner.test.ts +++ b/public/app/features/dashboard/state/PanelQueryRunner.test.ts @@ -7,7 +7,7 @@ import { LoadingState, ScopedVars, } from '@grafana/ui/src/types'; -import moment from 'moment'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; jest.mock('app/core/services/backend_srv'); @@ -68,8 +68,8 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn widthPixels: ctx.widthPixels, maxDataPoints: ctx.maxDataPoints, timeRange: { - from: moment().subtract(1, 'days'), - to: moment(), + from: dateTime().subtract(1, 'days'), + to: dateTime(), raw: { from: '1h', to: 'now' }, }, panelId: 0, diff --git a/public/app/features/dashboard/state/PanelQueryRunner.ts b/public/app/features/dashboard/state/PanelQueryRunner.ts index 04c4f9f7a746c..00f6e3a00f02b 100644 --- a/public/app/features/dashboard/state/PanelQueryRunner.ts +++ b/public/app/features/dashboard/state/PanelQueryRunner.ts @@ -10,10 +10,21 @@ import templateSrv from 'app/features/templating/template_srv'; import { PanelQueryState } from './PanelQueryState'; // Types -import { PanelData, DataQuery, TimeRange, ScopedVars, DataQueryRequest, DataSourceApi } from '@grafana/ui'; - -export interface QueryRunnerOptions { - datasource: string | DataSourceApi; +import { + PanelData, + DataQuery, + TimeRange, + ScopedVars, + DataQueryRequest, + DataSourceApi, + DataSourceJsonData, +} from '@grafana/ui'; + +export interface QueryRunnerOptions< + TQuery extends DataQuery = DataQuery, + TOptions extends DataSourceJsonData = DataSourceJsonData +> { + datasource: string | DataSourceApi; queries: TQuery[]; panelId: number; dashboardId?: number; diff --git a/public/app/features/dashboard/utils/panel.test.ts b/public/app/features/dashboard/utils/panel.test.ts index e32b7d84d6867..a05c54b2ab759 100644 --- a/public/app/features/dashboard/utils/panel.test.ts +++ b/public/app/features/dashboard/utils/panel.test.ts @@ -1,11 +1,11 @@ -import moment from 'moment'; import { TimeRange } from '@grafana/ui'; import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; import { advanceTo, clear } from 'jest-date-mock'; +import { dateTime, DateTime } from '@grafana/ui/src/utils/moment_wrapper'; const dashboardTimeRange: TimeRange = { - from: moment([2019, 1, 11, 12, 0]), - to: moment([2019, 1, 11, 18, 0]), + from: dateTime([2019, 1, 11, 12, 0]), + to: dateTime([2019, 1, 11, 18, 0]), raw: { from: 'now-6h', to: 'now', @@ -13,7 +13,7 @@ const dashboardTimeRange: TimeRange = { }; describe('applyPanelTimeOverrides', () => { - const fakeCurrentDate = moment([2019, 1, 11, 14, 0, 0]).toDate(); + const fakeCurrentDate = dateTime([2019, 1, 11, 14, 0, 0]).toDate(); beforeAll(() => { advanceTo(fakeCurrentDate); @@ -31,7 +31,7 @@ describe('applyPanelTimeOverrides', () => { // @ts-ignore: PanelModel type incositency const overrides = applyPanelTimeOverrides(panelModel, dashboardTimeRange); - expect(overrides.timeRange.from.toISOString()).toBe(moment([2019, 1, 11, 12]).toISOString()); + expect(overrides.timeRange.from.toISOString()).toBe(dateTime([2019, 1, 11, 12]).toISOString()); expect(overrides.timeRange.to.toISOString()).toBe(fakeCurrentDate.toISOString()); expect(overrides.timeRange.raw.from).toBe('now-2h'); expect(overrides.timeRange.raw.to).toBe('now'); @@ -42,16 +42,16 @@ describe('applyPanelTimeOverrides', () => { timeShift: '2h', }; - const expectedFromDate = moment([2019, 1, 11, 10, 0, 0]).toDate(); - const expectedToDate = moment([2019, 1, 11, 16, 0, 0]).toDate(); + const expectedFromDate = dateTime([2019, 1, 11, 10, 0, 0]).toDate(); + const expectedToDate = dateTime([2019, 1, 11, 16, 0, 0]).toDate(); // @ts-ignore: PanelModel type incositency const overrides = applyPanelTimeOverrides(panelModel, dashboardTimeRange); expect(overrides.timeRange.from.toISOString()).toBe(expectedFromDate.toISOString()); expect(overrides.timeRange.to.toISOString()).toBe(expectedToDate.toISOString()); - expect((overrides.timeRange.raw.from as moment.Moment).toISOString()).toEqual(expectedFromDate.toISOString()); - expect((overrides.timeRange.raw.to as moment.Moment).toISOString()).toEqual(expectedToDate.toISOString()); + expect((overrides.timeRange.raw.from as DateTime).toISOString()).toEqual(expectedFromDate.toISOString()); + expect((overrides.timeRange.raw.to as DateTime).toISOString()).toEqual(expectedToDate.toISOString()); }); it('should apply both relative time and time shift', () => { @@ -60,15 +60,15 @@ describe('applyPanelTimeOverrides', () => { timeShift: '2h', }; - const expectedFromDate = moment([2019, 1, 11, 10, 0, 0]).toDate(); - const expectedToDate = moment([2019, 1, 11, 12, 0, 0]).toDate(); + const expectedFromDate = dateTime([2019, 1, 11, 10, 0, 0]).toDate(); + const expectedToDate = dateTime([2019, 1, 11, 12, 0, 0]).toDate(); // @ts-ignore: PanelModel type incositency const overrides = applyPanelTimeOverrides(panelModel, dashboardTimeRange); expect(overrides.timeRange.from.toISOString()).toBe(expectedFromDate.toISOString()); expect(overrides.timeRange.to.toISOString()).toBe(expectedToDate.toISOString()); - expect((overrides.timeRange.raw.from as moment.Moment).toISOString()).toEqual(expectedFromDate.toISOString()); - expect((overrides.timeRange.raw.to as moment.Moment).toISOString()).toEqual(expectedToDate.toISOString()); + expect((overrides.timeRange.raw.from as DateTime).toISOString()).toEqual(expectedFromDate.toISOString()); + expect((overrides.timeRange.raw.to as DateTime).toISOString()).toEqual(expectedToDate.toISOString()); }); }); diff --git a/public/app/features/datasources/NewDataSourcePage.tsx b/public/app/features/datasources/NewDataSourcePage.tsx index ae0eae9e02f4a..6420225f2afec 100644 --- a/public/app/features/datasources/NewDataSourcePage.tsx +++ b/public/app/features/datasources/NewDataSourcePage.tsx @@ -1,13 +1,12 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent, FC } from 'react'; import { connect } from 'react-redux'; import { hot } from 'react-hot-loader'; import Page from 'app/core/components/Page/Page'; import { StoreState } from 'app/types'; import { addDataSource, loadDataSourceTypes, setDataSourceTypeSearchQuery } from './state/actions'; -import { getNavModel } from 'app/core/selectors/navModel'; import { getDataSourceTypes } from './state/selectors'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; -import { NavModel, DataSourcePluginMeta } from '@grafana/ui'; +import { NavModel, DataSourcePluginMeta, List } from '@grafana/ui'; export interface Props { navModel: NavModel; @@ -15,13 +14,40 @@ export interface Props { isLoading: boolean; addDataSource: typeof addDataSource; loadDataSourceTypes: typeof loadDataSourceTypes; - dataSourceTypeSearchQuery: string; + searchQuery: string; setDataSourceTypeSearchQuery: typeof setDataSourceTypeSearchQuery; } +interface DataSourceCategories { + [key: string]: DataSourcePluginMeta[]; +} + +interface DataSourceCategoryInfo { + id: string; + title: string; +} + class NewDataSourcePage extends PureComponent { + searchInput: HTMLElement; + categoryInfoList: DataSourceCategoryInfo[] = [ + { id: 'tsdb', title: 'Time series databases' }, + { id: 'logging', title: 'Logging & document databases' }, + { id: 'sql', title: 'SQL' }, + { id: 'cloud', title: 'Cloud' }, + { id: 'other', title: 'Others' }, + ]; + + sortingRules: { [id: string]: number } = { + prometheus: 100, + graphite: 95, + loki: 90, + mysql: 80, + postgres: 79, + }; + componentDidMount() { this.props.loadDataSourceTypes(); + this.searchInput.focus(); } onDataSourceTypeClicked = (plugin: DataSourcePluginMeta) => { @@ -32,34 +58,108 @@ class NewDataSourcePage extends PureComponent { this.props.setDataSourceTypeSearchQuery(value); }; + renderTypes(types: DataSourcePluginMeta[]) { + if (!types) { + return null; + } + + // apply custom sort ranking + types.sort((a, b) => { + const aSort = this.sortingRules[a.id] || 0; + const bSort = this.sortingRules[b.id] || 0; + if (aSort > bSort) { + return -1; + } + if (aSort < bSort) { + return 1; + } + + return a.name > b.name ? -1 : 1; + }); + + return ( + item.id.toString()} + renderItem={item => ( + this.onDataSourceTypeClicked(item)} + onLearnMoreClick={this.onLearnMoreClick} + /> + )} + /> + ); + } + + onLearnMoreClick = (evt: React.SyntheticEvent) => { + evt.stopPropagation(); + }; + + renderGroupedList() { + const { dataSourceTypes } = this.props; + + if (dataSourceTypes.length === 0) { + return null; + } + + const categories = dataSourceTypes.reduce( + (accumulator, item) => { + const category = item.category || 'other'; + const list = accumulator[category] || []; + list.push(item); + accumulator[category] = list; + return accumulator; + }, + {} as DataSourceCategories + ); + + return ( + <> + {this.categoryInfoList.map(category => ( +
    +
    {category.title}
    + {this.renderTypes(categories[category.id])} +
    + ))} +
    + + ); + } + render() { - const { navModel, dataSourceTypes, dataSourceTypeSearchQuery, isLoading } = this.props; + const { navModel, isLoading, searchQuery, dataSourceTypes } = this.props; + return ( -

    Choose data source type

    -
    - +
    +
    + (this.searchInput = elem)} + labelClassName="gf-form--has-input-icon" + inputClassName="gf-form-input width-30" + value={searchQuery} + onChange={this.onSearchQueryChange} + placeholder="Filter by name or type" + /> +
    + -
    - {dataSourceTypes.map((plugin, index) => { - return ( -
    this.onDataSourceTypeClicked(plugin)} - className="add-data-source-grid-item" - key={`${plugin.id}-${index}`} - > - - {plugin.name} -
    - ); - })} +
    + {searchQuery && this.renderTypes(dataSourceTypes)} + {!searchQuery && this.renderGroupedList()}
    @@ -67,11 +167,57 @@ class NewDataSourcePage extends PureComponent { } } +interface DataSourceTypeCardProps { + plugin: DataSourcePluginMeta; + onClick: () => void; + onLearnMoreClick: (evt: React.SyntheticEvent) => void; +} + +const DataSourceTypeCard: FC = props => { + const { plugin, onClick, onLearnMoreClick } = props; + + // find first plugin info link + const learnMoreLink = plugin.info.links && plugin.info.links.length > 0 ? plugin.info.links[0].url : null; + + return ( +
    + +
    + {plugin.name} + {plugin.info.description && {plugin.info.description}} +
    +
    + {learnMoreLink && ( + + Learn more + + )} + +
    +
    + ); +}; + +export function getNavModel(): NavModel { + const main = { + icon: 'gicon gicon-add-datasources', + id: 'datasource-new', + text: 'Add data source', + href: 'datasources/new', + subTitle: 'Choose a data source type', + }; + + return { + main: main, + node: main, + }; +} + function mapStateToProps(state: StoreState) { return { - navModel: getNavModel(state.navIndex, 'datasources'), + navModel: getNavModel(), dataSourceTypes: getDataSourceTypes(state.dataSources), - dataSourceTypeSearchQuery: state.dataSources.dataSourceTypeSearchQuery, + searchQuery: state.dataSources.dataSourceTypeSearchQuery, isLoading: state.dataSources.isLoadingDataSources, }; } diff --git a/public/app/features/datasources/settings/ButtonRow.tsx b/public/app/features/datasources/settings/ButtonRow.tsx index 2c048a33e7aff..16d25be293cc6 100644 --- a/public/app/features/datasources/settings/ButtonRow.tsx +++ b/public/app/features/datasources/settings/ButtonRow.tsx @@ -12,7 +12,13 @@ const ButtonRow: FC = ({ isReadOnly, onDelete, onSubmit, onTest }) => { return (
    {!isReadOnly && ( - )} diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx index 1ef8fef702694..e02684d57fcc5 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx @@ -5,7 +5,7 @@ import { connect } from 'react-redux'; // Components import Page from 'app/core/components/Page/Page'; -import PluginSettings from './PluginSettings'; +import { PluginSettings, GenericDataSourcePlugin } from './PluginSettings'; import BasicSettings from './BasicSettings'; import ButtonRow from './ButtonRow'; @@ -22,7 +22,7 @@ import { getRouteParamsId } from 'app/core/selectors/location'; // Types import { StoreState } from 'app/types/'; -import { NavModel, DataSourceSettings, DataSourcePlugin, DataSourcePluginMeta } from '@grafana/ui'; +import { NavModel, DataSourceSettings, DataSourcePluginMeta } from '@grafana/ui'; import { getDataSourceLoadingNav } from '../state/navModel'; import PluginStateinfo from 'app/features/plugins/PluginStateInfo'; import { importDataSourcePlugin } from 'app/features/plugins/plugin_loader'; @@ -37,12 +37,12 @@ export interface Props { setDataSourceName: typeof setDataSourceName; updateDataSource: typeof updateDataSource; setIsDefault: typeof setIsDefault; - plugin?: DataSourcePlugin; + plugin?: GenericDataSourcePlugin; } interface State { dataSource: DataSourceSettings; - plugin: DataSourcePlugin; + plugin: GenericDataSourcePlugin; isTesting?: boolean; testingMessage?: string; testingStatus?: string; @@ -60,7 +60,7 @@ export class DataSourceSettingsPage extends PureComponent { async loadPlugin(pluginId?: string) { const { dataSourceMeta } = this.props; - let importedPlugin: DataSourcePlugin; + let importedPlugin: GenericDataSourcePlugin; try { importedPlugin = await importDataSourcePlugin(dataSourceMeta); @@ -185,7 +185,14 @@ export class DataSourceSettingsPage extends PureComponent {
    {this.isReadOnly() && this.renderIsReadOnlyMessage()} - + {dataSourceMeta.state && ( +
    + + +
    + )} {
    {testingMessage && ( -
    +
    {testingStatus === 'error' ? ( @@ -214,7 +221,9 @@ export class DataSourceSettingsPage extends PureComponent { )}
    -
    {testingMessage}
    +
    + {testingMessage} +
    )} diff --git a/public/app/features/datasources/settings/PluginSettings.tsx b/public/app/features/datasources/settings/PluginSettings.tsx index 5b5efe95d5630..94c054d56ee94 100644 --- a/public/app/features/datasources/settings/PluginSettings.tsx +++ b/public/app/features/datasources/settings/PluginSettings.tsx @@ -1,10 +1,19 @@ import React, { PureComponent } from 'react'; import _ from 'lodash'; -import { DataSourceSettings, DataSourcePlugin, DataSourcePluginMeta } from '@grafana/ui'; +import { + DataSourceSettings, + DataSourcePlugin, + DataSourcePluginMeta, + DataSourceApi, + DataQuery, + DataSourceJsonData, +} from '@grafana/ui'; import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; +export type GenericDataSourcePlugin = DataSourcePlugin>; + export interface Props { - plugin: DataSourcePlugin; + plugin: GenericDataSourcePlugin; dataSource: DataSourceSettings; dataSourceMeta: DataSourcePluginMeta; onModelChange: (dataSource: DataSourceSettings) => void; diff --git a/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap b/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap index 00251237f8393..dc506449278a8 100644 --- a/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap +++ b/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap @@ -33,6 +33,7 @@ exports[`Render should render with buttons enabled 1`] = ` className="gf-form-button-row" >
    - = props => { + const { queryErrors } = props; + const refId = getValueWithRefId(queryErrors); + const queryError = refId ? null : getFirstQueryErrorWithoutRefId(queryErrors); + const showError = queryError ? true : false; + const duration = showError ? 100 : 10; + const message = queryError ? queryError.message : null; + + return ( + +
    +
    +
    + +
    +
    +
    {message}
    +
    +
    +
    +
    + ); +}; diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index b5863a27457ba..643e1d0d0f908 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -31,7 +31,7 @@ import { } from './state/actions'; // Types -import { RawTimeRange, DataQuery, ExploreStartPageProps, ExploreDataSourceApi } from '@grafana/ui'; +import { RawTimeRange, DataQuery, ExploreStartPageProps, ExploreDataSourceApi, DataQueryError } from '@grafana/ui'; import { ExploreItemState, ExploreUrlState, @@ -54,6 +54,7 @@ import { scanStopAction } from './state/actionTypes'; import { NoDataSourceCallToAction } from './NoDataSourceCallToAction'; import { FadeIn } from 'app/core/components/Animations/FadeIn'; import { getTimeZone } from '../profile/state/selectors'; +import { ErrorContainer } from './ErrorContainer'; interface ExploreProps { StartPage?: ComponentClass; @@ -86,6 +87,7 @@ interface ExploreProps { initialQueries: DataQuery[]; initialRange: RawTimeRange; initialUI: ExploreUIState; + queryErrors: DataQueryError[]; } /** @@ -236,6 +238,7 @@ export class Explore extends React.PureComponent { supportsLogs, supportsTable, queryKeys, + queryErrors, } = this.props; const exploreClass = split ? 'explore explore-split' : 'explore'; @@ -257,6 +260,7 @@ export class Explore extends React.PureComponent { {datasourceInstance && (
    + {({ width }) => { if (width === 0) { @@ -275,7 +279,6 @@ export class Explore extends React.PureComponent { { value={refreshInterval} tooltip="Refresh" /> - {refreshInterval && } + {refreshInterval && }
    @@ -203,14 +203,16 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps datasourceInstance, datasourceMissing, exploreDatasources, - queryTransactions, range, refreshInterval, + graphIsLoading, + logIsLoading, + tableIsLoading, } = exploreItem; const selectedDatasource = datasourceInstance ? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name) : undefined; - const loading = queryTransactions.some(qt => !qt.done); + const loading = graphIsLoading || logIsLoading || tableIsLoading; return { datasourceMissing, diff --git a/public/app/features/explore/GraphContainer.tsx b/public/app/features/explore/GraphContainer.tsx index ec3f330c61610..7033473a33b3a 100644 --- a/public/app/features/explore/GraphContainer.tsx +++ b/public/app/features/explore/GraphContainer.tsx @@ -1,7 +1,6 @@ import React, { PureComponent } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; -import moment from 'moment'; import { TimeRange, TimeZone, AbsoluteTimeRange } from '@grafana/ui'; import { ExploreId, ExploreItemState } from 'app/types/explore'; @@ -11,6 +10,7 @@ import { toggleGraph, changeTime } from './state/actions'; import Graph from './Graph'; import Panel from './Panel'; import { getTimeZone } from '../profile/state/selectors'; +import { toUtc, dateTime } from '@grafana/ui/src/utils/moment_wrapper'; interface GraphContainerProps { exploreId: ExploreId; @@ -34,8 +34,8 @@ export class GraphContainer extends PureComponent { onChangeTime = (absRange: AbsoluteTimeRange) => { const { exploreId, timeZone, changeTime } = this.props; const range = { - from: timeZone.isUtc ? moment.utc(absRange.from) : moment(absRange.from), - to: timeZone.isUtc ? moment.utc(absRange.to) : moment(absRange.to), + from: timeZone.isUtc ? toUtc(absRange.from) : dateTime(absRange.from), + to: timeZone.isUtc ? toUtc(absRange.to) : dateTime(absRange.to), }; changeTime(exploreId, range); @@ -71,8 +71,8 @@ function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const { split } = explore; const item: ExploreItemState = explore[exploreId]; - const { graphResult, queryTransactions, range, showingGraph, showingTable } = item; - const loading = queryTransactions.some(qt => qt.resultType === 'Graph' && !qt.done); + const { graphResult, graphIsLoading, range, showingGraph, showingTable } = item; + const loading = graphIsLoading; return { graphResult, loading, range, showingGraph, showingTable, split, timeZone: getTimeZone(state.user) }; } diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index f0b68cb9d7840..83cce42c7d195 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -1,13 +1,13 @@ import React, { PureComponent } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; -import { RawTimeRange, TimeRange, LogLevel, TimeZone, AbsoluteTimeRange } from '@grafana/ui'; +import { RawTimeRange, TimeRange, LogLevel, TimeZone, AbsoluteTimeRange, toUtc, dateTime } from '@grafana/ui'; import { ExploreId, ExploreItemState } from 'app/types/explore'; import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; import { StoreState } from 'app/types'; -import { toggleLogs, changeDedupStrategy } from './state/actions'; +import { toggleLogs, changeDedupStrategy, changeTime } from './state/actions'; import Logs from './Logs'; import Panel from './Panel'; import { toggleLogLevelAction } from 'app/features/explore/state/actionTypes'; @@ -20,7 +20,6 @@ interface LogsContainerProps { logsHighlighterExpressions?: string[]; logsResult?: LogsModel; dedupedResult?: LogsModel; - onChangeTime: (range: AbsoluteTimeRange) => void; onClickLabel: (key: string, value: string) => void; onStartScanning: () => void; onStopScanning: () => void; @@ -35,9 +34,20 @@ interface LogsContainerProps { dedupStrategy: LogsDedupStrategy; hiddenLogLevels: Set; width: number; + changeTime: typeof changeTime; } export class LogsContainer extends PureComponent { + onChangeTime = (absRange: AbsoluteTimeRange) => { + const { exploreId, timeZone, changeTime } = this.props; + const range = { + from: timeZone.isUtc ? toUtc(absRange.from) : dateTime(absRange.from), + to: timeZone.isUtc ? toUtc(absRange.to) : dateTime(absRange.to), + }; + + changeTime(exploreId, range); + }; + onClickLogsButton = () => { this.props.toggleLogs(this.props.exploreId, this.props.showingLogs); }; @@ -61,7 +71,6 @@ export class LogsContainer extends PureComponent { logsHighlighterExpressions, logsResult, dedupedResult, - onChangeTime, onClickLabel, onStartScanning, onStopScanning, @@ -83,7 +92,7 @@ export class LogsContainer extends PureComponent { exploreId={exploreId} highlighterExpressions={logsHighlighterExpressions} loading={loading} - onChangeTime={onChangeTime} + onChangeTime={this.onChangeTime} onClickLabel={onClickLabel} onStartScanning={onStartScanning} onStopScanning={onStopScanning} @@ -104,8 +113,8 @@ export class LogsContainer extends PureComponent { function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; - const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, range } = item; - const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done); + const { logsHighlighterExpressions, logsResult, logIsLoading, scanning, scanRange, range } = item; + const loading = logIsLoading; const { showingLogs, dedupStrategy } = exploreItemUIStateSelector(item); const hiddenLogLevels = new Set(item.hiddenLogLevels); const dedupedResult = deduplicatedLogsSelector(item); @@ -130,6 +139,7 @@ const mapDispatchToProps = { toggleLogs, changeDedupStrategy, toggleLogLevelAction, + changeTime, }; export default hot(module)( diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx index b271b0d9649b5..30f876886c19f 100644 --- a/public/app/features/explore/QueryEditor.tsx +++ b/public/app/features/explore/QueryEditor.tsx @@ -1,6 +1,5 @@ // Libraries import React, { PureComponent } from 'react'; -import moment from 'moment'; // Services import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; @@ -10,10 +9,11 @@ import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { Emitter } from 'app/core/utils/emitter'; import { DataQuery, TimeRange } from '@grafana/ui'; import 'app/features/plugins/plugin_loader'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; interface QueryEditorProps { + error?: any; datasource: any; - error?: string | JSX.Element; onExecuteQuery?: () => void; onQueryChange?: (value: DataQuery) => void; initialQuery: DataQuery; @@ -41,11 +41,15 @@ export default class QueryEditor extends PureComponent { datasource, target, refresh: () => { - this.props.onQueryChange(target); - this.props.onExecuteQuery(); + setTimeout(() => { + this.props.onQueryChange(target); + this.props.onExecuteQuery(); + }, 1); }, onQueryChange: () => { - this.props.onQueryChange(target); + setTimeout(() => { + this.props.onQueryChange(target); + }, 1); }, events: exploreEvents, panel: { datasource, targets: [target] }, @@ -54,7 +58,17 @@ export default class QueryEditor extends PureComponent { }; this.component = loader.load(this.element, scopeProps, template); - this.props.onQueryChange(target); + setTimeout(() => { + this.props.onQueryChange(target); + }, 1); + } + + componentDidUpdate(prevProps: QueryEditorProps) { + if (prevProps.error !== this.props.error && this.component) { + // Some query controllers listen to data error events and need a digest + // for some reason this needs to be done in next tick + setTimeout(this.component.digest); + } } componentWillUnmount() { @@ -67,8 +81,8 @@ export default class QueryEditor extends PureComponent { const timeSrv = getTimeSrv(); timeSrv.init({ time: { - from: moment(range.from), - to: moment(range.to), + from: dateTime(range.from), + to: dateTime(range.to), }, refresh: false, getTimezone: () => 'utc', diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index 58d860f54addc..e5d7ff4363139 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -36,8 +36,8 @@ export interface QueryFieldProps { cleanText?: (text: string) => string; disabled?: boolean; initialQuery: string | null; - onExecuteQuery?: () => void; - onQueryChange?: (value: string) => void; + onRunQuery?: () => void; + onChange?: (value: string) => void; onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput; onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string; placeholder?: string; @@ -149,7 +149,7 @@ export class QueryField extends React.PureComponent { - const { onQueryChange } = this.props; - if (onQueryChange) { - onQueryChange(Plain.serialize(this.state.value)); + const { onChange } = this.props; + if (onChange) { + onChange(Plain.serialize(this.state.value)); } }; - executeOnQueryChangeAndExecuteQueries = () => { + executeOnChangeAndRunQueries = () => { // Send text change to parent - const { onQueryChange, onExecuteQuery } = this.props; - if (onQueryChange) { - onQueryChange(Plain.serialize(this.state.value)); + const { onChange, onRunQuery } = this.props; + if (onChange) { + onChange(Plain.serialize(this.state.value)); } - if (onExecuteQuery) { - onExecuteQuery(); + if (onRunQuery) { + onRunQuery(); this.setState({ lastExecutedValue: this.state.value }); } }; @@ -330,7 +330,7 @@ export class QueryField extends React.PureComponent qt.hints && qt.hints.length > 0); - if (transaction) { - return transaction.hints[0]; - } - return undefined; -} +import QueryStatus from './QueryStatus'; interface QueryRowProps { addQueryRow: typeof addQueryRow; @@ -39,20 +40,22 @@ interface QueryRowProps { index: number; query: DataQuery; modifyQueries: typeof modifyQueries; - queryTransactions: QueryTransaction[]; exploreEvents: Emitter; range: TimeRange; removeQueryRowAction: typeof removeQueryRowAction; runQueries: typeof runQueries; + queryResponse: PanelData; + latency: number; + queryErrors: DataQueryError[]; } export class QueryRow extends PureComponent { - onExecuteQuery = () => { + onRunQuery = () => { const { exploreId } = this.props; this.props.runQueries(exploreId); }; - onChangeQuery = (query: DataQuery, override?: boolean) => { + onChange = (query: DataQuery, override?: boolean) => { const { datasourceInstance, exploreId, index } = this.props; this.props.changeQuery(exploreId, query, index, override); if (query && !override && datasourceInstance.getHighlighterExpression && index === 0) { @@ -71,7 +74,7 @@ export class QueryRow extends PureComponent { }; onClickClearButton = () => { - this.onChangeQuery(null, true); + this.onChange(null, true); }; onClickHintFix = (action: QueryFixAction) => { @@ -85,6 +88,7 @@ export class QueryRow extends PureComponent { onClickRemoveButton = () => { const { exploreId, index } = this.props; this.props.removeQueryRowAction({ exploreId, index }); + this.props.runQueries(exploreId); }; updateLogsHighlights = _.debounce((value: DataQuery) => { @@ -100,24 +104,20 @@ export class QueryRow extends PureComponent { const { datasourceInstance, history, - index, query, - queryTransactions, exploreEvents, range, datasourceStatus, + queryResponse, + latency, + queryErrors, } = this.props; - - const transactions = queryTransactions.filter(t => t.rowIndex === index); - const transactionWithError = transactions.find(t => t.error !== undefined); - const hint = getFirstHintFromTransactions(transactions); - const queryError = transactionWithError ? transactionWithError.error : null; const QueryField = datasourceInstance.components.ExploreQueryField; return (
    - +
    {QueryField ? ( @@ -125,19 +125,19 @@ export class QueryRow extends PureComponent { datasource={datasourceInstance} datasourceStatus={datasourceStatus} query={query} - error={queryError} - hint={hint} history={history} - onExecuteQuery={this.onExecuteQuery} - onExecuteHint={this.onClickHintFix} - onQueryChange={this.onChangeQuery} + onRunQuery={this.onRunQuery} + onHint={this.onClickHintFix} + onChange={this.onChange} + panelData={null} + queryResponse={queryResponse} /> ) : ( { function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; - const { datasourceInstance, history, queries, queryTransactions, range, datasourceError } = item; + const { + datasourceInstance, + history, + queries, + range, + datasourceError, + graphResult, + graphIsLoading, + tableIsLoading, + logIsLoading, + latency, + queryErrors, + } = item; const query = queries[index]; + const datasourceStatus = datasourceError ? DataSourceStatus.Disconnected : DataSourceStatus.Connected; + const error = queryErrors.filter(queryError => queryError.refId === query.refId)[0]; + const series = graphResult ? graphResult : []; // TODO: use SeriesData + const queryResponseState = + graphIsLoading || tableIsLoading || logIsLoading + ? LoadingState.Loading + : error + ? LoadingState.Error + : LoadingState.Done; + const queryResponse: PanelData = { + series, + state: queryResponseState, + error, + }; + return { datasourceInstance, history, query, - queryTransactions, range, - datasourceStatus: datasourceError ? DataSourceStatus.Disconnected : DataSourceStatus.Connected, + datasourceStatus, + queryResponse, + latency, + queryErrors, }; } diff --git a/public/app/features/explore/QueryStatus.tsx b/public/app/features/explore/QueryStatus.tsx new file mode 100644 index 0000000000000..54b3b8129ef7f --- /dev/null +++ b/public/app/features/explore/QueryStatus.tsx @@ -0,0 +1,47 @@ +import React, { PureComponent } from 'react'; + +import ElapsedTime from './ElapsedTime'; +import { PanelData, LoadingState } from '@grafana/ui'; + +function formatLatency(value) { + return `${(value / 1000).toFixed(1)}s`; +} + +interface QueryStatusItemProps { + queryResponse: PanelData; + latency: number; +} + +class QueryStatusItem extends PureComponent { + render() { + const { queryResponse, latency } = this.props; + const className = + queryResponse.state === LoadingState.Done || LoadingState.Error + ? 'query-transaction' + : 'query-transaction query-transaction--loading'; + return ( +
    + {/*
    {transaction.resultType}:
    */} +
    + {queryResponse.state === LoadingState.Done || LoadingState.Error ? formatLatency(latency) : } +
    +
    + ); + } +} + +interface QueryStatusProps { + queryResponse: PanelData; + latency: number; +} + +export default class QueryStatus extends PureComponent { + render() { + const { queryResponse, latency } = this.props; + return ( +
    + {queryResponse && } +
    + ); + } +} diff --git a/public/app/features/explore/QueryTransactionStatus.tsx b/public/app/features/explore/QueryTransactionStatus.tsx deleted file mode 100644 index 6f47f1476451b..0000000000000 --- a/public/app/features/explore/QueryTransactionStatus.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { PureComponent } from 'react'; - -import { QueryTransaction } from 'app/types/explore'; -import ElapsedTime from './ElapsedTime'; - -function formatLatency(value) { - return `${(value / 1000).toFixed(1)}s`; -} - -interface QueryTransactionStatusItemProps { - transaction: QueryTransaction; -} - -class QueryTransactionStatusItem extends PureComponent { - render() { - const { transaction } = this.props; - const className = transaction.done ? 'query-transaction' : 'query-transaction query-transaction--loading'; - return ( -
    -
    {transaction.resultType}:
    -
    - {transaction.done ? formatLatency(transaction.latency) : } -
    -
    - ); - } -} - -interface QueryTransactionStatusProps { - transactions: QueryTransaction[]; -} - -export default class QueryTransactionStatus extends PureComponent { - render() { - const { transactions } = this.props; - return ( -
    - {transactions.map((t, i) => ( - - ))} -
    - ); - } -} diff --git a/public/app/features/explore/TableContainer.tsx b/public/app/features/explore/TableContainer.tsx index e24ca070f2f7f..78f190a05cb87 100644 --- a/public/app/features/explore/TableContainer.tsx +++ b/public/app/features/explore/TableContainer.tsx @@ -42,8 +42,8 @@ export class TableContainer extends PureComponent { function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; - const { queryTransactions, showingTable, tableResult } = item; - const loading = queryTransactions.some(qt => qt.resultType === 'Table' && !qt.done); + const { tableIsLoading, showingTable, tableResult } = item; + const loading = tableIsLoading; return { loading, showingTable, tableResult }; } diff --git a/public/app/features/explore/TimePicker.test.tsx b/public/app/features/explore/TimePicker.test.tsx index 49242675ab902..9cdf7a9b517fd 100644 --- a/public/app/features/explore/TimePicker.test.tsx +++ b/public/app/features/explore/TimePicker.test.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { shallow } from 'enzyme'; import sinon from 'sinon'; -import moment from 'moment'; import * as dateMath from '@grafana/ui/src/utils/datemath'; import * as rangeUtil from '@grafana/ui/src/utils/rangeutil'; import TimePicker from './TimePicker'; import { RawTimeRange, TimeRange, TIME_FORMAT } from '@grafana/ui'; +import { toUtc, isDateTime, dateTime } from '@grafana/ui/src/utils/moment_wrapper'; const DEFAULT_RANGE = { from: 'now-6h', @@ -15,8 +15,8 @@ const DEFAULT_RANGE = { const fromRaw = (rawRange: RawTimeRange): TimeRange => { const raw = { - from: moment.isMoment(rawRange.from) ? moment(rawRange.from) : rawRange.from, - to: moment.isMoment(rawRange.to) ? moment(rawRange.to) : rawRange.to, + from: isDateTime(rawRange.from) ? dateTime(rawRange.from) : rawRange.from, + to: isDateTime(rawRange.to) ? dateTime(rawRange.to) : rawRange.to, }; return { @@ -73,19 +73,19 @@ describe('', () => { it('apply with absolute range and non-utc', () => { const range = { - from: moment.utc(1), - to: moment.utc(1000), + from: toUtc(1), + to: toUtc(1000), raw: { - from: moment.utc(1), - to: moment.utc(1000), + from: toUtc(1), + to: toUtc(1000), }, }; const localRange = { - from: moment(1), - to: moment(1000), + from: dateTime(1), + to: dateTime(1000), raw: { - from: moment(1), - to: moment(1000), + from: dateTime(1), + to: dateTime(1000), }, }; const expectedRangeString = rangeUtil.describeTimeRange(localRange); @@ -110,11 +110,11 @@ describe('', () => { it('apply with absolute range and utc', () => { const range = { - from: moment.utc(1), - to: moment.utc(1000), + from: toUtc(1), + to: toUtc(1000), raw: { - from: moment.utc(1), - to: moment.utc(1000), + from: toUtc(1), + to: toUtc(1000), }, }; const onChangeTime = sinon.spy(); @@ -137,11 +137,11 @@ describe('', () => { it('moves ranges backward by half the range on left arrow click when utc', () => { const rawRange = { - from: moment.utc(2000), - to: moment.utc(4000), + from: toUtc(2000), + to: toUtc(4000), raw: { - from: moment.utc(2000), - to: moment.utc(4000), + from: toUtc(2000), + to: toUtc(4000), }, }; const range = fromRaw(rawRange); @@ -159,19 +159,19 @@ describe('', () => { it('moves ranges backward by half the range on left arrow click when not utc', () => { const range = { - from: moment.utc(2000), - to: moment.utc(4000), + from: toUtc(2000), + to: toUtc(4000), raw: { - from: moment.utc(2000), - to: moment.utc(4000), + from: toUtc(2000), + to: toUtc(4000), }, }; const localRange = { - from: moment(2000), - to: moment(4000), + from: dateTime(2000), + to: dateTime(4000), raw: { - from: moment(2000), - to: moment(4000), + from: dateTime(2000), + to: dateTime(4000), }, }; @@ -188,11 +188,11 @@ describe('', () => { it('moves ranges forward by half the range on right arrow click when utc', () => { const range = { - from: moment.utc(1000), - to: moment.utc(3000), + from: toUtc(1000), + to: toUtc(3000), raw: { - from: moment.utc(1000), - to: moment.utc(3000), + from: toUtc(1000), + to: toUtc(3000), }, }; @@ -209,19 +209,19 @@ describe('', () => { it('moves ranges forward by half the range on right arrow click when not utc', () => { const range = { - from: moment.utc(1000), - to: moment.utc(3000), + from: toUtc(1000), + to: toUtc(3000), raw: { - from: moment.utc(1000), - to: moment.utc(3000), + from: toUtc(1000), + to: toUtc(3000), }, }; const localRange = { - from: moment(1000), - to: moment(3000), + from: dateTime(1000), + to: dateTime(3000), raw: { - from: moment(1000), - to: moment(3000), + from: dateTime(1000), + to: dateTime(3000), }, }; diff --git a/public/app/features/explore/TimePicker.tsx b/public/app/features/explore/TimePicker.tsx index 0e039d9a177b6..9a10edc062550 100644 --- a/public/app/features/explore/TimePicker.tsx +++ b/public/app/features/explore/TimePicker.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; -import moment from 'moment'; import * as rangeUtil from '@grafana/ui/src/utils/rangeutil'; import { Input, RawTimeRange, TimeRange, TIME_FORMAT } from '@grafana/ui'; +import { toUtc, isDateTime, dateTime } from '@grafana/ui/src/utils/moment_wrapper'; interface TimePickerProps { isOpen?: boolean; @@ -28,14 +28,14 @@ const getRaw = (isUtc: boolean, range: any) => { to: range.raw.to, }; - if (moment.isMoment(rawRange.from)) { + if (isDateTime(rawRange.from)) { if (!isUtc) { rawRange.from = rawRange.from.local(); } rawRange.from = rawRange.from.format(TIME_FORMAT); } - if (moment.isMoment(rawRange.to)) { + if (isDateTime(rawRange.to)) { if (!isUtc) { rawRange.to = rawRange.to.local(); } @@ -99,8 +99,8 @@ export default class TimePicker extends PureComponent DataQuery; } -export interface QueryTransactionFailurePayload { +export interface QueryFailurePayload { exploreId: ExploreId; - queryTransactions: QueryTransaction[]; + response: DataQueryError; + resultType: ResultType; } -export interface QueryTransactionStartPayload { +export interface QueryStartPayload { exploreId: ExploreId; resultType: ResultType; rowIndex: number; transaction: QueryTransaction; } -export interface QueryTransactionSuccessPayload { +export interface QuerySuccessPayload { + exploreId: ExploreId; + result: any; + resultType: ResultType; + latency: number; +} + +export interface HistoryUpdatedPayload { exploreId: ExploreId; history: HistoryItem[]; - queryTransactions: QueryTransaction[]; } export interface RemoveQueryRowPayload { @@ -222,6 +230,11 @@ export interface RunQueriesPayload { exploreId: ExploreId; } +export interface ResetQueryErrorPayload { + exploreId: ExploreId; + refIds: string[]; +} + /** * Adds a query row after the row with the given index. */ @@ -310,9 +323,7 @@ export const modifyQueriesAction = actionCreatorFactory('e * Mark a query transaction as failed with an error extracted from the query response. * The transaction will be marked as `done`. */ -export const queryTransactionFailureAction = actionCreatorFactory( - 'explore/QUERY_TRANSACTION_FAILURE' -).create(); +export const queryFailureAction = actionCreatorFactory('explore/QUERY_FAILURE').create(); /** * Start a query transaction for the given result type. @@ -321,9 +332,7 @@ export const queryTransactionFailureAction = actionCreatorFactory( - 'explore/QUERY_TRANSACTION_START' -).create(); +export const queryStartAction = actionCreatorFactory('explore/QUERY_START').create(); /** * Complete a query transaction, mark the transaction as `done` and store query state in URL. @@ -336,9 +345,7 @@ export const queryTransactionStartAction = actionCreatorFactory( - 'explore/QUERY_TRANSACTION_SUCCESS' -).create(); +export const querySuccessAction = actionCreatorFactory('explore/QUERY_SUCCESS').create(); /** * Remove query row of the given index, as well as associated query results. @@ -426,6 +433,10 @@ export const loadExploreDatasources = actionCreatorFactory('explore/HISTORY_UPDATED').create(); + +export const resetQueryErrorAction = actionCreatorFactory('explore/RESET_QUERY_ERROR').create(); + export type HigherOrderAction = | ActionOf | SplitOpenAction diff --git a/public/app/features/explore/state/actions.test.ts b/public/app/features/explore/state/actions.test.ts index 64aaa9b634268..0bae4dc1d9582 100644 --- a/public/app/features/explore/state/actions.test.ts +++ b/public/app/features/explore/state/actions.test.ts @@ -1,4 +1,3 @@ -import moment from 'moment'; import { refreshExplore, testDatasource, loadDatasource } from './actions'; import { ExploreId, ExploreUrlState, ExploreUpdateState } from 'app/types'; import { thunkTester } from 'test/core/thunk/thunkTester'; @@ -20,6 +19,7 @@ import { ActionOf } from 'app/core/redux/actionCreatorFactory'; import { makeInitialUpdateState } from './reducers'; import { DataQuery } from '@grafana/ui/src/types/datasource'; import { DefaultTimeZone, RawTimeRange } from '@grafana/ui'; +import { toUtc } from '@grafana/ui/src/utils/moment_wrapper'; jest.mock('app/features/plugins/datasource_srv', () => ({ getDatasourceSrv: () => ({ @@ -31,7 +31,7 @@ jest.mock('app/features/plugins/datasource_srv', () => ({ }), })); -const t = moment.utc(); +const t = toUtc(); const testRange = { from: t, to: t, diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 0a43072515510..12b1a1b8b69d1 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -1,6 +1,5 @@ // Libraries import _ from 'lodash'; -import moment from 'moment'; // Services & Utils import store from 'app/core/store'; @@ -19,20 +18,21 @@ import { parseUrlState, getTimeRange, getTimeRangeFromUrl, + generateNewKeyAndAddRefIdIfMissing, + instanceOfDataQueryError, + getRefIds, } from 'app/core/utils/explore'; // Actions import { updateLocation } from 'app/core/actions'; // Types -import { ResultGetter } from 'app/types/explore'; import { ThunkResult } from 'app/types'; import { RawTimeRange, DataSourceApi, DataQuery, DataSourceSelectItem, - QueryHint, QueryFixAction, TimeRange, } from '@grafana/ui/src/types'; @@ -62,9 +62,8 @@ import { LoadDatasourceReadyPayload, loadDatasourceReadyAction, modifyQueriesAction, - queryTransactionFailureAction, - queryTransactionStartAction, - queryTransactionSuccessAction, + queryFailureAction, + querySuccessAction, scanRangeAction, scanStartAction, setQueriesAction, @@ -83,10 +82,15 @@ import { testDataSourceSuccessAction, testDataSourceFailureAction, loadExploreDatasources, + queryStartAction, + historyUpdatedAction, + resetQueryErrorAction, } from './actionTypes'; import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory'; import { LogsDedupStrategy } from 'app/core/logs_model'; import { getTimeZone } from 'app/features/profile/state/selectors'; +import { isDateTime } from '@grafana/ui/src/utils/moment_wrapper'; +import { toDataQueryError } from 'app/features/dashboard/state/PanelQueryState'; /** * Updates UI state and save it to the URL @@ -103,7 +107,8 @@ const updateExploreUIState = (exploreId: ExploreId, uiStateFragment: Partial { return (dispatch, getState) => { - const query = generateEmptyQuery(getState().explore[exploreId].queries, index); + const queries = getState().explore[exploreId].queries; + const query = generateEmptyQuery(queries, index); dispatch(addQueryRowAction({ exploreId, index, query })); }; @@ -148,7 +153,9 @@ export function changeQuery( return (dispatch, getState) => { // Null query means reset if (query === null) { - query = { ...generateEmptyQuery(getState().explore[exploreId].queries) }; + const queries = getState().explore[exploreId].queries; + const { refId, key } = queries[index]; + query = generateNewKeyAndAddRefIdIfMissing({ refId, key }, queries, index); } dispatch(changeQueryAction({ exploreId, query, index, override })); @@ -306,10 +313,7 @@ export function importQueries( importedQueries = ensureQueries(); } - const nextQueries = importedQueries.map((q, i) => ({ - ...q, - ...generateEmptyQuery(queries), - })); + const nextQueries = ensureQueries(importedQueries); dispatch(queriesImportedAction({ exploreId, queries: nextQueries })); }; @@ -368,7 +372,11 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T } if (instance.init) { - instance.init(); + try { + instance.init(); + } catch (err) { + console.log(err); + } } if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) { @@ -401,140 +409,87 @@ export function modifyQueries( }; } -/** - * Mark a query transaction as failed with an error extracted from the query response. - * The transaction will be marked as `done`. - */ -export function queryTransactionFailure( +export function processQueryErrors( exploreId: ExploreId, - transactionId: string, response: any, + resultType: ResultType, datasourceId: string ): ThunkResult { return (dispatch, getState) => { - const { datasourceInstance, queryTransactions } = getState().explore[exploreId]; + const { datasourceInstance } = getState().explore[exploreId]; + if (datasourceInstance.meta.id !== datasourceId || response.cancelled) { // Navigated away, queries did not matter return; } - // Transaction might have been discarded - if (!queryTransactions.find(qt => qt.id === transactionId)) { - return; - } - - console.error(response); + console.error(response); // To help finding problems with query syntax - let error: string; - let errorDetails: string; - if (response.data) { - if (typeof response.data === 'string') { - error = response.data; - } else if (response.data.error) { - error = response.data.error; - if (response.data.response) { - errorDetails = response.data.response; - } - } else { - throw new Error('Could not handle error response'); - } - } else if (response.message) { - error = response.message; - } else if (typeof response === 'string') { - error = response; - } else { - error = 'Unknown error during query transaction. Please check JS console logs.'; - } - - // Mark transactions as complete - const nextQueryTransactions = queryTransactions.map(qt => { - if (qt.id === transactionId) { - return { - ...qt, - error, - errorDetails, - done: true, - }; - } - return qt; - }); + if (!instanceOfDataQueryError(response)) { + response = toDataQueryError(response); + } - dispatch(queryTransactionFailureAction({ exploreId, queryTransactions: nextQueryTransactions })); + dispatch( + queryFailureAction({ + exploreId, + response, + resultType, + }) + ); }; } /** - * Complete a query transaction, mark the transaction as `done` and store query state in URL. - * If the transaction was started by a scanner, it keeps on scanning for more results. - * Side-effect: the query is stored in localStorage. * @param exploreId Explore area - * @param transactionId ID - * @param result Response from `datasourceInstance.query()` + * @param response Response from `datasourceInstance.query()` * @param latency Duration between request and response - * @param queries Queries from all query rows + * @param resultType The type of result * @param datasourceId Origin datasource instance, used to discard results if current datasource is different */ -export function queryTransactionSuccess( +export function processQueryResults( exploreId: ExploreId, - transactionId: string, - result: any, + response: any, latency: number, - queries: DataQuery[], + resultType: ResultType, datasourceId: string ): ThunkResult { return (dispatch, getState) => { - const { datasourceInstance, history, queryTransactions, scanner, scanning } = getState().explore[exploreId]; + const { datasourceInstance, scanning, scanner } = getState().explore[exploreId]; // If datasource already changed, results do not matter if (datasourceInstance.meta.id !== datasourceId) { return; } - // Transaction might have been discarded - const transaction = queryTransactions.find(qt => qt.id === transactionId); - if (!transaction) { - return; - } + const series: any[] = response.data; + const refIds = getRefIds(series); - // Get query hints - let hints: QueryHint[]; - if (datasourceInstance.getQueryHints) { - hints = datasourceInstance.getQueryHints(transaction.query, result); - } - - // Mark transactions as complete and attach result - const nextQueryTransactions = queryTransactions.map(qt => { - if (qt.id === transactionId) { - return { - ...qt, - hints, - latency, - result, - done: true, - }; - } - return qt; - }); + // Clears any previous errors that now have a successful query, important so Angular editors are updated correctly + dispatch( + resetQueryErrorAction({ + exploreId, + refIds, + }) + ); - // Side-effect: Saving history in localstorage - const nextHistory = updateHistory(history, datasourceId, queries); + const resultGetter = + resultType === 'Graph' ? makeTimeSeriesList : resultType === 'Table' ? (data: any[]) => data : null; + const result = resultGetter ? resultGetter(series, null, []) : series; dispatch( - queryTransactionSuccessAction({ + querySuccessAction({ exploreId, - history: nextHistory, - queryTransactions: nextQueryTransactions, + result, + resultType, + latency, }) ); // Keep scanning for results if this was the last scanning transaction if (scanning) { if (_.size(result) === 0) { - const other = nextQueryTransactions.find(qt => qt.scanning && !qt.done); - if (!other) { - const range = scanner(); - dispatch(scanRangeAction({ exploreId, range })); - } + const range = scanner(); + dispatch(scanRangeAction({ exploreId, range })); } else { // We can stop scanning if we have a result dispatch(scanStopAction({ exploreId })); @@ -546,7 +501,7 @@ export function queryTransactionSuccess( /** * Main action to run queries and dispatches sub-actions based on which result viewers are active */ -export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkResult> { +export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkResult { return (dispatch, getState) => { const { datasourceInstance, @@ -563,13 +518,13 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkRe if (datasourceError) { // let's not run any queries if data source is in a faulty state - return Promise.resolve(); + return; } if (!hasNonEmptyQuery(queries)) { dispatch(clearQueriesAction({ exploreId })); dispatch(stateSave()); // Remember to saves to state and update location - return Promise.resolve(); + return; } // Some datasource's query builders allow per-query interval limits, @@ -578,46 +533,31 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkRe dispatch(runQueriesAction({ exploreId })); // Keep table queries first since they need to return quickly - const tableQueriesPromise = - (ignoreUIState || showingTable) && supportsTable - ? dispatch( - runQueriesForType( - exploreId, - 'Table', - { - interval, - format: 'table', - instant: true, - valueWithRefId: true, - }, - (data: any[]) => data[0] - ) - ) - : undefined; - const typeQueriesPromise = - (ignoreUIState || showingGraph) && supportsGraph - ? dispatch( - runQueriesForType( - exploreId, - 'Graph', - { - interval, - format: 'time_series', - instant: false, - maxDataPoints: containerWidth, - }, - makeTimeSeriesList - ) - ) - : undefined; - const logsQueriesPromise = - (ignoreUIState || showingLogs) && supportsLogs - ? dispatch(runQueriesForType(exploreId, 'Logs', { interval, format: 'logs' })) - : undefined; + if ((ignoreUIState || showingTable) && supportsTable) { + dispatch( + runQueriesForType(exploreId, 'Table', { + interval, + format: 'table', + instant: true, + valueWithRefId: true, + }) + ); + } + if ((ignoreUIState || showingGraph) && supportsGraph) { + dispatch( + runQueriesForType(exploreId, 'Graph', { + interval, + format: 'time_series', + instant: false, + maxDataPoints: containerWidth, + }) + ); + } + if ((ignoreUIState || showingLogs) && supportsLogs) { + dispatch(runQueriesForType(exploreId, 'Logs', { interval, format: 'logs' })); + } dispatch(stateSave()); - - return Promise.all([tableQueriesPromise, typeQueriesPromise, logsQueriesPromise]); }; } @@ -631,39 +571,28 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkRe function runQueriesForType( exploreId: ExploreId, resultType: ResultType, - queryOptions: QueryOptions, - resultGetter?: ResultGetter + queryOptions: QueryOptions ): ThunkResult { return async (dispatch, getState) => { - const { datasourceInstance, eventBridge, queries, queryIntervals, range, scanning } = getState().explore[exploreId]; + const { datasourceInstance, eventBridge, queries, queryIntervals, range, scanning, history } = getState().explore[ + exploreId + ]; const datasourceId = datasourceInstance.meta.id; - // Run all queries concurrently - const queryPromises = queries.map(async (query, rowIndex) => { - const transaction = buildQueryTransaction( - query, - rowIndex, - resultType, - queryOptions, - range, - queryIntervals, - scanning - ); - dispatch(queryTransactionStartAction({ exploreId, resultType, rowIndex, transaction })); - try { - const now = Date.now(); - const res = await datasourceInstance.query(transaction.options); - eventBridge.emit('data-received', res.data || []); - const latency = Date.now() - now; - const { queryTransactions } = getState().explore[exploreId]; - const results = resultGetter ? resultGetter(res.data, transaction, queryTransactions) : res.data; - dispatch(queryTransactionSuccess(exploreId, transaction.id, results, latency, queries, datasourceId)); - } catch (response) { - eventBridge.emit('data-error', response); - dispatch(queryTransactionFailure(exploreId, transaction.id, response, datasourceId)); - } - }); - - return Promise.all(queryPromises); + const transaction = buildQueryTransaction(queries, resultType, queryOptions, range, queryIntervals, scanning); + dispatch(queryStartAction({ exploreId, resultType, rowIndex: 0, transaction })); + try { + const now = Date.now(); + const response = await datasourceInstance.query(transaction.options); + eventBridge.emit('data-received', response.data || []); + const latency = Date.now() - now; + // Side-effect: Saving history in localstorage + const nextHistory = updateHistory(history, datasourceId, queries); + dispatch(historyUpdatedAction({ exploreId, history: nextHistory })); + dispatch(processQueryResults(exploreId, response, latency, resultType, datasourceId)); + } catch (err) { + eventBridge.emit('data-error', err); + dispatch(processQueryErrors(exploreId, err, resultType, datasourceId)); + } }; } @@ -690,8 +619,9 @@ export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkRes export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): ThunkResult { return (dispatch, getState) => { // Inject react keys into query objects - const queries = rawQueries.map(q => ({ ...q, ...generateEmptyQuery(getState().explore[exploreId].queries) })); - dispatch(setQueriesAction({ exploreId, queries })); + const queries = getState().explore[exploreId].queries; + const nextQueries = rawQueries.map((query, index) => generateNewKeyAndAddRefIdIfMissing(query, queries, index)); + dispatch(setQueriesAction({ exploreId, queries: nextQueries })); dispatch(runQueries(exploreId)); }; } @@ -732,12 +662,12 @@ export function splitOpen(): ThunkResult { const toRawTimeRange = (range: TimeRange): RawTimeRange => { let from = range.raw.from; - if (moment.isMoment(from)) { + if (isDateTime(from)) { from = from.valueOf().toString(10); } let to = range.raw.to; - if (moment.isMoment(to)) { + if (isDateTime(to)) { to = to.valueOf().toString(10); } @@ -855,7 +785,11 @@ export function refreshExplore(exploreId: ExploreId): ThunkResult { const { urlState, update, containerWidth, eventBridge } = itemState; const { datasource, queries, range: urlRange, ui } = urlState; - const refreshQueries = queries.map(q => ({ ...q, ...generateEmptyQuery(itemState.queries) })); + const refreshQueries: DataQuery[] = []; + for (let index = 0; index < queries.length; index++) { + const query = queries[index]; + refreshQueries.push(generateNewKeyAndAddRefIdIfMissing(query, refreshQueries, index)); + } const timeZone = getTimeZone(getState().user); const range = getTimeRangeFromUrl(urlRange, timeZone); diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts index f781eb684c06e..428d208e17a80 100644 --- a/public/app/features/explore/state/reducers.test.ts +++ b/public/app/features/explore/state/reducers.test.ts @@ -97,7 +97,6 @@ describe('Explore item reducer', () => { const queryTransactions: QueryTransaction[] = []; const initalState: Partial = { datasourceError: null, - queryTransactions: [{} as QueryTransaction], graphResult: [], tableResult: {} as TableModel, logsResult: {} as LogsModel, diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index c63b5fe4a1cf8..9807bcb8ad856 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -1,14 +1,14 @@ import _ from 'lodash'; import { calculateResultsFromQueryTransactions, - generateEmptyQuery, getIntervals, ensureQueries, getQueryKeys, parseUrlState, DEFAULT_UI_STATE, + generateNewKeyAndAddRefIdIfMissing, } from 'app/core/utils/explore'; -import { ExploreItemState, ExploreState, QueryTransaction, ExploreId, ExploreUpdateState } from 'app/types/explore'; +import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState } from 'app/types/explore'; import { DataQuery } from '@grafana/ui/src/types'; import { HigherOrderAction, @@ -20,6 +20,8 @@ import { SplitCloseActionPayload, loadExploreDatasources, runQueriesAction, + historyUpdatedAction, + resetQueryErrorAction, } from './actionTypes'; import { reducerFactory } from 'app/core/redux'; import { @@ -36,16 +38,14 @@ import { loadDatasourcePendingAction, loadDatasourceReadyAction, modifyQueriesAction, - queryTransactionFailureAction, - queryTransactionStartAction, - queryTransactionSuccessAction, + queryFailureAction, + queryStartAction, + querySuccessAction, removeQueryRowAction, scanRangeAction, scanStartAction, scanStopAction, setQueriesAction, - toggleGraphAction, - toggleLogsAction, toggleTableAction, queriesImportedAction, updateUIStateAction, @@ -53,6 +53,7 @@ import { } from './actionTypes'; import { updateLocation } from 'app/core/actions/location'; import { LocationUpdate } from 'app/types'; +import TableModel from 'app/core/table_model'; export const DEFAULT_RANGE = { from: 'now-6h', @@ -84,7 +85,6 @@ export const makeExploreItemState = (): ExploreItemState => ({ history: [], queries: [], initialized: false, - queryTransactions: [], queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL }, range: { from: null, @@ -96,12 +96,17 @@ export const makeExploreItemState = (): ExploreItemState => ({ showingGraph: true, showingLogs: true, showingTable: true, + graphIsLoading: false, + logIsLoading: false, + tableIsLoading: false, supportsGraph: null, supportsLogs: null, supportsTable: null, queryKeys: [], urlState: null, update: makeInitialUpdateState(), + queryErrors: [], + latency: 0, }); /** @@ -121,28 +126,16 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: addQueryRowAction, mapper: (state, action): ExploreItemState => { - const { queries, queryTransactions } = state; + const { queries } = state; const { index, query } = action.payload; // Add to queries, which will cause a new row to be rendered const nextQueries = [...queries.slice(0, index + 1), { ...query }, ...queries.slice(index + 1)]; - // Ongoing transactions need to update their row indices - const nextQueryTransactions = queryTransactions.map(qt => { - if (qt.rowIndex > index) { - return { - ...qt, - rowIndex: qt.rowIndex + 1, - }; - } - return qt; - }); - return { ...state, queries: nextQueries, logsHighlighterExpressions: undefined, - queryTransactions: nextQueryTransactions, queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), }; }, @@ -150,21 +143,17 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: changeQueryAction, mapper: (state, action): ExploreItemState => { - const { queries, queryTransactions } = state; + const { queries } = state; const { query, index } = action.payload; // Override path: queries are completely reset - const nextQuery: DataQuery = { ...query, ...generateEmptyQuery(state.queries) }; + const nextQuery: DataQuery = generateNewKeyAndAddRefIdIfMissing(query, queries, index); const nextQueries = [...queries]; nextQueries[index] = nextQuery; - // Discard ongoing transaction related to row query - const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index); - return { ...state, queries: nextQueries, - queryTransactions: nextQueryTransactions, queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), }; }, @@ -199,7 +188,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, queries: queries.slice(), - queryTransactions: [], showingStartPage: Boolean(state.StartPage), queryKeys: getQueryKeys(queries, state.datasourceInstance), }; @@ -244,6 +232,11 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, datasourceInstance, + queryErrors: [], + latency: 0, + graphIsLoading: false, + logIsLoading: false, + tableIsLoading: false, supportsGraph, supportsLogs, supportsTable, @@ -284,7 +277,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta datasourceLoading: false, datasourceMissing: false, logsHighlighterExpressions: undefined, - queryTransactions: [], update: makeInitialUpdateState(), }; }, @@ -292,95 +284,87 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: modifyQueriesAction, mapper: (state, action): ExploreItemState => { - const { queries, queryTransactions } = state; + const { queries } = state; const { modification, index, modifier } = action.payload; let nextQueries: DataQuery[]; - let nextQueryTransactions: QueryTransaction[]; if (index === undefined) { // Modify all queries - nextQueries = queries.map((query, i) => ({ - ...modifier({ ...query }, modification), - ...generateEmptyQuery(state.queries), - })); - // Discard all ongoing transactions - nextQueryTransactions = []; + nextQueries = queries.map((query, i) => { + const nextQuery = modifier({ ...query }, modification); + return generateNewKeyAndAddRefIdIfMissing(nextQuery, queries, i); + }); } else { // Modify query only at index nextQueries = queries.map((query, i) => { - // Synchronize all queries with local query cache to ensure consistency - // TODO still needed? - return i === index - ? { ...modifier({ ...query }, modification), ...generateEmptyQuery(state.queries) } - : query; + if (i === index) { + const nextQuery = modifier({ ...query }, modification); + return generateNewKeyAndAddRefIdIfMissing(nextQuery, queries, i); + } + + return query; }); - nextQueryTransactions = queryTransactions - // Consume the hint corresponding to the action - .map(qt => { - if (qt.hints != null && qt.rowIndex === index) { - qt.hints = qt.hints.filter(hint => hint.fix.action !== modification); - } - return qt; - }) - // Preserve previous row query transaction to keep results visible if next query is incomplete - .filter(qt => modification.preventSubmit || qt.rowIndex !== index); } return { ...state, queries: nextQueries, queryKeys: getQueryKeys(nextQueries, state.datasourceInstance), - queryTransactions: nextQueryTransactions, }; }, }) .addMapper({ - filter: queryTransactionFailureAction, + filter: queryFailureAction, mapper: (state, action): ExploreItemState => { - const { queryTransactions } = action.payload; + const { resultType, response } = action.payload; + const queryErrors = state.queryErrors.concat(response); + return { ...state, - queryTransactions, + graphResult: resultType === 'Graph' ? null : state.graphResult, + tableResult: resultType === 'Table' ? null : state.tableResult, + logsResult: resultType === 'Logs' ? null : state.logsResult, + latency: 0, + queryErrors, showingStartPage: false, + graphIsLoading: resultType === 'Graph' ? false : state.graphIsLoading, + logIsLoading: resultType === 'Logs' ? false : state.logIsLoading, + tableIsLoading: resultType === 'Table' ? false : state.tableIsLoading, update: makeInitialUpdateState(), }; }, }) .addMapper({ - filter: queryTransactionStartAction, + filter: queryStartAction, mapper: (state, action): ExploreItemState => { - const { queryTransactions } = state; - const { resultType, rowIndex, transaction } = action.payload; - // Discarding existing transactions of same type - const remainingTransactions = queryTransactions.filter( - qt => !(qt.resultType === resultType && qt.rowIndex === rowIndex) - ); - - // Append new transaction - const nextQueryTransactions: QueryTransaction[] = [...remainingTransactions, transaction]; + const { resultType } = action.payload; return { ...state, - queryTransactions: nextQueryTransactions, + queryErrors: [], + latency: 0, + graphIsLoading: resultType === 'Graph' ? true : state.graphIsLoading, + logIsLoading: resultType === 'Logs' ? true : state.logIsLoading, + tableIsLoading: resultType === 'Table' ? true : state.tableIsLoading, showingStartPage: false, update: makeInitialUpdateState(), }; }, }) .addMapper({ - filter: queryTransactionSuccessAction, + filter: querySuccessAction, mapper: (state, action): ExploreItemState => { - const { datasourceInstance, queryIntervals } = state; - const { history, queryTransactions } = action.payload; - const results = calculateResultsFromQueryTransactions( - queryTransactions, - datasourceInstance, - queryIntervals.intervalMs - ); + const { queryIntervals } = state; + const { result, resultType, latency } = action.payload; + const results = calculateResultsFromQueryTransactions(result, resultType, queryIntervals.intervalMs); return { ...state, - ...results, - history, - queryTransactions, + graphResult: resultType === 'Graph' ? results.graphResult : state.graphResult, + tableResult: resultType === 'Table' ? results.tableResult : state.tableResult, + logsResult: resultType === 'Logs' ? results.logsResult : state.logsResult, + latency, + graphIsLoading: false, + logIsLoading: false, + tableIsLoading: false, showingStartPage: false, update: makeInitialUpdateState(), }; @@ -389,7 +373,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: removeQueryRowAction, mapper: (state, action): ExploreItemState => { - const { datasourceInstance, queries, queryIntervals, queryTransactions, queryKeys } = state; + const { queries, queryKeys } = state; const { index } = action.payload; if (queries.length <= 1) { @@ -399,20 +383,10 @@ export const itemReducer = reducerFactory({} as ExploreItemSta const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)]; const nextQueryKeys = [...queryKeys.slice(0, index), ...queryKeys.slice(index + 1)]; - // Discard transactions related to row query - const nextQueryTransactions = queryTransactions.filter(qt => nextQueries.some(nq => nq.key === qt.query.key)); - const results = calculateResultsFromQueryTransactions( - nextQueryTransactions, - datasourceInstance, - queryIntervals.intervalMs - ); - return { ...state, - ...results, queries: nextQueries, logsHighlighterExpressions: undefined, - queryTransactions: nextQueryTransactions, queryKeys: nextQueryKeys, }; }, @@ -432,11 +406,8 @@ export const itemReducer = reducerFactory({} as ExploreItemSta .addMapper({ filter: scanStopAction, mapper: (state): ExploreItemState => { - const { queryTransactions } = state; - const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done); return { ...state, - queryTransactions: nextQueryTransactions, scanning: false, scanRange: undefined, scanner: undefined, @@ -461,47 +432,15 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, ...action.payload }; }, }) - .addMapper({ - filter: toggleGraphAction, - mapper: (state): ExploreItemState => { - const showingGraph = !state.showingGraph; - let nextQueryTransactions = state.queryTransactions; - if (!showingGraph) { - // Discard transactions related to Graph query - nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph'); - } - return { ...state, queryTransactions: nextQueryTransactions }; - }, - }) - .addMapper({ - filter: toggleLogsAction, - mapper: (state): ExploreItemState => { - const showingLogs = !state.showingLogs; - let nextQueryTransactions = state.queryTransactions; - if (!showingLogs) { - // Discard transactions related to Logs query - nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs'); - } - return { ...state, queryTransactions: nextQueryTransactions }; - }, - }) .addMapper({ filter: toggleTableAction, mapper: (state): ExploreItemState => { const showingTable = !state.showingTable; if (showingTable) { - return { ...state, queryTransactions: state.queryTransactions }; + return { ...state }; } - // Toggle off needs discarding of table queries and results - const nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Table'); - const results = calculateResultsFromQueryTransactions( - nextQueryTransactions, - state.datasourceInstance, - state.queryIntervals.intervalMs - ); - - return { ...state, ...results, queryTransactions: nextQueryTransactions }; + return { ...state, tableResult: new TableModel() }; }, }) .addMapper({ @@ -549,7 +488,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta return { ...state, datasourceError: action.payload.error, - queryTransactions: [], graphResult: undefined, tableResult: undefined, logsResult: undefined, @@ -581,6 +519,33 @@ export const itemReducer = reducerFactory({} as ExploreItemSta }; }, }) + .addMapper({ + filter: historyUpdatedAction, + mapper: (state, action): ExploreItemState => { + return { + ...state, + history: action.payload.history, + }; + }, + }) + .addMapper({ + filter: resetQueryErrorAction, + mapper: (state, action): ExploreItemState => { + const { refIds } = action.payload; + const queryErrors = state.queryErrors.reduce((allErrors, error) => { + if (error.refId && refIds.indexOf(error.refId) !== -1) { + return allErrors; + } + + return allErrors.concat(error); + }, []); + + return { + ...state, + queryErrors, + }; + }, + }) .create(); export const updateChildRefreshState = ( diff --git a/public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts b/public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts index 44f831af0c256..58ad4d8aac90e 100644 --- a/public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts +++ b/public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts @@ -5,7 +5,6 @@ import angular from 'angular'; const template = ` `; @@ -20,7 +19,7 @@ export function uploadDashboardDirective(timer, $location) { btnText: '@?', }, link: (scope, elem) => { - scope.btnText = angular.isDefined(scope.btnText) ? scope.btnText : 'Upload .json File'; + scope.btnText = angular.isDefined(scope.btnText) ? scope.btnText : 'Upload .json file'; function file_selected(evt) { const files = evt.target.files; // FileList object diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index b8ee1cff37280..068aa03d0f9e4 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -6,12 +6,23 @@ import { PanelCtrl } from 'app/features/panel/panel_ctrl'; import { getExploreUrl } from 'app/core/utils/explore'; import { applyPanelTimeOverrides, getResolution } from 'app/features/dashboard/utils/panel'; import { ContextSrv } from 'app/core/services/context_srv'; -import { toLegacyResponseData, isSeriesData, LegacyResponseData, TimeRange } from '@grafana/ui'; +import { + toLegacyResponseData, + isSeriesData, + LegacyResponseData, + TimeRange, + DataSourceApi, + PanelData, + LoadingState, + DataQueryResponse, +} from '@grafana/ui'; import { Unsubscribable } from 'rxjs'; +import { PanelModel } from 'app/features/dashboard/state'; +import { PanelQueryRunnerFormat } from '../dashboard/state/PanelQueryRunner'; class MetricsPanelCtrl extends PanelCtrl { scope: any; - datasource: any; + datasource: DataSourceApi; $q: any; $timeout: any; contextSrv: ContextSrv; @@ -24,11 +35,10 @@ class MetricsPanelCtrl extends PanelCtrl { resolution: any; timeInfo?: string; skipDataOnInit: boolean; - dataStream: any; - dataSubscription?: Unsubscribable; dataList: LegacyResponseData[]; + querySubscription?: Unsubscribable; - constructor($scope, $injector) { + constructor($scope: any, $injector: any) { super($scope, $injector); this.$q = $injector.get('$q'); @@ -44,9 +54,9 @@ class MetricsPanelCtrl extends PanelCtrl { } private onPanelTearDown() { - if (this.dataSubscription) { - this.dataSubscription.unsubscribe(); - this.dataSubscription = null; + if (this.querySubscription) { + this.querySubscription.unsubscribe(); + this.querySubscription = null; } } @@ -72,47 +82,76 @@ class MetricsPanelCtrl extends PanelCtrl { }); } - // // ignore if we have data stream - if (this.dataStream) { - return; - } - // clear loading/error state delete this.error; this.loading = true; // load datasource service - this.datasourceSrv - .get(this.panel.datasource, this.panel.scopedVars) - .then(this.updateTimeRange.bind(this)) - .then(this.issueQueries.bind(this)) - .then(this.handleQueryResult.bind(this)) - .catch(err => { - // if canceled keep loading set to true - if (err.cancelled) { - console.log('Panel request cancelled', err); - return; - } + return ( + this.datasourceSrv + .get(this.panel.datasource, this.panel.scopedVars) + .then(this.updateTimeRange.bind(this)) + .then(this.issueQueries.bind(this)) + // NOTE handleQueryResult is called by panelDataObserver + .catch((err: any) => { + this.processDataError(err); + }) + ); + } + + processDataError(err: any) { + // if canceled keep loading set to true + if (err.cancelled) { + console.log('Panel request cancelled', err); + return; + } + + this.loading = false; + this.error = err.message || 'Request Error'; + this.inspector = { error: err }; + if (err.data) { + if (err.data.message) { + this.error = err.data.message; + } + if (err.data.error) { + this.error = err.data.error; + } + } + + this.events.emit('data-error', err); + console.log('Panel data error:', err); + } + + // Updates the response with information from the stream + panelDataObserver = { + next: (data: PanelData) => { + if (data.state === LoadingState.Error) { + this.loading = false; + this.processDataError(data.error); + } else if (data.state === LoadingState.Done) { this.loading = false; - this.error = err.message || 'Request Error'; - this.inspector = { error: err }; - - if (err.data) { - if (err.data.message) { - this.error = err.data.message; - } - if (err.data.error) { - this.error = err.data.error; - } + + // The result should already be processed, but just in case + if (!data.legacy) { + data.legacy = data.series.map(v => { + if (isSeriesData(v)) { + return toLegacyResponseData(v); + } + return v; + }); } - this.events.emit('data-error', err); - console.log('Panel data error:', err); - }); - } + // Make the results look like they came directly from a <6.2 datasource request + // NOTE: any object other than 'data' is no longer supported supported + this.handleQueryResult({ + data: data.legacy, + }); + } + }, + }; - updateTimeRange(datasource?) { + updateTimeRange(datasource?: DataSourceApi) { this.datasource = datasource || this.datasource; this.range = this.timeSrv.timeRange(); this.resolution = getResolution(this.panel); @@ -141,46 +180,34 @@ class MetricsPanelCtrl extends PanelCtrl { this.intervalMs = res.intervalMs; } - issueQueries(datasource) { + issueQueries(datasource: DataSourceApi) { this.datasource = datasource; - if (!this.panel.targets || this.panel.targets.length === 0) { - return this.$q.when([]); - } + const panel = this.panel as PanelModel; + const queryRunner = panel.getQueryRunner(); - // make shallow copy of scoped vars, - // and add built in variables interval and interval_ms - const scopedVars = Object.assign({}, this.panel.scopedVars, { - __interval: { text: this.interval, value: this.interval }, - __interval_ms: { text: this.intervalMs, value: this.intervalMs }, - }); + if (!this.querySubscription) { + this.querySubscription = queryRunner.subscribe(this.panelDataObserver, PanelQueryRunnerFormat.legacy); + } - const metricsQuery = { - timezone: this.dashboard.getTimezone(), - panelId: this.panel.id, + return queryRunner.run({ + datasource: panel.datasource, + queries: panel.targets, + panelId: panel.id, dashboardId: this.dashboard.id, - range: this.range, - rangeRaw: this.range.raw, - interval: this.interval, - intervalMs: this.intervalMs, - targets: this.panel.targets, - maxDataPoints: this.resolution, - scopedVars: scopedVars, - cacheTimeout: this.panel.cacheTimeout, - }; - - return datasource.query(metricsQuery); + timezone: this.dashboard.timezone, + timeRange: this.range, + widthPixels: this.resolution, // The pixel width + maxDataPoints: panel.maxDataPoints, + minInterval: panel.interval, + scopedVars: panel.scopedVars, + cacheTimeout: panel.cacheTimeout, + }); } - handleQueryResult(result) { + handleQueryResult(result: DataQueryResponse) { this.loading = false; - // check for if data source returns subject - if (result && result.subscribe) { - this.handleDataStream(result); - return; - } - if (this.dashboard.snapshot) { this.panel.snapshotData = result.data; } @@ -190,41 +217,7 @@ class MetricsPanelCtrl extends PanelCtrl { result = { data: [] }; } - // Make sure the data is TableData | TimeSeries - const data = result.data.map(v => { - if (isSeriesData(v)) { - return toLegacyResponseData(v); - } - return v; - }); - this.events.emit('data-received', data); - } - - handleDataStream(stream) { - // if we already have a connection - if (this.dataStream) { - console.log('two stream observables!'); - return; - } - - this.dataStream = stream; - this.dataSubscription = stream.subscribe({ - next: data => { - console.log('dataSubject next!'); - if (data.range) { - this.range = data.range; - } - this.events.emit('data-received', data.data); - }, - error: error => { - this.events.emit('data-error', error); - console.log('panel: observer got error'); - }, - complete: () => { - console.log('panel: observer got complete'); - this.dataStream = null; - }, - }); + this.events.emit('data-received', result.data); } getAdditionalMenuItems() { diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index 68c7b64d170b1..8cfc627b424b6 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -17,7 +17,7 @@ const panelTemplate = ` - +
    diff --git a/public/app/features/panel/panel_header.ts b/public/app/features/panel/panel_header.ts index e844a73cfa9a7..26139e23ac815 100644 --- a/public/app/features/panel/panel_header.ts +++ b/public/app/features/panel/panel_header.ts @@ -34,7 +34,7 @@ function renderMenuItem(item, ctrl) { } html += `>`; - html += `${item.text}`; + html += `${item.text}`; if (item.shortcut) { html += `${item.shortcut}`; diff --git a/public/app/features/plugins/PluginStateInfo.tsx b/public/app/features/plugins/PluginStateInfo.tsx index 37f3f276c0dde..fe8ce13095ea6 100644 --- a/public/app/features/plugins/PluginStateInfo.tsx +++ b/public/app/features/plugins/PluginStateInfo.tsx @@ -1,22 +1,28 @@ -import React, { FC } from 'react'; -import { PluginState } from '@grafana/ui'; +import React, { FC, useContext } from 'react'; +import { css } from 'emotion'; +import { PluginState, Tooltip, ThemeContext } from '@grafana/ui'; +import { PopperContent } from '@grafana/ui/src/components/Tooltip/PopperController'; interface Props { state?: PluginState; } -function getPluginStateInfoText(state?: PluginState): string | null { +function getPluginStateInfoText(state?: PluginState): PopperContent | null { switch (state) { case PluginState.alpha: return ( - 'This plugin is marked as being in alpha state, which means it is in early development phase and updates' + - ' will include breaking changes.' +
    +
    Alpha Plugin
    +

    This plugin is a work in progress and updates may include breaking changes.

    +
    ); case PluginState.beta: return ( - 'This plugin is marked as being in a beta development state. This means it is in currently in active' + - ' development and could be missing important features.' +
    +
    Beta Plugin
    +

    There could be bugs and minor breaking changes to this plugin.

    +
    ); } return null; @@ -28,7 +34,27 @@ const PluginStateinfo: FC = props => { return null; } - return
    {text}
    ; + const theme = useContext(ThemeContext); + + const styles = css` + background: linear-gradient(to bottom, ${theme.colors.blueBase}, ${theme.colors.blueShade}); + color: ${theme.colors.gray7}; + white-space: nowrap; + border-radius: 3px; + text-shadow: none; + font-size: 13px; + padding: 4px 8px; + margin-left: 16px; + cursor: help; + `; + + return ( + +
    + {props.state} +
    +
    + ); }; export default PluginStateinfo; diff --git a/public/app/features/plugins/__mocks__/pluginMocks.ts b/public/app/features/plugins/__mocks__/pluginMocks.ts index 7106af6f0710d..c03c140a49427 100644 --- a/public/app/features/plugins/__mocks__/pluginMocks.ts +++ b/public/app/features/plugins/__mocks__/pluginMocks.ts @@ -1,4 +1,4 @@ -import { PanelPluginMeta, PluginMeta, PluginType, PanelDataFormat, PanelPlugin, PanelProps } from '@grafana/ui'; +import { PanelPluginMeta, PluginMeta, PluginType, PanelPlugin, PanelProps } from '@grafana/ui'; import { ComponentType } from 'enzyme'; export const getMockPlugins = (amount: number): PluginMeta[] => { @@ -46,7 +46,6 @@ export const getPanelPlugin = ( type: PluginType.panel, name: options.id, sort: options.sort || 1, - dataFormats: [PanelDataFormat.TimeSeries], info: { author: { name: options.id + 'name', diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index d9c891ca8bafa..6143a5e182ba7 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -65,8 +65,6 @@ export class DatasourceSrv { instanceSettings: dsConfig, }); - instance.id = dsConfig.id; - instance.name = name; instance.components = dsPlugin.components; instance.meta = dsConfig.meta; diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index c66803906f52b..2ee31a7b6aa26 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -1,3 +1,4 @@ +/* tslint:disable:import-blacklist */ import System from 'systemjs/dist/system.js'; import _ from 'lodash'; import * as sdk from 'app/plugins/sdk'; @@ -105,7 +106,7 @@ exposeToPlugin('app/core/services/backend_srv', { }); exposeToPlugin('app/plugins/sdk', sdk); -exposeToPlugin('@grafana/ui/src/utils/datemath', datemath); +exposeToPlugin('app/core/utils/datemath', datemath); exposeToPlugin('app/core/utils/file_export', fileExport); exposeToPlugin('app/core/utils/flatten', flatten); exposeToPlugin('app/core/utils/kbn', kbn); diff --git a/public/app/features/templating/specs/variable_srv.test.ts b/public/app/features/templating/specs/variable_srv.test.ts index 13281fecead27..eac11d524c207 100644 --- a/public/app/features/templating/specs/variable_srv.test.ts +++ b/public/app/features/templating/specs/variable_srv.test.ts @@ -1,8 +1,8 @@ import '../all'; import { VariableSrv } from '../variable_srv'; import { DashboardModel } from '../../dashboard/state/DashboardModel'; -import moment from 'moment'; import $q from 'q'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; describe('VariableSrv', function(this: any) { const ctx = { @@ -106,7 +106,7 @@ describe('VariableSrv', function(this: any) { }; const range = { - from: moment(new Date()) + from: dateTime(new Date()) .subtract(7, 'days') .toDate(), to: new Date(), @@ -514,7 +514,7 @@ describe('VariableSrv', function(this: any) { beforeEach(() => { const range = { - from: moment(new Date()) + from: dateTime(new Date()) .subtract(7, 'days') .toDate(), to: new Date(), diff --git a/public/app/partials/login.html b/public/app/partials/login.html index 33872c7d6d585..55bd8060a4f18 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -8,15 +8,15 @@ +
    + +
    + +
    +
    @@ -23,8 +29,13 @@
    - +
    diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/config.html b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/config.html index b3d7ad0f09237..0f5cb0c2be06f 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/config.html +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/config.html @@ -1,32 +1,21 @@ -

    Azure Monitor API Details

    +

    Azure Monitor Details

    - Azure Cloud -
    + Azure Cloud +
    - -

    Choose an Azure Cloud.

    -
    -
    -
    -
    -
    - Subscription Id - -

    In the Azure Portal, navigate to Subscriptions -> Choose subscription -> Overview -> Subscription ID.

    - **Click - here for detailed instructions on setting up an Azure Active Directory (AD) application.** +

    Choose an Azure Cloud.

    - Tenant Id + Tenant Id

    In the Azure Portal, navigate to Azure Active Directory -> Properties -> Directory ID.

    @@ -37,7 +26,7 @@

    Azure Monitor API Details

    - Client Id + Client Id

    In the Azure Portal, navigate to Azure Active Directory -> App Registrations -> Choose your app -> @@ -49,7 +38,7 @@

    Azure Monitor API Details

    - Client Secret + Client Secret

    To create a new key, log in to Azure Portal, navigate to Azure Active Directory -> App Registrations -> @@ -61,10 +50,26 @@

    Azure Monitor API Details

    - Client Secret + Client Secret reset
    +
    +
    +
    + Default Subscription + + + +

    Choose the default/preferred Subscription for Azure Metrics.

    +

    In the Azure Portal, navigate to Subscriptions -> Choose subscription -> Overview -> Subscription ID.

    + **Click + here for detailed instructions on setting up an Azure Active Directory (AD) application.** +
    +
    +
    +

    Azure Log Analytics API Details

    @@ -82,19 +87,7 @@

    Azure Log Analytics API Details

    - Subscription Id - - -

    In the Azure Portal, navigate to Subscriptions -> Choose subscription -> Overview -> Subscription ID.

    - **Click - here for detailed instructions on setting up an Azure Active Directory (AD) application.** -
    -
    -
    -
    -
    - Tenant Id + Tenant Id @@ -106,7 +99,7 @@

    Azure Log Analytics API Details

    - Client Id + Client Id @@ -120,7 +113,7 @@

    Azure Log Analytics API Details

    - Client Secret + Client Secret @@ -133,20 +126,34 @@

    Azure Log Analytics API Details

    - Client Secret + Client Secret reset
    +
    +
    + Default Subscription + + + +

    Choose the default/preferred Subscription for Azure Metrics.

    +

    In the Azure Portal, navigate to Subscriptions -> Choose subscription -> Overview -> Subscription ID.

    + **Click + here for detailed instructions on setting up an Azure Active Directory (AD) application.** +
    +
    +
    - Default Workspace -
    + Default Workspace +
    - +

    Choose the default/preferred Workspace for Azure Log Analytics queries.

    diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html index 6299947b30ac2..c2461eb67ead1 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html @@ -7,6 +7,12 @@ ng-change="ctrl.onQueryTypeChange()">
    +
    + + + +
    @@ -99,11 +105,9 @@
    -
    - -
    -
    + +
    @@ -118,9 +122,6 @@
    - -
    { panel: { scopedVars: [], targets: [] }, }; AzureMonitorQueryCtrl.prototype.target = {} as any; + AzureMonitorQueryCtrl.prototype.datasource = { + $q: Q, + appInsightsDatasource: { isConfigured: () => false }, + azureMonitorDatasource: { isConfigured: () => false }, + }; queryCtrl = new AzureMonitorQueryCtrl({}, {}, new TemplateSrv()); - queryCtrl.datasource = { $q: Q, appInsightsDatasource: { isConfigured: () => false } }; }); describe('init query_ctrl variables', () => { @@ -68,8 +72,10 @@ describe('AzureMonitorQueryCtrl', () => { ]; beforeEach(() => { + queryCtrl.target.subscription = 'sub1'; queryCtrl.target.azureMonitor.resourceGroup = 'test'; - queryCtrl.datasource.getMetricDefinitions = function(query) { + queryCtrl.datasource.getMetricDefinitions = function(subscriptionId, query) { + expect(subscriptionId).toBe('sub1'); expect(query).toBe('test'); return this.$q.when(response); }; @@ -99,9 +105,11 @@ describe('AzureMonitorQueryCtrl', () => { const response = [{ text: 'test1', value: 'test1' }, { text: 'test2', value: 'test2' }]; beforeEach(() => { + queryCtrl.target.subscription = 'sub1'; queryCtrl.target.azureMonitor.resourceGroup = 'test'; queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines'; - queryCtrl.datasource.getResourceNames = function(resourceGroup, metricDefinition) { + queryCtrl.datasource.getResourceNames = function(subscriptionId, resourceGroup, metricDefinition) { + expect(subscriptionId).toBe('sub1'); expect(resourceGroup).toBe('test'); expect(metricDefinition).toBe('Microsoft.Compute/virtualMachines'); return this.$q.when(response); @@ -133,10 +141,17 @@ describe('AzureMonitorQueryCtrl', () => { const response = [{ text: 'metric1', value: 'metric1' }, { text: 'metric2', value: 'metric2' }]; beforeEach(() => { + queryCtrl.target.subscription = 'sub1'; queryCtrl.target.azureMonitor.resourceGroup = 'test'; queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines'; queryCtrl.target.azureMonitor.resourceName = 'test'; - queryCtrl.datasource.getMetricNames = function(resourceGroup, metricDefinition, resourceName) { + queryCtrl.datasource.getMetricNames = function( + subscriptionId, + resourceGroup, + metricDefinition, + resourceName + ) { + expect(subscriptionId).toBe('sub1'); expect(resourceGroup).toBe('test'); expect(metricDefinition).toBe('Microsoft.Compute/virtualMachines'); expect(resourceName).toBe('test'); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts index 6374af8c5998e..3d896f27a0a1f 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts @@ -2,7 +2,6 @@ import _ from 'lodash'; import { QueryCtrl } from 'app/plugins/sdk'; // import './css/query_editor.css'; import TimegrainConverter from './time_grain_converter'; -// import './monaco/kusto_monaco_editor'; import './editor/editor_component'; export interface ResultFormat { @@ -18,6 +17,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { target: { refId: string; queryType: string; + subscription: string; azureMonitor: { resourceGroup: string; resourceName: string; @@ -100,6 +100,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { showLastQuery: boolean; lastQuery: string; lastQueryError?: string; + subscriptions: Array<{ text: string; value: string }>; /** @ngInject */ constructor($scope, $injector, private templateSrv) { @@ -112,6 +113,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope); this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope); this.resultFormats = [{ text: 'Time series', value: 'time_series' }, { text: 'Table', value: 'table' }]; + this.getSubscriptions(); if (this.target.queryType === 'Azure Log Analytics') { this.getWorkspaces(); } @@ -122,8 +124,8 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { this.lastQuery = ''; const anySeriesFromQuery: any = _.find(dataList, { refId: this.target.refId }); - if (anySeriesFromQuery) { - this.lastQuery = anySeriesFromQuery.query; + if (anySeriesFromQuery && anySeriesFromQuery.meta) { + this.lastQuery = anySeriesFromQuery.meta.query; } } @@ -179,13 +181,42 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { } } + getSubscriptions() { + if (!this.datasource.azureMonitorDatasource.isConfigured()) { + return; + } + + return this.datasource.azureMonitorDatasource.getSubscriptions().then(subs => { + this.subscriptions = subs; + if (!this.target.subscription && this.target.queryType === 'Azure Monitor') { + this.target.subscription = this.datasource.azureMonitorDatasource.subscriptionId; + } else if (!this.target.subscription && this.target.queryType === 'Azure Log Analytics') { + this.target.subscription = this.datasource.azureLogAnalyticsDatasource.logAnalyticsSubscriptionId; + } + + if (!this.target.subscription && this.subscriptions.length > 0) { + this.target.subscription = this.subscriptions[0].value; + } + }); + } + + onSubscriptionChange() { + if (this.target.queryType === 'Azure Log Analytics') { + return this.getWorkspaces(); + } + } + /* Azure Monitor Section */ getResourceGroups(query) { if (this.target.queryType !== 'Azure Monitor' || !this.datasource.azureMonitorDatasource.isConfigured()) { return; } - return this.datasource.getResourceGroups().catch(this.handleQueryCtrlError.bind(this)); + return this.datasource + .getResourceGroups( + this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId) + ) + .catch(this.handleQueryCtrlError.bind(this)); } getMetricDefinitions(query) { @@ -197,7 +228,10 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { return; } return this.datasource - .getMetricDefinitions(this.replace(this.target.azureMonitor.resourceGroup)) + .getMetricDefinitions( + this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId), + this.replace(this.target.azureMonitor.resourceGroup) + ) .catch(this.handleQueryCtrlError.bind(this)); } @@ -214,6 +248,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { return this.datasource .getResourceNames( + this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId), this.replace(this.target.azureMonitor.resourceGroup), this.replace(this.target.azureMonitor.metricDefinition) ) @@ -235,6 +270,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { return this.datasource .getMetricNames( + this.replace(this.target.subscription || this.datasource.azureMonitorDatasource.subscriptionId), this.replace(this.target.azureMonitor.resourceGroup), this.replace(this.target.azureMonitor.metricDefinition), this.replace(this.target.azureMonitor.resourceName) @@ -306,7 +342,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { getWorkspaces = () => { return this.datasource.azureLogAnalyticsDatasource - .getWorkspaces() + .getWorkspaces(this.target.subscription) .then(list => { this.workspaces = list; if (list.length > 0 && !this.target.azureLogAnalytics.workspace) { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/types.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/types.ts new file mode 100644 index 0000000000000..81d4fc501237a --- /dev/null +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/types.ts @@ -0,0 +1,99 @@ +import { DataQuery, DataSourceJsonData } from '@grafana/ui/src/types'; + +export interface AzureMonitorQuery extends DataQuery { + format: string; + subscription: string; + azureMonitor: AzureMetricQuery; + azureLogAnalytics: AzureLogsQuery; + // appInsights: any; +} + +export interface AzureDataSourceJsonData extends DataSourceJsonData { + subscriptionId: string; + cloudName: string; + + // monitor + tenantId?: string; + clientId?: string; + + // logs + logAnalyticsSubscriptionId?: string; + logAnalyticsTenantId?: string; + logAnalyticsClientId?: string; + azureLogAnalyticsSameAs?: string; + logAnalyticsDefaultWorkspace?: string; + + // App Insights + appInsightsAppId?: string; +} + +export interface AzureMetricQuery { + resourceGroup: string; + resourceName: string; + metricDefinition: string; + metricName: string; + timeGrainUnit: string; + timeGrain: string; + timeGrains: string[]; + aggregation: string; + dimension: string; + dimensionFilter: string; + alias: string; +} + +export interface AzureLogsQuery { + query: string; + resultFormat: string; + workspace: string; +} + +// Azure Log Analytics types +export interface KustoSchema { + Databases: { [key: string]: KustoDatabase }; + Plugins: any[]; +} +export interface KustoDatabase { + Name: string; + Tables: { [key: string]: KustoTable }; + Functions: { [key: string]: KustoFunction }; +} + +export interface KustoTable { + Name: string; + OrderedColumns: KustoColumn[]; +} + +export interface KustoColumn { + Name: string; + Type: string; +} + +export interface KustoFunction { + Name: string; + DocString: string; + Body: string; + Folder: string; + FunctionKind: string; + InputParameters: any[]; + OutputColumns: any[]; +} + +export interface AzureLogsVariable { + text: string; + value: string; +} + +export interface AzureLogsTableData { + columns: AzureLogsTableColumn[]; + rows: any[]; + type: string; + refId: string; + meta: { + query: string; + }; +} + +export interface AzureLogsTableColumn { + text: string; + type: string; +} diff --git a/public/app/plugins/datasource/grafana-live/_plugin.json b/public/app/plugins/datasource/grafana-live/_plugin.json deleted file mode 100644 index 1f2ec20494912..0000000000000 --- a/public/app/plugins/datasource/grafana-live/_plugin.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "datasource", - "name": "Grafana Live", - "id": "grafana-live", - - "metrics": true -} diff --git a/public/app/plugins/datasource/grafana-live/datasource.ts b/public/app/plugins/datasource/grafana-live/datasource.ts deleted file mode 100644 index d861400b2c8da..0000000000000 --- a/public/app/plugins/datasource/grafana-live/datasource.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { liveSrv } from 'app/core/core'; - -class DataObservable { - target: any; - - constructor(target) { - this.target = target; - } - - subscribe(options) { - const observable = liveSrv.subscribe(this.target.stream); - return observable.subscribe(data => { - console.log('grafana stream ds data!', data); - }); - } -} - -export class GrafanaStreamDS { - subscription: any; - - /** @ngInject */ - constructor() {} - - query(options): any { - if (options.targets.length === 0) { - return Promise.resolve({ data: [] }); - } - - const target = options.targets[0]; - const observable = new DataObservable(target); - - return Promise.resolve(observable); - } -} diff --git a/public/app/plugins/datasource/grafana-live/module.ts b/public/app/plugins/datasource/grafana-live/module.ts deleted file mode 100644 index d75680a494b4b..0000000000000 --- a/public/app/plugins/datasource/grafana-live/module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { GrafanaStreamDS } from './datasource'; -import { QueryCtrl } from 'app/plugins/sdk'; - -class GrafanaQueryCtrl extends QueryCtrl { - static templateUrl = 'partials/query.editor.html'; -} - -export { GrafanaStreamDS as Datasource, GrafanaQueryCtrl as QueryCtrl }; diff --git a/public/app/plugins/datasource/grafana-live/partials/query.editor.html b/public/app/plugins/datasource/grafana-live/partials/query.editor.html deleted file mode 100644 index 512263ee9ba1c..0000000000000 --- a/public/app/plugins/datasource/grafana-live/partials/query.editor.html +++ /dev/null @@ -1,8 +0,0 @@ - -
    -
    - - -
    -
    -
    diff --git a/public/app/plugins/datasource/grafana/specs/datasource.test.ts b/public/app/plugins/datasource/grafana/specs/datasource.test.ts index 3c0205640e214..4c189ac4caa98 100644 --- a/public/app/plugins/datasource/grafana/specs/datasource.test.ts +++ b/public/app/plugins/datasource/grafana/specs/datasource.test.ts @@ -1,6 +1,6 @@ import { GrafanaDatasource } from '../datasource'; import q from 'q'; -import moment from 'moment'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; describe('grafana data source', () => { describe('when executing an annotations query', () => { @@ -70,8 +70,8 @@ function setupAnnotationQueryOptions(annotation, dashboard?) { annotation: annotation, dashboard: dashboard, range: { - from: moment(1432288354), - to: moment(1432288401), + from: dateTime(1432288354), + to: dateTime(1432288401), }, rangeRaw: { from: 'now-24h', to: 'now' }, }; diff --git a/public/app/plugins/datasource/graphite/plugin.json b/public/app/plugins/datasource/graphite/plugin.json index a28ef8249a193..f66c7fb202f6e 100644 --- a/public/app/plugins/datasource/graphite/plugin.json +++ b/public/app/plugins/datasource/graphite/plugin.json @@ -2,6 +2,7 @@ "name": "Graphite", "type": "datasource", "id": "graphite", + "category": "tsdb", "includes": [{ "type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json" }], @@ -16,7 +17,7 @@ }, "info": { - "description": "Graphite Data Source for Grafana", + "description": "Open source time series database", "author": { "name": "Grafana Project", "url": "https://grafana.com" diff --git a/public/app/plugins/datasource/graphite/specs/datasource.test.ts b/public/app/plugins/datasource/graphite/specs/datasource.test.ts index cd60a05912392..de98f55172bd3 100644 --- a/public/app/plugins/datasource/graphite/specs/datasource.test.ts +++ b/public/app/plugins/datasource/graphite/specs/datasource.test.ts @@ -1,8 +1,8 @@ import { GraphiteDatasource } from '../datasource'; -import moment from 'moment'; import _ from 'lodash'; import $q from 'q'; import { TemplateSrvStub } from 'test/specs/helpers'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; describe('graphiteDatasource', () => { const ctx: any = { @@ -86,8 +86,8 @@ describe('graphiteDatasource', () => { tags: 'tag1', }, range: { - from: moment(1432288354), - to: moment(1432288401), + from: dateTime(1432288354), + to: dateTime(1432288401), }, rangeRaw: { from: 'now-24h', to: 'now' }, }; diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 40ba26c0b59b9..21e82f56155e6 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -34,7 +34,7 @@ export default class InfluxDatasource { this.withCredentials = instanceSettings.withCredentials; this.interval = (instanceSettings.jsonData || {}).timeInterval; this.responseParser = new ResponseParser(); - this.httpMode = instanceSettings.jsonData.httpMode; + this.httpMode = instanceSettings.jsonData.httpMode || 'GET'; } query(options) { diff --git a/public/app/plugins/datasource/influxdb/plugin.json b/public/app/plugins/datasource/influxdb/plugin.json index c666a2085eaec..fa660ee123295 100644 --- a/public/app/plugins/datasource/influxdb/plugin.json +++ b/public/app/plugins/datasource/influxdb/plugin.json @@ -2,6 +2,7 @@ "type": "datasource", "name": "InfluxDB", "id": "influxdb", + "category": "tsdb", "defaultMatchFormat": "regex values", "metrics": true, @@ -14,7 +15,7 @@ }, "info": { - "description": "InfluxDB Data Source for Grafana", + "description": "Open source time series database", "author": { "name": "Grafana Project", "url": "https://grafana.com" diff --git a/public/app/plugins/datasource/input/InputDatasource.ts b/public/app/plugins/datasource/input/InputDatasource.ts index 72b6190a67080..f208260aa2ae1 100644 --- a/public/app/plugins/datasource/input/InputDatasource.ts +++ b/public/app/plugins/datasource/input/InputDatasource.ts @@ -8,23 +8,13 @@ import { } from '@grafana/ui/src/types'; import { InputQuery, InputOptions } from './types'; -export class InputDatasource implements DataSourceApi { +export class InputDatasource extends DataSourceApi { data: SeriesData[]; - // Filled in by grafana plugin system - name?: string; - - // Filled in by grafana plugin system - id?: number; - constructor(instanceSettings: DataSourceInstanceSettings) { - if (instanceSettings.jsonData) { - this.data = instanceSettings.jsonData.data; - } + super(instanceSettings); - if (!this.data) { - this.data = []; - } + this.data = instanceSettings.jsonData.data ? instanceSettings.jsonData.data : []; } getDescription(data: SeriesData[]): string { diff --git a/public/app/plugins/datasource/input/InputQueryEditor.tsx b/public/app/plugins/datasource/input/InputQueryEditor.tsx index 1bbfb76f50b73..68e1cb5516eb3 100644 --- a/public/app/plugins/datasource/input/InputQueryEditor.tsx +++ b/public/app/plugins/datasource/input/InputQueryEditor.tsx @@ -3,11 +3,11 @@ import React, { PureComponent } from 'react'; // Types import { InputDatasource } from './InputDatasource'; -import { InputQuery } from './types'; +import { InputQuery, InputOptions } from './types'; import { FormLabel, Select, QueryEditorProps, SelectOptionItem, SeriesData, TableInputCSV, toCSV } from '@grafana/ui'; -type Props = QueryEditorProps; +type Props = QueryEditorProps; const options = [ { value: 'panel', label: 'Panel', description: 'Save data in the panel configuration.' }, diff --git a/public/app/plugins/datasource/input/module.ts b/public/app/plugins/datasource/input/module.ts index 05a48c965ca97..ef41af9ce0c09 100644 --- a/public/app/plugins/datasource/input/module.ts +++ b/public/app/plugins/datasource/input/module.ts @@ -6,6 +6,6 @@ import { InputQueryEditor } from './InputQueryEditor'; import { InputConfigEditor } from './InputConfigEditor'; import { InputOptions, InputQuery } from './types'; -export const plugin = new DataSourcePlugin(InputDatasource) +export const plugin = new DataSourcePlugin(InputDatasource) .setConfigEditor(InputConfigEditor) .setQueryEditor(InputQueryEditor); diff --git a/public/app/plugins/datasource/input/plugin.json b/public/app/plugins/datasource/input/plugin.json index 21e251915e0cc..91782a3480651 100644 --- a/public/app/plugins/datasource/input/plugin.json +++ b/public/app/plugins/datasource/input/plugin.json @@ -11,7 +11,7 @@ "explore": false, "info": { - "description": "User Input Data Source for Grafana", + "description": "Data source that supports manual table & CSV input", "author": { "name": "Grafana Project", "url": "https://grafana.com" diff --git a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx index a7b865cde3f35..35df8959e9df0 100644 --- a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx +++ b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx @@ -31,7 +31,7 @@ export default (props: any) => ( {item.expression && (
    props.onClickExample({ refId: '1', expr: item.expression })} + onClick={e => props.onClickExample({ refId: 'A', expr: item.expression })} > {item.expression}
    diff --git a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx index 6662e385e4a2b..76e94904c1f31 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx @@ -66,7 +66,7 @@ export interface CascaderOption { disabled?: boolean; } -export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps { +export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps, LokiQuery> { history: HistoryItem[]; syntax: any; logLabelOptions: any[]; @@ -86,14 +86,14 @@ export class LokiQueryFieldForm extends React.PureComponent node.type === 'code_block', getSyntax: (node: any) => 'promql', }), ]; - this.pluginsSearch = [RunnerPlugin({ handler: props.onExecuteQuery })]; + this.pluginsSearch = [RunnerPlugin({ handler: props.onRunQuery })]; } loadOptions = (selectedOptions: CascaderOption[]) => { @@ -111,24 +111,17 @@ export class LokiQueryFieldForm extends React.PureComponent { // Send text change to parent - const { query, onQueryChange, onExecuteQuery } = this.props; - if (onQueryChange) { + const { query, onChange, onRunQuery } = this.props; + if (onChange) { const nextQuery = { ...query, expr: value }; - onQueryChange(nextQuery); + onChange(nextQuery); - if (override && onExecuteQuery) { - onExecuteQuery(); + if (override && onRunQuery) { + onRunQuery(); } } }; - onClickHintFix = () => { - const { hint, onExecuteHint } = this.props; - if (onExecuteHint && hint && hint.fix) { - onExecuteHint(hint.fix.action); - } - }; - onTypeahead = (typeahead: TypeaheadInput): TypeaheadOutput => { const { datasource } = this.props; if (!datasource.languageProvider) { @@ -156,8 +149,7 @@ export class LokiQueryFieldForm extends React.PureComponent
    - {error ?
    {error}
    : null} - {hint ? ( -
    - {hint.label}{' '} - {hint.fix ? ( - - {hint.fix.label} - - ) : null} -
    + {queryResponse && queryResponse.error ? ( +
    {queryResponse.error.message}
    ) : null}
    diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index a8c3ce27500cd..8b970f05f63ea 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -2,6 +2,8 @@ import LokiDatasource from './datasource'; import { LokiQuery } from './types'; import { getQueryOptions } from 'test/helpers/getQueryOptions'; import { SeriesData } from '@grafana/ui'; +import { BackendSrv } from 'app/core/services/backend_srv'; +import { TemplateSrv } from 'app/features/templating/template_srv'; describe('LokiDatasource', () => { const instanceSettings: any = { @@ -21,14 +23,15 @@ describe('LokiDatasource', () => { describe('when querying', () => { const backendSrvMock = { datasourceRequest: jest.fn() }; + const backendSrv = (backendSrvMock as unknown) as BackendSrv; - const templateSrvMock = { + const templateSrvMock = ({ getAdhocFilters: () => [], replace: a => a, - }; + } as unknown) as TemplateSrv; test('should use default max lines when no limit given', () => { - const ds = new LokiDatasource(instanceSettings, backendSrvMock, templateSrvMock); + const ds = new LokiDatasource(instanceSettings, backendSrv, templateSrvMock); backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp)); const options = getQueryOptions({ targets: [{ expr: 'foo', refId: 'B' }] }); @@ -41,7 +44,7 @@ describe('LokiDatasource', () => { test('should use custom max lines if limit is set', () => { const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 }; const customSettings = { ...instanceSettings, jsonData: customData }; - const ds = new LokiDatasource(customSettings, backendSrvMock, templateSrvMock); + const ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock); backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp)); const options = getQueryOptions({ targets: [{ expr: 'foo', refId: 'B' }] }); @@ -54,7 +57,7 @@ describe('LokiDatasource', () => { test('should return series data', async done => { const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 }; const customSettings = { ...instanceSettings, jsonData: customData }; - const ds = new LokiDatasource(customSettings, backendSrvMock, templateSrvMock); + const ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock); backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp)); const options = getQueryOptions({ @@ -77,7 +80,7 @@ describe('LokiDatasource', () => { describe('and call succeeds', () => { beforeEach(async () => { - const backendSrv = { + const backendSrv = ({ async datasourceRequest() { return Promise.resolve({ status: 200, @@ -86,8 +89,8 @@ describe('LokiDatasource', () => { }, }); }, - }; - ds = new LokiDatasource(instanceSettings, backendSrv, {}); + } as unknown) as BackendSrv; + ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv); result = await ds.testDatasource(); }); @@ -98,7 +101,7 @@ describe('LokiDatasource', () => { describe('and call fails with 401 error', () => { beforeEach(async () => { - const backendSrv = { + const backendSrv = ({ async datasourceRequest() { return Promise.reject({ statusText: 'Unauthorized', @@ -108,8 +111,8 @@ describe('LokiDatasource', () => { }, }); }, - }; - ds = new LokiDatasource(instanceSettings, backendSrv, {}); + } as unknown) as BackendSrv; + ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv); result = await ds.testDatasource(); }); @@ -121,7 +124,7 @@ describe('LokiDatasource', () => { describe('and call fails with 404 error', () => { beforeEach(async () => { - const backendSrv = { + const backendSrv = ({ async datasourceRequest() { return Promise.reject({ statusText: 'Not found', @@ -129,8 +132,8 @@ describe('LokiDatasource', () => { data: '404 page not found', }); }, - }; - ds = new LokiDatasource(instanceSettings, backendSrv, {}); + } as unknown) as BackendSrv; + ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv); result = await ds.testDatasource(); }); @@ -142,7 +145,7 @@ describe('LokiDatasource', () => { describe('and call fails with 502 error', () => { beforeEach(async () => { - const backendSrv = { + const backendSrv = ({ async datasourceRequest() { return Promise.reject({ statusText: 'Bad Gateway', @@ -150,8 +153,8 @@ describe('LokiDatasource', () => { data: '', }); }, - }; - ds = new LokiDatasource(instanceSettings, backendSrv, {}); + } as unknown) as BackendSrv; + ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv); result = await ds.testDatasource(); }); diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 48e085682e99d..3e7e5f3b39689 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -9,8 +9,18 @@ import { logStreamToSeriesData } from './result_transformer'; import { formatQuery, parseQuery } from './query_utils'; // Types -import { PluginMeta, DataQueryRequest, SeriesData } from '@grafana/ui/src/types'; -import { LokiQuery } from './types'; +import { + PluginMeta, + DataQueryRequest, + SeriesData, + DataSourceApi, + DataSourceInstanceSettings, + DataQueryError, +} from '@grafana/ui/src/types'; +import { LokiQuery, LokiOptions } from './types'; +import { BackendSrv } from 'app/core/services/backend_srv'; +import { TemplateSrv } from 'app/features/templating/template_srv'; +import { safeStringifyValue } from 'app/core/utils/explore'; export const DEFAULT_MAX_LINES = 1000; @@ -30,12 +40,17 @@ function serializeParams(data: any) { .join('&'); } -export class LokiDatasource { +export class LokiDatasource extends DataSourceApi { languageProvider: LanguageProvider; maxLines: number; /** @ngInject */ - constructor(private instanceSettings, private backendSrv, private templateSrv) { + constructor( + private instanceSettings: DataSourceInstanceSettings, + private backendSrv: BackendSrv, + private templateSrv: TemplateSrv + ) { + super(instanceSettings); this.languageProvider = new LanguageProvider(this); const settingsData = instanceSettings.jsonData || {}; this.maxLines = parseInt(settingsData.maxLines, 10) || DEFAULT_MAX_LINES; @@ -52,16 +67,18 @@ export class LokiDatasource { return this.backendSrv.datasourceRequest(req); } - prepareQueryTarget(target, options) { + prepareQueryTarget(target: LokiQuery, options: DataQueryRequest) { const interpolated = this.templateSrv.replace(target.expr); const start = this.getTime(options.range.from, false); const end = this.getTime(options.range.to, true); + const refId = target.refId; return { ...DEFAULT_QUERY_PARAMS, ...parseQuery(interpolated), start, end, limit: this.maxLines, + refId, }; } @@ -74,16 +91,47 @@ export class LokiDatasource { return Promise.resolve({ data: [] }); } - const queries = queryTargets.map(target => this._request('/api/prom/query', target)); + const queries = queryTargets.map(target => + this._request('/api/prom/query', target).catch((err: any) => { + if (err.cancelled) { + return err; + } + + const error: DataQueryError = { + message: 'Unknown error during query transaction. Please check JS console logs.', + refId: target.refId, + }; + + if (err.data) { + if (typeof err.data === 'string') { + error.message = err.data; + } else if (err.data.error) { + error.message = safeStringifyValue(err.data.error); + } + } else if (err.message) { + error.message = err.message; + } else if (typeof err === 'string') { + error.message = err; + } + + error.status = err.status; + error.statusText = err.statusText; + + throw error; + }) + ); return Promise.all(queries).then((results: any[]) => { - const series: SeriesData[] = []; + const series: Array = []; for (let i = 0; i < results.length; i++) { const result = results[i]; + if (result.data) { + const refId = queryTargets[i].refId; for (const stream of result.data.streams || []) { const seriesData = logStreamToSeriesData(stream); + seriesData.refId = refId; seriesData.meta = { search: queryTargets[i].regexp, limit: this.maxLines, diff --git a/public/app/plugins/datasource/loki/language_provider.ts b/public/app/plugins/datasource/loki/language_provider.ts index c137deace1cb9..64bf876f2c770 100644 --- a/public/app/plugins/datasource/loki/language_provider.ts +++ b/public/app/plugins/datasource/loki/language_provider.ts @@ -1,6 +1,5 @@ // Libraries import _ from 'lodash'; -import moment from 'moment'; // Services & Utils import { parseSelector, labelRegexp, selectorRegexp } from 'app/plugins/datasource/prometheus/language_utils'; @@ -16,6 +15,7 @@ import { HistoryItem, } from 'app/types/explore'; import { LokiQuery } from './types'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; const DEFAULT_KEYS = ['job', 'namespace']; const EMPTY_SELECTOR = '{}'; @@ -34,7 +34,7 @@ export function addHistoryMetadata(item: CompletionItem, history: LokiHistoryIte const recent = historyForItem[0]; let hint = `Queried ${count} times in the last 24h.`; if (recent) { - const lastQueried = moment(recent.ts).fromNow(); + const lastQueried = dateTime(recent.ts).fromNow(); hint = `${hint} Last queried ${lastQueried}.`; } return { diff --git a/public/app/plugins/datasource/loki/plugin.json b/public/app/plugins/datasource/loki/plugin.json index 94ea089d93836..66e0f1027e9e9 100644 --- a/public/app/plugins/datasource/loki/plugin.json +++ b/public/app/plugins/datasource/loki/plugin.json @@ -2,6 +2,7 @@ "type": "datasource", "name": "Loki", "id": "loki", + "category": "logging", "metrics": true, "alerting": false, @@ -11,7 +12,7 @@ "tables": false, "info": { - "description": "Loki Logging Data Source for Grafana", + "description": "Like Prometheus but for logs. OSS logging solution from Grafana Labs", "author": { "name": "Grafana Project", "url": "https://grafana.com" @@ -22,7 +23,11 @@ }, "links": [ { - "name": "Loki", + "name": "Learn more", + "url": "https://grafana.com/loki" + }, + { + "name": "GitHub Project", "url": "https://github.com/grafana/loki" } ], diff --git a/public/app/plugins/datasource/loki/types.ts b/public/app/plugins/datasource/loki/types.ts index 6376f67b6943d..c4cfa3fcdc432 100644 --- a/public/app/plugins/datasource/loki/types.ts +++ b/public/app/plugins/datasource/loki/types.ts @@ -1,9 +1,13 @@ -import { DataQuery, Labels } from '@grafana/ui/src/types'; +import { DataQuery, Labels, DataSourceJsonData } from '@grafana/ui/src/types'; export interface LokiQuery extends DataQuery { expr: string; } +export interface LokiOptions extends DataSourceJsonData { + maxLines?: string; +} + export interface LokiLogsStream { labels: string; entries: LokiLogsStreamEntry[]; diff --git a/public/app/plugins/datasource/mixed/datasource.ts b/public/app/plugins/datasource/mixed/datasource.ts index 4f4b10dbe5b37..5798695e8444e 100644 --- a/public/app/plugins/datasource/mixed/datasource.ts +++ b/public/app/plugins/datasource/mixed/datasource.ts @@ -1,11 +1,13 @@ import _ from 'lodash'; -import { DataSourceApi, DataQuery, DataQueryRequest } from '@grafana/ui'; +import { DataSourceApi, DataQuery, DataQueryRequest, DataSourceInstanceSettings } from '@grafana/ui'; import DatasourceSrv from 'app/features/plugins/datasource_srv'; -class MixedDatasource implements DataSourceApi { +class MixedDatasource extends DataSourceApi { /** @ngInject */ - constructor(private datasourceSrv: DatasourceSrv) {} + constructor(instanceSettings: DataSourceInstanceSettings, private datasourceSrv: DatasourceSrv) { + super(instanceSettings); + } query(options: DataQueryRequest) { const sets = _.groupBy(options.targets, 'datasource'); diff --git a/public/app/plugins/datasource/mssql/plugin.json b/public/app/plugins/datasource/mssql/plugin.json index 80d70daada441..b3269b91100e2 100644 --- a/public/app/plugins/datasource/mssql/plugin.json +++ b/public/app/plugins/datasource/mssql/plugin.json @@ -2,9 +2,10 @@ "type": "datasource", "name": "Microsoft SQL Server", "id": "mssql", + "category": "sql", "info": { - "description": "Microsoft SQL Server Data Source for Grafana", + "description": "Data source for Microsoft SQL Server compatible databases", "author": { "name": "Grafana Project", "url": "https://grafana.com" diff --git a/public/app/plugins/datasource/mssql/specs/datasource.test.ts b/public/app/plugins/datasource/mssql/specs/datasource.test.ts index 49b133873693c..357d4e41ccff8 100644 --- a/public/app/plugins/datasource/mssql/specs/datasource.test.ts +++ b/public/app/plugins/datasource/mssql/specs/datasource.test.ts @@ -1,8 +1,8 @@ -import moment from 'moment'; import { MssqlDatasource } from '../datasource'; import { TemplateSrvStub, TimeSrvStub } from 'test/specs/helpers'; import { CustomVariable } from 'app/features/templating/custom_variable'; import q from 'q'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; describe('MSSQLDatasource', () => { const ctx: any = { @@ -29,8 +29,8 @@ describe('MSSQLDatasource', () => { rawQuery: 'select time, text, tags from table;', }, range: { - from: moment(1432288354), - to: moment(1432288401), + from: dateTime(1432288354), + to: dateTime(1432288401), }, }; @@ -209,8 +209,8 @@ describe('MSSQLDatasource', () => { }, }; const time = { - from: moment(1521545610656), - to: moment(1521546251185), + from: dateTime(1521545610656), + to: dateTime(1521546251185), }; beforeEach(() => { diff --git a/public/app/plugins/datasource/mysql/plugin.json b/public/app/plugins/datasource/mysql/plugin.json index cbb22130c13ec..49d1996332fa6 100644 --- a/public/app/plugins/datasource/mysql/plugin.json +++ b/public/app/plugins/datasource/mysql/plugin.json @@ -2,9 +2,10 @@ "type": "datasource", "name": "MySQL", "id": "mysql", + "category": "sql", "info": { - "description": "MySQL Data Source for Grafana", + "description": "Data source for MySQL databases", "author": { "name": "Grafana Project", "url": "https://grafana.com" diff --git a/public/app/plugins/datasource/mysql/specs/datasource.test.ts b/public/app/plugins/datasource/mysql/specs/datasource.test.ts index f3fbcd933332e..9b3249c87e1c0 100644 --- a/public/app/plugins/datasource/mysql/specs/datasource.test.ts +++ b/public/app/plugins/datasource/mysql/specs/datasource.test.ts @@ -1,6 +1,6 @@ -import moment from 'moment'; import { MysqlDatasource } from '../datasource'; import { CustomVariable } from 'app/features/templating/custom_variable'; +import { toUtc, dateTime } from '@grafana/ui/src/utils/moment_wrapper'; describe('MySQLDatasource', () => { const instanceSettings = { name: 'mysql' }; @@ -10,8 +10,8 @@ describe('MySQLDatasource', () => { }; const raw = { - from: moment.utc('2018-04-25 10:00'), - to: moment.utc('2018-04-25 11:00'), + from: toUtc('2018-04-25 10:00'), + to: toUtc('2018-04-25 11:00'), }; const ctx = { backendSrv, @@ -39,8 +39,8 @@ describe('MySQLDatasource', () => { rawQuery: 'select time_sec, text, tags from table;', }, range: { - from: moment(1432288354), - to: moment(1432288401), + from: dateTime(1432288354), + to: dateTime(1432288401), }, }; diff --git a/public/app/plugins/datasource/opentsdb/plugin.json b/public/app/plugins/datasource/opentsdb/plugin.json index 4dbd677af844b..e7cae327c5b51 100644 --- a/public/app/plugins/datasource/opentsdb/plugin.json +++ b/public/app/plugins/datasource/opentsdb/plugin.json @@ -2,6 +2,7 @@ "type": "datasource", "name": "OpenTSDB", "id": "opentsdb", + "category": "tsdb", "metrics": true, "defaultMatchFormat": "pipe", @@ -10,7 +11,7 @@ "tables": false, "info": { - "description": "OpenTSDB Data Source for Grafana", + "description": "Open source time series database", "author": { "name": "Grafana Project", "url": "https://grafana.com" diff --git a/public/app/plugins/datasource/postgres/plugin.json b/public/app/plugins/datasource/postgres/plugin.json index bce485264ea8e..994578a7f2c8d 100644 --- a/public/app/plugins/datasource/postgres/plugin.json +++ b/public/app/plugins/datasource/postgres/plugin.json @@ -2,9 +2,10 @@ "type": "datasource", "name": "PostgreSQL", "id": "postgres", + "category": "sql", "info": { - "description": "PostgreSQL Data Source for Grafana", + "description": "Data source for PostgreSQL and compatible databases", "author": { "name": "Grafana Project", "url": "https://grafana.com" diff --git a/public/app/plugins/datasource/postgres/specs/datasource.test.ts b/public/app/plugins/datasource/postgres/specs/datasource.test.ts index 49541028cafac..7c55b8768233d 100644 --- a/public/app/plugins/datasource/postgres/specs/datasource.test.ts +++ b/public/app/plugins/datasource/postgres/specs/datasource.test.ts @@ -1,6 +1,6 @@ -import moment from 'moment'; import { PostgresDatasource } from '../datasource'; import { CustomVariable } from 'app/features/templating/custom_variable'; +import { toUtc, dateTime } from '@grafana/ui/src/utils/moment_wrapper'; describe('PostgreSQLDatasource', () => { const instanceSettings = { name: 'postgresql' }; @@ -10,8 +10,8 @@ describe('PostgreSQLDatasource', () => { replace: jest.fn(text => text), }; const raw = { - from: moment.utc('2018-04-25 10:00'), - to: moment.utc('2018-04-25 11:00'), + from: toUtc('2018-04-25 10:00'), + to: toUtc('2018-04-25 11:00'), }; const ctx = { backendSrv, @@ -39,8 +39,8 @@ describe('PostgreSQLDatasource', () => { rawQuery: 'select time, title, text, tags from table;', }, range: { - from: moment(1432288354), - to: moment(1432288401), + from: dateTime(1432288354), + to: dateTime(1432288401), }, }; diff --git a/public/app/plugins/datasource/prometheus/add_label_to_query.ts b/public/app/plugins/datasource/prometheus/add_label_to_query.ts index b64a6886b966e..190439492e21c 100644 --- a/public/app/plugins/datasource/prometheus/add_label_to_query.ts +++ b/public/app/plugins/datasource/prometheus/add_label_to_query.ts @@ -30,8 +30,12 @@ export function addLabelToQuery(query: string, key: string, value: string, opera const insideSelector = isPositionInsideChars(query, offset, '{', '}'); // Handle "sum by (key) (metric)" const previousWordIsKeyWord = previousWord && keywords.split('|').indexOf(previousWord) > -1; + + // check for colon as as "word boundary" symbol + const isColonBounded = word.endsWith(':'); + previousWord = word; - if (!insideSelector && !previousWordIsKeyWord && builtInWords.indexOf(word) === -1) { + if (!insideSelector && !isColonBounded && !previousWordIsKeyWord && builtInWords.indexOf(word) === -1) { return `${word}{}`; } return word; diff --git a/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx b/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx index 41606d198c62e..f3b3dcb7f8376 100644 --- a/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx @@ -27,7 +27,7 @@ export default (props: any) => (
    {item.title}
    props.onClickExample({ refId: '1', expr: item.expression })} + onClick={e => props.onClickExample({ refId: 'A', expr: item.expression })} > {item.expression}
    diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx index f34e8a54ecd4c..a63e2ef52b971 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx @@ -16,7 +16,7 @@ import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; import { PromQuery } from '../types'; import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise'; -import { ExploreDataSourceApi, ExploreQueryFieldProps, DataSourceStatus } from '@grafana/ui'; +import { ExploreDataSourceApi, ExploreQueryFieldProps, DataSourceStatus, QueryHint } from '@grafana/ui'; const HISTOGRAM_GROUP = '__histograms__'; const METRIC_MARK = 'metric'; @@ -102,13 +102,14 @@ interface CascaderOption { disabled?: boolean; } -interface PromQueryFieldProps extends ExploreQueryFieldProps { +interface PromQueryFieldProps extends ExploreQueryFieldProps, PromQuery> { history: HistoryItem[]; } interface PromQueryFieldState { metricsOptions: any[]; syntaxLoaded: boolean; + hint: QueryHint; } class PromQueryField extends React.PureComponent { @@ -125,7 +126,7 @@ class PromQueryField extends React.PureComponent node.type === 'code_block', getSyntax: (node: any) => 'promql', @@ -135,6 +136,7 @@ class PromQueryField extends React.PureComponent 0; + if (currentHasSeries && prevProps.queryResponse.series !== this.props.queryResponse.series) { + this.refreshHint(); + } + const reconnected = prevProps.datasourceStatus === DataSourceStatus.Disconnected && this.props.datasourceStatus === DataSourceStatus.Connected; @@ -167,6 +175,17 @@ class PromQueryField extends React.PureComponent { + const { datasource, query, queryResponse } = this.props; + if (queryResponse.series && queryResponse.series.length === 0) { + return; + } + + const hints = datasource.getQueryHints(query, queryResponse.series); + const hint = hints && hints.length > 0 ? hints[0] : null; + this.setState({ hint }); + }; + refreshMetrics = (cancelablePromise: CancelablePromise) => { this.languageProviderInitializationPromise = cancelablePromise; this.languageProviderInitializationPromise.promise @@ -204,21 +223,22 @@ class PromQueryField extends React.PureComponent { // Send text change to parent - const { query, onQueryChange, onExecuteQuery } = this.props; - if (onQueryChange) { + const { query, onChange, onRunQuery } = this.props; + if (onChange) { const nextQuery: PromQuery = { ...query, expr: value }; - onQueryChange(nextQuery); + onChange(nextQuery); - if (override && onExecuteQuery) { - onExecuteQuery(); + if (override && onRunQuery) { + onRunQuery(); } } }; onClickHintFix = () => { - const { hint, onExecuteHint } = this.props; - if (onExecuteHint && hint && hint.fix) { - onExecuteHint(hint.fix.action); + const { hint } = this.state; + const { onHint } = this.props; + if (onHint && hint && hint.fix) { + onHint(hint.fix.action); } }; @@ -273,8 +293,8 @@ class PromQueryField extends React.PureComponent
    - {error ?
    {error}
    : null} + {queryResponse && queryResponse.error ? ( +
    {queryResponse.error.message}
    + ) : null} {hint ? (
    {hint.label}{' '} diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index e445eec96688d..c80c30fc6827f 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -14,14 +14,22 @@ import { getQueryHints } from './query_hints'; import { expandRecordingRules } from './language_utils'; // Types -import { PromQuery } from './types'; -import { DataQueryRequest, DataSourceApi, AnnotationEvent } from '@grafana/ui/src/types'; +import { PromQuery, PromOptions } from './types'; +import { + DataQueryRequest, + DataSourceApi, + AnnotationEvent, + DataSourceInstanceSettings, + DataQueryError, +} from '@grafana/ui/src/types'; import { ExploreUrlState } from 'app/types/explore'; +import { safeStringifyValue } from 'app/core/utils/explore'; +import { TemplateSrv } from 'app/features/templating/template_srv'; +import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; -export class PrometheusDatasource implements DataSourceApi { +export class PrometheusDatasource extends DataSourceApi { type: string; editorSrc: string; - name: string; ruleMappings: { [index: string]: string }; url: string; directUrl: string; @@ -35,25 +43,32 @@ export class PrometheusDatasource implements DataSourceApi { resultTransformer: ResultTransformer; /** @ngInject */ - constructor(instanceSettings, private $q, private backendSrv: BackendSrv, private templateSrv, private timeSrv) { + constructor( + instanceSettings: DataSourceInstanceSettings, + private $q: angular.IQService, + private backendSrv: BackendSrv, + private templateSrv: TemplateSrv, + private timeSrv: TimeSrv + ) { + super(instanceSettings); + this.type = 'prometheus'; this.editorSrc = 'app/features/prometheus/partials/query.editor.html'; - this.name = instanceSettings.name; this.url = instanceSettings.url; - this.directUrl = instanceSettings.directUrl; this.basicAuth = instanceSettings.basicAuth; this.withCredentials = instanceSettings.withCredentials; this.interval = instanceSettings.jsonData.timeInterval || '15s'; this.queryTimeout = instanceSettings.jsonData.queryTimeout; this.httpMethod = instanceSettings.jsonData.httpMethod || 'GET'; + this.directUrl = instanceSettings.jsonData.directUrl; this.resultTransformer = new ResultTransformer(templateSrv); this.ruleMappings = {}; this.languageProvider = new PrometheusLanguageProvider(this); } - init() { + init = () => { this.loadRules(); - } + }; getQueryDisplayText(query: PromQuery) { return query.expr; @@ -126,7 +141,7 @@ export class PrometheusDatasource implements DataSourceApi { return this.templateSrv.variableExists(target.expr); } - query(options: DataQueryRequest) { + query(options: DataQueryRequest): Promise<{ data: any }> { const start = this.getPrometheusTime(options.range.from, false); const end = this.getPrometheusTime(options.range.to, true); @@ -146,7 +161,7 @@ export class PrometheusDatasource implements DataSourceApi { // No valid targets, return the empty result to save a round trip. if (_.isEmpty(queries)) { - return this.$q.when({ data: [] }); + return this.$q.when({ data: [] }) as Promise<{ data: any }>; } const allQueryPromise = _.map(queries, query => { @@ -157,16 +172,12 @@ export class PrometheusDatasource implements DataSourceApi { } }); - return this.$q.all(allQueryPromise).then(responseList => { + const allPromise = this.$q.all(allQueryPromise).then((responseList: any) => { let result = []; _.each(responseList, (response, index) => { - if (response.status === 'error') { - const error = { - index, - ...response.error, - }; - throw error; + if (response.cancelled) { + return; } // Keeping original start/end for transformers @@ -187,6 +198,8 @@ export class PrometheusDatasource implements DataSourceApi { return { data: result }; }); + + return allPromise as Promise<{ data: any }>; } createQuery(target, options, start, end) { @@ -233,6 +246,7 @@ export class PrometheusDatasource implements DataSourceApi { // Only replace vars in expression after having (possibly) updated interval vars query.expr = this.templateSrv.replace(expr, scopedVars, this.interpolateQueryExpr); query.requestId = options.panelId + target.refId; + query.refId = target.refId; // Align query interval with step to allow query caching and to ensure // that about-same-time query results look the same. @@ -268,7 +282,9 @@ export class PrometheusDatasource implements DataSourceApi { if (this.queryTimeout) { data['timeout'] = this.queryTimeout; } - return this._request(url, data, { requestId: query.requestId, headers: query.headers }); + return this._request(url, data, { requestId: query.requestId, headers: query.headers }).catch((err: any) => + this.handleErrors(err, query) + ); } performInstantQuery(query, time) { @@ -280,9 +296,39 @@ export class PrometheusDatasource implements DataSourceApi { if (this.queryTimeout) { data['timeout'] = this.queryTimeout; } - return this._request(url, data, { requestId: query.requestId, headers: query.headers }); + return this._request(url, data, { requestId: query.requestId, headers: query.headers }).catch((err: any) => + this.handleErrors(err, query) + ); } + handleErrors = (err: any, target: PromQuery) => { + if (err.cancelled) { + return err; + } + + const error: DataQueryError = { + message: 'Unknown error during query transaction. Please check JS console logs.', + refId: target.refId, + }; + + if (err.data) { + if (typeof err.data === 'string') { + error.message = err.data; + } else if (err.data.error) { + error.message = safeStringifyValue(err.data.error); + } + } else if (err.message) { + error.message = err.message; + } else if (typeof err === 'string') { + error.message = err; + } + + error.status = err.status; + error.statusText = err.statusText; + + throw error; + }; + performSuggestQuery(query, cache = false) { const url = '/api/v1/label/__name__/values'; diff --git a/public/app/plugins/datasource/prometheus/language_provider.ts b/public/app/plugins/datasource/prometheus/language_provider.ts index 9acc7a33f2141..b281fe0333681 100644 --- a/public/app/plugins/datasource/prometheus/language_provider.ts +++ b/public/app/plugins/datasource/prometheus/language_provider.ts @@ -1,5 +1,4 @@ import _ from 'lodash'; -import moment from 'moment'; import { CompletionItem, @@ -11,6 +10,7 @@ import { import { parseSelector, processLabels, processHistogramLabels } from './language_utils'; import PromqlSyntax, { FUNCTIONS, RATE_RANGES } from './promql'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; const DEFAULT_KEYS = ['job', 'instance']; const EMPTY_SELECTOR = '{}'; @@ -31,7 +31,7 @@ export function addHistoryMetadata(item: CompletionItem, history: any[]): Comple const recent = historyForItem[0]; let hint = `Queried ${count} times in the last 24h.`; if (recent) { - const lastQueried = moment(recent.ts).fromNow(); + const lastQueried = dateTime(recent.ts).fromNow(); hint = `${hint} Last queried ${lastQueried}.`; } return { diff --git a/public/app/plugins/datasource/prometheus/plugin.json b/public/app/plugins/datasource/prometheus/plugin.json index 6d2b526a7c78a..fb9ebbb52b10d 100644 --- a/public/app/plugins/datasource/prometheus/plugin.json +++ b/public/app/plugins/datasource/prometheus/plugin.json @@ -2,6 +2,8 @@ "type": "datasource", "name": "Prometheus", "id": "prometheus", + "category": "tsdb", + "includes": [ { "type": "dashboard", @@ -28,7 +30,7 @@ "minInterval": true }, "info": { - "description": "Prometheus Data Source for Grafana", + "description": "Open source time series database & alerting", "author": { "name": "Grafana Project", "url": "https://grafana.com" diff --git a/public/app/plugins/datasource/prometheus/specs/add_label_to_query.test.ts b/public/app/plugins/datasource/prometheus/specs/add_label_to_query.test.ts index df26da0333d7e..9c3dcc3db94e9 100644 --- a/public/app/plugins/datasource/prometheus/specs/add_label_to_query.test.ts +++ b/public/app/plugins/datasource/prometheus/specs/add_label_to_query.test.ts @@ -29,6 +29,9 @@ describe('addLabelToQuery()', () => { 'foo{bar="baz",instance="my-host.com:9100"}' ); expect(addLabelToQuery('foo:metric:rate1m', 'bar', 'baz')).toBe('foo:metric:rate1m{bar="baz"}'); + expect(addLabelToQuery('avg(foo:metric:rate1m{a="b"})', 'bar', 'baz')).toBe( + 'avg(foo:metric:rate1m{a="b",bar="baz"})' + ); expect(addLabelToQuery('foo{list="a,b,c"}', 'bar', 'baz')).toBe('foo{bar="baz",list="a,b,c"}'); }); diff --git a/public/app/plugins/datasource/prometheus/specs/completer.test.ts b/public/app/plugins/datasource/prometheus/specs/completer.test.ts index 7d2e7de8648e9..2580b87f6d7f6 100644 --- a/public/app/plugins/datasource/prometheus/specs/completer.test.ts +++ b/public/app/plugins/datasource/prometheus/specs/completer.test.ts @@ -1,6 +1,11 @@ import { PromCompleter } from '../completer'; import { PrometheusDatasource } from '../datasource'; import { BackendSrv } from 'app/core/services/backend_srv'; +import { DataSourceInstanceSettings } from '@grafana/ui'; +import { PromOptions } from '../types'; +import { TemplateSrv } from 'app/features/templating/template_srv'; +import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { IQService } from 'angular'; jest.mock('../datasource'); jest.mock('app/core/services/backend_srv'); @@ -16,7 +21,13 @@ describe('Prometheus editor completer', () => { const editor = {}; const backendSrv = {} as BackendSrv; - const datasourceStub = new PrometheusDatasource({}, {}, backendSrv, {}, {}); + const datasourceStub = new PrometheusDatasource( + {} as DataSourceInstanceSettings, + {} as IQService, + backendSrv, + {} as TemplateSrv, + {} as TimeSrv + ); datasourceStub.metadataRequest = jest.fn(() => Promise.resolve({ data: { data: [{ metric: { job: 'node', instance: 'localhost:9100' } }] } }) diff --git a/public/app/plugins/datasource/prometheus/specs/datasource.test.ts b/public/app/plugins/datasource/prometheus/specs/datasource.test.ts index 05808464115c6..275671e0283d9 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource.test.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource.test.ts @@ -1,5 +1,4 @@ import _ from 'lodash'; -import moment from 'moment'; import q from 'q'; import { alignRange, @@ -8,6 +7,11 @@ import { prometheusRegularEscape, prometheusSpecialRegexEscape, } from '../datasource'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; +import { DataSourceInstanceSettings } from '@grafana/ui'; +import { PromOptions } from '../types'; +import { TemplateSrv } from 'app/features/templating/template_srv'; +import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; jest.mock('../metric_find_query'); @@ -18,13 +22,13 @@ const DEFAULT_TEMPLATE_SRV_MOCK = { describe('PrometheusDatasource', () => { const ctx: any = {}; - const instanceSettings = { + const instanceSettings = ({ url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp', jsonData: {} as any, - }; + } as unknown) as DataSourceInstanceSettings; ctx.backendSrvMock = {}; @@ -33,8 +37,8 @@ describe('PrometheusDatasource', () => { ctx.timeSrvMock = { timeRange: () => { return { - from: moment(1531468681), - to: moment(1531489712), + from: dateTime(1531468681), + to: dateTime(1531489712), }; }, }; @@ -135,7 +139,7 @@ describe('PrometheusDatasource', () => { describe('When converting prometheus histogram to heatmap format', () => { beforeEach(() => { ctx.query = { - range: { from: moment(1443454528000), to: moment(1443454528000) }, + range: { from: dateTime(1443454528000), to: dateTime(1443454528000) }, targets: [{ expr: 'test{job="testjob"}', format: 'heatmap', legendFormat: '{{le}}' }], interval: '1s', }; @@ -310,8 +314,8 @@ describe('PrometheusDatasource', () => { ctx.templateSrvMock.replace = jest.fn(); ctx.timeSrvMock.timeRange = () => { return { - from: moment(1531468681), - to: moment(1531489712), + from: dateTime(1531468681), + to: dateTime(1531489712), }; }; ctx.ds = new PrometheusDatasource(instanceSettings, q, ctx.backendSrvMock, ctx.templateSrvMock, ctx.timeSrvMock); @@ -344,30 +348,30 @@ const SECOND = 1000; const MINUTE = 60 * SECOND; const HOUR = 60 * MINUTE; -const time = ({ hours = 0, seconds = 0, minutes = 0 }) => moment(hours * HOUR + minutes * MINUTE + seconds * SECOND); +const time = ({ hours = 0, seconds = 0, minutes = 0 }) => dateTime(hours * HOUR + minutes * MINUTE + seconds * SECOND); const ctx = {} as any; -const instanceSettings = { +const instanceSettings = ({ url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp', jsonData: { httpMethod: 'GET' }, -}; +} as unknown) as DataSourceInstanceSettings; const backendSrv = { datasourceRequest: jest.fn(), } as any; -const templateSrv = { +const templateSrv = ({ getAdhocFilters: () => [], replace: jest.fn(str => str), -}; +} as unknown) as TemplateSrv; -const timeSrv = { +const timeSrv = ({ timeRange: () => { return { to: { diff: () => 2000 }, from: '' }; }, -}; +} as unknown) as TimeSrv; describe('PrometheusDatasource', () => { describe('When querying prometheus with one target using query editor target spec', () => { @@ -397,7 +401,7 @@ describe('PrometheusDatasource', () => { }, }; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query).then(data => { results = data; @@ -447,7 +451,7 @@ describe('PrometheusDatasource', () => { }; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query).then(data => { results = data; @@ -508,7 +512,7 @@ describe('PrometheusDatasource', () => { }; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query).then(data => { results = data; @@ -565,7 +569,7 @@ describe('PrometheusDatasource', () => { beforeEach(async () => { options.annotation.useValueForTime = false; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.annotationQuery(options).then(data => { results = data; @@ -585,7 +589,7 @@ describe('PrometheusDatasource', () => { beforeEach(async () => { options.annotation.useValueForTime = true; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.annotationQuery(options).then(data => { results = data; @@ -600,7 +604,7 @@ describe('PrometheusDatasource', () => { describe('step parameter', () => { beforeEach(() => { backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); }); it('should use default step for short range if no interval is given', () => { @@ -696,7 +700,7 @@ describe('PrometheusDatasource', () => { }; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query).then(data => { results = data; }); @@ -733,7 +737,7 @@ describe('PrometheusDatasource', () => { const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -749,7 +753,7 @@ describe('PrometheusDatasource', () => { }; const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=1'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -770,7 +774,7 @@ describe('PrometheusDatasource', () => { }; const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -787,7 +791,7 @@ describe('PrometheusDatasource', () => { const start = 60 * 60; const urlExpected = 'proxied/api/v1/query_range?query=test&start=' + start + '&end=' + end + '&step=2'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -809,7 +813,7 @@ describe('PrometheusDatasource', () => { // times get rounded up to interval const urlExpected = 'proxied/api/v1/query_range?query=test&start=50&end=400&step=50'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -830,7 +834,7 @@ describe('PrometheusDatasource', () => { }; const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=60&end=420&step=15'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -852,7 +856,7 @@ describe('PrometheusDatasource', () => { // times get aligned to interval const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=0&end=400&step=100'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -874,7 +878,7 @@ describe('PrometheusDatasource', () => { const start = 0; const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=100'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -896,7 +900,7 @@ describe('PrometheusDatasource', () => { const start = 0; const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=60'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -939,7 +943,7 @@ describe('PrometheusDatasource', () => { templateSrv.replace = jest.fn(str => str); backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -979,7 +983,7 @@ describe('PrometheusDatasource', () => { '&start=60&end=420&step=10'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); templateSrv.replace = jest.fn(str => str); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -1020,7 +1024,7 @@ describe('PrometheusDatasource', () => { '&start=0&end=400&step=100'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); templateSrv.replace = jest.fn(str => str); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -1067,7 +1071,7 @@ describe('PrometheusDatasource', () => { templateSrv.replace = jest.fn(str => str); backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -1108,7 +1112,7 @@ describe('PrometheusDatasource', () => { '&start=60&end=420&step=15'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -1154,7 +1158,7 @@ describe('PrometheusDatasource', () => { '&step=60'; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); templateSrv.replace = jest.fn(str => str); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query); const res = backendSrv.datasourceRequest.mock.calls[0][0]; expect(res.method).toBe('GET'); @@ -1177,13 +1181,13 @@ describe('PrometheusDatasource', () => { describe('PrometheusDatasource for POST', () => { // const ctx = new helpers.ServiceTestContext(); - const instanceSettings = { + const instanceSettings = ({ url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp', jsonData: { httpMethod: 'POST' }, - }; + } as unknown) as DataSourceInstanceSettings; describe('When querying prometheus with one target using query editor target spec', () => { let results; @@ -1216,7 +1220,7 @@ describe('PrometheusDatasource for POST', () => { }, }; backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response)); - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); await ctx.ds.query(query).then(data => { results = data; }); @@ -1241,7 +1245,7 @@ describe('PrometheusDatasource for POST', () => { }; it('with proxy access tracing headers should be added', () => { - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); ctx.ds._addTracingHeaders(httpOptions, options); expect(httpOptions.headers['X-Dashboard-Id']).toBe(1); expect(httpOptions.headers['X-Panel-Id']).toBe(2); @@ -1249,7 +1253,7 @@ describe('PrometheusDatasource for POST', () => { it('with direct access tracing headers should not be added', () => { instanceSettings.url = 'http://127.0.0.1:8000'; - ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv); + ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any); ctx.ds._addTracingHeaders(httpOptions, options); expect(httpOptions.headers['X-Dashboard-Id']).toBe(undefined); expect(httpOptions.headers['X-Panel-Id']).toBe(undefined); diff --git a/public/app/plugins/datasource/prometheus/specs/metric_find_query.test.ts b/public/app/plugins/datasource/prometheus/specs/metric_find_query.test.ts index 2ca7c97876237..aacfc5dd904d3 100644 --- a/public/app/plugins/datasource/prometheus/specs/metric_find_query.test.ts +++ b/public/app/plugins/datasource/prometheus/specs/metric_find_query.test.ts @@ -1,19 +1,21 @@ -import moment from 'moment'; import { PrometheusDatasource } from '../datasource'; import PrometheusMetricFindQuery from '../metric_find_query'; import q from 'q'; +import { toUtc } from '@grafana/ui/src/utils/moment_wrapper'; +import { DataSourceInstanceSettings } from '@grafana/ui'; +import { PromOptions } from '../types'; describe('PrometheusMetricFindQuery', () => { - const instanceSettings = { + const instanceSettings = ({ url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp', jsonData: { httpMethod: 'GET' }, - }; + } as unknown) as DataSourceInstanceSettings; const raw = { - from: moment.utc('2018-04-25 10:00'), - to: moment.utc('2018-04-25 11:00'), + from: toUtc('2018-04-25 10:00'), + to: toUtc('2018-04-25 11:00'), }; const ctx: any = { backendSrvMock: { diff --git a/public/app/plugins/datasource/prometheus/types.ts b/public/app/plugins/datasource/prometheus/types.ts index 81fc4d8108d34..e83029df8356e 100644 --- a/public/app/plugins/datasource/prometheus/types.ts +++ b/public/app/plugins/datasource/prometheus/types.ts @@ -1,5 +1,12 @@ -import { DataQuery } from '@grafana/ui/src/types'; +import { DataQuery, DataSourceJsonData } from '@grafana/ui/src/types'; export interface PromQuery extends DataQuery { expr: string; } + +export interface PromOptions extends DataSourceJsonData { + timeInterval: string; + queryTimeout: string; + httpMethod: string; + directUrl: string; +} diff --git a/public/app/plugins/datasource/stackdriver/datasource.ts b/public/app/plugins/datasource/stackdriver/datasource.ts index 73b61912e967d..b2ca64668ccf4 100644 --- a/public/app/plugins/datasource/stackdriver/datasource.ts +++ b/public/app/plugins/datasource/stackdriver/datasource.ts @@ -2,11 +2,13 @@ import { stackdriverUnitMappings } from './constants'; import appEvents from 'app/core/app_events'; import _ from 'lodash'; import StackdriverMetricFindQuery from './StackdriverMetricFindQuery'; -import { StackdriverQuery, MetricDescriptor } from './types'; -import { DataSourceApi, DataQueryRequest } from '@grafana/ui/src/types'; +import { StackdriverQuery, MetricDescriptor, StackdriverOptions } from './types'; +import { DataSourceApi, DataQueryRequest, DataSourceInstanceSettings, ScopedVars } from '@grafana/ui/src/types'; +import { BackendSrv } from 'app/core/services/backend_srv'; +import { TemplateSrv } from 'app/features/templating/template_srv'; +import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; -export default class StackdriverDatasource implements DataSourceApi { - id: number; +export default class StackdriverDatasource extends DataSourceApi { url: string; baseUrl: string; projectName: string; @@ -15,10 +17,15 @@ export default class StackdriverDatasource implements DataSourceApi, + private backendSrv: BackendSrv, + private templateSrv: TemplateSrv, + private timeSrv: TimeSrv + ) { + super(instanceSettings); this.baseUrl = `/stackdriver/`; this.url = instanceSettings.url; - this.id = instanceSettings.id; this.projectName = instanceSettings.jsonData.defaultProject || ''; this.authenticationType = instanceSettings.jsonData.authenticationType || 'jwt'; this.metricTypes = []; @@ -62,7 +69,7 @@ export default class StackdriverDatasource implements DataSourceApi { return this.templateSrv.replace(f, scopedVars || {}, 'regex'); }); diff --git a/public/app/plugins/datasource/stackdriver/plugin.json b/public/app/plugins/datasource/stackdriver/plugin.json index 1ee3d57e9b15c..620a7b1c8cea5 100644 --- a/public/app/plugins/datasource/stackdriver/plugin.json +++ b/public/app/plugins/datasource/stackdriver/plugin.json @@ -2,6 +2,8 @@ "name": "Stackdriver", "type": "datasource", "id": "stackdriver", + "category": "cloud", + "metrics": true, "alerting": true, "annotations": true, @@ -10,8 +12,9 @@ "maxDataPoints": true, "cacheTimeout": true }, + "info": { - "description": "Google Stackdriver Datasource for Grafana", + "description": "Data source for Google's monitoring service", "version": "1.0.0", "logos": { "small": "img/stackdriver_logo.svg", diff --git a/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts b/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts index 032f10d8ca594..09aac57002973 100644 --- a/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts @@ -1,28 +1,32 @@ import StackdriverDataSource from '../datasource'; import { metricDescriptors } from './testData'; -import moment from 'moment'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { CustomVariable } from 'app/features/templating/all'; +import { toUtc } from '@grafana/ui/src/utils/moment_wrapper'; +import { DataSourceInstanceSettings } from '@grafana/ui'; +import { StackdriverOptions } from '../types'; +import { BackendSrv } from 'app/core/services/backend_srv'; +import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; describe('StackdriverDataSource', () => { - const instanceSettings = { + const instanceSettings = ({ jsonData: { defaultProject: 'testproject', }, - }; + } as unknown) as DataSourceInstanceSettings; const templateSrv = new TemplateSrv(); - const timeSrv = {}; + const timeSrv = {} as TimeSrv; describe('when performing testDataSource', () => { describe('and call to stackdriver api succeeds', () => { let ds; let result; beforeEach(async () => { - const backendSrv = { + const backendSrv = ({ async datasourceRequest() { return Promise.resolve({ status: 200 }); }, - }; + } as unknown) as BackendSrv; ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv, timeSrv); result = await ds.testDatasource(); }); @@ -35,9 +39,9 @@ describe('StackdriverDataSource', () => { let ds; let result; beforeEach(async () => { - const backendSrv = { + const backendSrv = ({ datasourceRequest: async () => Promise.resolve({ status: 200, data: metricDescriptors }), - }; + } as unknown) as BackendSrv; ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv, timeSrv); result = await ds.testDatasource(); }); @@ -50,7 +54,7 @@ describe('StackdriverDataSource', () => { let ds; let result; beforeEach(async () => { - const backendSrv = { + const backendSrv = ({ datasourceRequest: async () => Promise.reject({ statusText: 'Bad Request', @@ -58,7 +62,7 @@ describe('StackdriverDataSource', () => { error: { code: 400, message: 'Field interval.endTime had an invalid value' }, }, }), - }; + } as unknown) as BackendSrv; ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv, timeSrv); result = await ds.testDatasource(); }); @@ -73,8 +77,8 @@ describe('StackdriverDataSource', () => { describe('When performing query', () => { const options = { range: { - from: moment.utc('2017-08-22T20:00:00Z'), - to: moment.utc('2017-08-22T23:59:00Z'), + from: toUtc('2017-08-22T20:00:00Z'), + to: toUtc('2017-08-22T23:59:00Z'), }, rangeRaw: { from: 'now-4h', @@ -103,9 +107,9 @@ describe('StackdriverDataSource', () => { }; beforeEach(() => { - const backendSrv = { + const backendSrv = ({ datasourceRequest: async () => Promise.resolve({ status: 200, data: response }), - }; + } as unknown) as BackendSrv; ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv, timeSrv); }); @@ -122,7 +126,7 @@ describe('StackdriverDataSource', () => { let ds; let result; beforeEach(async () => { - const backendSrv = { + const backendSrv = ({ async datasourceRequest() { return Promise.resolve({ data: { @@ -139,7 +143,7 @@ describe('StackdriverDataSource', () => { }, }); }, - }; + } as unknown) as BackendSrv; ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv, timeSrv); result = await ds.getMetricTypes(); }); @@ -155,12 +159,14 @@ describe('StackdriverDataSource', () => { }); }); + const noopBackendSrv = ({} as unknown) as BackendSrv; + describe('when interpolating a template variable for the filter', () => { let interpolated; describe('and is single value variable', () => { beforeEach(() => { const filterTemplateSrv = initTemplateSrv('filtervalue1'); - const ds = new StackdriverDataSource(instanceSettings, {}, filterTemplateSrv, timeSrv); + const ds = new StackdriverDataSource(instanceSettings, noopBackendSrv, filterTemplateSrv, timeSrv); interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '${test}'], {}); }); @@ -173,7 +179,7 @@ describe('StackdriverDataSource', () => { describe('and is multi value variable', () => { beforeEach(() => { const filterTemplateSrv = initTemplateSrv(['filtervalue1', 'filtervalue2'], true); - const ds = new StackdriverDataSource(instanceSettings, {}, filterTemplateSrv, timeSrv); + const ds = new StackdriverDataSource(instanceSettings, noopBackendSrv, filterTemplateSrv, timeSrv); interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '[[test]]'], {}); }); @@ -189,7 +195,7 @@ describe('StackdriverDataSource', () => { describe('and is single value variable', () => { beforeEach(() => { const groupByTemplateSrv = initTemplateSrv('groupby1'); - const ds = new StackdriverDataSource(instanceSettings, {}, groupByTemplateSrv, timeSrv); + const ds = new StackdriverDataSource(instanceSettings, noopBackendSrv, groupByTemplateSrv, timeSrv); interpolated = ds.interpolateGroupBys(['[[test]]'], {}); }); @@ -202,7 +208,7 @@ describe('StackdriverDataSource', () => { describe('and is multi value variable', () => { beforeEach(() => { const groupByTemplateSrv = initTemplateSrv(['groupby1', 'groupby2'], true); - const ds = new StackdriverDataSource(instanceSettings, {}, groupByTemplateSrv, timeSrv); + const ds = new StackdriverDataSource(instanceSettings, noopBackendSrv, groupByTemplateSrv, timeSrv); interpolated = ds.interpolateGroupBys(['[[test]]'], {}); }); @@ -217,7 +223,7 @@ describe('StackdriverDataSource', () => { describe('unit parsing', () => { let ds, res; beforeEach(() => { - ds = new StackdriverDataSource(instanceSettings, {}, templateSrv, timeSrv); + ds = new StackdriverDataSource(instanceSettings, noopBackendSrv, templateSrv, timeSrv); }); describe('when theres only one target', () => { describe('and the stackdriver unit doesnt have a corresponding grafana unit', () => { diff --git a/public/app/plugins/datasource/stackdriver/types.ts b/public/app/plugins/datasource/stackdriver/types.ts index b9a6893d4bd98..f9a829279c32c 100644 --- a/public/app/plugins/datasource/stackdriver/types.ts +++ b/public/app/plugins/datasource/stackdriver/types.ts @@ -1,4 +1,4 @@ -import { DataQuery } from '@grafana/ui/src/types'; +import { DataQuery, DataSourceJsonData } from '@grafana/ui/src/types'; export enum MetricFindQueryTypes { Services = 'services', @@ -40,6 +40,11 @@ export interface StackdriverQuery extends DataQuery { view?: string; } +export interface StackdriverOptions extends DataSourceJsonData { + defaultProject?: string; + authenticationType?: string; +} + export interface AnnotationTarget { defaultProject: string; metricType: string; diff --git a/public/app/plugins/datasource/testdata/StreamHandler.ts b/public/app/plugins/datasource/testdata/StreamHandler.ts index 11ede1346bd8a..389c20a8bf356 100644 --- a/public/app/plugins/datasource/testdata/StreamHandler.ts +++ b/public/app/plugins/datasource/testdata/StreamHandler.ts @@ -9,6 +9,7 @@ import { DataStreamState, LoadingState, LogLevel, + CSVReader, } from '@grafana/ui'; import { TestDataQuery, StreamingQuery } from './types'; @@ -57,6 +58,8 @@ export class StreamHandler { this.workers[key] = new SignalWorker(key, query, req, observer); } else if (type === 'logs') { this.workers[key] = new LogsWorker(key, query, req, observer); + } else if (type === 'fetch') { + this.workers[key] = new FetchWorker(key, query, req, observer); } else { throw { message: 'Unknown Stream type: ' + type, @@ -72,6 +75,7 @@ export class StreamHandler { * Manages a single stream request */ export class StreamWorker { + refId: string; query: StreamingQuery; stream: DataStreamState; observer: DataStreamObserver; @@ -85,6 +89,7 @@ export class StreamWorker { request, unsubscribe: this.unsubscribe, }; + this.refId = query.refId; this.query = query.stream; this.last = Date.now(); this.observer = observer; @@ -207,6 +212,56 @@ export class SignalWorker extends StreamWorker { }; } +export class FetchWorker extends StreamWorker { + csv: CSVReader; + reader: ReadableStreamReader; + + constructor(key: string, query: TestDataQuery, request: DataQueryRequest, observer: DataStreamObserver) { + super(key, query, request, observer); + if (!query.stream.url) { + throw new Error('Missing Fetch URL'); + } + if (!query.stream.url.startsWith('http')) { + throw new Error('Fetch URL must be absolute'); + } + + this.csv = new CSVReader({ callback: this }); + fetch(new Request(query.stream.url)).then(response => { + this.reader = response.body.getReader(); + this.reader.read().then(this.processChunk); + }); + } + + processChunk = (value: ReadableStreamReadResult): any => { + if (this.observer == null) { + return; // Nothing more to do + } + + if (value.value) { + const text = new TextDecoder().decode(value.value); + this.csv.readCSV(text); + } + + if (value.done) { + console.log('Finished stream'); + this.stream.state = LoadingState.Done; + return; + } + + return this.reader.read().then(this.processChunk); + }; + + onHeader = (series: SeriesData) => { + series.refId = this.refId; + this.stream.series = [series]; + }; + + onRow = (row: any[]) => { + // TODO?? this will send an event for each row, even if the chunk passed a bunch of them + this.appendRows([row]); + }; +} + export class LogsWorker extends StreamWorker { index = 0; diff --git a/public/app/plugins/datasource/testdata/datasource.ts b/public/app/plugins/datasource/testdata/datasource.ts index 52f6080bbd50a..0f69028f7848a 100644 --- a/public/app/plugins/datasource/testdata/datasource.ts +++ b/public/app/plugins/datasource/testdata/datasource.ts @@ -17,13 +17,12 @@ export interface TestDataRegistry { [key: string]: TestData[]; } -export class TestDataDatasource implements DataSourceApi { - id: number; +export class TestDataDatasource extends DataSourceApi { streams = new StreamHandler(); /** @ngInject */ constructor(instanceSettings: DataSourceInstanceSettings) { - this.id = instanceSettings.id; + super(instanceSettings); } query(options: DataQueryRequest, observer: DataStreamObserver) { @@ -78,6 +77,7 @@ export class TestDataDatasource implements DataSourceApi { for (const t of results.tables || []) { const table = t as TableData; table.refId = query.refId; + table.name = query.alias; data.push(table); } diff --git a/public/app/plugins/datasource/testdata/img/testdata.svg b/public/app/plugins/datasource/testdata/img/testdata.svg new file mode 100644 index 0000000000000..13b413dc85f67 --- /dev/null +++ b/public/app/plugins/datasource/testdata/img/testdata.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/app/plugins/datasource/testdata/partials/query.editor.html b/public/app/plugins/datasource/testdata/partials/query.editor.html index bd54f503a5ec5..e982ce9fb61fb 100644 --- a/public/app/plugins/datasource/testdata/partials/query.editor.html +++ b/public/app/plugins/datasource/testdata/partials/query.editor.html @@ -3,7 +3,7 @@
    - +
    @@ -43,7 +43,7 @@
    @@ -78,7 +78,16 @@ step="0.1" ng-change="ctrl.streamChanged()" />
    -
    +
    + + +
    +
    diff --git a/public/app/plugins/datasource/testdata/plugin.json b/public/app/plugins/datasource/testdata/plugin.json index 63a9b261de7f5..96c23b196659f 100644 --- a/public/app/plugins/datasource/testdata/plugin.json +++ b/public/app/plugins/datasource/testdata/plugin.json @@ -12,13 +12,14 @@ }, "info": { + "description": "Generates test data in different forms", "author": { "name": "Grafana Project", "url": "https://grafana.com" }, "logos": { - "small": "../../../../img/grafana_icon.svg", - "large": "../../../../img/grafana_icon.svg" + "small": "img/testdata.svg", + "large": "img/testdata.svg" } }, diff --git a/public/app/plugins/datasource/testdata/query_ctrl.ts b/public/app/plugins/datasource/testdata/query_ctrl.ts index c0667a3300086..79242ba0c5042 100644 --- a/public/app/plugins/datasource/testdata/query_ctrl.ts +++ b/public/app/plugins/datasource/testdata/query_ctrl.ts @@ -1,9 +1,9 @@ import _ from 'lodash'; import { QueryCtrl } from 'app/plugins/sdk'; -import moment from 'moment'; import { defaultQuery } from './StreamHandler'; import { getBackendSrv } from 'app/core/services/backend_srv'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; export class TestDataQueryCtrl extends QueryCtrl { static templateUrl = 'partials/query.editor.html'; @@ -20,14 +20,14 @@ export class TestDataQueryCtrl extends QueryCtrl { this.target.scenarioId = this.target.scenarioId || 'random_walk'; this.scenarioList = []; - this.newPointTime = moment(); + this.newPointTime = dateTime(); this.selectedPoint = { text: 'Select point', value: null }; } getPoints() { return _.map(this.target.points, (point, index) => { return { - text: moment(point[1]).format('MMMM Do YYYY, H:mm:ss') + ' : ' + point[0], + text: dateTime(point[1]).format('MMMM Do YYYY, H:mm:ss') + ' : ' + point[0], value: index, }; }); diff --git a/public/app/plugins/datasource/testdata/types.ts b/public/app/plugins/datasource/testdata/types.ts index c17097cd01f7b..73d562add926c 100644 --- a/public/app/plugins/datasource/testdata/types.ts +++ b/public/app/plugins/datasource/testdata/types.ts @@ -14,9 +14,10 @@ export interface TestDataQuery extends DataQuery { } export interface StreamingQuery { - type: 'signal' | 'logs'; + type: 'signal' | 'logs' | 'fetch'; speed: number; spread: number; noise: number; // wiggle around the signal for min/max buffer?: number; + url?: string; // the Fetch URL } diff --git a/public/app/plugins/panel/alertlist/module.ts b/public/app/plugins/panel/alertlist/module.ts index 56cfe47ca6f63..d602c6d77a0c1 100644 --- a/public/app/plugins/panel/alertlist/module.ts +++ b/public/app/plugins/panel/alertlist/module.ts @@ -1,9 +1,9 @@ import _ from 'lodash'; -import moment from 'moment'; import alertDef from '../../../features/alerting/state/alertDef'; import { PanelCtrl } from 'app/plugins/sdk'; import * as dateMath from '@grafana/ui/src/utils/datemath'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; class AlertListPanel extends PanelCtrl { static templateUrl = 'module.html'; @@ -157,7 +157,7 @@ class AlertListPanel extends PanelCtrl { this.currentAlerts = this.sortResult( _.map(res, al => { al.stateModel = alertDef.getStateDisplayModel(al.state); - al.newStateDateAgo = moment(al.newStateDate) + al.newStateDateAgo = dateTime(al.newStateDate) .locale('en') .fromNow(true); return al; diff --git a/public/app/plugins/panel/alertlist/plugin.json b/public/app/plugins/panel/alertlist/plugin.json index e1f427b1611ea..af82f0870e0bf 100644 --- a/public/app/plugins/panel/alertlist/plugin.json +++ b/public/app/plugins/panel/alertlist/plugin.json @@ -3,7 +3,7 @@ "name": "Alert List", "id": "alertlist", - "dataFormats": [], + "skipDataQuery": true, "info": { "description": "Shows list of alerts and their current status", diff --git a/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx b/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx index ddfb88222574e..87e52b2e08fec 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx @@ -10,6 +10,7 @@ import { FieldDisplayOptions, Field, FieldPropertiesEditor, + PanelOptionsGroup, } from '@grafana/ui'; // Types @@ -54,7 +55,8 @@ export class BarGaugePanelEditor extends PureComponent - + +
    Orientation -
    -
    -
    - - - -
    -
    - -
    -
    - Search options - Query - - - -
    - -
    - Tags - - - -
    -
    - -
    -
    - Limit number to - -
    -
    -
    diff --git a/public/app/plugins/panel/gettingstarted/module.html b/public/app/plugins/panel/gettingstarted/module.html deleted file mode 100644 index cd1f1c4265dc1..0000000000000 --- a/public/app/plugins/panel/gettingstarted/module.html +++ /dev/null @@ -1,16 +0,0 @@ -
    - -
    diff --git a/public/app/plugins/panel/gettingstarted/module.ts b/public/app/plugins/panel/gettingstarted/module.ts index c65a169028f3b..d0cfb4eb31104 100644 --- a/public/app/plugins/panel/gettingstarted/module.ts +++ b/public/app/plugins/panel/gettingstarted/module.ts @@ -1,118 +1,5 @@ -import { PanelCtrl } from 'app/plugins/sdk'; +import { PanelPlugin } from '@grafana/ui'; +import { GettingStarted } from './GettingStarted'; -import { contextSrv } from 'app/core/core'; - -class GettingStartedPanelCtrl extends PanelCtrl { - static templateUrl = 'public/app/plugins/panel/gettingstarted/module.html'; - checksDone: boolean; - stepIndex: number; - steps: any; - - /** @ngInject */ - constructor($scope, $injector, private backendSrv, datasourceSrv, private $q) { - super($scope, $injector); - - this.stepIndex = 0; - this.steps = []; - - this.steps.push({ - title: 'Install Grafana', - icon: 'icon-gf icon-gf-check', - href: 'http://docs.grafana.org/', - target: '_blank', - note: 'Review the installation docs', - check: () => $q.when(true), - }); - - this.steps.push({ - title: 'Create your first data source', - cta: 'Add data source', - icon: 'gicon gicon-datasources', - href: 'datasources/new?gettingstarted', - check: () => { - return $q.when( - datasourceSrv.getMetricSources().filter(item => { - return item.meta.builtIn !== true; - }).length > 0 - ); - }, - }); - - this.steps.push({ - title: 'Create your first dashboard', - cta: 'New dashboard', - icon: 'gicon gicon-dashboard', - href: 'dashboard/new?gettingstarted', - check: () => { - return this.backendSrv.search({ limit: 1 }).then(result => { - return result.length > 0; - }); - }, - }); - - this.steps.push({ - title: 'Invite your team', - cta: 'Add Users', - icon: 'gicon gicon-team', - href: 'org/users?gettingstarted', - check: () => { - return this.backendSrv.get('/api/org/users').then(res => { - return res.length > 1; - }); - }, - }); - - this.steps.push({ - title: 'Install apps & plugins', - cta: 'Explore plugin repository', - icon: 'gicon gicon-plugins', - href: 'https://grafana.com/plugins?utm_source=grafana_getting_started', - check: () => { - return this.backendSrv.get('/api/plugins', { embedded: 0, core: 0 }).then(plugins => { - return plugins.length > 0; - }); - }, - }); - } - - $onInit() { - this.stepIndex = -1; - return this.nextStep().then(res => { - this.checksDone = true; - }); - } - - nextStep() { - if (this.stepIndex === this.steps.length - 1) { - return this.$q.when(); - } - - this.stepIndex += 1; - const currentStep = this.steps[this.stepIndex]; - return currentStep.check().then(passed => { - if (passed) { - currentStep.cssClass = 'completed'; - return this.nextStep(); - } - - currentStep.cssClass = 'active'; - return this.$q.when(); - }); - } - - dismiss() { - this.dashboard.removePanel(this.panel, false); - - this.backendSrv - .request({ - method: 'PUT', - url: '/api/user/helpflags/1', - showSuccessAlert: false, - }) - .then(res => { - contextSrv.user.helpFlags1 = res.helpFlags1; - }); - } -} - -export { GettingStartedPanelCtrl, GettingStartedPanelCtrl as PanelCtrl }; +// Simplest possible panel plugin +export const plugin = new PanelPlugin(GettingStarted); diff --git a/public/app/plugins/panel/graph/axes_editor.html b/public/app/plugins/panel/graph/axes_editor.html index ced0d8157e979..12a717248f9ae 100644 --- a/public/app/plugins/panel/graph/axes_editor.html +++ b/public/app/plugins/panel/graph/axes_editor.html @@ -44,7 +44,7 @@
    Right Y
    -
    +
    X-Axis
    diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index abed226f9d623..dad49788eb091 100644 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -10,7 +10,6 @@ import './jquery.flot.events'; import $ from 'jquery'; import _ from 'lodash'; -import moment from 'moment'; import { tickStep } from 'app/core/utils/ticks'; import { appEvents, coreModule, updateLegendValues } from 'app/core/core'; import GraphTooltip from './graph_tooltip'; @@ -27,6 +26,7 @@ import { Legend, GraphLegendProps } from './Legend/Legend'; import { GraphCtrl } from './module'; import { getValueFormat } from '@grafana/ui'; import { provideTheme } from 'app/core/utils/ConfigProvider'; +import { toUtc } from '@grafana/ui/src/utils/moment_wrapper'; const LegendWithThemeProvider = provideTheme(Legend); @@ -164,8 +164,8 @@ class GraphElement { } else { this.scope.$apply(() => { this.timeSrv.setTime({ - from: moment.utc(ranges.xaxis.from), - to: moment.utc(ranges.xaxis.to), + from: toUtc(ranges.xaxis.from), + to: toUtc(ranges.xaxis.to), }); }); } diff --git a/public/app/plugins/panel/graph/plugin.json b/public/app/plugins/panel/graph/plugin.json index 7aed6f972cd0e..77ae2adb132b9 100644 --- a/public/app/plugins/panel/graph/plugin.json +++ b/public/app/plugins/panel/graph/plugin.json @@ -3,8 +3,6 @@ "name": "Graph", "id": "graph", - "dataFormats": ["time_series", "table"], - "info": { "description": "Graph Panel for Grafana", "author": { diff --git a/public/app/plugins/panel/graph/specs/graph.test.ts b/public/app/plugins/panel/graph/specs/graph.test.ts index ff81a8ae6eb08..aa022c686079b 100644 --- a/public/app/plugins/panel/graph/specs/graph.test.ts +++ b/public/app/plugins/panel/graph/specs/graph.test.ts @@ -24,17 +24,17 @@ import { PanelCtrl } from 'app/features/panel/panel_ctrl'; import config from 'app/core/config'; import TimeSeries from 'app/core/time_series2'; -import moment from 'moment'; import $ from 'jquery'; import { graphDirective } from '../graph'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; const ctx = {} as any; let ctrl; const scope = { ctrl: {}, range: { - from: moment([2015, 1, 1]), - to: moment([2015, 11, 20]), + from: dateTime([2015, 1, 1]), + to: dateTime([2015, 11, 20]), }, $on: () => {}, }; @@ -85,8 +85,8 @@ describe('grafanaGraph', () => { getTimezone: () => 'browser', }, range: { - from: moment([2015, 1, 1, 10]), - to: moment([2015, 1, 1, 22]), + from: dateTime([2015, 1, 1, 10]), + to: dateTime([2015, 1, 1, 22]), }, } as any; @@ -443,8 +443,8 @@ describe('grafanaGraph', () => { describe('and the range is less than 24 hours', () => { beforeEach(() => { setupCtx(() => { - ctrl.range.from = moment([2015, 1, 1, 10]); - ctrl.range.to = moment([2015, 1, 1, 22]); + ctrl.range.from = dateTime([2015, 1, 1, 10]); + ctrl.range.to = dateTime([2015, 1, 1, 22]); }); }); @@ -457,8 +457,8 @@ describe('grafanaGraph', () => { describe('and the range is less than one year', () => { beforeEach(() => { setupCtx(() => { - ctrl.range.from = moment([2015, 1, 1]); - ctrl.range.to = moment([2015, 11, 20]); + ctrl.range.from = dateTime([2015, 1, 1]); + ctrl.range.to = dateTime([2015, 11, 20]); }); }); diff --git a/public/app/plugins/panel/graph/specs/graph_ctrl.test.ts b/public/app/plugins/panel/graph/specs/graph_ctrl.test.ts index 826b71f429e5c..114d32eba4a71 100644 --- a/public/app/plugins/panel/graph/specs/graph_ctrl.test.ts +++ b/public/app/plugins/panel/graph/specs/graph_ctrl.test.ts @@ -1,5 +1,5 @@ -import moment from 'moment'; import { GraphCtrl } from '../module'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; jest.mock('../graph', () => ({})); @@ -50,7 +50,7 @@ describe('GraphCtrl', () => { }, ]; - ctx.ctrl.range = { from: moment().valueOf(), to: moment().valueOf() }; + ctx.ctrl.range = { from: dateTime().valueOf(), to: dateTime().valueOf() }; ctx.ctrl.onDataReceived(data); }); @@ -62,10 +62,10 @@ describe('GraphCtrl', () => { describe('when time series are inside range', () => { beforeEach(() => { const range = { - from: moment() + from: dateTime() .subtract(1, 'days') .valueOf(), - to: moment().valueOf(), + to: dateTime().valueOf(), }; const data = [ diff --git a/public/app/plugins/panel/graph/specs/time_region_manager.test.ts b/public/app/plugins/panel/graph/specs/time_region_manager.test.ts index 266838ce9b018..97d63bbb75a02 100644 --- a/public/app/plugins/panel/graph/specs/time_region_manager.test.ts +++ b/public/app/plugins/panel/graph/specs/time_region_manager.test.ts @@ -1,5 +1,5 @@ import { TimeRegionManager, colorModes } from '../time_region_manager'; -import moment from 'moment'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; describe('TimeRegionManager', () => { function plotOptionsScenario(desc, func) { @@ -32,9 +32,11 @@ describe('TimeRegionManager', () => { `Time range: from=${ctx.panelCtrl.range.from.format()}, to=${ctx.panelCtrl.range.to.format()}`, ctx.panelCtrl.range.from._isUTC ); - ctx.options.grid.markings.forEach((m, i) => { + ctx.options.grid.markings.forEach((m: any, i: number) => { console.log( - `Marking (${i}): from=${moment(m.xaxis.from).format()}, to=${moment(m.xaxis.to).format()}, color=${m.color}` + `Marking (${i}): from=${dateTime(m.xaxis.from).format()}, to=${dateTime(m.xaxis.to).format()}, color=${ + m.color + }` ); }); }; @@ -48,16 +50,16 @@ describe('TimeRegionManager', () => { const regions = [ { fromDayOfWeek: 1, toDayOfWeek: 1, fill: true, line: true, lineColor: '#ffffff', colorMode: 'custom' }, ]; - const from = moment('2018-01-01T00:00:00+01:00'); - const to = moment('2018-01-01T23:59:00+01:00'); + const from = dateTime('2018-01-01T00:00:00+01:00'); + const to = dateTime('2018-01-01T23:59:00+01:00'); expect(() => ctx.setup(regions, from, to)).not.toThrow(); }); plotOptionsScenario('should not throw an error when lineColor is undefined', ctx => { const regions = [ { fromDayOfWeek: 1, toDayOfWeek: 1, fill: true, fillColor: '#ffffff', line: true, colorMode: 'custom' }, ]; - const from = moment('2018-01-01T00:00:00+01:00'); - const to = moment('2018-01-01T23:59:00+01:00'); + const from = dateTime('2018-01-01T00:00:00+01:00'); + const to = dateTime('2018-01-01T23:59:00+01:00'); expect(() => ctx.setup(regions, from, to)).not.toThrow(); }); }); @@ -65,8 +67,8 @@ describe('TimeRegionManager', () => { describe('When creating plot markings using local time', () => { plotOptionsScenario('for day of week region', ctx => { const regions = [{ fromDayOfWeek: 1, toDayOfWeek: 1, fill: true, line: true, colorMode: 'red' }]; - const from = moment('2018-01-01T00:00:00+01:00'); - const to = moment('2018-01-01T23:59:00+01:00'); + const from = dateTime('2018-01-01T00:00:00+01:00'); + const to = dateTime('2018-01-01T23:59:00+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -75,30 +77,30 @@ describe('TimeRegionManager', () => { it('should add fill', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-01T01:00:00+01:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-02T00:59:59+01:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-01-01T01:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-01-02T00:59:59+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); }); it('should add line before', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-01T01:00:00+01:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-01T01:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.from).format()).toBe(dateTime('2018-01-01T01:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.to).format()).toBe(dateTime('2018-01-01T01:00:00+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.line); }); it('should add line after', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-02T00:59:59+01:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-02T00:59:59+01:00').format()); + expect(dateTime(markings[2].xaxis.from).format()).toBe(dateTime('2018-01-02T00:59:59+01:00').format()); + expect(dateTime(markings[2].xaxis.to).format()).toBe(dateTime('2018-01-02T00:59:59+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.line); }); }); plotOptionsScenario('for time from region', ctx => { const regions = [{ from: '05:00', fill: true, colorMode: 'red' }]; - const from = moment('2018-01-01T00:00+01:00'); - const to = moment('2018-01-03T23:59+01:00'); + const from = dateTime('2018-01-01T00:00+01:00'); + const to = dateTime('2018-01-03T23:59+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -108,24 +110,24 @@ describe('TimeRegionManager', () => { it('should add one fill at 05:00 each day', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-01T06:00:00+01:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-01T06:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-01-01T06:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-01-01T06:00:00+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-02T06:00:00+01:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-02T06:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.from).format()).toBe(dateTime('2018-01-02T06:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.to).format()).toBe(dateTime('2018-01-02T06:00:00+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-03T06:00:00+01:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-03T06:00:00+01:00').format()); + expect(dateTime(markings[2].xaxis.from).format()).toBe(dateTime('2018-01-03T06:00:00+01:00').format()); + expect(dateTime(markings[2].xaxis.to).format()).toBe(dateTime('2018-01-03T06:00:00+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); plotOptionsScenario('for time to region', ctx => { const regions = [{ to: '05:00', fill: true, colorMode: 'red' }]; - const from = moment('2018-02-01T00:00+01:00'); - const to = moment('2018-02-03T23:59+01:00'); + const from = dateTime('2018-02-01T00:00+01:00'); + const to = dateTime('2018-02-03T23:59+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -135,24 +137,24 @@ describe('TimeRegionManager', () => { it('should add one fill at 05:00 each day', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-02-01T06:00:00+01:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-02-01T06:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-02-01T06:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-02-01T06:00:00+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-02-02T06:00:00+01:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-02-02T06:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.from).format()).toBe(dateTime('2018-02-02T06:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.to).format()).toBe(dateTime('2018-02-02T06:00:00+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-02-03T06:00:00+01:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-02-03T06:00:00+01:00').format()); + expect(dateTime(markings[2].xaxis.from).format()).toBe(dateTime('2018-02-03T06:00:00+01:00').format()); + expect(dateTime(markings[2].xaxis.to).format()).toBe(dateTime('2018-02-03T06:00:00+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); plotOptionsScenario('for time from/to region', ctx => { const regions = [{ from: '00:00', to: '05:00', fill: true, colorMode: 'red' }]; - const from = moment('2018-12-01T00:00+01:00'); - const to = moment('2018-12-03T23:59+01:00'); + const from = dateTime('2018-12-01T00:00+01:00'); + const to = dateTime('2018-12-03T23:59+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -162,24 +164,24 @@ describe('TimeRegionManager', () => { it('should add one fill between 00:00 and 05:00 each day', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-12-01T01:00:00+01:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-12-01T06:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-12-01T01:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-12-01T06:00:00+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-12-02T01:00:00+01:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-12-02T06:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.from).format()).toBe(dateTime('2018-12-02T01:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.to).format()).toBe(dateTime('2018-12-02T06:00:00+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-12-03T01:00:00+01:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-12-03T06:00:00+01:00').format()); + expect(dateTime(markings[2].xaxis.from).format()).toBe(dateTime('2018-12-03T01:00:00+01:00').format()); + expect(dateTime(markings[2].xaxis.to).format()).toBe(dateTime('2018-12-03T06:00:00+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); plotOptionsScenario('for time from/to region crossing midnight', ctx => { const regions = [{ from: '22:00', to: '00:30', fill: true, colorMode: 'red' }]; - const from = moment('2018-12-01T12:00+01:00'); - const to = moment('2018-12-04T08:00+01:00'); + const from = dateTime('2018-12-01T12:00+01:00'); + const to = dateTime('2018-12-04T08:00+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -189,24 +191,24 @@ describe('TimeRegionManager', () => { it('should add one fill between 22:00 and 00:30 each day', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-12-01T23:00:00+01:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-12-02T01:30:00+01:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-12-01T23:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-12-02T01:30:00+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-12-02T23:00:00+01:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-12-03T01:30:00+01:00').format()); + expect(dateTime(markings[1].xaxis.from).format()).toBe(dateTime('2018-12-02T23:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.to).format()).toBe(dateTime('2018-12-03T01:30:00+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-12-03T23:00:00+01:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-12-04T01:30:00+01:00').format()); + expect(dateTime(markings[2].xaxis.from).format()).toBe(dateTime('2018-12-03T23:00:00+01:00').format()); + expect(dateTime(markings[2].xaxis.to).format()).toBe(dateTime('2018-12-04T01:30:00+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); plotOptionsScenario('for day of week from/to region', ctx => { const regions = [{ fromDayOfWeek: 7, toDayOfWeek: 7, fill: true, colorMode: 'red' }]; - const from = moment('2018-01-01T18:45:05+01:00'); - const to = moment('2018-01-22T08:27:00+01:00'); + const from = dateTime('2018-01-01T18:45:05+01:00'); + const to = dateTime('2018-01-22T08:27:00+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -216,24 +218,24 @@ describe('TimeRegionManager', () => { it('should add one fill at each sunday', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07T01:00:00+01:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-08T00:59:59+01:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-01-07T01:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-01-08T00:59:59+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14T01:00:00+01:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-15T00:59:59+01:00').format()); + expect(dateTime(markings[1].xaxis.from).format()).toBe(dateTime('2018-01-14T01:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.to).format()).toBe(dateTime('2018-01-15T00:59:59+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21T01:00:00+01:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-22T00:59:59+01:00').format()); + expect(dateTime(markings[2].xaxis.from).format()).toBe(dateTime('2018-01-21T01:00:00+01:00').format()); + expect(dateTime(markings[2].xaxis.to).format()).toBe(dateTime('2018-01-22T00:59:59+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); plotOptionsScenario('for day of week from region', ctx => { const regions = [{ fromDayOfWeek: 7, fill: true, colorMode: 'red' }]; - const from = moment('2018-01-01T18:45:05+01:00'); - const to = moment('2018-01-22T08:27:00+01:00'); + const from = dateTime('2018-01-01T18:45:05+01:00'); + const to = dateTime('2018-01-22T08:27:00+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -243,24 +245,24 @@ describe('TimeRegionManager', () => { it('should add one fill at each sunday', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07T01:00:00+01:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-08T00:59:59+01:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-01-07T01:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-01-08T00:59:59+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14T01:00:00+01:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-15T00:59:59+01:00').format()); + expect(dateTime(markings[1].xaxis.from).format()).toBe(dateTime('2018-01-14T01:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.to).format()).toBe(dateTime('2018-01-15T00:59:59+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21T01:00:00+01:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-22T00:59:59+01:00').format()); + expect(dateTime(markings[2].xaxis.from).format()).toBe(dateTime('2018-01-21T01:00:00+01:00').format()); + expect(dateTime(markings[2].xaxis.to).format()).toBe(dateTime('2018-01-22T00:59:59+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); plotOptionsScenario('for day of week to region', ctx => { const regions = [{ toDayOfWeek: 7, fill: true, colorMode: 'red' }]; - const from = moment('2018-01-01T18:45:05+01:00'); - const to = moment('2018-01-22T08:27:00+01:00'); + const from = dateTime('2018-01-01T18:45:05+01:00'); + const to = dateTime('2018-01-22T08:27:00+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -270,24 +272,24 @@ describe('TimeRegionManager', () => { it('should add one fill at each sunday', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07T01:00:00+01:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-08T00:59:59+01:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-01-07T01:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-01-08T00:59:59+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14T01:00:00+01:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-15T00:59:59+01:00').format()); + expect(dateTime(markings[1].xaxis.from).format()).toBe(dateTime('2018-01-14T01:00:00+01:00').format()); + expect(dateTime(markings[1].xaxis.to).format()).toBe(dateTime('2018-01-15T00:59:59+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21T01:00:00+01:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-22T00:59:59+01:00').format()); + expect(dateTime(markings[2].xaxis.from).format()).toBe(dateTime('2018-01-21T01:00:00+01:00').format()); + expect(dateTime(markings[2].xaxis.to).format()).toBe(dateTime('2018-01-22T00:59:59+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); plotOptionsScenario('for day of week from/to time region', ctx => { const regions = [{ fromDayOfWeek: 7, from: '23:00', toDayOfWeek: 1, to: '01:40', fill: true, colorMode: 'red' }]; - const from = moment('2018-12-07T12:51:19+01:00'); - const to = moment('2018-12-10T13:51:29+01:00'); + const from = dateTime('2018-12-07T12:51:19+01:00'); + const to = dateTime('2018-12-10T13:51:29+01:00'); ctx.setup(regions, from, to); it('should add 1 marking', () => { @@ -297,15 +299,15 @@ describe('TimeRegionManager', () => { it('should add one fill between sunday 23:00 and monday 01:40', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-12-10T00:00:00+01:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-12-10T02:40:00+01:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-12-10T00:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-12-10T02:40:00+01:00').format()); }); }); plotOptionsScenario('for day of week from/to time region', ctx => { const regions = [{ fromDayOfWeek: 6, from: '03:00', toDayOfWeek: 7, to: '02:00', fill: true, colorMode: 'red' }]; - const from = moment('2018-12-07T12:51:19+01:00'); - const to = moment('2018-12-10T13:51:29+01:00'); + const from = dateTime('2018-12-07T12:51:19+01:00'); + const to = dateTime('2018-12-10T13:51:29+01:00'); ctx.setup(regions, from, to); it('should add 1 marking', () => { @@ -315,15 +317,15 @@ describe('TimeRegionManager', () => { it('should add one fill between saturday 03:00 and sunday 02:00', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-12-08T04:00:00+01:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-12-09T03:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-12-08T04:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-12-09T03:00:00+01:00').format()); }); }); plotOptionsScenario('for day of week from/to time region with daylight saving time', ctx => { const regions = [{ fromDayOfWeek: 7, from: '20:00', toDayOfWeek: 7, to: '23:00', fill: true, colorMode: 'red' }]; - const from = moment('2018-03-17T06:00:00+01:00'); - const to = moment('2018-04-03T06:00:00+02:00'); + const from = dateTime('2018-03-17T06:00:00+01:00'); + const to = dateTime('2018-04-03T06:00:00+02:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -333,21 +335,21 @@ describe('TimeRegionManager', () => { it('should add one fill at each sunday between 20:00 and 23:00', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-03-18T21:00:00+01:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-03-19T00:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-03-18T21:00:00+01:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-03-19T00:00:00+01:00').format()); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-03-25T22:00:00+02:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-03-26T01:00:00+02:00').format()); + expect(dateTime(markings[1].xaxis.from).format()).toBe(dateTime('2018-03-25T22:00:00+02:00').format()); + expect(dateTime(markings[1].xaxis.to).format()).toBe(dateTime('2018-03-26T01:00:00+02:00').format()); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-04-01T22:00:00+02:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-04-02T01:00:00+02:00').format()); + expect(dateTime(markings[2].xaxis.from).format()).toBe(dateTime('2018-04-01T22:00:00+02:00').format()); + expect(dateTime(markings[2].xaxis.to).format()).toBe(dateTime('2018-04-02T01:00:00+02:00').format()); }); }); plotOptionsScenario('for each day of week with winter time', ctx => { const regions = [{ fromDayOfWeek: 7, toDayOfWeek: 7, fill: true, colorMode: 'red' }]; - const from = moment('2018-10-20T14:50:11+02:00'); - const to = moment('2018-11-07T12:56:23+01:00'); + const from = dateTime('2018-10-20T14:50:11+02:00'); + const to = dateTime('2018-11-07T12:56:23+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -357,14 +359,14 @@ describe('TimeRegionManager', () => { it('should add one fill at each sunday', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-10-21T02:00:00+02:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-10-22T01:59:59+02:00').format()); + expect(dateTime(markings[0].xaxis.from).format()).toBe(dateTime('2018-10-21T02:00:00+02:00').format()); + expect(dateTime(markings[0].xaxis.to).format()).toBe(dateTime('2018-10-22T01:59:59+02:00').format()); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-10-28T02:00:00+02:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-10-29T00:59:59+01:00').format()); + expect(dateTime(markings[1].xaxis.from).format()).toBe(dateTime('2018-10-28T02:00:00+02:00').format()); + expect(dateTime(markings[1].xaxis.to).format()).toBe(dateTime('2018-10-29T00:59:59+01:00').format()); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-11-04T01:00:00+01:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-11-05T00:59:59+01:00').format()); + expect(dateTime(markings[2].xaxis.from).format()).toBe(dateTime('2018-11-04T01:00:00+01:00').format()); + expect(dateTime(markings[2].xaxis.to).format()).toBe(dateTime('2018-11-05T00:59:59+01:00').format()); }); }); }); diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts index a04096bc37154..1aaeb09b44a6f 100644 --- a/public/app/plugins/panel/graph/time_region_manager.ts +++ b/public/app/plugins/panel/graph/time_region_manager.ts @@ -1,7 +1,7 @@ import 'vendor/flot/jquery.flot'; import _ from 'lodash'; -import moment from 'moment'; import { GrafanaThemeType, getColorFromHexRgbOrName } from '@grafana/ui'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; type TimeRegionColorDefinition = { fill: string; @@ -83,7 +83,10 @@ export class TimeRegionManager { return; } - const tRange = { from: moment(this.panelCtrl.range.from).utc(), to: moment(this.panelCtrl.range.to).utc() }; + const tRange = { + from: dateTime(this.panelCtrl.range.from).utc(), + to: dateTime(this.panelCtrl.range.to).utc(), + }; let i, hRange, timeRegion, regions, fromStart, fromEnd, timeRegionColor: TimeRegionColorDefinition; @@ -143,7 +146,7 @@ export class TimeRegionManager { regions = []; - fromStart = moment(tRange.from); + fromStart = dateTime(tRange.from); fromStart.set('hour', 0); fromStart.set('minute', 0); fromStart.set('second', 0); @@ -160,7 +163,7 @@ export class TimeRegionManager { break; } - fromEnd = moment(fromStart); + fromEnd = dateTime(fromStart); if (hRange.from.h <= hRange.to.h) { fromEnd.add(hRange.to.h - hRange.from.h, 'hours'); diff --git a/public/app/plugins/panel/heatmap/plugin.json b/public/app/plugins/panel/heatmap/plugin.json index d72f12f4723d6..7022958560f2d 100644 --- a/public/app/plugins/panel/heatmap/plugin.json +++ b/public/app/plugins/panel/heatmap/plugin.json @@ -3,8 +3,6 @@ "name": "Heatmap", "id": "heatmap", - "dataFormats": ["time_series"], - "info": { "description": "Heatmap Panel for Grafana", "author": { diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index 91bf5ae85bbfd..ef146f2a4ecbb 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -1,6 +1,5 @@ import _ from 'lodash'; import $ from 'jquery'; -import moment from 'moment'; import * as d3 from 'd3'; import { appEvents, contextSrv } from 'app/core/core'; import * as ticksUtils from 'app/core/utils/ticks'; @@ -8,6 +7,7 @@ import { HeatmapTooltip } from './heatmap_tooltip'; import { mergeZeroBuckets } from './heatmap_data_converter'; import { getColorScale, getOpacityScale } from './color_scale'; import { GrafanaThemeType, getColorFromHexRgbOrName, getValueFormat } from '@grafana/ui'; +import { toUtc } from '@grafana/ui/src/utils/moment_wrapper'; const MIN_CARD_SIZE = 1, CARD_PADDING = 1, @@ -713,8 +713,8 @@ export class HeatmapRenderer { const timeTo = this.xScale.invert(Math.max(this.selection.x1, this.selection.x2) - this.yAxisWidth); this.ctrl.timeSrv.setTime({ - from: moment.utc(timeFrom), - to: moment.utc(timeTo), + from: toUtc(timeFrom), + to: toUtc(timeTo), }); } diff --git a/public/app/plugins/panel/heatmap/specs/heatmap_ctrl.test.ts b/public/app/plugins/panel/heatmap/specs/heatmap_ctrl.test.ts index a51be8eb7232f..3eb4eb3812108 100644 --- a/public/app/plugins/panel/heatmap/specs/heatmap_ctrl.test.ts +++ b/public/app/plugins/panel/heatmap/specs/heatmap_ctrl.test.ts @@ -1,5 +1,5 @@ -import moment from 'moment'; import { HeatmapCtrl } from '../heatmap_ctrl'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; describe('HeatmapCtrl', () => { const ctx = {} as any; @@ -32,7 +32,7 @@ describe('HeatmapCtrl', () => { }, ]; - ctx.ctrl.range = { from: moment().valueOf(), to: moment().valueOf() }; + ctx.ctrl.range = { from: dateTime().valueOf(), to: dateTime().valueOf() }; ctx.ctrl.onDataReceived(data); }); @@ -44,10 +44,10 @@ describe('HeatmapCtrl', () => { describe('when time series are inside range', () => { beforeEach(() => { const range = { - from: moment() + from: dateTime() .subtract(1, 'days') .valueOf(), - to: moment().valueOf(), + to: dateTime().valueOf(), }; const data = [ diff --git a/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx b/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx index c3c3582e70a34..2576b21c68165 100644 --- a/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx +++ b/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx @@ -8,6 +8,7 @@ import { FieldDisplayOptions, FieldPropertiesEditor, Field, + PanelOptionsGroup, } from '@grafana/ui'; import { PieChartOptionsBox } from './PieChartOptionsBox'; @@ -40,14 +41,13 @@ export class PieChartPanelEditor extends PureComponent - - - + + + + + + + diff --git a/public/app/plugins/panel/piechart/plugin.json b/public/app/plugins/panel/piechart/plugin.json index cf9ac759653ef..f7949a76af4c9 100644 --- a/public/app/plugins/panel/piechart/plugin.json +++ b/public/app/plugins/panel/piechart/plugin.json @@ -4,8 +4,6 @@ "id": "piechart", "state": "alpha", - "dataFormats": ["time_series"], - "info": { "author": { "name": "Grafana Project", diff --git a/public/app/plugins/panel/pluginlist/plugin.json b/public/app/plugins/panel/pluginlist/plugin.json index 95166f57e9e6c..84369cecd7c97 100644 --- a/public/app/plugins/panel/pluginlist/plugin.json +++ b/public/app/plugins/panel/pluginlist/plugin.json @@ -3,7 +3,7 @@ "name": "Plugin list", "id": "pluginlist", - "dataFormats": [], + "skipDataQuery": true, "info": { "description": "Plugin List for Grafana", diff --git a/public/app/plugins/panel/singlestat/plugin.json b/public/app/plugins/panel/singlestat/plugin.json index 3f803a6b270c8..96def09282778 100644 --- a/public/app/plugins/panel/singlestat/plugin.json +++ b/public/app/plugins/panel/singlestat/plugin.json @@ -3,8 +3,6 @@ "name": "Singlestat", "id": "singlestat", - "dataFormats": ["time_series", "table"], - "info": { "description": "Singlestat Panel for Grafana", "author": { diff --git a/public/app/plugins/panel/singlestat/specs/singlestat.test.ts b/public/app/plugins/panel/singlestat/specs/singlestat.test.ts index 902d722f0adfc..3f10d3ce9f675 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat.test.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat.test.ts @@ -1,5 +1,5 @@ import { SingleStatCtrl } from '../module'; -import moment from 'moment'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; describe('SingleStatCtrl', () => { const ctx = {} as any; @@ -89,7 +89,7 @@ describe('SingleStatCtrl', () => { }); it('should set formatted value', () => { - expect(moment(ctx.data.valueFormatted).valueOf()).toBe(1505634997000); + expect(dateTime(ctx.data.valueFormatted).valueOf()).toBe(1505634997000); }); }); @@ -120,7 +120,7 @@ describe('SingleStatCtrl', () => { }); it('should set formatted value', () => { - expect(ctx.data.valueFormatted).toBe(moment(1505634997920).format('MM/DD/YYYY h:mm:ss a')); + expect(ctx.data.valueFormatted).toBe(dateTime(1505634997920).format('MM/DD/YYYY h:mm:ss a')); }); }); diff --git a/public/app/plugins/panel/singlestat2/SingleStatEditor.tsx b/public/app/plugins/panel/singlestat2/SingleStatEditor.tsx index c348dc95db1cb..232030dd12b83 100644 --- a/public/app/plugins/panel/singlestat2/SingleStatEditor.tsx +++ b/public/app/plugins/panel/singlestat2/SingleStatEditor.tsx @@ -11,6 +11,7 @@ import { FieldDisplayEditor, FieldPropertiesEditor, Field, + PanelOptionsGroup, } from '@grafana/ui'; import { SingleStatOptions, SparklineOptions } from './types'; @@ -57,14 +58,13 @@ export class SingleStatEditor extends PureComponent - + + + - + + + diff --git a/public/app/plugins/panel/singlestat2/plugin.json b/public/app/plugins/panel/singlestat2/plugin.json index 6828399ec2bf0..670bd67643d49 100644 --- a/public/app/plugins/panel/singlestat2/plugin.json +++ b/public/app/plugins/panel/singlestat2/plugin.json @@ -4,8 +4,6 @@ "id": "singlestat2", "state": "alpha", - "dataFormats": ["time_series", "table"], - "info": { "description": "Singlestat Panel for Grafana", "author": { diff --git a/public/app/plugins/panel/table/plugin.json b/public/app/plugins/panel/table/plugin.json index 6177a3d2695fd..2b96af1af4882 100644 --- a/public/app/plugins/panel/table/plugin.json +++ b/public/app/plugins/panel/table/plugin.json @@ -3,8 +3,6 @@ "name": "Table", "id": "table", - "dataFormats": ["table", "time_series"], - "info": { "description": "Table Panel for Grafana", "author": { diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts index c14e031273774..b138415eb0e60 100644 --- a/public/app/plugins/panel/table/renderer.ts +++ b/public/app/plugins/panel/table/renderer.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; -import moment from 'moment'; import { getValueFormat, getColorFromHexRgbOrName, GrafanaThemeType, stringToJsRegex } from '@grafana/ui'; import { ColumnStyle } from '@grafana/ui/src/components/Table/TableCellBuilder'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; export class TableRenderer { formatters: any[]; @@ -105,7 +105,7 @@ export class TableRenderer { v = parseInt(v, 10); } - let date = moment(v); + let date = dateTime(v); if (this.isUtc) { date = date.utc(); diff --git a/public/app/plugins/panel/table2/plugin.json b/public/app/plugins/panel/table2/plugin.json index 4fa7728bd5521..4855b3880f77c 100644 --- a/public/app/plugins/panel/table2/plugin.json +++ b/public/app/plugins/panel/table2/plugin.json @@ -4,8 +4,6 @@ "id": "table2", "state": "alpha", - "dataFormats": ["table"], - "info": { "author": { "name": "Grafana Project", diff --git a/public/app/plugins/panel/text/plugin.json b/public/app/plugins/panel/text/plugin.json index e0f03f580e4be..3444f92cd4282 100644 --- a/public/app/plugins/panel/text/plugin.json +++ b/public/app/plugins/panel/text/plugin.json @@ -3,7 +3,7 @@ "name": "Text", "id": "text", - "dataFormats": [], + "skipDataQuery": true, "info": { "author": { diff --git a/public/app/plugins/panel/text2/plugin.json b/public/app/plugins/panel/text2/plugin.json index 5afecd424d70a..ee63a18555bd1 100644 --- a/public/app/plugins/panel/text2/plugin.json +++ b/public/app/plugins/panel/text2/plugin.json @@ -4,7 +4,7 @@ "id": "text2", "state": "alpha", - "dataFormats": [], + "skipDataQuery": true, "info": { "author": { diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index c057785bb9019..404e53c283205 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -10,6 +10,7 @@ import { ExploreStartPageProps, LogLevel, TimeRange, + DataQueryError, } from '@grafana/ui'; import { Emitter, TimeSeries } from 'app/core/core'; @@ -178,14 +179,6 @@ export interface ExploreItemState { * Needs to be updated when `datasourceInstance` or `containerWidth` is changed. */ queryIntervals: QueryIntervals; - /** - * List of query transaction to track query duration and query result. - * Graph/Logs/Table results are calculated on the fly from the transaction, - * based on the transaction's result types. Transaction also holds the row index - * so that results can be dropped and re-computed without running queries again - * when query rows are removed. - */ - queryTransactions: QueryTransaction[]; /** * Time range for this Explore. Managed by the time picker and used by all query runs. */ @@ -230,6 +223,10 @@ export interface ExploreItemState { * True if `datasourceInstance` supports table queries. */ supportsTable: boolean | null; + + graphIsLoading: boolean; + logIsLoading: boolean; + tableIsLoading: boolean; /** * Table model that combines all query table results into a single table. */ @@ -258,6 +255,9 @@ export interface ExploreItemState { urlState: ExploreUrlState; update: ExploreUpdateState; + + queryErrors: DataQueryError[]; + latency: number; } export interface ExploreUpdateState { @@ -332,10 +332,9 @@ export interface QueryTransaction { hints?: QueryHint[]; latency: number; options: any; - query: DataQuery; + queries: DataQuery[]; result?: any; // Table model / Timeseries[] / Logs resultType: ResultType; - rowIndex: number; scanning?: boolean; } diff --git a/public/e2e-test/core/constants.ts b/public/e2e-test/core/constants.ts new file mode 100644 index 0000000000000..e91ad2a3a0712 --- /dev/null +++ b/public/e2e-test/core/constants.ts @@ -0,0 +1,6 @@ +export const constants = { + baseUrl: process.env.BASE_URL || 'http://localhost:3000', + chromiumRevision: '650629', + screenShotsTruthDir: './public/e2e-test/screenShots/theTruth', + screenShotsOutputDir: './public/e2e-test/screenShots/theOutput', +}; diff --git a/public/e2e-test/core/images.ts b/public/e2e-test/core/images.ts new file mode 100644 index 0000000000000..eb4ca3538d232 --- /dev/null +++ b/public/e2e-test/core/images.ts @@ -0,0 +1,51 @@ +import fs from 'fs'; +import { PNG } from 'pngjs'; +import { Page } from 'puppeteer-core'; +import pixelmatch from 'pixelmatch'; + +import { constants } from './constants'; + +export const takeScreenShot = async (page: Page, fileName: string) => { + const outputFolderExists = fs.existsSync(constants.screenShotsOutputDir); + if (!outputFolderExists) { + fs.mkdirSync(constants.screenShotsOutputDir); + } + const path = `${constants.screenShotsOutputDir}/${fileName}.png`; + await page.screenshot({ path, type: 'png', fullPage: false }); +}; + +export const compareScreenShots = async (fileName: string) => + new Promise(resolve => { + let filesRead = 0; + + const doneReading = () => { + if (++filesRead < 2) { + return; + } + + expect(screenShotFromTest.width).toEqual(screenShotFromTruth.width); + expect(screenShotFromTest.height).toEqual(screenShotFromTruth.height); + + const diff = new PNG({ width: screenShotFromTest.width, height: screenShotFromTruth.height }); + const numDiffPixels = pixelmatch( + screenShotFromTest.data, + screenShotFromTruth.data, + diff.data, + screenShotFromTest.width, + screenShotFromTest.height, + { threshold: 0.1 } + ); + + expect(numDiffPixels).toBe(0); + resolve(); + }; + + const screenShotFromTest = fs + .createReadStream(`${constants.screenShotsOutputDir}/${fileName}.png`) + .pipe(new PNG()) + .on('parsed', doneReading); + const screenShotFromTruth = fs + .createReadStream(`${constants.screenShotsTruthDir}/${fileName}.png`) + .pipe(new PNG()) + .on('parsed', doneReading); + }); diff --git a/public/e2e-test/core/launcher.ts b/public/e2e-test/core/launcher.ts new file mode 100644 index 0000000000000..7f81e8c30ee42 --- /dev/null +++ b/public/e2e-test/core/launcher.ts @@ -0,0 +1,29 @@ +import puppeteer, { Browser } from 'puppeteer-core'; + +export const launchBrowser = async (): Promise => { + const browserFetcher = puppeteer.createBrowserFetcher(); + const localRevisions = await browserFetcher.localRevisions(); + if (localRevisions.length === 0) { + throw new Error('Could not launch browser because there is no local revisions.'); + } + + let executablePath = null; + executablePath = browserFetcher.revisionInfo(localRevisions[0]).executablePath; + + const browser = await puppeteer.launch({ + headless: process.env.BROWSER ? false : true, + slowMo: process.env.SLOWMO ? 100 : 0, + defaultViewport: { + width: 1920, + height: 1080, + deviceScaleFactor: 1, + isMobile: false, + hasTouch: false, + isLandscape: false, + }, + args: ['--start-fullscreen'], + executablePath, + }); + + return browser; +}; diff --git a/public/e2e-test/core/login.ts b/public/e2e-test/core/login.ts new file mode 100644 index 0000000000000..bdf57e384242c --- /dev/null +++ b/public/e2e-test/core/login.ts @@ -0,0 +1,22 @@ +import { Page } from 'puppeteer-core'; + +import { constants } from './constants'; +import { loginPage } from 'e2e-test/pages/start/loginPage'; + +export const login = async (page: Page) => { + await loginPage.init(page); + await loginPage.navigateTo(); + + await loginPage.pageObjects.username.enter('admin'); + await loginPage.pageObjects.password.enter('admin'); + await loginPage.pageObjects.submit.click(); + await loginPage.waitForResponse(); +}; + +export const ensureLoggedIn = async (page: Page) => { + await page.goto(`${constants.baseUrl}`); + if (page.url().indexOf('login') > -1) { + console.log('Redirected to login page. Logging in...'); + await login(page); + } +}; diff --git a/public/e2e-test/core/pageObjects.ts b/public/e2e-test/core/pageObjects.ts new file mode 100644 index 0000000000000..5c60b8d0547f0 --- /dev/null +++ b/public/e2e-test/core/pageObjects.ts @@ -0,0 +1,84 @@ +import { Page } from 'puppeteer-core'; + +export class Selector { + static fromAriaLabel = (selector: string) => { + return `[aria-label="${selector}"]`; + }; + + static fromSelector = (selector: string) => { + return selector; + }; +} + +export interface PageObjectType { + init: (page: Page) => Promise; + exists: () => Promise; + containsText: (text: string) => Promise; +} + +export interface ClickablePageObjectType extends PageObjectType { + click: () => Promise; +} + +export interface InputPageObjectType extends PageObjectType { + enter: (text: string) => Promise; +} + +export interface SelectPageObjectType extends PageObjectType { + select: (text: string) => Promise; +} + +export class PageObject implements PageObjectType { + protected page: Page = null; + + constructor(protected selector: string) {} + + init = async (page: Page): Promise => { + this.page = page; + }; + + exists = async (): Promise => { + const options = { visible: true } as any; + await expect(this.page).not.toBeNull(); + await expect(this.page).toMatchElement(this.selector, options); + }; + + containsText = async (text: string): Promise => { + const options = { visible: true, text } as any; + await expect(this.page).not.toBeNull(); + await expect(this.page).toMatchElement(this.selector, options); + }; +} + +export class ClickablePageObject extends PageObject implements ClickablePageObjectType { + constructor(selector: string) { + super(selector); + } + + click = async (): Promise => { + await expect(this.page).not.toBeNull(); + await expect(this.page).toClick(this.selector); + }; +} + +export class InputPageObject extends PageObject implements InputPageObjectType { + constructor(selector: string) { + super(selector); + } + + enter = async (text: string): Promise => { + await expect(this.page).not.toBeNull(); + await expect(this.page).toFill(this.selector, text); + }; +} + +export class SelectPageObject extends PageObject implements SelectPageObjectType { + constructor(selector: string) { + super(selector); + } + + select = async (text: string): Promise => { + await expect(this.page).not.toBeNull(); + await this.page.select(this.selector, text); + }; +} diff --git a/public/e2e-test/core/pages.ts b/public/e2e-test/core/pages.ts new file mode 100644 index 0000000000000..2c64b2729a5a3 --- /dev/null +++ b/public/e2e-test/core/pages.ts @@ -0,0 +1,110 @@ +import { Page } from 'puppeteer-core'; +import { constants } from './constants'; +import { PageObject } from './pageObjects'; + +export interface ExpectSelectorConfig { + selector: string; + containsText?: string; + isVisible?: boolean; +} + +export interface TestPageType { + init: (page: Page) => Promise; + getUrl: () => Promise; + getUrlWithoutBaseUrl: () => Promise; + navigateTo: () => Promise; + expectSelector: (config: ExpectSelectorConfig) => Promise; + waitForResponse: () => Promise; + waitForNavigation: () => Promise; + waitFor: (milliseconds: number) => Promise; + pageObjects: PageObjects; +} + +type PageObjects = { [P in keyof T]: T[P] }; + +export interface TestPageConfig { + url?: string; + pageObjects?: PageObjects; +} + +export class TestPage implements TestPageType { + pageObjects: PageObjects = null; + private page: Page = null; + private pageUrl: string = null; + + constructor(config: TestPageConfig) { + if (config.url) { + this.pageUrl = `${constants.baseUrl}${config.url}`; + } + if (config.pageObjects) { + this.pageObjects = config.pageObjects; + } + } + + init = async (page: Page): Promise => { + this.page = page; + + if (!this.pageObjects) { + return; + } + + Object.keys(this.pageObjects).forEach(key => { + const pageObject: PageObject = this.pageObjects[key]; + pageObject.init(page); + }); + }; + + navigateTo = async (): Promise => { + this.throwIfNotInitialized(); + + await this.page.goto(this.pageUrl); + }; + + expectSelector = async (config: ExpectSelectorConfig): Promise => { + this.throwIfNotInitialized(); + + const { selector, containsText, isVisible } = config; + const visible = isVisible || true; + const text = containsText; + const options = { visible, text } as any; + await expect(this.page).toMatchElement(selector, options); + }; + + waitForResponse = async (): Promise => { + this.throwIfNotInitialized(); + + await this.page.waitForResponse(response => response.url() === this.pageUrl && response.status() === 200); + }; + + waitForNavigation = async (): Promise => { + this.throwIfNotInitialized(); + + await this.page.waitForNavigation(); + }; + + getUrl = async (): Promise => { + this.throwIfNotInitialized(); + + return await this.page.url(); + }; + + getUrlWithoutBaseUrl = async (): Promise => { + this.throwIfNotInitialized(); + + const url = await this.getUrl(); + + return url.replace(constants.baseUrl, ''); + }; + + waitFor = async (milliseconds: number) => { + this.throwIfNotInitialized(); + + await this.page.waitFor(milliseconds); + }; + + private throwIfNotInitialized = () => { + if (!this.page) { + throw new Error('pageFactory has not been initilized, did you forget to call init with a page?'); + } + }; +} diff --git a/public/e2e-test/core/scenario.ts b/public/e2e-test/core/scenario.ts new file mode 100644 index 0000000000000..a16f1e467cefb --- /dev/null +++ b/public/e2e-test/core/scenario.ts @@ -0,0 +1,30 @@ +import { Browser, Page } from 'puppeteer-core'; +import { launchBrowser } from './launcher'; +import { ensureLoggedIn } from './login'; + +export const e2eScenario = ( + title: string, + testDescription: string, + callback: (browser: Browser, page: Page) => void +) => { + describe(title, () => { + let browser: Browser = null; + let page: Page = null; + + beforeAll(async () => { + browser = await launchBrowser(); + page = await browser.newPage(); + await ensureLoggedIn(page); + }); + + afterAll(async () => { + if (browser) { + await browser.close(); + } + }); + + it(testDescription, async () => { + await callback(browser, page); + }); + }); +}; diff --git a/public/e2e-test/install/install.ts b/public/e2e-test/install/install.ts new file mode 100644 index 0000000000000..fa71acfb7851c --- /dev/null +++ b/public/e2e-test/install/install.ts @@ -0,0 +1,22 @@ +import puppeteer from 'puppeteer-core'; +import { constants } from 'e2e-test/core/constants'; + +export const downloadBrowserIfNeeded = async (): Promise => { + const browserFetcher = puppeteer.createBrowserFetcher(); + const localRevisions = await browserFetcher.localRevisions(); + if (localRevisions && localRevisions.length > 0) { + console.log('Found a local revision for browser, exiting install.'); + return; + } + + console.log('Did not find any local revisions for browser, downloading latest this might take a while.'); + await browserFetcher.download(constants.chromiumRevision, (downloaded, total) => { + console.log(`Downloaded ${downloaded}bytes of ${total}bytes.`); + }); +}; + +beforeAll(async () => { + console.log('Checking Chromium'); + jest.setTimeout(60 * 1000); + await downloadBrowserIfNeeded(); +}); diff --git a/public/e2e-test/pages/dashboards/createDashboardPage.ts b/public/e2e-test/pages/dashboards/createDashboardPage.ts new file mode 100644 index 0000000000000..de6a0b36bff7f --- /dev/null +++ b/public/e2e-test/pages/dashboards/createDashboardPage.ts @@ -0,0 +1,13 @@ +import { ClickablePageObjectType, ClickablePageObject, Selector } from 'e2e-test/core/pageObjects'; +import { TestPage } from 'e2e-test/core/pages'; + +export interface CreateDashboardPage { + addQuery: ClickablePageObjectType; +} + +export const createDashboardPage = new TestPage({ + url: '/dashboard/new', + pageObjects: { + addQuery: new ClickablePageObject(Selector.fromAriaLabel('Add Query CTA button')), + }, +}); diff --git a/public/e2e-test/pages/dashboards/dashboardsPage.ts b/public/e2e-test/pages/dashboards/dashboardsPage.ts new file mode 100644 index 0000000000000..be14e5e23ce4e --- /dev/null +++ b/public/e2e-test/pages/dashboards/dashboardsPage.ts @@ -0,0 +1,14 @@ +import { ClickablePageObjectType, ClickablePageObject, Selector } from 'e2e-test/core/pageObjects'; +import { TestPage } from 'e2e-test/core/pages'; + +export interface DashboardsPage { + dashboard: ClickablePageObjectType; +} + +export const dashboardsPageFactory = (dashboardTitle: string) => + new TestPage({ + url: '/dashboards', + pageObjects: { + dashboard: new ClickablePageObject(Selector.fromAriaLabel(dashboardTitle)), + }, + }); diff --git a/public/e2e-test/pages/dashboards/saveDashboardModal.ts b/public/e2e-test/pages/dashboards/saveDashboardModal.ts new file mode 100644 index 0000000000000..3340ca5c708cb --- /dev/null +++ b/public/e2e-test/pages/dashboards/saveDashboardModal.ts @@ -0,0 +1,20 @@ +import { + ClickablePageObjectType, + ClickablePageObject, + Selector, + InputPageObjectType, + InputPageObject, +} from 'e2e-test/core/pageObjects'; +import { TestPage } from 'e2e-test/core/pages'; + +export interface SaveDashboardModal { + name: InputPageObjectType; + save: ClickablePageObjectType; +} + +export const saveDashboardModal = new TestPage({ + pageObjects: { + name: new InputPageObject(Selector.fromAriaLabel('Save dashboard title field')), + save: new ClickablePageObject(Selector.fromAriaLabel('Save dashboard button')), + }, +}); diff --git a/public/e2e-test/pages/datasources/addDataSourcePage.ts b/public/e2e-test/pages/datasources/addDataSourcePage.ts new file mode 100644 index 0000000000000..9ae8972f54212 --- /dev/null +++ b/public/e2e-test/pages/datasources/addDataSourcePage.ts @@ -0,0 +1,13 @@ +import { ClickablePageObject, Selector, ClickablePageObjectType } from 'e2e-test/core/pageObjects'; +import { TestPage } from 'e2e-test/core/pages'; + +export interface AddDataSourcePage { + testDataDB: ClickablePageObjectType; +} + +export const addDataSourcePage = new TestPage({ + url: '/datasources/new', + pageObjects: { + testDataDB: new ClickablePageObject(Selector.fromAriaLabel('TestData DB datasource plugin')), + }, +}); diff --git a/public/e2e-test/pages/datasources/dataSources.ts b/public/e2e-test/pages/datasources/dataSources.ts new file mode 100644 index 0000000000000..114b3017abfe0 --- /dev/null +++ b/public/e2e-test/pages/datasources/dataSources.ts @@ -0,0 +1,7 @@ +import { TestPage } from 'e2e-test/core/pages'; + +export interface DataSourcesPage {} + +export const dataSourcesPage = new TestPage({ + url: '/datasources', +}); diff --git a/public/e2e-test/pages/datasources/editDataSourcePage.ts b/public/e2e-test/pages/datasources/editDataSourcePage.ts new file mode 100644 index 0000000000000..9e9dd58dd8c56 --- /dev/null +++ b/public/e2e-test/pages/datasources/editDataSourcePage.ts @@ -0,0 +1,22 @@ +import { + ClickablePageObjectType, + PageObjectType, + ClickablePageObject, + PageObject, + Selector, +} from 'e2e-test/core/pageObjects'; +import { TestPage } from 'e2e-test/core/pages'; + +export interface EditDataSourcePage { + saveAndTest: ClickablePageObjectType; + alert: PageObjectType; + alertMessage: PageObjectType; +} + +export const editDataSourcePage = new TestPage({ + pageObjects: { + saveAndTest: new ClickablePageObject(Selector.fromAriaLabel('Save and Test button')), + alert: new PageObject(Selector.fromAriaLabel('Datasource settings page Alert')), + alertMessage: new PageObject(Selector.fromAriaLabel('Datasource settings page Alert message')), + }, +}); diff --git a/public/e2e-test/pages/panels/editPanel.ts b/public/e2e-test/pages/panels/editPanel.ts new file mode 100644 index 0000000000000..ff1263baa2e5a --- /dev/null +++ b/public/e2e-test/pages/panels/editPanel.ts @@ -0,0 +1,26 @@ +import { + SelectPageObjectType, + SelectPageObject, + Selector, + ClickablePageObjectType, + ClickablePageObject, +} from 'e2e-test/core/pageObjects'; +import { TestPage } from 'e2e-test/core/pages'; + +export interface EditPanelPage { + queriesTab: ClickablePageObjectType; + saveDashboard: ClickablePageObjectType; + scenarioSelect: SelectPageObjectType; + showXAxis: ClickablePageObjectType; + visualizationTab: ClickablePageObjectType; +} + +export const editPanelPage = new TestPage({ + pageObjects: { + queriesTab: new ClickablePageObject(Selector.fromAriaLabel('Queries tab button')), + saveDashboard: new ClickablePageObject(Selector.fromAriaLabel('Save dashboard navbar button')), + scenarioSelect: new SelectPageObject(Selector.fromAriaLabel('Scenario Select')), + showXAxis: new ClickablePageObject(Selector.fromSelector('[aria-label="X-Axis section"] > gf-form-switch')), + visualizationTab: new ClickablePageObject(Selector.fromAriaLabel('Visualization tab button')), + }, +}); diff --git a/public/e2e-test/pages/panels/panel.ts b/public/e2e-test/pages/panels/panel.ts new file mode 100644 index 0000000000000..6a59c0d6f2baf --- /dev/null +++ b/public/e2e-test/pages/panels/panel.ts @@ -0,0 +1,14 @@ +import { ClickablePageObjectType, ClickablePageObject, Selector } from 'e2e-test/core/pageObjects'; +import { TestPage } from 'e2e-test/core/pages'; + +export interface Panel { + panelTitle: ClickablePageObjectType; + share: ClickablePageObjectType; +} + +export const panel = new TestPage({ + pageObjects: { + panelTitle: new ClickablePageObject(Selector.fromAriaLabel('Panel Title')), + share: new ClickablePageObject(Selector.fromAriaLabel('Share panel menu item')), + }, +}); diff --git a/public/e2e-test/pages/panels/sharePanelModal.ts b/public/e2e-test/pages/panels/sharePanelModal.ts new file mode 100644 index 0000000000000..3ead88ad536e5 --- /dev/null +++ b/public/e2e-test/pages/panels/sharePanelModal.ts @@ -0,0 +1,12 @@ +import { ClickablePageObjectType, ClickablePageObject, Selector } from 'e2e-test/core/pageObjects'; +import { TestPage } from 'e2e-test/core/pages'; + +export interface SharePanelModal { + directLinkRenderedImage: ClickablePageObjectType; +} + +export const sharePanelModal = new TestPage({ + pageObjects: { + directLinkRenderedImage: new ClickablePageObject(Selector.fromAriaLabel('Link to rendered image')), + }, +}); diff --git a/public/e2e-test/pages/start/loginPage.ts b/public/e2e-test/pages/start/loginPage.ts new file mode 100644 index 0000000000000..7d4d31b8506c3 --- /dev/null +++ b/public/e2e-test/pages/start/loginPage.ts @@ -0,0 +1,23 @@ +import { + InputPageObject, + ClickablePageObject, + Selector, + InputPageObjectType, + ClickablePageObjectType, +} from 'e2e-test/core/pageObjects'; +import { TestPage } from 'e2e-test/core/pages'; + +export interface LoginPage { + username: InputPageObjectType; + password: InputPageObjectType; + submit: ClickablePageObjectType; +} + +export const loginPage = new TestPage({ + url: '/login', + pageObjects: { + username: new InputPageObject(Selector.fromAriaLabel('Username input field')), + password: new InputPageObject(Selector.fromAriaLabel('Password input field')), + submit: new ClickablePageObject(Selector.fromAriaLabel('Login button')), + }, +}); diff --git a/public/e2e-test/scenarios/smoke.test.ts b/public/e2e-test/scenarios/smoke.test.ts new file mode 100644 index 0000000000000..1fb4aa10d2995 --- /dev/null +++ b/public/e2e-test/scenarios/smoke.test.ts @@ -0,0 +1,85 @@ +import { Browser, Page, Target } from 'puppeteer-core'; + +import { e2eScenario } from 'e2e-test/core/scenario'; +import { addDataSourcePage } from 'e2e-test/pages/datasources/addDataSourcePage'; +import { editDataSourcePage } from 'e2e-test/pages/datasources/editDataSourcePage'; +import { dataSourcesPage } from 'e2e-test/pages/datasources/dataSources'; +import { createDashboardPage } from 'e2e-test/pages/dashboards/createDashboardPage'; +import { saveDashboardModal } from 'e2e-test/pages/dashboards/saveDashboardModal'; +import { dashboardsPageFactory } from 'e2e-test/pages/dashboards/dashboardsPage'; +import { panel } from 'e2e-test/pages/panels/panel'; +import { editPanelPage } from 'e2e-test/pages/panels/editPanel'; +import { constants } from 'e2e-test/core/constants'; +import { sharePanelModal } from 'e2e-test/pages/panels/sharePanelModal'; +import { takeScreenShot, compareScreenShots } from 'e2e-test/core/images'; + +e2eScenario( + 'Login scenario, create test data source, dashboard, panel, and export scenario', + 'should pass', + async (browser: Browser, page: Page) => { + // Add TestData DB + await addDataSourcePage.init(page); + await addDataSourcePage.navigateTo(); + await addDataSourcePage.pageObjects.testDataDB.exists(); + await addDataSourcePage.pageObjects.testDataDB.click(); + + await editDataSourcePage.init(page); + await editDataSourcePage.waitForNavigation(); + await editDataSourcePage.pageObjects.saveAndTest.click(); + await editDataSourcePage.pageObjects.alert.exists(); + await editDataSourcePage.pageObjects.alertMessage.containsText('Data source is working'); + + // Verify that data source is listed + const url = await editDataSourcePage.getUrlWithoutBaseUrl(); + const expectedUrl = url.substring(1, url.length - 1); + const selector = `a[href="${expectedUrl}"]`; + + await dataSourcesPage.init(page); + await dataSourcesPage.navigateTo(); + await dataSourcesPage.expectSelector({ selector }); + + // Create a new Dashboard + await createDashboardPage.init(page); + await createDashboardPage.navigateTo(); + await createDashboardPage.pageObjects.addQuery.click(); + + await editPanelPage.init(page); + await editPanelPage.waitForNavigation(); + await editPanelPage.pageObjects.queriesTab.click(); + await editPanelPage.pageObjects.scenarioSelect.select('string:csv_metric_values'); + await editPanelPage.pageObjects.visualizationTab.click(); + await editPanelPage.pageObjects.showXAxis.click(); + await editPanelPage.pageObjects.saveDashboard.click(); + + // Confirm save modal + await saveDashboardModal.init(page); + await saveDashboardModal.expectSelector({ selector: 'save-dashboard-as-modal' }); + const dashboardTitle = new Date().toISOString(); + await saveDashboardModal.pageObjects.name.enter(dashboardTitle); + await saveDashboardModal.pageObjects.save.click(); + + // Share the dashboard + const dashboardsPage = dashboardsPageFactory(dashboardTitle); + await dashboardsPage.init(page); + await dashboardsPage.navigateTo(); + await dashboardsPage.pageObjects.dashboard.exists(); + await dashboardsPage.pageObjects.dashboard.click(); + + await panel.init(page); + await panel.pageObjects.panelTitle.click(); + await panel.pageObjects.share.click(); + + // Verify that a new tab is opened + const targetPromise = new Promise(resolve => browser.once('targetcreated', resolve)); + await sharePanelModal.init(page); + await sharePanelModal.pageObjects.directLinkRenderedImage.click(); + const newTarget: Target = (await targetPromise) as Target; + expect(newTarget.url()).toContain(`${constants.baseUrl}/render/d-solo`); + + // Take snapshot of page + const newPage = await newTarget.page(); + const fileName = 'smoke-test-scenario'; + await takeScreenShot(newPage, fileName); + await compareScreenShots(fileName); + } +); diff --git a/public/e2e-test/screenShots/theTruth/smoke-test-scenario.png b/public/e2e-test/screenShots/theTruth/smoke-test-scenario.png new file mode 100644 index 0000000000000..8ea1294d4d6c1 Binary files /dev/null and b/public/e2e-test/screenShots/theTruth/smoke-test-scenario.png differ diff --git a/public/sass/components/_add_data_source.scss b/public/sass/components/_add_data_source.scss index 4046be2a7230d..c14455d35c14d 100644 --- a/public/sass/components/_add_data_source.scss +++ b/public/sass/components/_add_data_source.scss @@ -10,37 +10,74 @@ margin-bottom: $space-lg; } -.add-data-source-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - grid-row-gap: 10px; - grid-column-gap: 10px; - - @include media-breakpoint-up(md) { - grid-template-columns: repeat(3, 1fr); - } +.add-data-source-category { + margin-bottom: $space-md; } -.add-data-source-grid-item { - padding: 15px; +.add-data-source-category__header { + font-size: $font-size-h5; + margin-bottom: $space-sm; +} + +.add-data-source-item { + padding: $space-md; display: flex; align-items: center; cursor: pointer; - background: $card-background; box-shadow: $card-shadow; - color: $text-color; + background: $panel-editor-viz-item-bg; + border: 1px solid transparent; + border-radius: 3px; + margin-bottom: $space-xxs; &:hover { - background: $card-background-hover; + box-shadow: $panel-editor-viz-item-shadow-hover; + background: $panel-editor-viz-item-bg-hover; + border: $panel-editor-viz-item-border-hover; color: $text-color-strong; + + .add-data-source-item-actions { + opacity: 1; + transition: 0.15s opacity ease-in-out; + } } } -.add-data-source-grid-item-text { +.add-data-source-item-text-wrapper { + display: flex; + flex-direction: column; + flex-grow: 1; +} + +.add-data-source-item-desc { + font-size: $font-size-sm; + color: $text-color-weak; +} + +.add-data-source-item-text { font-size: $font-size-h5; } -.add-data-source-grid-item-logo { - margin: 0 $space-md; +.add-data-source-item-logo { + margin-right: $space-lg; + margin-left: $space-sm; width: 55px; + max-height: 55px; +} + +.add-data-source-item-actions { + opacity: 0; + padding-left: $space-md; + display: flex; + align-items: center; + + > button { + margin-left: $space-md; + cursor: pointer; + } +} + +.add-data-source-more { + text-align: center; + margin: $space-xl; } diff --git a/public/sass/components/_panel_gettingstarted.scss b/public/sass/components/_panel_gettingstarted.scss index b55d2cc056553..3afbe87e63ee1 100644 --- a/public/sass/components/_panel_gettingstarted.scss +++ b/public/sass/components/_panel_gettingstarted.scss @@ -13,18 +13,22 @@ $marker-size-half: ($marker-size / 2); $path-height: 2px !default; $path-position: $marker-size-half - ($path-height / 2); -.dashlist-cta-close-btn { +.progress-tracker-container { + height: 100%; + display: flex; + align-items: center; +} + +.progress-tracker-close-btn { color: $text-color-weak; - float: right; - padding: 0; - margin: 0 2px 0 0; + position: absolute; + z-index: $panel-header-z-index; + top: 8px; + right: 8px; + font-size: $font-size-lg; background-color: transparent; border: none; - i { - font-size: 80%; - } - &:hover { color: $white; } @@ -33,9 +37,9 @@ $path-position: $marker-size-half - ($path-height / 2); // Container element .progress-tracker { display: flex; - margin: 0 auto; + width: 100%; padding: 0; - list-style: none; + align-items: center; } // Step container that creates lines between steps @@ -46,6 +50,7 @@ $path-position: $marker-size-half - ($path-height / 2); margin: 0; padding: 0; color: $text-color-weak; + height: 84px; // For a flexbox bug in firefox that wont allow the text overflow on the text min-width: $marker-size; @@ -54,7 +59,7 @@ $path-position: $marker-size-half - ($path-height / 2); content: ''; display: block; position: absolute; - z-index: 1; + z-index: 0; top: $path-position; bottom: $path-position; right: -$marker-size-half; @@ -134,11 +139,10 @@ $path-position: $marker-size-half - ($path-height / 2); width: $marker-size; height: $marker-size; padding-bottom: 2px; // To align text within the marker - z-index: 20; + z-index: 1; background-color: $panel-bg; margin-left: auto; margin-right: auto; - margin-bottom: $spacer; color: $text-color-weak; font-size: 35px; vertical-align: sub; diff --git a/public/sass/components/_panel_header.scss b/public/sass/components/_panel_header.scss index 3d6b52dc71963..40afc2cd3aae4 100644 --- a/public/sass/components/_panel_header.scss +++ b/public/sass/components/_panel_header.scss @@ -126,6 +126,7 @@ $panel-header-no-title-zindex: 1; left: 0; width: $panel-header-height; height: $panel-header-height; + z-index: $panel-header-no-title-zindex + 1; top: 0; .fa { @@ -138,7 +139,8 @@ $panel-header-no-title-zindex: 1; &--info { display: block; - @include panel-corner-color(lighten($panel-corner, 4%)); + @include panel-corner-color(lighten($panel-corner, 6%)); + .fa:before { content: '\f129'; } @@ -146,7 +148,7 @@ $panel-header-no-title-zindex: 1; &--links { display: block; - @include panel-corner-color(lighten($panel-corner, 4%)); + @include panel-corner-color(lighten($panel-corner, 6%)); .fa { left: 4px; } diff --git a/public/sass/components/_panel_singlestat.scss b/public/sass/components/_panel_singlestat.scss index 708fd65de94da..7854ac2093fa5 100644 --- a/public/sass/components/_panel_singlestat.scss +++ b/public/sass/components/_panel_singlestat.scss @@ -6,16 +6,17 @@ } .singlestat-panel-value-container { - line-height: 1; + // line-height 0 is imporant here as the font-size is on this + // level but overriden one level deeper and but the line-height: is still + // based on the base font size on this level. Using line-height: 0 fixes that + line-height: 0; display: table-cell; vertical-align: middle; text-align: center; position: relative; z-index: 1; - font-size: 3em; font-weight: $font-weight-semi-bold; - // helps make the title feel more centered when there is a panel title - padding-bottom: $panel-padding; + font-size: 38px; } // Helps diff --git a/public/test/helpers/getQueryOptions.ts b/public/test/helpers/getQueryOptions.ts index 290c450b248f6..2d63e1667761a 100644 --- a/public/test/helpers/getQueryOptions.ts +++ b/public/test/helpers/getQueryOptions.ts @@ -1,11 +1,11 @@ import { DataQueryRequest, DataQuery } from '@grafana/ui'; -import moment from 'moment'; +import { dateTime } from '@grafana/ui/src/utils/moment_wrapper'; export function getQueryOptions( options: Partial> ): DataQueryRequest { const raw = { from: 'now', to: 'now-1h' }; - const range = { from: moment(), to: moment(), raw: raw }; + const range = { from: dateTime(), to: dateTime(), raw: raw }; const defaults: DataQueryRequest = { requestId: 'TEST', diff --git a/public/test/mocks/datasource_srv.ts b/public/test/mocks/datasource_srv.ts index 4a0e9809aad32..5c4d8944cb635 100644 --- a/public/test/mocks/datasource_srv.ts +++ b/public/test/mocks/datasource_srv.ts @@ -1,4 +1,4 @@ -import { DataSourceApi, DataQueryRequest, DataQueryResponse } from '@grafana/ui'; +import { DataSourceApi, DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings } from '@grafana/ui'; export class DatasourceSrvMock { constructor(private defaultDS: DataSourceApi, private datasources: { [name: string]: DataSourceApi }) { @@ -17,14 +17,15 @@ export class DatasourceSrvMock { } } -export class MockDataSourceApi implements DataSourceApi { - name: string; - +export class MockDataSourceApi extends DataSourceApi { result: DataQueryResponse = { data: [] }; queryResolver: Promise; - constructor(DataQueryResponse, name?: string) { - this.name = name ? name : 'MockDataSourceApi'; + constructor(name?: string, result?: DataQueryResponse) { + super({ name: name ? name : 'MockDataSourceApi' } as DataSourceInstanceSettings); + if (result) { + this.result = result; + } } query(request: DataQueryRequest): Promise { diff --git a/scripts/ci-frontend-metrics.sh b/scripts/ci-frontend-metrics.sh index 9c1c062e91c36..85464aff3e0a9 100755 --- a/scripts/ci-frontend-metrics.sh +++ b/scripts/ci-frontend-metrics.sh @@ -2,7 +2,7 @@ echo -e "Collecting code stats (typescript errors & more)" -ERROR_COUNT_LIMIT=5617 +ERROR_COUNT_LIMIT=5564 DIRECTIVES_LIMIT=172 CONTROLLERS_LIMIT=139 diff --git a/scripts/cli/index.ts b/scripts/cli/index.ts index 49e266a31f8f0..de7ced12dc866 100644 --- a/scripts/cli/index.ts +++ b/scripts/cli/index.ts @@ -6,6 +6,7 @@ import { buildTask } from './tasks/grafanaui.build'; import { releaseTask } from './tasks/grafanaui.release'; import { changelogTask } from './tasks/changelog'; import { cherryPickTask } from './tasks/cherrypick'; +import { precommitTask } from './tasks/precommit'; program.option('-d, --depreciate ', 'Inform about npm script deprecation', v => v.split(',')); @@ -64,6 +65,13 @@ program await execTask(cherryPickTask)({}); }); +program + .command('precommit') + .description('Executes checks') + .action(async cmd => { + await execTask(precommitTask)({}); + }); + program.parse(process.argv); if (program.depreciate && program.depreciate.length === 2) { diff --git a/scripts/cli/tasks/precommit.ts b/scripts/cli/tasks/precommit.ts new file mode 100644 index 0000000000000..b759aa414f20c --- /dev/null +++ b/scripts/cli/tasks/precommit.ts @@ -0,0 +1,81 @@ +import { Task, TaskRunner } from './task'; +import chalk from 'chalk'; +import get from 'lodash/get'; +import flatten from 'lodash/flatten'; +import execa = require('execa'); +const simpleGit = require('simple-git/promise')(process.cwd()); + +interface PrecommitOptions {} + +const tasks = { + lint: { + sass: ['newer:sasslint'], + core: ['newer:exec:tslintRoot'], + gui: ['newer:exec:tslintPackages'], + }, + typecheck: { + core: ['newer:exec:typecheckRoot'], + gui: ['newer:exec:typecheckPackages'], + }, + test: { + lint: { + ts: ['no-only-tests'], + go: ['no-focus-convey-tests'], + }, + }, +}; + +const precommitRunner: TaskRunner = async () => { + const status = await simpleGit.status(); + const sassFiles = status.files.filter( + file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.scss)$/g) || file.path.indexOf('.sass-lint.yml') > -1 + ); + + const tsFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.(ts|tsx))$/g)); + const testFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.test.(ts|tsx))$/g)); + const goTestFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\_test.go)$/g)); + const grafanaUiFiles = tsFiles.filter(file => (file.path as string).indexOf('grafana-ui') > -1); + + const grafanaUIFilesChangedOnly = tsFiles.length > 0 && tsFiles.length - grafanaUiFiles.length === 0; + const coreFilesChangedOnly = tsFiles.length > 0 && grafanaUiFiles.length === 0; + + const taskPaths = []; + + if (sassFiles.length > 0) { + taskPaths.push('lint.sass'); + } + + if (testFiles.length) { + taskPaths.push('test.lint.ts'); + } + + if (goTestFiles.length) { + taskPaths.push('test.lint.go'); + } + + if (tsFiles.length > 0) { + if (grafanaUIFilesChangedOnly) { + taskPaths.push('lint.gui', 'typecheck.core', 'typecheck.gui'); + } else if (coreFilesChangedOnly) { + taskPaths.push('lint.core', 'typecheck.core'); + } else { + taskPaths.push('lint.core', 'lint.gui', 'typecheck.core', 'typecheck.gui'); + } + } + + const gruntTasks = flatten(taskPaths.map(path => get(tasks, path))); + if (gruntTasks.length > 0) { + console.log(chalk.yellow(`Precommit checks: ${taskPaths.join(', ')}`)); + const task = execa('grunt', gruntTasks); + // @ts-ignore + const stream = task.stdout; + stream.pipe(process.stdout); + return task; + } + console.log(chalk.yellow('Skipping precommit checks, not front-end changes detected')); + return; +}; + +export const precommitTask = new Task(); +precommitTask.setName('Precommit task'); +precommitTask.setRunner(precommitRunner); diff --git a/scripts/cli/tasks/task.ts b/scripts/cli/tasks/task.ts index d88860b701724..cc7d77a0664e9 100644 --- a/scripts/cli/tasks/task.ts +++ b/scripts/cli/tasks/task.ts @@ -1,8 +1,8 @@ -export type TaskRunner = (options: T) => Promise; +export type TaskRunner = (options: T) => Promise; export class Task { name: string; - runner: (options: TOptions) => Promise; + runner: (options: TOptions) => Promise; options: TOptions; setName = name => { diff --git a/scripts/grunt/default_task.js b/scripts/grunt/default_task.js index 7619ef8601cde..95a2522ccfc51 100644 --- a/scripts/grunt/default_task.js +++ b/scripts/grunt/default_task.js @@ -31,15 +31,6 @@ module.exports = function(grunt) { 'newer:exec:typecheckRoot' ]); - // prettier-ignore - grunt.registerTask('precommit', [ - 'newer:sasslint', - 'typecheck', - 'tslint', - 'no-only-tests', - 'no-focus-convey-tests' - ]); - grunt.registerTask('no-only-tests', function() { var files = grunt.file.expand( 'public/**/*@(_specs|.test).@(ts|js|tsx|jsx)', @@ -65,4 +56,3 @@ module.exports = function(grunt) { }); } }; - diff --git a/tsconfig.json b/tsconfig.json index 35ebddc09aa09..7eaffeac07839 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,5 +34,11 @@ }, "skipLibCheck": true }, - "include": ["public/app/**/*.ts", "public/app/**/*.tsx", "public/test/**/*.ts", "public/vendor/**/*.ts"] + "include": [ + "public/app/**/*.ts", + "public/app/**/*.tsx", + "public/test/**/*.ts", + "public/vendor/**/*.ts", + "public/e2e-test/**/*.ts" + ] } diff --git a/tslint.json b/tslint.json index 4c7ea71366cd0..472d74b1b3c4a 100644 --- a/tslint.json +++ b/tslint.json @@ -2,14 +2,18 @@ "rules": { "array-type": [true, "array-simple"], "arrow-return-shorthand": true, - "ban": [true, - {"name": "Array", "message": "tsstyle#array-constructor"} + "ban": [ + true, + { "name": "Array", "message": "tsstyle#array-constructor" } + // { "name": "moment", "message": "Use 'dateTime' instead." } ], - "ban-types": [true, + "ban-types": [ + true, ["Object", "Use {} instead."], ["String", "Use 'string' instead."], ["Number", "Use 'number' instead."], - ["Boolean", "Use 'boolean' instead."] + ["Boolean", "Use 'boolean' instead."], + ["Moment", "Use 'DateTime' instead."] ], "interface-name": [true, "never-prefix"], "no-string-throw": true, @@ -71,6 +75,7 @@ "allow-pascal-case" ], "use-isnan": true, - "whitespace": [true, "check-branch", "check-decl", "check-type", "check-preblock"] + "whitespace": [true, "check-branch", "check-decl", "check-type", "check-preblock"], + "import-blacklist": [true, "moment"] } } diff --git a/yarn.lock b/yarn.lock index d4893e14f24c3..624f08d556b85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,7 +29,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@7.4.3", "@babel/core@^7.1.0", "@babel/core@^7.4.3": +"@babel/core@7.4.3": version "7.4.3" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.3.tgz#198d6d3af4567be3989550d97e068de94503074f" integrity sha512-oDpASqKFlbspQfzAE7yaeTmdljSH2ADIvBlb0RwbStltTuWa0+7CCI1fYVINNv9saHPa1W7oaKeuNuKj+RQCvA== @@ -49,12 +49,32 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.0.0", "@babel/generator@^7.2.2", "@babel/generator@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.0.tgz#c230e79589ae7a729fd4631b9ded4dc220418196" - integrity sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ== +"@babel/core@^7.1.0", "@babel/core@^7.4.3": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.4.tgz#84055750b05fcd50f9915a826b44fa347a825250" + integrity sha512-lQgGX3FPRgbz2SKmhMtYgJvVzGZrmjaF4apZ2bLwofAKiSjxU0drPh4S/VasyYXwaTs+A1gvQ45BN8SQJzHsQQ== dependencies: - "@babel/types" "^7.4.0" + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.4.4" + "@babel/helpers" "^7.4.4" + "@babel/parser" "^7.4.4" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" + convert-source-map "^1.1.0" + debug "^4.1.0" + json5 "^2.1.0" + lodash "^4.17.11" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.2.2", "@babel/generator@^7.4.0", "@babel/generator@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041" + integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ== + dependencies: + "@babel/types" "^7.4.4" jsesc "^2.5.1" lodash "^4.17.11" source-map "^0.5.0" @@ -83,34 +103,34 @@ "@babel/types" "^7.3.0" esutils "^2.0.0" -"@babel/helper-call-delegate@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.4.0.tgz#f308eabe0d44f451217853aedf4dea5f6fe3294f" - integrity sha512-SdqDfbVdNQCBp3WhK2mNdDvHd3BD6qbmIc43CAyjnsfCmgHMeqgDcM3BzY2lchi7HBJGJ2CVdynLWbezaE4mmQ== +"@babel/helper-call-delegate@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz#87c1f8ca19ad552a736a7a27b1c1fcf8b1ff1f43" + integrity sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ== dependencies: - "@babel/helper-hoist-variables" "^7.4.0" - "@babel/traverse" "^7.4.0" - "@babel/types" "^7.4.0" + "@babel/helper-hoist-variables" "^7.4.4" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" -"@babel/helper-create-class-features-plugin@^7.3.0", "@babel/helper-create-class-features-plugin@^7.4.0": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.3.tgz#5bbd279c6c3ac6a60266b89bbfe7f8021080a1ef" - integrity sha512-UMl3TSpX11PuODYdWGrUeW6zFkdYhDn7wRLrOuNVM6f9L+S9CzmDXYyrp3MTHcwWjnzur1f/Op8A7iYZWya2Yg== +"@babel/helper-create-class-features-plugin@^7.3.0", "@babel/helper-create-class-features-plugin@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz#fc3d690af6554cc9efc607364a82d48f58736dba" + integrity sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA== dependencies: "@babel/helper-function-name" "^7.1.0" "@babel/helper-member-expression-to-functions" "^7.0.0" "@babel/helper-optimise-call-expression" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.4.0" - "@babel/helper-split-export-declaration" "^7.4.0" + "@babel/helper-replace-supers" "^7.4.4" + "@babel/helper-split-export-declaration" "^7.4.4" -"@babel/helper-define-map@^7.1.0", "@babel/helper-define-map@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.4.0.tgz#cbfd8c1b2f12708e262c26f600cd16ed6a3bc6c9" - integrity sha512-wAhQ9HdnLIywERVcSvX40CEJwKdAa1ID4neI9NXQPDOHwwA+57DqwLiPEVy2AIyWzAk0CQ8qx4awO0VUURwLtA== +"@babel/helper-define-map@^7.1.0", "@babel/helper-define-map@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz#6969d1f570b46bdc900d1eba8e5d59c48ba2c12a" + integrity sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg== dependencies: "@babel/helper-function-name" "^7.1.0" - "@babel/types" "^7.4.0" + "@babel/types" "^7.4.4" lodash "^4.17.11" "@babel/helper-explode-assignable-expression@^7.1.0": @@ -137,12 +157,12 @@ dependencies: "@babel/types" "^7.0.0" -"@babel/helper-hoist-variables@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.0.tgz#25b621399ae229869329730a62015bbeb0a6fbd6" - integrity sha512-/NErCuoe/et17IlAQFKWM24qtyYYie7sFIrW/tIQXpck6vAu2hhtYYsKLBWQV+BQZMbcIYPU/QMYuTufrY4aQw== +"@babel/helper-hoist-variables@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz#0298b5f25c8c09c53102d52ac4a98f773eb2850a" + integrity sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w== dependencies: - "@babel/types" "^7.4.0" + "@babel/types" "^7.4.4" "@babel/helper-member-expression-to-functions@^7.0.0": version "7.0.0" @@ -158,16 +178,16 @@ dependencies: "@babel/types" "^7.0.0" -"@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.4.3.tgz#b1e357a1c49e58a47211a6853abb8e2aaefeb064" - integrity sha512-H88T9IySZW25anu5uqyaC1DaQre7ofM+joZtAaO2F8NBdFfupH0SZ4gKjgSFVcvtx/aAirqA9L9Clio2heYbZA== +"@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz#96115ea42a2f139e619e98ed46df6019b94414b8" + integrity sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-simple-access" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/template" "^7.2.2" - "@babel/types" "^7.2.2" + "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/template" "^7.4.4" + "@babel/types" "^7.4.4" lodash "^4.17.11" "@babel/helper-optimise-call-expression@^7.0.0": @@ -182,10 +202,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== -"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.4.3.tgz#9d6e5428bfd638ab53b37ae4ec8caf0477495147" - integrity sha512-hnoq5u96pLCfgjXuj8ZLX3QQ+6nAulS+zSgi6HulUwFbEruRAKwbGLU5OvXkE14L8XW6XsQEKsIDfgthKLRAyA== +"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.4.4.tgz#a47e02bc91fb259d2e6727c2a30013e3ac13c4a2" + integrity sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q== dependencies: lodash "^4.17.11" @@ -200,15 +220,15 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helper-replace-supers@^7.0.0", "@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.4.0.tgz#4f56adb6aedcd449d2da9399c2dcf0545463b64c" - integrity sha512-PVwCVnWWAgnal+kJ+ZSAphzyl58XrFeSKSAJRiqg5QToTsjL+Xu1f9+RJ+d+Q0aPhPfBGaYfkox66k86thxNSg== +"@babel/helper-replace-supers@^7.0.0", "@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz#aee41783ebe4f2d3ab3ae775e1cc6f1a90cefa27" + integrity sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg== dependencies: "@babel/helper-member-expression-to-functions" "^7.0.0" "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/traverse" "^7.4.0" - "@babel/types" "^7.4.0" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" "@babel/helper-simple-access@^7.1.0": version "7.1.0" @@ -218,12 +238,12 @@ "@babel/template" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helper-split-export-declaration@^7.0.0", "@babel/helper-split-export-declaration@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz#571bfd52701f492920d63b7f735030e9a3e10b55" - integrity sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw== +"@babel/helper-split-export-declaration@^7.0.0", "@babel/helper-split-export-declaration@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" + integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== dependencies: - "@babel/types" "^7.4.0" + "@babel/types" "^7.4.4" "@babel/helper-wrap-function@^7.1.0": version "7.2.0" @@ -235,14 +255,14 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.2.0" -"@babel/helpers@^7.2.0", "@babel/helpers@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.3.tgz#7b1d354363494b31cb9a2417ae86af32b7853a3b" - integrity sha512-BMh7X0oZqb36CfyhvtbSmcWc3GXocfxv3yNsAEuM0l+fAqSO22rQrUpijr3oE/10jCTrB6/0b9kzmG4VetCj8Q== +"@babel/helpers@^7.2.0", "@babel/helpers@^7.4.3", "@babel/helpers@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.4.tgz#868b0ef59c1dd4e78744562d5ce1b59c89f2f2a5" + integrity sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A== dependencies: - "@babel/template" "^7.4.0" - "@babel/traverse" "^7.4.3" - "@babel/types" "^7.4.0" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" "@babel/highlight@^7.0.0": version "7.0.0" @@ -253,10 +273,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.3", "@babel/parser@^7.2.2", "@babel/parser@^7.4.0", "@babel/parser@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.3.tgz#eb3ac80f64aa101c907d4ce5406360fe75b7895b" - integrity sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.1.3", "@babel/parser@^7.2.2", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.4.tgz#5977129431b8fe33471730d255ce8654ae1250b6" + integrity sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w== "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" @@ -276,11 +296,11 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-class-properties@^7.3.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.0.tgz#d70db61a2f1fd79de927eea91f6411c964e084b8" - integrity sha512-t2ECPNOXsIeK1JxJNKmgbzQtoG27KIlVE61vTqX0DKR9E9sZlVVxWUtEW9D5FlZ8b8j7SBNCHY47GgPKCKlpPg== + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz#93a6486eed86d53452ab9bab35e368e9461198ce" + integrity sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.4.0" + "@babel/helper-create-class-features-plugin" "^7.4.4" "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-decorators@7.3.0": @@ -308,10 +328,10 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" -"@babel/plugin-proposal-object-rest-spread@^7.3.1", "@babel/plugin-proposal-object-rest-spread@^7.3.2", "@babel/plugin-proposal-object-rest-spread@^7.4.0", "@babel/plugin-proposal-object-rest-spread@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.3.tgz#be27cd416eceeba84141305b93c282f5de23bbb4" - integrity sha512-xC//6DNSSHVjq8O2ge0dyYlhshsH4T7XdCVoxbi5HzLYWfsC5ooFlJjrXk8RcAT+hjHAK9UjBXdylzSoDK3t4g== +"@babel/plugin-proposal-object-rest-spread@^7.3.1", "@babel/plugin-proposal-object-rest-spread@^7.3.2", "@babel/plugin-proposal-object-rest-spread@^7.4.0", "@babel/plugin-proposal-object-rest-spread@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz#1ef173fcf24b3e2df92a678f027673b55e7e3005" + integrity sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" @@ -324,13 +344,13 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" -"@babel/plugin-proposal-unicode-property-regex@^7.2.0", "@babel/plugin-proposal-unicode-property-regex@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.0.tgz#202d91ee977d760ef83f4f416b280d568be84623" - integrity sha512-h/KjEZ3nK9wv1P1FSNb9G079jXrNYR0Ko+7XkOx85+gM24iZbPn0rh4vCftk+5QKY7y1uByFataBTmX7irEF1w== +"@babel/plugin-proposal-unicode-property-regex@^7.2.0", "@babel/plugin-proposal-unicode-property-regex@^7.4.0", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78" + integrity sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.0.0" + "@babel/helper-regex" "^7.4.4" regexpu-core "^4.5.4" "@babel/plugin-syntax-async-generators@^7.2.0": @@ -410,10 +430,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-async-to-generator@^7.2.0", "@babel/plugin-transform-async-to-generator@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.0.tgz#234fe3e458dce95865c0d152d256119b237834b0" - integrity sha512-EeaFdCeUULM+GPFEsf7pFcNSxM7hYjoj5fiYbyuiXobW4JhFnjAv9OWzNwHyHcKoPNpAfeRDuW6VyaXEDUBa7g== +"@babel/plugin-transform-async-to-generator@^7.2.0", "@babel/plugin-transform-async-to-generator@^7.4.0", "@babel/plugin-transform-async-to-generator@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz#a3f1d01f2f21cadab20b33a82133116f14fb5894" + integrity sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" @@ -426,10 +446,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-block-scoping@^7.2.0", "@babel/plugin-transform-block-scoping@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.0.tgz#164df3bb41e3deb954c4ca32ffa9fcaa56d30bcb" - integrity sha512-AWyt3k+fBXQqt2qb9r97tn3iBwFpiv9xdAiG+Gr2HpAZpuayvbL55yWrsV3MyHvXk/4vmSiedhDRl1YI2Iy5nQ== +"@babel/plugin-transform-block-scoping@^7.2.0", "@babel/plugin-transform-block-scoping@^7.4.0", "@babel/plugin-transform-block-scoping@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz#c13279fabf6b916661531841a23c4b7dae29646d" + integrity sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" lodash "^4.17.11" @@ -448,18 +468,18 @@ "@babel/helper-split-export-declaration" "^7.0.0" globals "^11.1.0" -"@babel/plugin-transform-classes@^7.2.0", "@babel/plugin-transform-classes@^7.4.0", "@babel/plugin-transform-classes@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.3.tgz#adc7a1137ab4287a555d429cc56ecde8f40c062c" - integrity sha512-PUaIKyFUDtG6jF5DUJOfkBdwAS/kFFV3XFk7Nn0a6vR7ZT8jYw5cGtIlat77wcnd0C6ViGqo/wyNf4ZHytF/nQ== +"@babel/plugin-transform-classes@^7.2.0", "@babel/plugin-transform-classes@^7.4.0", "@babel/plugin-transform-classes@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz#0ce4094cdafd709721076d3b9c38ad31ca715eb6" + integrity sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw== dependencies: "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-define-map" "^7.4.0" + "@babel/helper-define-map" "^7.4.4" "@babel/helper-function-name" "^7.1.0" "@babel/helper-optimise-call-expression" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.4.0" - "@babel/helper-split-export-declaration" "^7.4.0" + "@babel/helper-replace-supers" "^7.4.4" + "@babel/helper-split-export-declaration" "^7.4.4" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.2.0": @@ -476,20 +496,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-destructuring@^7.2.0", "@babel/plugin-transform-destructuring@^7.4.0", "@babel/plugin-transform-destructuring@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.3.tgz#1a95f5ca2bf2f91ef0648d5de38a8d472da4350f" - integrity sha512-rVTLLZpydDFDyN4qnXdzwoVpk1oaXHIvPEOkOLyr88o7oHxVc/LyrnDx+amuBWGOwUb7D1s/uLsKBNTx08htZg== +"@babel/plugin-transform-destructuring@^7.2.0", "@babel/plugin-transform-destructuring@^7.4.0", "@babel/plugin-transform-destructuring@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz#9d964717829cc9e4b601fc82a26a71a4d8faf20f" + integrity sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-dotall-regex@^7.2.0", "@babel/plugin-transform-dotall-regex@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.3.tgz#fceff1c16d00c53d32d980448606f812cd6d02bf" - integrity sha512-9Arc2I0AGynzXRR/oPdSALv3k0rM38IMFyto7kOCwb5F9sLUt2Ykdo3V9yUPR+Bgr4kb6bVEyLkPEiBhzcTeoA== +"@babel/plugin-transform-dotall-regex@^7.2.0", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz#361a148bc951444312c69446d76ed1ea8e4450c3" + integrity sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.4.3" + "@babel/helper-regex" "^7.4.4" regexpu-core "^4.5.4" "@babel/plugin-transform-duplicate-keys@^7.2.0": @@ -516,24 +536,24 @@ "@babel/plugin-syntax-flow" "^7.2.0" "@babel/plugin-transform-flow-strip-types@^7.0.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.4.0.tgz#f3c59eecff68c99b9c96eaafe4fe9d1fa8947138" - integrity sha512-C4ZVNejHnfB22vI2TYN4RUp2oCmq6cSEAg4RygSvYZUECRqUu9O4PMEMNJ4wsemaRGg27BbgYctG4BZh+AgIHw== + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.4.4.tgz#d267a081f49a8705fc9146de0768c6b58dccd8f7" + integrity sha512-WyVedfeEIILYEaWGAUWzVNyqG4sfsNooMhXWsu/YzOvVGcsnPb5PguysjJqI3t3qiaYj0BR8T2f5njdjTGe44Q== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-flow" "^7.2.0" -"@babel/plugin-transform-for-of@^7.2.0", "@babel/plugin-transform-for-of@^7.4.0", "@babel/plugin-transform-for-of@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.3.tgz#c36ff40d893f2b8352202a2558824f70cd75e9fe" - integrity sha512-UselcZPwVWNSURnqcfpnxtMehrb8wjXYOimlYQPBnup/Zld426YzIhNEvuRsEWVHfESIECGrxoI6L5QqzuLH5Q== +"@babel/plugin-transform-for-of@^7.2.0", "@babel/plugin-transform-for-of@^7.4.0", "@babel/plugin-transform-for-of@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556" + integrity sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-function-name@^7.2.0", "@babel/plugin-transform-function-name@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.3.tgz#130c27ec7fb4f0cba30e958989449e5ec8d22bbd" - integrity sha512-uT5J/3qI/8vACBR9I1GlAuU/JqBtWdfCrynuOkrWG6nCDieZd5przB1vfP59FRHBZQ9DC2IUfqr/xKqzOD5x0A== +"@babel/plugin-transform-function-name@^7.2.0", "@babel/plugin-transform-function-name@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz#e1436116abb0610c2259094848754ac5230922ad" + integrity sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA== dependencies: "@babel/helper-function-name" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" @@ -560,21 +580,21 @@ "@babel/helper-module-transforms" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-modules-commonjs@^7.2.0", "@babel/plugin-transform-modules-commonjs@^7.4.0", "@babel/plugin-transform-modules-commonjs@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.3.tgz#3917f260463ac08f8896aa5bd54403f6e1fed165" - integrity sha512-sMP4JqOTbMJMimqsSZwYWsMjppD+KRyDIUVW91pd7td0dZKAvPmhCaxhOzkzLParKwgQc7bdL9UNv+rpJB0HfA== +"@babel/plugin-transform-modules-commonjs@^7.2.0", "@babel/plugin-transform-modules-commonjs@^7.4.0", "@babel/plugin-transform-modules-commonjs@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz#0bef4713d30f1d78c2e59b3d6db40e60192cac1e" + integrity sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw== dependencies: - "@babel/helper-module-transforms" "^7.4.3" + "@babel/helper-module-transforms" "^7.4.4" "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-simple-access" "^7.1.0" -"@babel/plugin-transform-modules-systemjs@^7.2.0", "@babel/plugin-transform-modules-systemjs@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.0.tgz#c2495e55528135797bc816f5d50f851698c586a1" - integrity sha512-gjPdHmqiNhVoBqus5qK60mWPp1CmYWp/tkh11mvb0rrys01HycEGD7NvvSoKXlWEfSM9TcL36CpsK8ElsADptQ== +"@babel/plugin-transform-modules-systemjs@^7.2.0", "@babel/plugin-transform-modules-systemjs@^7.4.0", "@babel/plugin-transform-modules-systemjs@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz#dc83c5665b07d6c2a7b224c00ac63659ea36a405" + integrity sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ== dependencies: - "@babel/helper-hoist-variables" "^7.4.0" + "@babel/helper-hoist-variables" "^7.4.4" "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-transform-modules-umd@^7.2.0": @@ -585,17 +605,17 @@ "@babel/helper-module-transforms" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-named-capturing-groups-regex@^7.3.0", "@babel/plugin-transform-named-capturing-groups-regex@^7.4.2": - version "7.4.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.2.tgz#800391136d6cbcc80728dbdba3c1c6e46f86c12e" - integrity sha512-NsAuliSwkL3WO2dzWTOL1oZJHm0TM8ZY8ZSxk2ANyKkt5SQlToGA4pzctmq1BEjoacurdwZ3xp2dCQWJkME0gQ== +"@babel/plugin-transform-named-capturing-groups-regex@^7.3.0", "@babel/plugin-transform-named-capturing-groups-regex@^7.4.2", "@babel/plugin-transform-named-capturing-groups-regex@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.4.tgz#5611d96d987dfc4a3a81c4383bb173361037d68d" + integrity sha512-Ki+Y9nXBlKfhD+LXaRS7v95TtTGYRAf9Y1rTDiE75zf8YQz4GDaWRXosMfJBXxnk88mGFjWdCRIeqDbon7spYA== dependencies: regexp-tree "^0.1.0" -"@babel/plugin-transform-new-target@^7.0.0", "@babel/plugin-transform-new-target@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.0.tgz#67658a1d944edb53c8d4fa3004473a0dd7838150" - integrity sha512-6ZKNgMQmQmrEX/ncuCwnnw1yVGoaOW5KpxNhoWI7pCQdA0uZ0HqHGqenCUIENAnxRjy2WwNQ30gfGdIgqJXXqw== +"@babel/plugin-transform-new-target@^7.0.0", "@babel/plugin-transform-new-target@^7.4.0", "@babel/plugin-transform-new-target@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz#18d120438b0cc9ee95a47f2c72bc9768fbed60a5" + integrity sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -607,12 +627,12 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-replace-supers" "^7.1.0" -"@babel/plugin-transform-parameters@^7.2.0", "@babel/plugin-transform-parameters@^7.4.0", "@babel/plugin-transform-parameters@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.3.tgz#e5ff62929fdf4cf93e58badb5e2430303003800d" - integrity sha512-ULJYC2Vnw96/zdotCZkMGr2QVfKpIT/4/K+xWWY0MbOJyMZuk660BGkr3bEKWQrrciwz6xpmft39nA4BF7hJuA== +"@babel/plugin-transform-parameters@^7.2.0", "@babel/plugin-transform-parameters@^7.4.0", "@babel/plugin-transform-parameters@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz#7556cf03f318bd2719fe4c922d2d808be5571e16" + integrity sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw== dependencies: - "@babel/helper-call-delegate" "^7.4.0" + "@babel/helper-call-delegate" "^7.4.4" "@babel/helper-get-function-arity" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" @@ -663,10 +683,10 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-jsx" "^7.2.0" -"@babel/plugin-transform-regenerator@^7.0.0", "@babel/plugin-transform-regenerator@^7.4.0", "@babel/plugin-transform-regenerator@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.3.tgz#2a697af96887e2bbf5d303ab0221d139de5e739c" - integrity sha512-kEzotPuOpv6/iSlHroCDydPkKYw7tiJGKlmYp6iJn4a6C/+b2FdttlJsLKYxolYHgotTJ5G5UY5h0qey5ka3+A== +"@babel/plugin-transform-regenerator@^7.0.0", "@babel/plugin-transform-regenerator@^7.4.0", "@babel/plugin-transform-regenerator@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.4.tgz#5b4da4df79391895fca9e28f99e87e22cfc02072" + integrity sha512-Zz3w+pX1SI0KMIiqshFZkwnVGUhDZzpX2vtPzfJBKQQq8WsP/Xy9DNdELWivxcKOCX/Pywge4SiEaPaLtoDT4g== dependencies: regenerator-transform "^0.13.4" @@ -709,10 +729,10 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.0.0" -"@babel/plugin-transform-template-literals@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz#d87ed01b8eaac7a92473f608c97c089de2ba1e5b" - integrity sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg== +"@babel/plugin-transform-template-literals@^7.2.0", "@babel/plugin-transform-template-literals@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz#9d28fea7bbce637fb7612a0750989d8321d4bcb0" + integrity sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g== dependencies: "@babel/helper-annotate-as-pure" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" @@ -725,20 +745,20 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-transform-typescript@^7.1.0", "@babel/plugin-transform-typescript@^7.3.2": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.4.0.tgz#0389ec53a34e80f99f708c4ca311181449a68eb1" - integrity sha512-U7/+zKnRZg04ggM/Bm+xmu2B/PrwyDQTT/V89FXWYWNMxBDwSx56u6jtk9SEbfLFbZaEI72L+5LPvQjeZgFCrQ== + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.4.4.tgz#93e9c3f2a546e6d3da1e9cc990e30791b807aa9f" + integrity sha512-rwDvjaMTx09WC0rXGBRlYSSkEHOKRrecY6hEr3SVIPKII8DVWXtapNAfAyMC0dovuO+zYArcAuKeu3q9DNRfzA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-typescript" "^7.2.0" -"@babel/plugin-transform-unicode-regex@^7.2.0", "@babel/plugin-transform-unicode-regex@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.3.tgz#3868703fc0e8f443dda65654b298df576f7b863b" - integrity sha512-lnSNgkVjL8EMtnE8eSS7t2ku8qvKH3eqNf/IwIfnSPUqzgqYmRwzdsQWv4mNQAN9Nuo6Gz1Y0a4CSmdpu1Pp6g== +"@babel/plugin-transform-unicode-regex@^7.2.0", "@babel/plugin-transform-unicode-regex@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz#ab4634bb4f14d36728bf5978322b35587787970f" + integrity sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.4.3" + "@babel/helper-regex" "^7.4.4" regexpu-core "^4.5.4" "@babel/polyfill@7.2.5": @@ -850,53 +870,53 @@ semver "^5.3.0" "@babel/preset-env@^7.4.1", "@babel/preset-env@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.3.tgz#e71e16e123dc0fbf65a52cbcbcefd072fbd02880" - integrity sha512-FYbZdV12yHdJU5Z70cEg0f6lvtpZ8jFSDakTm7WXeJbLXh4R0ztGEu/SW7G1nJ2ZvKwDhz8YrbA84eYyprmGqw== + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.4.tgz#b6f6825bfb27b3e1394ca3de4f926482722c1d6f" + integrity sha512-FU1H+ACWqZZqfw1x2G1tgtSSYSfxJLkpaUQL37CenULFARDo+h4xJoVHzRoHbK+85ViLciuI7ME4WTIhFRBBlw== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-async-generator-functions" "^7.2.0" "@babel/plugin-proposal-json-strings" "^7.2.0" - "@babel/plugin-proposal-object-rest-spread" "^7.4.3" + "@babel/plugin-proposal-object-rest-spread" "^7.4.4" "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" "@babel/plugin-syntax-async-generators" "^7.2.0" "@babel/plugin-syntax-json-strings" "^7.2.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" "@babel/plugin-transform-arrow-functions" "^7.2.0" - "@babel/plugin-transform-async-to-generator" "^7.4.0" + "@babel/plugin-transform-async-to-generator" "^7.4.4" "@babel/plugin-transform-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.4.0" - "@babel/plugin-transform-classes" "^7.4.3" + "@babel/plugin-transform-block-scoping" "^7.4.4" + "@babel/plugin-transform-classes" "^7.4.4" "@babel/plugin-transform-computed-properties" "^7.2.0" - "@babel/plugin-transform-destructuring" "^7.4.3" - "@babel/plugin-transform-dotall-regex" "^7.4.3" + "@babel/plugin-transform-destructuring" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" "@babel/plugin-transform-duplicate-keys" "^7.2.0" "@babel/plugin-transform-exponentiation-operator" "^7.2.0" - "@babel/plugin-transform-for-of" "^7.4.3" - "@babel/plugin-transform-function-name" "^7.4.3" + "@babel/plugin-transform-for-of" "^7.4.4" + "@babel/plugin-transform-function-name" "^7.4.4" "@babel/plugin-transform-literals" "^7.2.0" "@babel/plugin-transform-member-expression-literals" "^7.2.0" "@babel/plugin-transform-modules-amd" "^7.2.0" - "@babel/plugin-transform-modules-commonjs" "^7.4.3" - "@babel/plugin-transform-modules-systemjs" "^7.4.0" + "@babel/plugin-transform-modules-commonjs" "^7.4.4" + "@babel/plugin-transform-modules-systemjs" "^7.4.4" "@babel/plugin-transform-modules-umd" "^7.2.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.2" - "@babel/plugin-transform-new-target" "^7.4.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.4" + "@babel/plugin-transform-new-target" "^7.4.4" "@babel/plugin-transform-object-super" "^7.2.0" - "@babel/plugin-transform-parameters" "^7.4.3" + "@babel/plugin-transform-parameters" "^7.4.4" "@babel/plugin-transform-property-literals" "^7.2.0" - "@babel/plugin-transform-regenerator" "^7.4.3" + "@babel/plugin-transform-regenerator" "^7.4.4" "@babel/plugin-transform-reserved-words" "^7.2.0" "@babel/plugin-transform-shorthand-properties" "^7.2.0" "@babel/plugin-transform-spread" "^7.2.0" "@babel/plugin-transform-sticky-regex" "^7.2.0" - "@babel/plugin-transform-template-literals" "^7.2.0" + "@babel/plugin-transform-template-literals" "^7.4.4" "@babel/plugin-transform-typeof-symbol" "^7.2.0" - "@babel/plugin-transform-unicode-regex" "^7.4.3" - "@babel/types" "^7.4.0" + "@babel/plugin-transform-unicode-regex" "^7.4.4" + "@babel/types" "^7.4.4" browserslist "^4.5.2" core-js-compat "^3.0.0" invariant "^2.2.2" @@ -953,40 +973,40 @@ regenerator-runtime "^0.12.0" "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.3.tgz#79888e452034223ad9609187a0ad1fe0d2ad4bdc" - integrity sha512-9lsJwJLxDh/T3Q3SZszfWOTkk3pHbkmH+3KY+zwIDmsNlxsumuhS2TH3NIpktU4kNvfzy+k3eLT7aTJSPTo0OA== + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.4.tgz#dc2e34982eb236803aa27a07fea6857af1b9171d" + integrity sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg== dependencies: regenerator-runtime "^0.13.2" -"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.2.2", "@babel/template@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.0.tgz#12474e9c077bae585c5d835a95c0b0b790c25c8b" - integrity sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw== +"@babel/template@^7.1.0", "@babel/template@^7.2.2", "@babel/template@^7.4.0", "@babel/template@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" + integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.4.0" - "@babel/types" "^7.4.0" + "@babel/parser" "^7.4.4" + "@babel/types" "^7.4.4" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.2.2", "@babel/traverse@^7.4.0", "@babel/traverse@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.3.tgz#1a01f078fc575d589ff30c0f71bf3c3d9ccbad84" - integrity sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.2.2", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.4.tgz#0776f038f6d78361860b6823887d4f3937133fe8" + integrity sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.4.0" + "@babel/generator" "^7.4.4" "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.0" - "@babel/parser" "^7.4.3" - "@babel/types" "^7.4.0" + "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/parser" "^7.4.4" + "@babel/types" "^7.4.4" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.11" -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.0.tgz#670724f77d24cce6cc7d8cf64599d511d164894c" - integrity sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA== +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0" + integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ== dependencies: esutils "^2.0.2" lodash "^4.17.11" @@ -1162,32 +1182,32 @@ chalk "^2.0.1" slash "^2.0.0" -"@jest/core@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.7.1.tgz#6707f50db238d0c5988860680e2e414df0032024" - integrity sha512-ivlZ8HX/FOASfHcb5DJpSPFps8ydfUYzLZfgFFqjkLijYysnIEOieg72YRhO4ZUB32xu40hsSMmaw+IGYeKONA== +"@jest/core@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.8.0.tgz#fbbdcd42a41d0d39cddbc9f520c8bab0c33eed5b" + integrity sha512-R9rhAJwCBQzaRnrRgAdVfnglUuATXdwTRsYqs6NMdVcAl5euG8LtWDe+fVkN27YfKVBW61IojVsXKaOmSnqd/A== dependencies: "@jest/console" "^24.7.1" - "@jest/reporters" "^24.7.1" - "@jest/test-result" "^24.7.1" - "@jest/transform" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/reporters" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" ansi-escapes "^3.0.0" chalk "^2.0.1" exit "^0.1.2" graceful-fs "^4.1.15" - jest-changed-files "^24.7.0" - jest-config "^24.7.1" - jest-haste-map "^24.7.1" - jest-message-util "^24.7.1" + jest-changed-files "^24.8.0" + jest-config "^24.8.0" + jest-haste-map "^24.8.0" + jest-message-util "^24.8.0" jest-regex-util "^24.3.0" - jest-resolve-dependencies "^24.7.1" - jest-runner "^24.7.1" - jest-runtime "^24.7.1" - jest-snapshot "^24.7.1" - jest-util "^24.7.1" - jest-validate "^24.7.0" - jest-watcher "^24.7.1" + jest-resolve-dependencies "^24.8.0" + jest-runner "^24.8.0" + jest-runtime "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" + jest-watcher "^24.8.0" micromatch "^3.1.10" p-each-series "^1.0.0" pirates "^4.0.1" @@ -1195,45 +1215,46 @@ rimraf "^2.5.4" strip-ansi "^5.0.0" -"@jest/environment@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.7.1.tgz#9b9196bc737561f67ac07817d4c5ece772e33135" - integrity sha512-wmcTTYc4/KqA+U5h1zQd5FXXynfa7VGP2NfF+c6QeGJ7c+2nStgh65RQWNX62SC716dTtqheTRrZl0j+54oGHw== - dependencies: - "@jest/fake-timers" "^24.7.1" - "@jest/transform" "^24.7.1" - "@jest/types" "^24.7.0" - jest-mock "^24.7.0" - -"@jest/fake-timers@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.7.1.tgz#56e5d09bdec09ee81050eaff2794b26c71d19db2" - integrity sha512-4vSQJDKfR2jScOe12L9282uiwuwQv9Lk7mgrCSZHA9evB9efB/qx8i0KJxsAKtp8fgJYBJdYY7ZU6u3F4/pyjA== - dependencies: - "@jest/types" "^24.7.0" - jest-message-util "^24.7.1" - jest-mock "^24.7.0" - -"@jest/reporters@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.7.1.tgz#38ac0b096cd691bbbe3051ddc25988d42e37773a" - integrity sha512-bO+WYNwHLNhrjB9EbPL4kX/mCCG4ZhhfWmO3m4FSpbgr7N83MFejayz30kKjgqr7smLyeaRFCBQMbXpUgnhAJw== - dependencies: - "@jest/environment" "^24.7.1" - "@jest/test-result" "^24.7.1" - "@jest/transform" "^24.7.1" - "@jest/types" "^24.7.0" +"@jest/environment@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.8.0.tgz#0342261383c776bdd652168f68065ef144af0eac" + integrity sha512-vlGt2HLg7qM+vtBrSkjDxk9K0YtRBi7HfRFaDxoRtyi+DyVChzhF20duvpdAnKVBV6W5tym8jm0U9EfXbDk1tw== + dependencies: + "@jest/fake-timers" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" + +"@jest/fake-timers@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.8.0.tgz#2e5b80a4f78f284bcb4bd5714b8e10dd36a8d3d1" + integrity sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw== + dependencies: + "@jest/types" "^24.8.0" + jest-message-util "^24.8.0" + jest-mock "^24.8.0" + +"@jest/reporters@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.8.0.tgz#075169cd029bddec54b8f2c0fc489fd0b9e05729" + integrity sha512-eZ9TyUYpyIIXfYCrw0UHUWUvE35vx5I92HGMgS93Pv7du+GHIzl+/vh8Qj9MCWFK/4TqyttVBPakWMOfZRIfxw== + dependencies: + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" chalk "^2.0.1" exit "^0.1.2" glob "^7.1.2" - istanbul-api "^2.1.1" istanbul-lib-coverage "^2.0.2" istanbul-lib-instrument "^3.0.1" + istanbul-lib-report "^2.0.4" istanbul-lib-source-maps "^3.0.1" - jest-haste-map "^24.7.1" - jest-resolve "^24.7.1" - jest-runtime "^24.7.1" - jest-util "^24.7.1" + istanbul-reports "^2.1.1" + jest-haste-map "^24.8.0" + jest-resolve "^24.8.0" + jest-runtime "^24.8.0" + jest-util "^24.8.0" jest-worker "^24.6.0" node-notifier "^5.2.1" slash "^2.0.0" @@ -1249,52 +1270,53 @@ graceful-fs "^4.1.15" source-map "^0.6.0" -"@jest/test-result@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.7.1.tgz#19eacdb29a114300aed24db651e5d975f08b6bbe" - integrity sha512-3U7wITxstdEc2HMfBX7Yx3JZgiNBubwDqQMh+BXmZXHa3G13YWF3p6cK+5g0hGkN3iufg/vGPl3hLxQXD74Npg== +"@jest/test-result@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.8.0.tgz#7675d0aaf9d2484caa65e048d9b467d160f8e9d3" + integrity sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng== dependencies: "@jest/console" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" "@types/istanbul-lib-coverage" "^2.0.0" -"@jest/test-sequencer@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.7.1.tgz#9c18e428e1ad945fa74f6233a9d35745ca0e63e0" - integrity sha512-84HQkCpVZI/G1zq53gHJvSmhUer4aMYp9tTaffW28Ih5OxfCg8hGr3nTSbL1OhVDRrFZwvF+/R9gY6JRkDUpUA== +"@jest/test-sequencer@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz#2f993bcf6ef5eb4e65e8233a95a3320248cf994b" + integrity sha512-OzL/2yHyPdCHXEzhoBuq37CE99nkme15eHkAzXRVqthreWZamEMA0WoetwstsQBCXABhczpK03JNbc4L01vvLg== dependencies: - "@jest/test-result" "^24.7.1" - jest-haste-map "^24.7.1" - jest-runner "^24.7.1" - jest-runtime "^24.7.1" + "@jest/test-result" "^24.8.0" + jest-haste-map "^24.8.0" + jest-runner "^24.8.0" + jest-runtime "^24.8.0" -"@jest/transform@^24.6.0", "@jest/transform@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.7.1.tgz#872318f125bcfab2de11f53b465ab1aa780789c2" - integrity sha512-EsOUqP9ULuJ66IkZQhI5LufCHlTbi7hrcllRMUEV/tOgqBVQi93+9qEvkX0n8mYpVXQ8VjwmICeRgg58mrtIEw== +"@jest/transform@^24.6.0", "@jest/transform@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.8.0.tgz#628fb99dce4f9d254c6fd9341e3eea262e06fef5" + integrity sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" babel-plugin-istanbul "^5.1.0" chalk "^2.0.1" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.1.15" - jest-haste-map "^24.7.1" + jest-haste-map "^24.8.0" jest-regex-util "^24.3.0" - jest-util "^24.7.1" + jest-util "^24.8.0" micromatch "^3.1.10" realpath-native "^1.1.0" slash "^2.0.0" source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/types@^24.5.0", "@jest/types@^24.6.0", "@jest/types@^24.7.0": - version "24.7.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.7.0.tgz#c4ec8d1828cdf23234d9b4ee31f5482a3f04f48b" - integrity sha512-ipJUa2rFWiKoBqMKP63Myb6h9+iT3FHRTF2M8OR6irxWzItisa8i4dcSg14IbvmXUnBlHBlUQPYUHWyX3UPpYA== +"@jest/types@^24.5.0", "@jest/types@^24.6.0", "@jest/types@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad" + integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^12.0.9" "@mrmlnc/readdir-enhanced@^2.2.1": @@ -2170,11 +2192,33 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/events@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + +"@types/expect-puppeteer@3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/expect-puppeteer/-/expect-puppeteer-3.3.1.tgz#46e5944bf425b86ea13a563c7c8b86901414988d" + integrity sha512-3raSnf28NelDtv0ksvQPZs410taJZ4d70vA8sVzmbRPV04fpmQm9/BOxUCloETD/ZI1EXRpv0pzOQKhPTbm4jg== + dependencies: + "@types/jest" "*" + "@types/puppeteer" "*" + "@types/geojson@*": version "7946.0.7" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad" integrity sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ== +"@types/glob@^7.1.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" + integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== + dependencies: + "@types/events" "*" + "@types/minimatch" "*" + "@types/node" "*" + "@types/inquirer@0.0.43": version "0.0.43" resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-0.0.43.tgz#1eb0bbb4648e6cc568bd396c1e989f620ad01273" @@ -2183,16 +2227,38 @@ "@types/rx" "*" "@types/through" "*" -"@types/istanbul-lib-coverage@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz#1eb8c033e98cf4e1a4cedcaf8bcafe8cb7591e85" - integrity sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg== +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" + integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== + +"@types/istanbul-lib-report@*": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#e5471e7fa33c61358dd38426189c037a58433b8c" + integrity sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a" + integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA== + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" "@types/jest-diff@*": version "20.0.1" resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89" integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA== +"@types/jest@*": + version "24.0.12" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.12.tgz#0553dd0a5ac744e7dc4e8700da6d3baedbde3e8f" + integrity sha512-60sjqMhat7i7XntZckcSGV8iREJyXXI6yFHZkSZvCPUeOnEJ/VP1rU/WpEWQ56mvoh8NhC+sfKAuJRTyGtCOow== + dependencies: + "@types/jest-diff" "*" + "@types/jest@23.3.14": version "23.3.14" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.14.tgz#37daaf78069e7948520474c87b80092ea912520a" @@ -2215,16 +2281,31 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.123.tgz#39be5d211478c8dd3bdae98ee75bb7efe4abfe4d" integrity sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q== -"@types/node@*", "@types/node@11.13.4", "@types/node@^11.9.5": - version "11.13.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.4.tgz#f83ec3c3e05b174b7241fadeb6688267fe5b22ca" - integrity sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ== +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + +"@types/node@*": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.0.tgz#d11813b9c0ff8aaca29f04cbc12817f4c7d656e5" + integrity sha512-Jrb/x3HT4PTJp6a4avhmJCDEVrPdqLfl3e8GGMbpkGGdwAV5UGlIs4vVEfsHHfylZVOKZWpOqmqFH8CbfOZ6kg== "@types/node@10.14.1": version "10.14.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.1.tgz#8701cd760acc20beba5ffe0b7a1b879f39cb8c41" integrity sha512-Rymt08vh1GaW4vYB6QP61/5m/CFLGnFZP++bJpWbiNxceNa6RBipDmb413jvtSf/R1gg5a/jQVl2jY4XVRscEA== +"@types/node@11.13.4": + version "11.13.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.4.tgz#f83ec3c3e05b174b7241fadeb6688267fe5b22ca" + integrity sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ== + +"@types/node@^11.9.5": + version "11.13.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.10.tgz#4df59e5966b56f512bac98898bcbee5067411f0f" + integrity sha512-leUNzbFTMX94TWaIKz8N15Chu55F9QSH+INKayQr5xpkasBQBRF3qQXfo3/dOnMU/dEIit+Y/SU8HyOjq++GwA== + "@types/papaparse@4.5.9": version "4.5.9" resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-4.5.9.tgz#ff887bd362f57cd0c87320d2de38ac232bb55e81" @@ -2232,15 +2313,43 @@ dependencies: "@types/node" "*" +"@types/pixelmatch@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/pixelmatch/-/pixelmatch-4.0.0.tgz#7b017c6c85e96715337f46eafbabc5a44b177530" + integrity sha512-pOF+6b0UbePCuPv1BS2k1IEeTk8ae8mhNiHms05s5WM+xV47g8Fb7KQcMn1fkJ9ccbs2IDpgPv+fGmHHvHHnrA== + dependencies: + "@types/node" "*" + +"@types/pngjs@3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.2.tgz#8ed3bd655ab3a92ea32ada7a21f618e63b93b1d4" + integrity sha512-/SBsv93rVnjByzcau24rBwb+N7BHFp2LateaXz1e7m7M0Wzck/ymXTNdWVrCtkuMbwTHAnfdc3X/I/5szsTEAA== + dependencies: + "@types/node" "*" + "@types/pretty-format@20.0.1": version "20.0.1" resolved "https://registry.yarnpkg.com/@types/pretty-format/-/pretty-format-20.0.1.tgz#7ce03b403887b087701a2b4534464f48ce7b2f48" integrity sha512-Oh7wnvVUCtVIWnCHQWe9qDZKn0fGyk5AMq99jXml0x39K59P+z9qe31CNRtop9TceCpS7NmoK+J9eGeCnyFgnw== "@types/prop-types@*": - version "15.7.0" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.0.tgz#4c48fed958d6dcf9487195a0ef6456d5f6e0163a" - integrity sha512-eItQyV43bj4rR3JPV0Skpl1SncRCdziTEK9/v8VwXmV6d/qOUO8/EuWeHBbCZcsfSHfzI5UyMJLCSXtxxznyZg== + version "15.7.1" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.1.tgz#f1a11e7babb0c3cad68100be381d1e064c68f1f6" + integrity sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg== + +"@types/puppeteer-core@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@types/puppeteer-core/-/puppeteer-core-1.9.0.tgz#5ceb397e3ff769081fb07d71289b5009392d24d3" + integrity sha512-YJwGTq0a8xZxN7/QDeW59XMdKTRNzDTc8ZVBPDB6J13GgXn1+QzgMA8pAq1/bj2FD0R7xj3nYoZra10b0HLzFw== + dependencies: + "@types/puppeteer" "*" + +"@types/puppeteer@*": + version "1.12.3" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.12.3.tgz#1309882d368ed21004dfc4520864fdafcf126277" + integrity sha512-mJtUPdXqB8THRwiHPbx8pkGYi+8IPf3dMuwJS9hHpr59BwkuLDkkEJ4qMST0k6TbOUXp+wyMJii30ouSkoEtaw== + dependencies: + "@types/node" "*" "@types/q@^1.5.1": version "1.5.2" @@ -2292,9 +2401,9 @@ "@types/react" "*" "@types/react-transition-group@*": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-2.9.0.tgz#2a74a885432d673a93a2c93c34ce5dbf9f1426f8" - integrity sha512-hP7vUaZMVSWKxo133P8U51U6UZ7+pbY+eAQb8+p6SZ2rB1rj3mOTDgTzhhi+R2SCB4S+sWekAAGoxdiZPG0ReQ== + version "2.9.1" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-2.9.1.tgz#66c9ca5d0b20bae72fe6b797e0d362b996d55e9f" + integrity sha512-1usq4DRUVBFnxc9KGJAlJO9EpQrLZGDDEC8wDOn2+2ODSyudYo8FiIzPDRaX/hfQjHqGeeoNaNdA2bj0l35hZQ== dependencies: "@types/react" "*" @@ -2320,18 +2429,10 @@ dependencies: "@types/react" "*" -"@types/react@*": - version "16.8.8" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.8.tgz#4b60a469fd2469f7aa6eaa0f8cfbc51f6d76e662" - integrity sha512-xwEvyet96u7WnB96kqY0yY7qxx/pEpU51QeACkKFtrgjjXITQn0oO1iwPEraXVgh10ZFPix7gs1R4OJXF7P5sg== - dependencies: - "@types/prop-types" "*" - csstype "^2.2.0" - -"@types/react@16.8.13": - version "16.8.13" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.13.tgz#a82b15aad9ab91c40edca0d6889b7745ae24f053" - integrity sha512-otJ4ntMuHGrvm67CdDJMAls4WqotmAmW0g3HmWi9LCjSWXrxoXY/nHXrtmMfvPEEmGFNm6NdgMsJmnfH820Qaw== +"@types/react@*", "@types/react@16.8.16": + version "16.8.16" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.16.tgz#2bf980b4fb29cceeb01b2c139b3e185e57d3e08e" + integrity sha512-A0+6kS6zwPtvubOLiCJmZ8li5bm3wKIkoKV0h3RdMDOnCj9cYkUnj3bWbE03/lcICdQmwBmUfoFiHeNhbFiyHQ== dependencies: "@types/prop-types" "*" csstype "^2.2.0" @@ -2444,6 +2545,11 @@ "@types/rx-lite-time" "*" "@types/rx-lite-virtualtime" "*" +"@types/sinon@^7.0.11": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.11.tgz#6f28f005a36e779b7db0f1359b9fb9eef72aae88" + integrity sha512-6ee09Ugx6GyEr0opUIakmxIWFNmqYPjkqa3/BuxCBokA0klsOLPgMD5K4q40lH7/yZVuJVzOfQpd7pipwjngkQ== + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -2721,12 +2827,12 @@ abbrev@1, abbrev@~1.1.1: integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== accepts@~1.3.4, accepts@~1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" - integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== dependencies: - mime-types "~2.1.18" - negotiator "0.6.1" + mime-types "~2.1.24" + negotiator "0.6.2" acorn-dynamic-import@^4.0.0: version "4.0.0" @@ -2739,9 +2845,9 @@ acorn-es7-plugin@^1.0.12: integrity sha1-8u4fMiipDurRJF+asZIusucdM2s= acorn-globals@^4.1.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.1.tgz#deb149c59276657ebd40ba2ba849ddd529763ccf" - integrity sha512-gJSiKY8dBIjV/0jagZIFBdVMtfQyA5QHCvAT48H2q8REQoW8Fs5AOjqBql1LgSXgrMWdevcE+8cdZ33NtVbIBA== + version "4.3.2" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.2.tgz#4e2c2313a597fd589720395f6354b41cd5ec8006" + integrity sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ== dependencies: acorn "^6.0.1" acorn-walk "^6.0.1" @@ -2790,11 +2896,16 @@ add-px-to-style@1.0.0: resolved "https://registry.yarnpkg.com/add-px-to-style/-/add-px-to-style-1.0.0.tgz#d0c135441fa8014a8137904531096f67f28f263a" integrity sha1-0ME1RB+oAUqBN5BFMQlvZ/KPJjo= -address@1.0.3, address@^1.0.1: +address@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9" integrity sha512-z55ocwKBRLryBs394Sm3ushTtBeg6VAeuku7utSoSnsJKvKcnXFIyC6vh27n3rXyxSgkJBBCAvyOn7gSUcTYjg== +address@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/address/-/address-1.1.0.tgz#ef8e047847fcd2c5b6f50c16965f924fd99fe709" + integrity sha512-4diPfzWbLEIElVG4AnqP+00SULlPzNuyJFNnmMrLgyaxG6tZXJ1sn7mjBu4fHrJE+Yp/jgylOweJn2xsLMFggQ== + agent-base@4, agent-base@^4.1.0, agent-base@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -2810,22 +2921,24 @@ agentkeepalive@^3.4.1: humanize-ms "^1.2.1" "airbnb-js-shims@^1 || ^2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-2.1.1.tgz#a509611480db7e6d9db62fe2acfaeb473b6842ac" - integrity sha512-h8UtyB/TCdOwWoEPQJGHgsWwSnTqPrRZbhyZYjAwY9/AbjdjfkKy9L/T3fIFS6MKX8YrpWFRm6xqFSgU+2DRGw== + version "2.2.0" + resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-2.2.0.tgz#46e1d9d9516f704ef736de76a3b6d484df9a96d8" + integrity sha512-pcSQf1+Kx7/0ibRmxj6rmMYc5V8SHlKu+rkQ80h0bjSLDaIxHg/3PiiFJi4A9mDc01CoBHoc8Fls2G/W0/+s5g== dependencies: array-includes "^3.0.3" array.prototype.flat "^1.2.1" array.prototype.flatmap "^1.2.1" - es5-shim "^4.5.10" - es6-shim "^0.35.3" + es5-shim "^4.5.13" + es6-shim "^0.35.5" function.prototype.name "^1.1.0" - object.entries "^1.0.4" - object.fromentries "^1.0.0" + globalthis "^1.0.0" + object.entries "^1.1.0" + object.fromentries "^2.0.0 || ^1.0.0" object.getownpropertydescriptors "^2.0.3" - object.values "^1.0.4" + object.values "^1.1.0" + promise.allsettled "^1.0.0" promise.prototype.finally "^3.1.0" - string.prototype.matchall "^3.0.0" + string.prototype.matchall "^3.0.1" string.prototype.padend "^3.0.0" string.prototype.padstart "^3.0.0" symbol.prototype.description "^1.0.0" @@ -3024,13 +3137,6 @@ app-root-dir@^1.0.2: resolved "https://registry.yarnpkg.com/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118" integrity sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg= -append-transform@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" - integrity sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw== - dependencies: - default-require-extensions "^2.0.0" - aproba@^1.0.3, aproba@^1.1.1, aproba@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -3293,9 +3399,9 @@ astral-regex@^1.0.0: integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== async-each@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.2.tgz#8b8a7ca2a658f927e9f307d6d1a42f4199f0f735" - integrity sha512-6xrbvN0MOBKSJDdonmSSz2OwFSgxRaVtBDes26mj9KIGtDo+g9xosFRSC+i1gQh2oAN/tQ62AI/pGZGQjVOiRg== + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== async-foreach@^0.1.3: version "0.1.3" @@ -3312,7 +3418,7 @@ async@^1.5.2, async@~1.5.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.0.0, async@^2.1.4, async@^2.6.1: +async@^2.0.0, async@^2.1.4: version "2.6.2" resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== @@ -3457,13 +3563,13 @@ babel-jest@24.6.0: chalk "^2.4.2" slash "^2.0.0" -babel-jest@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.7.1.tgz#73902c9ff15a7dfbdc9994b0b17fcefd96042178" - integrity sha512-GPnLqfk8Mtt0i4OemjWkChi73A3ALs4w2/QbG64uAj8b5mmwzxc7jbJVRZt8NJkxi6FopVHog9S3xX6UJKb2qg== +babel-jest@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589" + integrity sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw== dependencies: - "@jest/transform" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" "@types/babel__core" "^7.1.0" babel-plugin-istanbul "^5.1.0" babel-preset-jest "^24.6.0" @@ -3536,13 +3642,13 @@ babel-plugin-emotion@^9.2.11: touch "^2.0.1" babel-plugin-istanbul@^5.1.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.2.tgz#d8c2e2e83f72695d6bfdcd297719c66161d5f0f9" - integrity sha512-U3ZVajC+Z69Gim7ZzmD4Wcsq76i/1hqDamBfowc1tWzWjybRy70iWfngP2ME+1CrgcgZ/+muIbPY/Yi0dxdIkQ== + version "5.1.4" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.4.tgz#841d16b9a58eeb407a0ddce622ba02fe87a752ba" + integrity sha512-dySz4VJMH+dpndj0wjJ8JPs/7i1TdSPb1nRrn56/92pKOF9VKC1FMFJmMXjzlGGusnCAqujP6PBCiKq0sVA+YQ== dependencies: find-up "^3.0.0" - istanbul-lib-instrument "^3.2.0" - test-exclude "^5.2.2" + istanbul-lib-instrument "^3.3.0" + test-exclude "^5.2.3" babel-plugin-jest-hoist@^24.6.0: version "24.6.0" @@ -3643,9 +3749,9 @@ babel-plugin-minify-type-constructors@^0.4.3: babel-helper-is-void-0 "^0.4.3" babel-plugin-named-asset-import@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.1.tgz#5ec13ec446d0a1e5bb6c57a1f94c9cdedb0c50d6" - integrity sha512-vzZlo+yEB5YHqI6CRRTDojeT43J3Wf3C/MVkZW5UlbSeIIVUYRKtxaFT2L/VTv9mbIyatCW39+9g/SZolvwRUQ== + version "0.3.2" + resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.2.tgz#20978ed446b8e1bf4a2f42d0a94c0ece85f75f4f" + integrity sha512-CxwvxrZ9OirpXQ201Ec57OmGhmI8/ui/GwTDy0hSp6CmRvgRC0pSair6Z04Ck+JStA0sMPZzSJ3uE4n17EXpPQ== babel-plugin-react-docgen@^2.0.2: version "2.0.2" @@ -4101,13 +4207,13 @@ browserslist@4.4.1: node-releases "^1.1.3" browserslist@^4.0.0, browserslist@^4.3.4, browserslist@^4.4.2, browserslist@^4.5.2, browserslist@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.4.tgz#166c4ecef3b51737a42436ea8002aeea466ea2c7" - integrity sha512-rAjx494LMjqKnMPhFkuLmLp8JWEX0o8ADTGeAbOqaF+XCvYLreZrG5uVjnPBlAQ8REZK4pzXGvp0bWgrFtKaag== + version "4.5.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.6.tgz#ea42e8581ca2513fa7f371d4dd66da763938163d" + integrity sha512-o/hPOtbU9oX507lIqon+UvPYqpx3mHc8cV3QemSBTXwkG8gSQSK6UKvXcE/DcleU3+A59XTUHyCvZ5qGy8xVAg== dependencies: - caniuse-lite "^1.0.30000955" - electron-to-chromium "^1.3.122" - node-releases "^1.1.13" + caniuse-lite "^1.0.30000963" + electron-to-chromium "^1.3.127" + node-releases "^1.1.17" bs-logger@0.x: version "0.2.6" @@ -4360,10 +4466,10 @@ caniuse-db@1.0.30000772: resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b" integrity sha1-UarokXaChureSj2DGep21qAbUSs= -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000947, caniuse-lite@^1.0.30000955, caniuse-lite@^1.0.30000957: - version "1.0.30000960" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000960.tgz#ec48297037e5607f582f246ae7b12bee66a78999" - integrity sha512-7nK5qs17icQaX6V3/RYrJkOsZyRNnroA4+ZwxaKJzIKy+crIy0Mz5CBlLySd2SNV+4nbUZeqeNfiaEieUBu3aA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000947, caniuse-lite@^1.0.30000957, caniuse-lite@^1.0.30000963: + version "1.0.30000966" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000966.tgz#f3c6fefacfbfbfb981df6dfa68f2aae7bff41b64" + integrity sha512-qqLQ/uYrpZmFhPY96VuBkMEo8NhVFBZ9y/Bh+KnvGzGJ5I8hvpIaWlF2pw5gqe4PLAL+ZjsPgMOvoXSpX21Keg== capture-exit@^2.0.0: version "2.0.0" @@ -4781,9 +4887,9 @@ color-string@^1.5.2: simple-swizzle "^0.2.2" color@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.0.tgz#d8e9fb096732875774c84bf922815df0308d0ffc" - integrity sha512-CwyopLkuRYO5ei2EpzpIh6LqJMt6Mt+jZhO5VI5f/wJLZriXQE32/SSqzmrh+QB+AZT81Cj8yv+7zwToW8ahZg== + version "3.1.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.1.tgz#7abf5c0d38e89378284e873c207ae2172dcc8a61" + integrity sha512-PvUltIXRjehRKPSy89VnDWFKY58xyhTLyxIg21vwQBI6qLwZNPmC8k3C1uytIgFKEpOIzN4y32iPm8231zFHIg== dependencies: color-convert "^1.9.1" color-string "^1.5.2" @@ -4814,11 +4920,9 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: delayed-stream "~1.0.0" comma-separated-tokens@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.5.tgz#b13793131d9ea2d2431cf5b507ddec258f0ce0db" - integrity sha512-Cg90/fcK93n0ecgYTAz1jaA3zvnQ0ExlmKY1rdbyHqAx6BHxwoJc+J7HDu0iuQ7ixEs1qaa+WyQ6oeuBpYP1iA== - dependencies: - trim "0.0.1" + version "1.0.6" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.6.tgz#3cd3d8adc725ab473843db338bcdfd4a7bb087bf" + integrity sha512-f20oA7jsrrmERTS70r3tmRSxR8IJV2MTN7qe6hzgX+3ARfXrdMJFvGWvWQK0xpcBurg9j9eO2MiqzZ8Y+/UPCA== commander@*, commander@2, commander@^2.12.1, commander@^2.14.1, commander@^2.18.0, commander@^2.19.0, commander@^2.8.1, commander@^2.9.0, commander@~2.20.0: version "2.20.0" @@ -4864,11 +4968,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -compare-versions@^3.2.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.4.0.tgz#e0747df5c9cb7f054d6d3dc3e1dbc444f9e92b26" - integrity sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg== - component-classes@^1.2.5: version "1.2.6" resolved "https://registry.yarnpkg.com/component-classes/-/component-classes-1.2.6.tgz#c642394c3618a4d8b0b8919efccbbd930e5cd691" @@ -4897,11 +4996,11 @@ compress-commons@^1.2.0: readable-stream "^2.0.0" compressible@~2.0.16: - version "2.0.16" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.16.tgz#a49bf9858f3821b64ce1be0296afc7380466a77f" - integrity sha512-JQfEOdnI7dASwCuSPWIeVYwc/zMsu/+tRhoUvEfXz2gxOA2DNjmG5vhtFdBlhWPPGo+RdT9S3tgc/uH5qgDiiA== + version "2.0.17" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.17.tgz#6e8c108a16ad58384a977f3a482ca20bff2f38c1" + integrity sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw== dependencies: - mime-db ">= 1.38.0 < 2" + mime-db ">= 1.40.0 < 2" compression@^1.5.2: version "1.7.4" @@ -5046,9 +5145,9 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= copy-to-clipboard@^3.0.8: - version "3.1.0" - resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.1.0.tgz#0a28141899e6bd217b9dc13fd1689b3b38820b44" - integrity sha512-+RNyDq266tv5aGhfRsL6lxgj8Y6sCvTrVJnFUVvuxuqkcSMaLISt1wd4JkdQSphbcLTIQ9kEpTULNnoCXAFdng== + version "3.2.0" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz#d2724a3ccbfed89706fac8a894872c979ac74467" + integrity sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w== dependencies: toggle-selection "^1.0.6" @@ -5444,9 +5543,9 @@ cssstyle@^1.0.0: cssom "0.3.x" csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.7: - version "2.6.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.3.tgz#b701e5968245bf9b08d54ac83d00b624e622a9fa" - integrity sha512-rINUZXOkcBmoHWEyu7JdHu5JMzkGRoMX4ov9830WNgxf5UYxcBUO0QTKAqeJ5EZfSdlrcJYkC8WwfVW7JYi4yg== + version "2.6.4" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.4.tgz#d585a6062096e324e7187f80e04f92bd0f00e37f" + integrity sha512-lAJUJP3M6HxFXbqtGRc0iZrdyeN+WzOWeY0q/VnFzI+kqVrYIzC7bWlKqCW7oCIdzoPkvfp82EVvrTlQ8zsWQg== currently-unhandled@^0.4.1: version "0.4.1" @@ -6092,13 +6191,6 @@ default-gateway@^4.2.0: execa "^1.0.0" ip-regex "^2.1.0" -default-require-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" - integrity sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc= - dependencies: - strip-bom "^3.0.0" - defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -6148,10 +6240,11 @@ del@^3.0.0: rimraf "^2.2.8" del@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/del/-/del-4.1.0.tgz#049543b8290e1a9293e2bd150ab3a06f637322b8" - integrity sha512-C4kvKNlYrwXhKxz97BuohF8YoGgQ23Xm9lvoHmgT7JaPGprSEjk3+XFled74Yt/x0ZABUHg2D67covzAPUKx5Q== + version "4.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" + integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== dependencies: + "@types/glob" "^7.1.1" globby "^6.1.0" is-path-cwd "^2.0.0" is-path-in-cwd "^2.0.0" @@ -6370,14 +6463,14 @@ dom-serializer@0, dom-serializer@~0.1.1: entities "^1.1.1" dom-testing-library@^3.19.0: - version "3.19.1" - resolved "https://registry.yarnpkg.com/dom-testing-library/-/dom-testing-library-3.19.1.tgz#ccde9044cacd6d95e5e5629b9cdd2f4de3ea5e1d" - integrity sha512-cv5k+OiDhmdjHimDcal5Yz6yHv6nXwhzW3wyXrFeIGk7jSdqqFxqzhjriPvYMrV8XNNZHUDwp0dTu6SeCtO/4w== + version "3.19.4" + resolved "https://registry.yarnpkg.com/dom-testing-library/-/dom-testing-library-3.19.4.tgz#f5b737f59ee9749a4568fa353f1f59be97c888c3" + integrity sha512-GJOx8CLpnkvM3takILOsld/itUUc9+7Qh6caN1Spj6+9jIgNPY36fsvoH7sEgYokC0lBRdttO7G7fIFYCXlmcA== dependencies: - "@babel/runtime" "^7.3.4" + "@babel/runtime" "^7.4.3" "@sheerun/mutationobserver-shim" "^0.3.2" - pretty-format "^24.5.0" - wait-for-expect "^1.1.0" + pretty-format "^24.7.0" + wait-for-expect "^1.1.1" dom-walk@^0.1.0: version "0.1.1" @@ -6505,7 +6598,7 @@ editor@~1.0.0: resolved "https://registry.yarnpkg.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742" integrity sha1-YMf4e9YrzGqJT6jM1q+3gjok90I= -editorconfig@^0.15.2: +editorconfig@^0.15.3: version "0.15.3" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" integrity sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g== @@ -6525,10 +6618,10 @@ ejs@^2.6.1: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ== -electron-to-chromium@^1.3.103, electron-to-chromium@^1.3.122: - version "1.3.124" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.124.tgz#861fc0148748a11b3e5ccebdf8b795ff513fa11f" - integrity sha512-glecGr/kFdfeXUHOHAWvGcXrxNU+1wSO/t5B23tT1dtlvYB26GY8aHzZSWD7HqhqC800Lr+w/hQul6C5AF542w== +electron-to-chromium@^1.3.103, electron-to-chromium@^1.3.127: + version "1.3.131" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.131.tgz#205a0b7a276b3f56bc056f19178909243054252a" + integrity sha512-NSO4jLeyGLWrT4mzzfYX8vt1MYCoMI5LxSYAjt0H9+LF/14JyiKJSyyjA6AJTxflZlEM5v3QU33F0ohbPMCAPg== elegant-spinner@^1.0.1: version "1.0.1" @@ -6709,7 +6802,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.7.0, es-abstract@^1.9.0: +es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.13.0, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.7.0, es-abstract@^1.9.0: version "1.13.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== @@ -6731,15 +6824,15 @@ es-to-primitive@^1.2.0: is-symbol "^1.0.2" es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: - version "0.10.49" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.49.tgz#059a239de862c94494fec28f8150c977028c6c5e" - integrity sha512-3NMEhi57E31qdzmYp2jwRArIUsj1HI/RxbQ4bgnSB+AIKIxsAmTiK83bYMifIcpWvEc3P1X30DhUKOqEtF/kvg== + version "0.10.50" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778" + integrity sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw== dependencies: es6-iterator "~2.0.3" es6-symbol "~3.1.1" next-tick "^1.0.0" -es5-shim@^4.5.10: +es5-shim@^4.5.13: version "4.5.13" resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.5.13.tgz#5d88062de049f8969f83783f4a4884395f21d28b" integrity sha512-xi6hh6gsvDE0MaW4Vp1lgNEBpVcCXRWfPXj5egDvtgLz4L9MEvNwYEMdJH+JJinWkwa8c3c3o5HduV7dB/e1Hw== @@ -6793,7 +6886,7 @@ es6-set@~0.1.5: es6-symbol "3.1.1" event-emitter "~0.3.5" -es6-shim@0.35.5, es6-shim@^0.35.3: +es6-shim@0.35.5, es6-shim@^0.35.5: version "0.35.5" resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.5.tgz#46f59dc0a84a1c5029e8ff1166ca0a902077a9ab" integrity sha512-E9kK/bjtCQRpN1K28Xh4BlmP8egvZBGJJ+9GtnzOwt7mdqtrjHFuVGr7QJfdjBIKqrlU5duPf3pCBoDrkjVYFg== @@ -6989,9 +7082,9 @@ eventemitter3@2.0.3: integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= eventemitter3@^3.0.0, eventemitter3@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" - integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== events@^3.0.0: version "3.0.0" @@ -7098,21 +7191,26 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" +expect-puppeteer@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/expect-puppeteer/-/expect-puppeteer-4.1.1.tgz#cda2ab7b6fa27ac24eba273bbb0296a0de538e6d" + integrity sha512-xNpu6uYJL9Qrrp4Z31MOpDWK68zAi+2qg5aMQlyOTVZNy7cAgBZiPvKCN0C1JmP3jgPZfcxhetVjZLaw/KcJOQ== + expect.js@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/expect.js/-/expect.js-0.2.0.tgz#1028533d2c1c363f74a6796ff57ec0520ded2be1" integrity sha1-EChTPSwcNj90pnlv9X7AUg3tK+E= -expect@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-24.7.1.tgz#d91defbab4e627470a152feaf35b3c31aa1c7c14" - integrity sha512-mGfvMTPduksV3xoI0xur56pQsg2vJjNf5+a+bXOjqCkiCBbmCayrBbHS/75y9K430cfqyocPr2ZjiNiRx4SRKw== +expect@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.8.0.tgz#471f8ec256b7b6129ca2524b2a62f030df38718d" + integrity sha512-/zYvP8iMDrzaaxHVa724eJBCKqSHmO0FA7EDkBiRHxg6OipmMn1fN+C8T9L9K8yr7UONkOifu6+LLH+z76CnaA== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" ansi-styles "^3.2.0" - jest-get-type "^24.3.0" - jest-matcher-utils "^24.7.0" - jest-message-util "^24.7.1" + jest-get-type "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" jest-regex-util "^24.3.0" expose-loader@0.7.5: @@ -7206,7 +7304,7 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-zip@^1.6.5: +extract-zip@^1.6.5, extract-zip@^1.6.6: version "1.6.7" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= @@ -7253,6 +7351,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-text-encoding@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz#3e5ce8293409cfaa7177a71b9ca84e1b1e6f25ef" + integrity sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ== + fastparse@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" @@ -7374,14 +7477,6 @@ filename-regex@^2.0.0: resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= -fileset@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" - integrity sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA= - dependencies: - glob "^7.0.3" - minimatch "^3.0.3" - filesize@3.6.1, filesize@^3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" @@ -7507,10 +7602,10 @@ fn-name@~2.0.1: resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" integrity sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc= -focus-lock@^0.6.0: - version "0.6.2" - resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.2.tgz#d8ac9dbc46250779789c3e6f43d978c7dfa59dcd" - integrity sha512-Wuq6TSOgsGQmzbpvinl1TcEw4BAUhD9T06myl5HvaBlO0geAz9ABtqBIqPkkNyO3AgPzEDazetL5hSRgj+2iqQ== +focus-lock@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.3.tgz#ef0e82ebac0023f841039d60bf329725d6438028" + integrity sha512-EU6ePgEauhWrzJEN5RtG1d1ayrWXhEnfzTjnieHj+jG9tNHDEhKTAnCn1TN3gs9h6XWCDH6cpeX1VXY/lzLwZg== follow-redirects@^1.0.0, follow-redirects@^1.3.0: version "1.7.0" @@ -7697,12 +7792,12 @@ fs.realpath@^1.0.0: integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fsevents@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" - integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw== + version "1.2.9" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" + integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== dependencies: - nan "^2.9.2" - node-pre-gyp "^0.10.0" + nan "^2.12.1" + node-pre-gyp "^0.12.0" fstream@^1.0.0, fstream@^1.0.2: version "1.0.11" @@ -8001,15 +8096,24 @@ global@^4.3.0, global@^4.3.2: process "~0.5.1" globals@^11.1.0: - version "11.11.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.11.0.tgz#dcf93757fa2de5486fbeed7118538adf789e9c2e" - integrity sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw== + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^9.2.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== +globalthis@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.0.tgz#c5fb98213a9b4595f59cf3e7074f141b4169daae" + integrity sha512-vcCAZTJ3r5Qcu5l8/2oyVdoFwxKgfYnMTR2vwWeux/NAVZK3PwcMaWkdUIn4GJbmKuRK7xcvDsLuK+CKcXyodg== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + object-keys "^1.0.12" + globby@8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d" @@ -8280,7 +8384,7 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== -handlebars@^4.1.0: +handlebars@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw== @@ -9037,12 +9141,7 @@ ip@^1.1.0, ip@^1.1.5: resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= -ipaddr.js@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" - integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4= - -ipaddr.js@^1.9.0: +ipaddr.js@1.9.0, ipaddr.js@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== @@ -9079,6 +9178,11 @@ is-alphanumerical@^1.0.0: is-alphabetical "^1.0.0" is-decimal "^1.0.0" +is-arguments@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" + integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -9269,6 +9373,11 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" + integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw== + is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -9388,9 +9497,9 @@ is-path-cwd@^1.0.0: integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0= is-path-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.0.0.tgz#d4777a8e227a00096a31f030db3770f84b116c02" - integrity sha512-m5dHHzpOXEiv18JEORttBO64UgTEypx99vCxQLjbBvGhOJxnTNglYoFXxwo6AbsQb79sqqycQEHv2hWkHZAijA== + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.1.0.tgz#2e0c7e463ff5b7a0eb60852d851a6809347a124c" + integrity sha512-Sc5j3/YnM8tDeyCsVeKlm/0p95075DyLmDEIkSgQ7mXkrOX+uTCtmQFm0CYzVyJwcCCmO3k8qfJt17SxQwB5Zw== is-path-in-cwd@^1.0.0: version "1.0.1" @@ -9400,11 +9509,11 @@ is-path-in-cwd@^1.0.0: is-path-inside "^1.0.0" is-path-in-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.0.0.tgz#68e452a6eec260500cec21e029c0a44cc0dcd2ea" - integrity sha512-6Vz5Gc9s/sDA3JBVu0FzWufm8xaBsqy1zn8Q6gmvGP6nSDMw78aS4poBNeatWjaRpTpxxLn1WOndAiOlk+qY8A== + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" + integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== dependencies: - is-path-inside "^1.0.0" + is-path-inside "^2.1.0" is-path-inside@^1.0.0: version "1.0.1" @@ -9413,6 +9522,13 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" +is-path-inside@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" + integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== + dependencies: + path-is-inside "^1.0.2" + is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -9583,126 +9699,100 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -istanbul-api@^2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.1.4.tgz#5c7fe1d1c9b96ba3184ac4bbff7fc04eaccbdd8b" - integrity sha512-aAFQL0HA2BLUl18XmTQ7H7CGKI58DtZFvvfmg6e+rA3iNFergvpi16czLV4CpI7HOImMeZ5mqI62dvSNVtUQVA== - dependencies: - async "^2.6.1" - compare-versions "^3.2.1" - fileset "^2.0.3" - istanbul-lib-coverage "^2.0.4" - istanbul-lib-hook "^2.0.6" - istanbul-lib-instrument "^3.2.0" - istanbul-lib-report "^2.0.7" - istanbul-lib-source-maps "^3.0.5" - istanbul-reports "^2.2.2" - js-yaml "^3.13.0" - make-dir "^2.1.0" - minimatch "^3.0.4" - once "^1.4.0" - -istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#927a354005d99dd43a24607bb8b33fd4e9aca1ad" - integrity sha512-LXTBICkMARVgo579kWDm8SqfB6nvSDKNqIOBEjmJRnL04JvoMHCYGWaMddQnseJYtkEuEvO/sIcOxPLk9gERug== - -istanbul-lib-hook@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.6.tgz#5baa6067860a38290aef038b389068b225b01b7d" - integrity sha512-829DKONApZ7UCiPXcOYWSgkFXa4+vNYoNOt3F+4uDJLKL1OotAoVwvThoEj1i8jmOj7odbYcR3rnaHu+QroaXg== - dependencies: - append-transform "^1.0.0" +istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" + integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== -istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.2.0.tgz#c549208da8a793f6622257a2da83e0ea96ae6a93" - integrity sha512-06IM3xShbNW4NgZv5AP4QH0oHqf1/ivFo8eFys0ZjPXHGldHJQWb3riYOKXqmOqfxXBfxu4B+g/iuhOPZH0RJg== +istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" + integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== dependencies: - "@babel/generator" "^7.0.0" - "@babel/parser" "^7.0.0" - "@babel/template" "^7.0.0" - "@babel/traverse" "^7.0.0" - "@babel/types" "^7.0.0" - istanbul-lib-coverage "^2.0.4" + "@babel/generator" "^7.4.0" + "@babel/parser" "^7.4.3" + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.3" + "@babel/types" "^7.4.0" + istanbul-lib-coverage "^2.0.5" semver "^6.0.0" -istanbul-lib-report@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.7.tgz#370d80d433c4dbc7f58de63618f49599c74bd954" - integrity sha512-wLH6beJBFbRBLiTlMOBxmb85cnVM1Vyl36N48e4e/aTKSM3WbOx7zbVIH1SQ537fhhsPbX0/C5JB4qsmyRXXyA== +istanbul-lib-report@^2.0.4: + version "2.0.8" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" + integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== dependencies: - istanbul-lib-coverage "^2.0.4" + istanbul-lib-coverage "^2.0.5" make-dir "^2.1.0" - supports-color "^6.0.0" + supports-color "^6.1.0" -istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.5.tgz#1d9ee9d94d2633f15611ee7aae28f9cac6d1aeb9" - integrity sha512-eDhZ7r6r1d1zQPVZehLc3D0K14vRba/eBYkz3rw16DLOrrTzve9RmnkcwrrkWVgO1FL3EK5knujVe5S8QHE9xw== +istanbul-lib-source-maps@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" + integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== dependencies: debug "^4.1.1" - istanbul-lib-coverage "^2.0.4" + istanbul-lib-coverage "^2.0.5" make-dir "^2.1.0" - rimraf "^2.6.2" + rimraf "^2.6.3" source-map "^0.6.1" -istanbul-reports@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.2.tgz#b84303c46fc0541c3c3b69abae687c42a6907d44" - integrity sha512-ZFuTdBQ3PSaPnm02aEA4R6mzQ2AF9w03CYiXADzWbbE48v/EFOWF4MaX4FT0NRdqIk48I7o0RPi+S8TMswaCbQ== +istanbul-reports@^2.1.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.4.tgz#4e0d0ddf0f0ad5b49a314069d31b4f06afe49ad3" + integrity sha512-QCHGyZEK0bfi9GR215QSm+NJwFKEShbtc7tfbUdLAEzn3kKhLDDZqvljn8rPZM9v8CEOhzL1nlYoO4r1ryl67w== dependencies: - handlebars "^4.1.0" + handlebars "^4.1.2" -jest-changed-files@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.7.0.tgz#39d723a11b16ed7b373ac83adc76a69464b0c4fa" - integrity sha512-33BgewurnwSfJrW7T5/ZAXGE44o7swLslwh8aUckzq2e17/2Os1V0QU506ZNik3hjs8MgnEMKNkcud442NCDTw== +jest-changed-files@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.8.0.tgz#7e7eb21cf687587a85e50f3d249d1327e15b157b" + integrity sha512-qgANC1Yrivsq+UrLXsvJefBKVoCsKB0Hv+mBb6NMjjZ90wwxCDmU3hsCXBya30cH+LnPYjwgcU65i6yJ5Nfuug== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" execa "^1.0.0" throat "^4.0.0" jest-cli@^24.6.0: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.7.1.tgz#6093a539073b6f4953145abeeb9709cd621044f1" - integrity sha512-32OBoSCVPzcTslGFl6yVCMzB2SqX3IrWwZCY5mZYkb0D2WsogmU3eV2o8z7+gRQa4o4sZPX/k7GU+II7CxM6WQ== + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.8.0.tgz#b075ac914492ed114fa338ade7362a301693e989" + integrity sha512-+p6J00jSMPQ116ZLlHJJvdf8wbjNbZdeSX9ptfHX06/MSNaXmKihQzx5vQcw0q2G6JsdVkUIdWbOWtSnaYs3yA== dependencies: - "@jest/core" "^24.7.1" - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/core" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" chalk "^2.0.1" exit "^0.1.2" import-local "^2.0.0" is-ci "^2.0.0" - jest-config "^24.7.1" - jest-util "^24.7.1" - jest-validate "^24.7.0" + jest-config "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" prompts "^2.0.1" realpath-native "^1.1.0" yargs "^12.0.2" -jest-config@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.7.1.tgz#6c1dd4db82a89710a3cf66bdba97827c9a1cf052" - integrity sha512-8FlJNLI+X+MU37j7j8RE4DnJkvAghXmBWdArVzypW6WxfGuxiL/CCkzBg0gHtXhD2rxla3IMOSUAHylSKYJ83g== +jest-config@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.8.0.tgz#77db3d265a6f726294687cbbccc36f8a76ee0f4f" + integrity sha512-Czl3Nn2uEzVGsOeaewGWoDPD8GStxCpAe0zOYs2x2l0fZAgPbCr3uwUkgNKV3LwE13VXythM946cd5rdGkkBZw== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^24.7.1" - "@jest/types" "^24.7.0" - babel-jest "^24.7.1" + "@jest/test-sequencer" "^24.8.0" + "@jest/types" "^24.8.0" + babel-jest "^24.8.0" chalk "^2.0.1" glob "^7.1.1" - jest-environment-jsdom "^24.7.1" - jest-environment-node "^24.7.1" - jest-get-type "^24.3.0" - jest-jasmine2 "^24.7.1" + jest-environment-jsdom "^24.8.0" + jest-environment-node "^24.8.0" + jest-get-type "^24.8.0" + jest-jasmine2 "^24.8.0" jest-regex-util "^24.3.0" - jest-resolve "^24.7.1" - jest-util "^24.7.1" - jest-validate "^24.7.0" + jest-resolve "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" micromatch "^3.1.10" - pretty-format "^24.7.0" + pretty-format "^24.8.0" realpath-native "^1.1.0" jest-date-mock@1.0.7: @@ -9710,15 +9800,15 @@ jest-date-mock@1.0.7: resolved "https://registry.yarnpkg.com/jest-date-mock/-/jest-date-mock-1.0.7.tgz#b1e5071beb314cbfcfb0a21d8560a2122519f5c3" integrity sha512-ZgbYlURRhVpf52Jho4tlyUgpJJ+nYvhxIWhIIEdarmsSuTxFZlC9DmKCyQXsuEljfxK+7HsqdBX/L6rxwWraYw== -jest-diff@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.7.0.tgz#5d862899be46249754806f66e5729c07fcb3580f" - integrity sha512-ULQZ5B1lWpH70O4xsANC4tf4Ko6RrpwhE3PtG6ERjMg1TiYTC2Wp4IntJVGro6a8HG9luYHhhmF4grF0Pltckg== +jest-diff@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172" + integrity sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g== dependencies: chalk "^2.0.1" diff-sequences "^24.3.0" - jest-get-type "^24.3.0" - pretty-format "^24.7.0" + jest-get-type "^24.8.0" + pretty-format "^24.8.0" jest-docblock@^24.3.0: version "24.3.0" @@ -9727,57 +9817,57 @@ jest-docblock@^24.3.0: dependencies: detect-newline "^2.1.0" -jest-each@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.7.1.tgz#fcc7dda4147c28430ad9fb6dc7211cd17ab54e74" - integrity sha512-4fsS8fEfLa3lfnI1Jw6NxjhyRTgfpuOVTeUZZFyVYqeTa4hPhr2YkToUhouuLTrL2eMGOfpbdMyRx0GQ/VooKA== +jest-each@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.8.0.tgz#a05fd2bf94ddc0b1da66c6d13ec2457f35e52775" + integrity sha512-NrwK9gaL5+XgrgoCsd9svsoWdVkK4gnvyhcpzd6m487tXHqIdYeykgq3MKI1u4I+5Zf0tofr70at9dWJDeb+BA== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" chalk "^2.0.1" - jest-get-type "^24.3.0" - jest-util "^24.7.1" - pretty-format "^24.7.0" - -jest-environment-jsdom@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.7.1.tgz#a40e004b4458ebeb8a98082df135fd501b9fbbd6" - integrity sha512-Gnhb+RqE2JuQGb3kJsLF8vfqjt3PHKSstq4Xc8ic+ax7QKo4Z0RWGucU3YV+DwKR3T9SYc+3YCUQEJs8r7+Jxg== - dependencies: - "@jest/environment" "^24.7.1" - "@jest/fake-timers" "^24.7.1" - "@jest/types" "^24.7.0" - jest-mock "^24.7.0" - jest-util "^24.7.1" + jest-get-type "^24.8.0" + jest-util "^24.8.0" + pretty-format "^24.8.0" + +jest-environment-jsdom@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz#300f6949a146cabe1c9357ad9e9ecf9f43f38857" + integrity sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ== + dependencies: + "@jest/environment" "^24.8.0" + "@jest/fake-timers" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" + jest-util "^24.8.0" jsdom "^11.5.1" -jest-environment-node@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.7.1.tgz#fa2c047a31522a48038d26ee4f7c8fd9c1ecfe12" - integrity sha512-GJJQt1p9/C6aj6yNZMvovZuxTUd+BEJprETdvTKSb4kHcw4mFj8777USQV0FJoJ4V3djpOwA5eWyPwfq//PFBA== +jest-environment-node@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.8.0.tgz#d3f726ba8bc53087a60e7a84ca08883a4c892231" + integrity sha512-vIGUEScd1cdDgR6sqn2M08sJTRLQp6Dk/eIkCeO4PFHxZMOgy+uYLPMC4ix3PEfM5Au/x3uQ/5Tl0DpXXZsJ/Q== dependencies: - "@jest/environment" "^24.7.1" - "@jest/fake-timers" "^24.7.1" - "@jest/types" "^24.7.0" - jest-mock "^24.7.0" - jest-util "^24.7.1" + "@jest/environment" "^24.8.0" + "@jest/fake-timers" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" + jest-util "^24.8.0" -jest-get-type@^24.3.0: - version "24.3.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.3.0.tgz#582cfd1a4f91b5cdad1d43d2932f816d543c65da" - integrity sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow== +jest-get-type@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc" + integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ== -jest-haste-map@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.7.1.tgz#772e215cd84080d4bbcb759cfb668ad649a21471" - integrity sha512-g0tWkzjpHD2qa03mTKhlydbmmYiA2KdcJe762SbfFo/7NIMgBWAA0XqQlApPwkWOF7Cxoi/gUqL0i6DIoLpMBw== +jest-haste-map@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.0.tgz#51794182d877b3ddfd6e6d23920e3fe72f305800" + integrity sha512-ZBPRGHdPt1rHajWelXdqygIDpJx8u3xOoLyUBWRW28r3tagrgoepPrzAozW7kW9HrQfhvmiv1tncsxqHJO1onQ== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" anymatch "^2.0.0" fb-watchman "^2.0.0" graceful-fs "^4.1.15" invariant "^2.2.4" jest-serializer "^24.4.0" - jest-util "^24.7.1" + jest-util "^24.8.0" jest-worker "^24.6.0" micromatch "^3.1.10" sane "^4.0.3" @@ -9785,65 +9875,65 @@ jest-haste-map@^24.7.1: optionalDependencies: fsevents "^1.2.7" -jest-jasmine2@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.7.1.tgz#01398686dabe46553716303993f3be62e5d9d818" - integrity sha512-Y/9AOJDV1XS44wNwCaThq4Pw3gBPiOv/s6NcbOAkVRRUEPu+36L2xoPsqQXsDrxoBerqeyslpn2TpCI8Zr6J2w== +jest-jasmine2@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz#a9c7e14c83dd77d8b15e820549ce8987cc8cd898" + integrity sha512-cEky88npEE5LKd5jPpTdDCLvKkdyklnaRycBXL6GNmpxe41F0WN44+i7lpQKa/hcbXaQ+rc9RMaM4dsebrYong== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^24.7.1" - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" chalk "^2.0.1" co "^4.6.0" - expect "^24.7.1" + expect "^24.8.0" is-generator-fn "^2.0.0" - jest-each "^24.7.1" - jest-matcher-utils "^24.7.0" - jest-message-util "^24.7.1" - jest-runtime "^24.7.1" - jest-snapshot "^24.7.1" - jest-util "^24.7.1" - pretty-format "^24.7.0" + jest-each "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" + jest-runtime "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + pretty-format "^24.8.0" throat "^4.0.0" -jest-leak-detector@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.7.0.tgz#323ff93ed69be12e898f5b040952f08a94288ff9" - integrity sha512-zV0qHKZGXtmPVVzT99CVEcHE9XDf+8LwiE0Ob7jjezERiGVljmqKFWpV2IkG+rkFIEUHFEkMiICu7wnoPM/RoQ== +jest-leak-detector@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.8.0.tgz#c0086384e1f650c2d8348095df769f29b48e6980" + integrity sha512-cG0yRSK8A831LN8lIHxI3AblB40uhv0z+SsQdW3GoMMVcK+sJwrIIyax5tu3eHHNJ8Fu6IMDpnLda2jhn2pD/g== dependencies: - pretty-format "^24.7.0" + pretty-format "^24.8.0" -jest-matcher-utils@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.7.0.tgz#bbee1ff37bc8b2e4afcaabc91617c1526af4bcd4" - integrity sha512-158ieSgk3LNXeUhbVJYRXyTPSCqNgVXOp/GT7O94mYd3pk/8+odKTyR1JLtNOQSPzNi8NFYVONtvSWA/e1RDXg== +jest-matcher-utils@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz#2bce42204c9af12bde46f83dc839efe8be832495" + integrity sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw== dependencies: chalk "^2.0.1" - jest-diff "^24.7.0" - jest-get-type "^24.3.0" - pretty-format "^24.7.0" + jest-diff "^24.8.0" + jest-get-type "^24.8.0" + pretty-format "^24.8.0" -jest-message-util@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.7.1.tgz#f1dc3a6c195647096a99d0f1dadbc447ae547018" - integrity sha512-dk0gqVtyqezCHbcbk60CdIf+8UHgD+lmRHifeH3JRcnAqh4nEyPytSc9/L1+cQyxC+ceaeP696N4ATe7L+omcg== +jest-message-util@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.8.0.tgz#0d6891e72a4beacc0292b638685df42e28d6218b" + integrity sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g== dependencies: "@babel/code-frame" "^7.0.0" - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" "@types/stack-utils" "^1.0.1" chalk "^2.0.1" micromatch "^3.1.10" slash "^2.0.0" stack-utils "^1.0.1" -jest-mock@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.7.0.tgz#e49ce7262c12d7f5897b0d8af77f6db8e538023b" - integrity sha512-6taW4B4WUcEiT2V9BbOmwyGuwuAFT2G8yghF7nyNW1/2gq5+6aTqSPcS9lS6ArvEkX55vbPAS/Jarx5LSm4Fng== +jest-mock@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56" + integrity sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" jest-pnp-resolver@^1.2.1: version "1.2.1" @@ -9855,75 +9945,75 @@ jest-regex-util@^24.3.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.3.0.tgz#d5a65f60be1ae3e310d5214a0307581995227b36" integrity sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg== -jest-resolve-dependencies@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.7.1.tgz#cf93bbef26999488a96a2b2012f9fe7375aa378f" - integrity sha512-2Eyh5LJB2liNzfk4eo7bD1ZyBbqEJIyyrFtZG555cSWW9xVHxII2NuOkSl1yUYTAYCAmM2f2aIT5A7HzNmubyg== +jest-resolve-dependencies@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz#19eec3241f2045d3f990dba331d0d7526acff8e0" + integrity sha512-hyK1qfIf/krV+fSNyhyJeq3elVMhK9Eijlwy+j5jqmZ9QsxwKBiP6qukQxaHtK8k6zql/KYWwCTQ+fDGTIJauw== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" jest-regex-util "^24.3.0" - jest-snapshot "^24.7.1" + jest-snapshot "^24.8.0" -jest-resolve@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.7.1.tgz#e4150198299298380a75a9fd55043fa3b9b17fde" - integrity sha512-Bgrc+/UUZpGJ4323sQyj85hV9d+ANyPNu6XfRDUcyFNX1QrZpSoM0kE4Mb2vZMAYTJZsBFzYe8X1UaOkOELSbw== +jest-resolve@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.8.0.tgz#84b8e5408c1f6a11539793e2b5feb1b6e722439f" + integrity sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" browser-resolve "^1.11.3" chalk "^2.0.1" jest-pnp-resolver "^1.2.1" realpath-native "^1.1.0" -jest-runner@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.7.1.tgz#41c8a02a06aa23ea82d8bffd69d7fa98d32f85bf" - integrity sha512-aNFc9liWU/xt+G9pobdKZ4qTeG/wnJrJna3VqunziDNsWT3EBpmxXZRBMKCsNMyfy+A/XHiV+tsMLufdsNdgCw== +jest-runner@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.8.0.tgz#4f9ae07b767db27b740d7deffad0cf67ccb4c5bb" + integrity sha512-utFqC5BaA3JmznbissSs95X1ZF+d+4WuOWwpM9+Ak356YtMhHE/GXUondZdcyAAOTBEsRGAgH/0TwLzfI9h7ow== dependencies: "@jest/console" "^24.7.1" - "@jest/environment" "^24.7.1" - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" chalk "^2.4.2" exit "^0.1.2" graceful-fs "^4.1.15" - jest-config "^24.7.1" + jest-config "^24.8.0" jest-docblock "^24.3.0" - jest-haste-map "^24.7.1" - jest-jasmine2 "^24.7.1" - jest-leak-detector "^24.7.0" - jest-message-util "^24.7.1" - jest-resolve "^24.7.1" - jest-runtime "^24.7.1" - jest-util "^24.7.1" + jest-haste-map "^24.8.0" + jest-jasmine2 "^24.8.0" + jest-leak-detector "^24.8.0" + jest-message-util "^24.8.0" + jest-resolve "^24.8.0" + jest-runtime "^24.8.0" + jest-util "^24.8.0" jest-worker "^24.6.0" source-map-support "^0.5.6" throat "^4.0.0" -jest-runtime@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.7.1.tgz#2ffd70b22dd03a5988c0ab9465c85cdf5d25c597" - integrity sha512-0VAbyBy7tll3R+82IPJpf6QZkokzXPIS71aDeqh+WzPRXRCNz6StQ45otFariPdJ4FmXpDiArdhZrzNAC3sj6A== +jest-runtime@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.8.0.tgz#05f94d5b05c21f6dc54e427cd2e4980923350620" + integrity sha512-Mq0aIXhvO/3bX44ccT+czU1/57IgOMyy80oM0XR/nyD5zgBcesF84BPabZi39pJVA6UXw+fY2Q1N+4BiVUBWOA== dependencies: "@jest/console" "^24.7.1" - "@jest/environment" "^24.7.1" + "@jest/environment" "^24.8.0" "@jest/source-map" "^24.3.0" - "@jest/transform" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" "@types/yargs" "^12.0.2" chalk "^2.0.1" exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.1.15" - jest-config "^24.7.1" - jest-haste-map "^24.7.1" - jest-message-util "^24.7.1" - jest-mock "^24.7.0" + jest-config "^24.8.0" + jest-haste-map "^24.8.0" + jest-message-util "^24.8.0" + jest-mock "^24.8.0" jest-regex-util "^24.3.0" - jest-resolve "^24.7.1" - jest-snapshot "^24.7.1" - jest-util "^24.7.1" - jest-validate "^24.7.0" + jest-resolve "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" realpath-native "^1.1.0" slash "^2.0.0" strip-bom "^3.0.0" @@ -9934,34 +10024,34 @@ jest-serializer@^24.4.0: resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.4.0.tgz#f70c5918c8ea9235ccb1276d232e459080588db3" integrity sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q== -jest-snapshot@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.7.1.tgz#bd5a35f74aedff070975e9e9c90024f082099568" - integrity sha512-8Xk5O4p+JsZZn4RCNUS3pxA+ORKpEKepE+a5ejIKrId9CwrVN0NY+vkqEkXqlstA5NMBkNahXkR/4qEBy0t5yA== +jest-snapshot@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.8.0.tgz#3bec6a59da2ff7bc7d097a853fb67f9d415cb7c6" + integrity sha512-5ehtWoc8oU9/cAPe6fez6QofVJLBKyqkY2+TlKTOf0VllBB/mqUNdARdcjlZrs9F1Cv+/HKoCS/BknT0+tmfPg== dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" chalk "^2.0.1" - expect "^24.7.1" - jest-diff "^24.7.0" - jest-matcher-utils "^24.7.0" - jest-message-util "^24.7.1" - jest-resolve "^24.7.1" + expect "^24.8.0" + jest-diff "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" + jest-resolve "^24.8.0" mkdirp "^0.5.1" natural-compare "^1.4.0" - pretty-format "^24.7.0" + pretty-format "^24.8.0" semver "^5.5.0" -jest-util@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.7.1.tgz#b4043df57b32a23be27c75a2763d8faf242038ff" - integrity sha512-/KilOue2n2rZ5AnEBYoxOXkeTu6vi7cjgQ8MXEkih0oeAXT6JkS3fr7/j8+engCjciOU1Nq5loMSKe0A1oeX0A== +jest-util@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1" + integrity sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA== dependencies: "@jest/console" "^24.7.1" - "@jest/fake-timers" "^24.7.1" + "@jest/fake-timers" "^24.8.0" "@jest/source-map" "^24.3.0" - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" callsites "^3.0.0" chalk "^2.0.1" graceful-fs "^4.1.15" @@ -9970,29 +10060,29 @@ jest-util@^24.7.1: slash "^2.0.0" source-map "^0.6.0" -jest-validate@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.7.0.tgz#70007076f338528ee1b1c8a8258b1b0bb982508d" - integrity sha512-cgai/gts9B2chz1rqVdmLhzYxQbgQurh1PEQSvSgPZ8KGa1AqXsqC45W5wKEwzxKrWqypuQrQxnF4+G9VejJJA== +jest-validate@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.8.0.tgz#624c41533e6dfe356ffadc6e2423a35c2d3b4849" + integrity sha512-+/N7VOEMW1Vzsrk3UWBDYTExTPwf68tavEPKDnJzrC6UlHtUDU/fuEdXqFoHzv9XnQ+zW6X3qMZhJ3YexfeLDA== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" camelcase "^5.0.0" chalk "^2.0.1" - jest-get-type "^24.3.0" + jest-get-type "^24.8.0" leven "^2.1.0" - pretty-format "^24.7.0" + pretty-format "^24.8.0" -jest-watcher@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.7.1.tgz#e161363d7f3f4e1ef3d389b7b3a0aad247b673f5" - integrity sha512-Wd6TepHLRHVKLNPacEsBwlp9raeBIO+01xrN24Dek4ggTS8HHnOzYSFnvp+6MtkkJ3KfMzy220KTi95e2rRkrw== +jest-watcher@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.8.0.tgz#58d49915ceddd2de85e238f6213cef1c93715de4" + integrity sha512-SBjwHt5NedQoVu54M5GEx7cl7IGEFFznvd/HNT8ier7cCAx/Qgu9ZMlaTQkvK22G1YOpcWBLQPFSImmxdn3DAw== dependencies: - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" "@types/yargs" "^12.0.9" ansi-escapes "^3.0.0" chalk "^2.0.1" - jest-util "^24.7.1" + jest-util "^24.8.0" string-length "^2.0.0" jest-worker@^24.0.0, jest-worker@^24.6.0: @@ -10022,14 +10112,14 @@ js-base64@^2.1.8, js-base64@^2.1.9: integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw== js-beautify@^1.8.9: - version "1.9.1" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.9.1.tgz#6f9ef915f5d8d92b9f907606fce63795884c8040" - integrity sha512-oxxvVZdOdUfzk8IOLBF2XUZvl2GoBEfA+b0of4u2EBY/46NlXasi8JdFvazA5lCrf9/lQhTjyVy2QCUW7iq0MQ== + version "1.10.0" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.0.tgz#9753a13c858d96828658cd18ae3ca0e5783ea672" + integrity sha512-OMwf/tPDpE/BLlYKqZOhqWsd3/z2N3KOlyn1wsCRGFwViE8LOQTcDtathQvHvZc+q+zWmcNAbwKSC+iJoMaH2Q== dependencies: config-chain "^1.1.12" - editorconfig "^0.15.2" + editorconfig "^0.15.3" glob "^7.1.3" - mkdirp "~0.5.0" + mkdirp "~0.5.1" nopt "~4.0.1" js-levenshtein@^1.1.3: @@ -10047,7 +10137,7 @@ js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@^3.13.0, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.7.0, js-yaml@^3.9.0: +js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.7.0, js-yaml@^3.9.0: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -11008,9 +11098,9 @@ map-visit@^1.0.0: object-visit "^1.0.0" markdown-to-jsx@^6.9.1: - version "6.9.3" - resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-6.9.3.tgz#31719e3c54517ba9805db81d53701b89f5d2ed7e" - integrity sha512-iXteiv317VZd1vk/PBH5MWMD4r0XWekoWCHRVVadBcnCtxavhtfV1UaEaQgq9KyckTv31L60ASh5ZVVrOh37Qg== + version "6.9.4" + resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-6.9.4.tgz#ed1e6925e643f7a0ee2fed90f28d16b4402aad09" + integrity sha512-Fvx2ZhiknGmcLsWVjIq6MmiN9gcCot8w+jzwN2mLXZcQsJGRN3Zes5Sp5M9YNIzUy/sDyuOTjimFdtAcvvmAPQ== dependencies: prop-types "^15.6.2" unquote "^1.1.0" @@ -11128,9 +11218,9 @@ meow@^3.3.0, meow@^3.7.0: trim-newlines "^1.0.0" merge-class-names@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/merge-class-names/-/merge-class-names-1.1.1.tgz#3bd2f38eb5418c464a0fef615484fdf6c8932256" - integrity sha512-+UUWBUoFw9QLY/UlBKU/xk9h6OhyG3BUDDuF2eIJcxmusWb/uedvNpZGkysqMw5b/ds+wkX7NJTDSdUuRsCNyA== + version "1.2.0" + resolved "https://registry.yarnpkg.com/merge-class-names/-/merge-class-names-1.2.0.tgz#cb30ecfc3bdbd96b6f76d0a98777907e5fbb3462" + integrity sha512-ifHxhC8DojHT1rG3PHCaJYInUqPd0WO+PxsaYDMkgy7RzfyOFtnlpr/hbhki+m/3R/ujIRVnZkD/AHjgjb5uhg== merge-deep@^3.0.2: version "3.0.2" @@ -11214,22 +11304,17 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.38.0 < 2": - version "1.39.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.39.0.tgz#f95a20275742f7d2ad0429acfe40f4233543780e" - integrity sha512-DTsrw/iWVvwHH+9Otxccdyy0Tgiil6TWK/xhfARJZF/QFhwOgZgOIvA2/VIGpM8U7Q8z5nDmdDWC6tuVMJNibw== - -mime-db@~1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" - integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== +mime-db@1.40.0, "mime-db@>= 1.40.0 < 2": + version "1.40.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" + integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.19: - version "2.1.22" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" - integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog== +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: + version "2.1.24" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" + integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== dependencies: - mime-db "~1.38.0" + mime-db "1.40.0" mime@1.4.1: version "1.4.1" @@ -11282,7 +11367,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.0, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.0, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -11386,6 +11471,11 @@ mocha@4.1.0: mkdirp "0.5.1" supports-color "4.4.0" +module-alias@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.0.tgz#a2e32275381642252bf0c51405f7a09a367479b5" + integrity sha512-O4bbvlZkHj2LUQhieQWWCr486ddc8X+WwRqi3QGnFKfknaxdHTOB7+xRgeyWHc6arpjgtT5SLLMMTFwUM3/x5w== + moment@2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" @@ -11471,7 +11561,7 @@ mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@^2.10.0, nan@^2.6.2, nan@^2.9.2: +nan@^2.10.0, nan@^2.12.1, nan@^2.6.2: version "2.13.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== @@ -11517,18 +11607,18 @@ nearley@^2.7.10: semver "^5.4.1" needle@^2.2.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.0.tgz#ce3fea21197267bacb310705a7bbe24f2a3a3492" - integrity sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg== + version "2.3.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.1.tgz#d272f2f4034afb9c4c9ab1379aabc17fc85c9388" + integrity sha512-CaLXV3W8Vnbps8ZANqDGz7j4x7Yj1LW4TWF/TQuDfj7Cfx4nAPTvw98qgTevtto1oHDrh3pQkaODbqupXlsWTg== dependencies: debug "^4.1.0" iconv-lite "^0.4.4" sax "^1.2.4" -negotiator@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" - integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== neo-async@^2.5.0, neo-async@^2.6.0: version "2.6.0" @@ -11621,9 +11711,9 @@ no-case@^2.2.0, no-case@^2.3.2: lower-case "^1.1.1" node-abi@^2.2.0: - version "2.7.1" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.7.1.tgz#a8997ae91176a5fbaa455b194976e32683cda643" - integrity sha512-OV8Bq1OrPh6z+Y4dqwo05HqrRL9YNF7QVMRfq1/pguwKLG+q9UB/Lk0x5qXjO23JjJg+/jqCHSTaG1P3tfKfuw== + version "2.8.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.8.0.tgz#bd2e88dbe6a6871e6dd08553e0605779325737ec" + integrity sha512-1/aa2clS0pue0HjckL62CsbhWWU35HARvBDXcJtYKbYR7LnIutmpxmXbuDMV9kEviD2lP/wACOgWmmwljghHyQ== dependencies: semver "^5.4.1" @@ -11652,9 +11742,9 @@ node-fetch@^1.0.1: is-stream "^1.0.1" node-fetch@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5" - integrity sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA== + version "2.5.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.5.0.tgz#8028c49fc1191bba56a07adc6e2a954644a48501" + integrity sha512-YuZKluhWGJwCcUu4RlZstdAxr8bFfOVHakc1mplwHkk8J+tqM1Y5yraYvIUpeX8aY7+crCwiELJq7Vl0o0LWXw== node-forge@0.7.5: version "0.7.5" @@ -11729,10 +11819,10 @@ node-notifier@^5.2.1: shellwords "^0.1.1" which "^1.3.0" -node-pre-gyp@^0.10.0: - version "0.10.3" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" - integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== +node-pre-gyp@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" + integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== dependencies: detect-libc "^1.0.2" mkdirp "^0.5.1" @@ -11745,10 +11835,10 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" -node-releases@^1.1.13, node-releases@^1.1.3: - version "1.1.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.14.tgz#f1f41c83cac82caebd6739e6313d56b3b09c9189" - integrity sha512-d58EpVZRhQE60kWiWUaaPlK9dyC4zg3ZoMcHcky2d4hDksyQj0rUozwInOl0C66mBsqo01Tuns8AvxnL5S7PKg== +node-releases@^1.1.17, node-releases@^1.1.3: + version "1.1.17" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.17.tgz#71ea4631f0a97d5cd4f65f7d04ecf9072eac711a" + integrity sha512-/SCjetyta1m7YXLgtACZGDYJdCSIBAWorDWkGCGZlydP2Ll7J48l7j/JxNYZ+xsgSPbWfdulVS/aY+GdjUsQ7Q== dependencies: semver "^5.3.0" @@ -12124,9 +12214,9 @@ number-is-nan@^1.0.0: integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= nwsapi@^2.0.7: - version "2.1.3" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.3.tgz#25f3a5cec26c654f7376df6659cdf84b99df9558" - integrity sha512-RowAaJGEgYXEZfQ7tvvdtAQUKPyTR6T6wNu0fwlNsGQYr/h3yQc6oI8WnVZh3Y/Sylwc+dtAlvPqfFZjhTyk3A== + version "2.1.4" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.4.tgz#e006a878db23636f8e8a67d33ca0e4edf61a842f" + integrity sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw== oauth-sign@~0.9.0: version "0.9.0" @@ -12194,17 +12284,7 @@ object.entries@^1.0.4, object.entries@^1.1.0: function-bind "^1.1.1" has "^1.0.3" -object.fromentries@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-1.0.0.tgz#e90ec27445ec6e37f48be9af9077d9aa8bef0d40" - integrity sha512-F7XUm84lg0uNXNzrRAC5q8KJe0yYaxgLU9hTSqWYM6Rfnh0YjP24EG3xq7ncj2Wu1AdfueNHKCOlamIonG4UHQ== - dependencies: - define-properties "^1.1.2" - es-abstract "^1.11.0" - function-bind "^1.1.1" - has "^1.0.1" - -object.fromentries@^2.0.0: +object.fromentries@^2.0.0, "object.fromentries@^2.0.0 || ^1.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.0.tgz#49a543d92151f8277b3ac9600f1e930b189d30ab" integrity sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA== @@ -12794,6 +12874,13 @@ pirates@^4.0.1: dependencies: node-modules-regexp "^1.0.0" +pixelmatch@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854" + integrity sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ= + dependencies: + pngjs "^3.0.0" + pkg-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" @@ -12832,6 +12919,11 @@ pn@^1.1.0: resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== +pngjs@3.4.0, pngjs@^3.0.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" + integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== + polished@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/polished/-/polished-2.3.3.tgz#bdbaba962ba8271b0e11aa287f2befd4c87be99a" @@ -13223,9 +13315,9 @@ postcss@^5.0.0, postcss@^5.0.4: supports-color "^3.2.3" postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: - version "7.0.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5" - integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg== + version "7.0.16" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.16.tgz#48f64f1b4b558cb8b52c88987724359acb010da2" + integrity sha512-MOo8zNSlIqh22Uaa3drkdIAgUGEL+AD1ESiSdmElLUmE2uVDo1QloiT/IfW9qRw8Gw+Y/w69UVMGwbufMSftxA== dependencies: chalk "^2.4.2" source-map "^0.6.1" @@ -13407,12 +13499,12 @@ pretty-format@^21.2.1: ansi-regex "^3.0.0" ansi-styles "^3.2.0" -pretty-format@^24.5.0, pretty-format@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.7.0.tgz#d23106bc2edcd776079c2daa5da02bcb12ed0c10" - integrity sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA== +pretty-format@^24.7.0, pretty-format@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" + integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" ansi-regex "^4.0.0" ansi-styles "^3.2.0" react-is "^16.8.4" @@ -13454,6 +13546,11 @@ progress@^1.1.8: resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= +progress@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + promise-inflight@^1.0.1, promise-inflight@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -13472,6 +13569,15 @@ promise-retry@^1.1.1: err-code "^1.0.0" retry "^0.10.0" +promise.allsettled@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.1.tgz#afe4bfcc13b26e2263a97a7fbbb19b8ca6eb619c" + integrity sha512-3ST7RS7TY3TYLOIe+OACZFvcWVe1osbgz2x07nTb446pa3t4GUZWidMDzQ4zf9jC2l6mRa1/3X81icFYbi+D/g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.13.0" + function-bind "^1.1.1" + promise.prototype.finally@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.0.tgz#66f161b1643636e50e7cf201dc1b84a857f3864e" @@ -13546,12 +13652,17 @@ protoduck@^5.0.1: genfun "^5.0.0" proxy-addr@~2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" - integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA== + version "2.0.5" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" + integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ== dependencies: forwarded "~0.1.2" - ipaddr.js "1.8.0" + ipaddr.js "1.9.0" + +proxy-from-env@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= prr@~1.0.1: version "1.0.1" @@ -13633,6 +13744,20 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +puppeteer-core@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-1.15.0.tgz#c8ccf246493349e5d898041f205fbeec4ed845ab" + integrity sha512-AH82x8Tx0/JkubeF6U12y8SuVB5vFgsw8lt/Ox5MhXaAktREFiotCTq324U2nPtJUnh2A8yJciDnzAmhbHidqQ== + dependencies: + debug "^4.1.0" + extract-zip "^1.6.6" + https-proxy-agent "^2.2.1" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^2.6.1" + ws "^6.1.0" + q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -13654,9 +13779,9 @@ qs@^6.5.2: integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== query-string@^6.2.0: - version "6.4.2" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.4.2.tgz#8be1dbd105306aebf86022144f575a29d516b713" - integrity sha512-DfJqAen17LfLA3rQ+H5S4uXphrF+ANU1lT2ijds4V/Tj4gZxA3gx5/tg1bz7kYCmwna7LyJNCYqO7jNRzo3aLw== + version "6.5.0" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.5.0.tgz#2e1a70125af01f6f04573692d02c09302a1d8bfc" + integrity sha512-TYC4hDjZSvVxLMEucDMySkuAS9UIzSbAiYGyA9GWCjLKB8fQpviFbjd20fD7uejCDxZS+ftSdBKE6DS+xucJFg== dependencies: decode-uri-component "^0.2.0" split-on-first "^1.0.0" @@ -13672,7 +13797,7 @@ querystring@0.2.0, querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -querystringify@^2.0.0: +querystringify@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== @@ -13765,9 +13890,9 @@ rc-align@^2.4.0: rc-util "^4.0.4" rc-animate@2.x: - version "2.6.0" - resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.6.0.tgz#ca8440d042781af7a1329d84f97ea94794c5ec15" - integrity sha512-JXDycchgbOI+7T/VKmFWnAIn042LLScK1fNkmNunb0jz5q5aPGCAybx2bTo7X5t31Jkj9OsxKNb/vZPDPWufCg== + version "2.8.2" + resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-2.8.2.tgz#1c517728e361745efead6dd8c3c74ecfa8e54cda" + integrity sha512-JUKpst+OSDFQjqhhZliBcmO3Fie1SeiIxsEhS7PbZVz/UjCC8uDtp31+NRxidxy3BnfXbbfZdtG9mNWIDqIfTw== dependencies: babel-runtime "6.x" classnames "^2.2.6" @@ -13847,7 +13972,7 @@ react-clientside-effect@^1.2.0: "@babel/runtime" "^7.0.0" shallowequal "^1.1.0" -react-color@2.17.0, react-color@^2.17.0: +react-color@2.17.0: version "2.17.0" resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.17.0.tgz#e14b8a11f4e89163f65a34c8b43faf93f7f02aaa" integrity sha512-kJfE5tSaFe6GzalXOHksVjqwCPAsTl+nzS9/BWfP7j3EXbQ4IiLAF9sZGNzk3uq7HfofGYgjmcUgh0JP7xAQ0w== @@ -13859,6 +13984,18 @@ react-color@2.17.0, react-color@^2.17.0: reactcss "^1.2.0" tinycolor2 "^1.4.1" +react-color@^2.17.0: + version "2.17.3" + resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.17.3.tgz#b8556d744f95193468c7061d2aa19180118d4a48" + integrity sha512-1dtO8LqAVotPIChlmo6kLtFS1FP89ll8/OiA8EcFRDR+ntcK+0ukJgByuIQHRtzvigf26dV5HklnxDIvhON9VQ== + dependencies: + "@icons/material" "^0.2.4" + lodash "^4.17.11" + material-colors "^1.2.1" + prop-types "^15.5.10" + reactcss "^1.2.0" + tinycolor2 "^1.4.1" + react-custom-scrollbars@4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz#830fd9502927e97e8a78c2086813899b2a8b66db" @@ -13916,9 +14053,9 @@ react-docgen-typescript-webpack-plugin@1.1.0: react-docgen-typescript "^1.2.3" react-docgen-typescript@^1.2.3, react-docgen-typescript@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-1.12.3.tgz#fe62a5ce82e93573e316366e53adfe8273121c70" - integrity sha512-s1XswWs4ykNdWKsPyfM4qptV5dT8nnjnVi2IcjoS/vGlRNYrc0TkW0scVOrinHZ+ndKhPqA4iVNrdwhxZBzJcg== + version "1.12.4" + resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-1.12.4.tgz#9ec8080e9ef41067ff17153bf42b3b2d49ae5503" + integrity sha512-OSmUfmdtcz4kLRWPiR8uUdE8ta+s5DV0uXOz1YsWaAUf3Ty64use7DYWK97zH8ZOlD4slq5zUfGc+UbfGLqfEQ== react-docgen@^3.0.0: version "3.0.0" @@ -13944,9 +14081,9 @@ react-dom@16.8.6, react-dom@^16.8.1: scheduler "^0.13.6" react-draggable@3.x, "react-draggable@^2.2.6 || ^3.0.3", react-draggable@^3.1.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.2.1.tgz#45d09a9a227988dc85674b23ab3c75b6e820dae5" - integrity sha512-r+3Bs9InID2lyIEbR8UIRVtpn4jgu1ArFEZgIy8vibJjijLSdNLX7rH9U68BBVD4RD9v44RXbaK4EHLyKXzNQw== + version "3.3.0" + resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.3.0.tgz#2ed7ea3f92e7d742d747f9e6324860606cd4d997" + integrity sha512-U7/jD0tAW4T0S7DCPK0kkKLyL0z61sC/eqU+NUfDjnq+JtBKaYKDHpsK2wazctiA4alEzCXUnzkREoxppOySVw== dependencies: classnames "^2.2.5" prop-types "^15.6.0" @@ -13960,9 +14097,9 @@ react-element-to-jsx-string@^14.0.2: stringify-object "3.2.2" react-error-overlay@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.4.tgz#88dfb88857c18ceb3b9f95076f850d7121776991" - integrity sha512-fp+U98OMZcnduQ+NSEiQa4s/XMsbp+5KlydmkbESOw4P69iWZ68ZMFM5a2BuE0FgqPBKApJyRuYHR95jM8lAmg== + version "5.1.5" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.5.tgz#884530fd055476c764eaa8ab13b8ecf1f57bbf2c" + integrity sha512-O9JRum1Zq/qCPFH5qVEvDDrVun8Jv9vbHtZXCR1EuRj9sKg1xJTlHxBzU6AkCzpvxRLuiY4OKImy3cDLQ+UTdg== react-fast-compare@^2.0.2: version "2.0.4" @@ -13970,12 +14107,12 @@ react-fast-compare@^2.0.2: integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== react-focus-lock@^1.17.7: - version "1.18.3" - resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-1.18.3.tgz#19d84afeab935c0b5de196922f71db7c481baba4" - integrity sha512-4fPAHnsr8oCYPgVmcMZ8NbAE9jm/OshPjXEM5PHseu2lDernzm/b1sHhYzZUO4OoW9D/u1AQsV6n4trRllow7w== + version "1.19.1" + resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-1.19.1.tgz#2f3429793edaefe2d077121f973ce5a3c7a0651a" + integrity sha512-TPpfiack1/nF4uttySfpxPk4rGZTLXlaZl7ncZg/ELAk24Iq2B1UUaUioID8H8dneUXqznT83JTNDHDj+kwryw== dependencies: "@babel/runtime" "^7.0.0" - focus-lock "^0.6.0" + focus-lock "^0.6.3" prop-types "^15.6.2" react-clientside-effect "^1.2.0" @@ -14138,9 +14275,9 @@ react-resize-detector@^3.2.1: resize-observer-polyfill "^1.5.1" react-select@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-2.4.2.tgz#27da80e3880e92b081be607207bebdf63bcf4f3a" - integrity sha512-5xFOQ6JJktkY5NTaHrc6x9mKwIjhNIiBkGic1j71uyY+ulFpRFra6f4WKLd9fuCylk4WjLpO5zDhdF4CAcwFzA== + version "2.4.3" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-2.4.3.tgz#62efdf76d7e33e9bde22d907a0cc8abd0aeab656" + integrity sha512-cmxNaiHpviRYkojeW9rGEUJ4jpX7QTmPe2wcscwA4d1lStzw/cJtr4ft5H2O/YhfpkrcwaLghu3XmEYdXhBo8Q== dependencies: classnames "^2.2.5" emotion "^9.1.2" @@ -14548,9 +14685,9 @@ reflect.ownkeys@^0.2.0: integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= refractor@^2.4.1: - version "2.8.0" - resolved "https://registry.yarnpkg.com/refractor/-/refractor-2.8.0.tgz#29d7b2254e823edd2e3e476af286af1c11472bfa" - integrity sha512-w+jG49/1MX60GeE9u8lyx1KYMBRdAHjOIfgcDJ0wq2ogOnEmab0MgIj+AtPq6kelw0mr1l9U0i2rFvLlOCkxiw== + version "2.9.0" + resolved "https://registry.yarnpkg.com/refractor/-/refractor-2.9.0.tgz#0a381aadb51513e4e6ec1ed410b5104dd65e2489" + integrity sha512-lCnCYvXpqd8hC7ksuvo516rz5q4NwzBbq0X5qjH5pxRfcQKiQxKZ8JctrSQmrR/7pcV2TRrs9TT+Whmq/wtluQ== dependencies: hastscript "^5.0.0" parse-entities "^1.1.2" @@ -14613,9 +14750,9 @@ regexp-replace-loader@1.0.1: loader-utils "^1.0.2" regexp-tree@^0.1.0: - version "0.1.5" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.5.tgz#7cd71fca17198d04b4176efd79713f2998009397" - integrity sha512-nUmxvfJyAODw+0B13hj8CFVAxhe7fDEAgJgaotBu3nnR+IgGgZq59YedJP5VYTlkEfqjuK6TuRpnymKdatLZfQ== + version "0.1.6" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.6.tgz#84900fa12fdf428a2ac25f04300382a7c0148479" + integrity sha512-LFrA98Dw/heXqDojz7qKFdygZmFoiVlvE1Zp7Cq2cvF+ZA+03Gmhy0k0PQlsC1jvHPiTUSs+pDHEuSWv6+6D7w== regexp.prototype.flags@^1.2.0: version "1.2.0" @@ -14885,9 +15022,9 @@ resolve@1.8.1: path-parse "^1.0.5" resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.8.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" - integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== + version "1.10.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18" + integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA== dependencies: path-parse "^1.0.6" @@ -15106,13 +15243,20 @@ rx-lite@^3.1.2: resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= -rxjs@6.4.0, rxjs@^6.1.0, rxjs@^6.3.3, rxjs@^6.4.0: +rxjs@6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== dependencies: tslib "^1.9.0" +rxjs@^6.1.0, rxjs@^6.3.3, rxjs@^6.4.0: + version "6.5.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.1.tgz#f7a005a9386361921b8524f38f54cbf80e5d08f4" + integrity sha512-y0j31WJc83wPu31vS1VlAFW5JGrnGC+j+TtGAa1fRQphy48+fDYiDmX8tjGloToEsMkxnouOg/1IzXGKkJnZMg== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -15175,7 +15319,7 @@ sass-graph@^2.2.4: scss-tokenizer "^0.2.3" yargs "^7.0.0" -sass-lint@1.12.1, sass-lint@^1.12.0: +sass-lint@1.12.1: version "1.12.1" resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.12.1.tgz#630f69c216aa206b8232fb2aa907bdf3336b6d83" integrity sha1-Yw9pwhaqIGuCMvsqqQe98zNrbYM= @@ -15195,6 +15339,26 @@ sass-lint@1.12.1, sass-lint@^1.12.0: path-is-absolute "^1.0.0" util "^0.10.3" +sass-lint@^1.12.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.13.1.tgz#5fd2b2792e9215272335eb0f0dc607f61e8acc8f" + integrity sha512-DSyah8/MyjzW2BWYmQWekYEKir44BpLqrCFsgs9iaWiVTcwZfwXHF586hh3D1n+/9ihUNMfd8iHAyb9KkGgs7Q== + dependencies: + commander "^2.8.1" + eslint "^2.7.0" + front-matter "2.1.2" + fs-extra "^3.0.1" + glob "^7.0.0" + globule "^1.0.0" + gonzales-pe-sl "^4.2.3" + js-yaml "^3.5.4" + known-css-properties "^0.3.0" + lodash.capitalize "^4.1.0" + lodash.kebabcase "^4.0.0" + merge "^1.2.0" + path-is-absolute "^1.0.0" + util "^0.10.3" + sass-loader@7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.1.0.tgz#16fd5138cb8b424bf8a759528a1972d72aad069d" @@ -15522,10 +15686,10 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" -simple-git@^1.85.0: - version "1.110.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.110.0.tgz#54eb179089d055a7783d32399246cebc9d9933e9" - integrity sha512-UYY0rQkknk0P5eb+KW+03F4TevZ9ou0H+LoGaj7iiVgpnZH4wdj/HTViy/1tNNkmIPcmtxuBqXWiYt2YwlRKOQ== +simple-git@^1.112.0, simple-git@^1.85.0: + version "1.112.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.112.0.tgz#2aec1b124c3705adf6baf977b663a0632f50267f" + integrity sha512-3vY0SW+RkO+ElWH07n/PQuKmuNLZSz3VAkxKMr3UMm/QnaSnYFjg3nqT8V6a0QCcUFpkyAWVsruQt4oSIIzPXw== dependencies: debug "^4.0.1" @@ -15567,9 +15731,9 @@ slash@^2.0.0: integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== slate-base64-serializer@^0.2.36: - version "0.2.99" - resolved "https://registry.yarnpkg.com/slate-base64-serializer/-/slate-base64-serializer-0.2.99.tgz#22f7208850ceb4f5fee0d5f2a4c1442c13082150" - integrity sha512-sdZdSkJvOwuhqPgH2POBNYyYAdjAaejvLRBfMp+nJp4Gs1G9jc3K+N9w8MX4qr/mmrWWHrXqc8LuwBmcDYZOKA== + version "0.2.102" + resolved "https://registry.yarnpkg.com/slate-base64-serializer/-/slate-base64-serializer-0.2.102.tgz#05cdb9149172944b55c8d0a0d14b4499a1c3b5a2" + integrity sha512-44BI/jEbSMDNjEpOd92dVtpKyoFEaQtS3YgDoD30gHsvJvuzdRd/EQjp01BAUhDLLgDINi9qIHZS3AkpqKOtow== dependencies: isomorphic-base64 "^1.0.2" @@ -15821,11 +15985,9 @@ sourcemap-codec@^1.4.4: integrity sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg== space-separated-tokens@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412" - integrity sha512-G3jprCEw+xFEs0ORweLmblJ3XLymGGr6hxZYTYZjIlvDti9vOBUjRQa1Rzjt012aRrocKstHwdNi+F7HguPsEA== - dependencies: - trim "0.0.1" + version "1.1.3" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.3.tgz#bc6500e116d13285a94b59b58c44c7f045fe6124" + integrity sha512-/M5RAdBuQlSDPNfA5ube+fkHbHyY08pMuADLmsAQURzo56w90r681oiOoz3o3ZQyWdSeNucpTFjL+Ggd5qui3w== spawn-command@^0.0.2-1: version "0.0.2-1" @@ -15889,9 +16051,9 @@ spdy@^4.0.0: spdy-transport "^3.0.0" split-on-first@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.0.0.tgz#648af4ce9a28fbcaadd43274455f298b55025fc6" - integrity sha512-mjA57TQtdWztVZ9THAjGNpgbuIrNfsNrGa5IyK94NoPaT4N14M+GI4jD7t4arLjFkYRQWdETC5RxFzLWouoB3A== + version "1.1.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -16066,7 +16228,7 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string.prototype.matchall@^3.0.0: +string.prototype.matchall@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-3.0.1.tgz#5a9e0b64bcbeb336aa4814820237c2006985646d" integrity sha512-NSiU0ILQr9PQ1SZmM1X327U5LsM+KfDTassJfqN1al1+0iNpKzmQ4BfXOJwRnTEqv8nKJ67mFpqRoPaGWwvy5A== @@ -16287,7 +16449,7 @@ supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" -supports-color@^6.0.0, supports-color@^6.1.0: +supports-color@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== @@ -16303,9 +16465,9 @@ svg-url-loader@^2.3.2: loader-utils "1.1.0" svgo@^1.0.0, svgo@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.2.1.tgz#3fedde75a4016193e1c2608b5fdef6f3e4a9fd99" - integrity sha512-Y1+LyT4/y1ms4/0yxPMSlvx6dIbgklE9w8CIOnfeoFGB74MEkq8inSfEr6NhocTaFbyYp0a1dvNgRKGRmEBlzA== + version "1.2.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.2.2.tgz#0253d34eccf2aed4ad4f283e11ee75198f9d7316" + integrity sha512-rAfulcwp2D9jjdGu+0CuqlrAUin6bBWrpoqXWwKDZZZJfXcUXQSxLJOFJCQCSA0x0pP2U0TxSlJu2ROq5Bq6qA== dependencies: chalk "^2.4.1" coa "^2.0.2" @@ -16314,7 +16476,7 @@ svgo@^1.0.0, svgo@^1.2.1: css-tree "1.0.0-alpha.28" css-url-regex "^1.1.0" csso "^3.5.1" - js-yaml "^3.13.0" + js-yaml "^3.13.1" mkdirp "~0.5.1" object.values "^1.1.0" sax "~1.2.4" @@ -16468,10 +16630,10 @@ terser@^3.14.1, terser@^3.16.1: source-map "~0.6.1" source-map-support "~0.5.10" -test-exclude@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.2.tgz#7322f8ab037b0b93ad2aab35fe9068baf997a4c4" - integrity sha512-N2pvaLpT8guUpb5Fe1GJlmvmzH3x+DAKmmyEQmFP792QcLYoGE1syxztSvPD1V8yPe6VrcCt6YGQVjSRjCASsA== +test-exclude@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" + integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== dependencies: glob "^7.1.3" minimatch "^3.0.4" @@ -16484,11 +16646,16 @@ test-exclude@^5.2.2: dependencies: tether "^1.1.0" -tether@1.4.5, tether@^1.1.0: +tether@1.4.5: version "1.4.5" resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.5.tgz#8efd7b35572767ba502259ba9b1cc167fcf6f2c1" integrity sha512-fysT1Gug2wbRi7a6waeu39yVDwiNtvwj5m9eRD+qZDSHKNghLo6KqP/U3yM2ap6TNUL2skjXGJaJJTJqoC31vw== +tether@^1.1.0: + version "1.4.6" + resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.6.tgz#30c538eebc8ab72a7ac0840843efdd4542d57e5f" + integrity sha512-TyWPw9O0ExqH9/ZBnQ0P1/mNI6LX16YPx5XvixC/ZvAqMkhGeXmKTTsMbSBn3ViOrPuQi/Uef11bVp3sd5UcQQ== + text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -16715,11 +16882,6 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= - trough@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.3.tgz#e29bd1614c6458d44869fc28b255ab7857ef7c24" @@ -16853,12 +17015,12 @@ type-check@~0.3.2: prelude-ls "~1.1.2" type-is@~1.6.16: - version "1.6.16" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" - integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== dependencies: media-typer "0.3.0" - mime-types "~2.1.18" + mime-types "~2.1.24" type-name@^2.0.1: version "2.0.2" @@ -16914,9 +17076,9 @@ uglify-js@3.4.x: source-map "~0.6.1" uglify-js@^3.1.4: - version "3.5.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.4.tgz#4a64d57f590e20a898ba057f838dcdfb67a939b9" - integrity sha512-GpKo28q/7Bm5BcX9vOu4S46FwisbPbAmkkqPnGIpKvKTM96I85N6XHQV+k4I6FA2wxgLhcsSyHoNhzucwCflvA== + version "3.5.11" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.11.tgz#833442c0aa29b3a7d34344c7c63adaa3f3504f6a" + integrity sha512-izPJg8RsSyqxbdnqX36ExpbH3K7tDBsAU/VfNv89VkMFy3z39zFjunQGsSHOlGlyIfGLGprGeosgQno3bo2/Kg== dependencies: commander "~2.20.0" source-map "~0.6.1" @@ -17154,11 +17316,11 @@ url-parse-lax@^1.0.0: prepend-http "^1.0.1" url-parse@^1.4.3: - version "1.4.6" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.6.tgz#baf91d6e6783c8a795eb476892ffef2737fc0456" - integrity sha512-/B8AD9iQ01seoXmXf9z/MjLZQIdOoYl/+gvsQF6+mpnxaTfG9P7srYaiqaDMyKkR36XMXfhqSHss5MyFAO8lew== + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== dependencies: - querystringify "^2.0.0" + querystringify "^2.1.1" requires-port "^1.0.0" url@^0.11.0: @@ -17206,12 +17368,16 @@ util@0.10.3: dependencies: inherits "2.0.1" -"util@>=0.10.3 <1", util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== +"util@>=0.10.3 <1": + version "0.12.0" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.0.tgz#bb5e3d29ba2703c7add0ad337003be3ca477798a" + integrity sha512-pPSOFl7VLhZ7LO/SFABPraZEEurkJUWSMn3MuA/r3WQZc+Z1fqou2JqLSOZbCLl73EUIxuUVX8X4jkX2vfJeAA== dependencies: inherits "2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + object.entries "^1.1.0" + safe-buffer "^5.1.2" util@^0.10.3: version "0.10.4" @@ -17220,6 +17386,13 @@ util@^0.10.3: dependencies: inherits "2.0.3" +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + utila@^0.4.0, utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" @@ -17334,7 +17507,7 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" -wait-for-expect@^1.1.0: +wait-for-expect@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.1.1.tgz#9cd10e07d52810af9e0aaf509872e38f3c3d81ae" integrity sha512-vd9JOqqEcBbCDhARWhW85ecjaEcfBLuXgVBqatfS3iw6oU4kzAcs+sCNjF+TC9YHPImCW7ypsuQc+htscIAQCw== @@ -17498,9 +17671,9 @@ webpack-dev-server@3.2.1: yargs "12.0.2" webpack-hot-middleware@^2.24.3: - version "2.24.3" - resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.24.3.tgz#5bb76259a8fc0d97463ab517640ba91d3382d4a6" - integrity sha512-pPlmcdoR2Fn6UhYjAhp1g/IJy1Yc9hD+T6O9mjRcWV2pFbBjIFoJXhP0CoD0xPOhWJuWXuZXGBga9ybbOdzXpg== + version "2.24.4" + resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.24.4.tgz#0ae1eeca000c6ffdcb22eb574d0e6d7717672b0f" + integrity sha512-YFA4j2tg9WPkcQKcyHMZn6787QngWf/ahXvAJRZ1ripySa+4ihjzDcYBsfC4ihOucTd02IJ12v+VTGMsEGxq0w== dependencies: ansi-html "0.0.7" html-entities "^1.2.0" @@ -17712,9 +17885,9 @@ wordwrap@~1.0.0: integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= worker-farm@^1.5.2, worker-farm@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0" - integrity sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ== + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== dependencies: errno "~0.1.7" @@ -17771,7 +17944,7 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" -ws@^6.0.0: +ws@^6.0.0, ws@^6.1.0: version "6.2.1" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==