From 700ab8f3d891bc9702f3eacfe47c5903f51829ef Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Wed, 1 Jul 2020 13:26:36 +0200 Subject: [PATCH 0001/1228] Chore: bumped version to next minor. (#25971) --- lerna.json | 2 +- package.json | 2 +- packages/grafana-data/package.json | 2 +- packages/grafana-e2e-selectors/package.json | 2 +- packages/grafana-e2e/package.json | 4 ++-- packages/grafana-runtime/package.json | 8 ++++---- packages/grafana-toolkit/package.json | 2 +- packages/grafana-ui/package.json | 10 +++++----- packages/jaeger-ui-components/package.json | 4 ++-- plugins-bundled/internal/input-datasource/package.json | 8 ++++---- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lerna.json b/lerna.json index 6306c7d49a274..43e356d23c93b 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "npmClient": "yarn", "useWorkspaces": true, "packages": ["packages/*"], - "version": "7.1.0-pre.0" + "version": "7.2.0-pre.0" } diff --git a/package.json b/package.json index bc855d7a0a788..c9d287e81f6d0 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "Apache-2.0", "private": true, "name": "grafana", - "version": "7.1.0-pre", + "version": "7.2.0-pre", "repository": "github:grafana/grafana", "scripts": { "api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 8b0c593a1dee9..efb9456e3acd7 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/data", - "version": "7.1.0-pre.0", + "version": "7.2.0-pre.0", "description": "Grafana Data Library", "keywords": [ "typescript" diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 8f5564fe619e4..3ccd780c7d10a 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/e2e-selectors", - "version": "7.1.0-pre.0", + "version": "7.2.0-pre.0", "description": "Grafana End-to-End Test Selectors Library", "keywords": [ "cli", diff --git a/packages/grafana-e2e/package.json b/packages/grafana-e2e/package.json index c4f35b2e1e5a5..5c0c7ff729944 100644 --- a/packages/grafana-e2e/package.json +++ b/packages/grafana-e2e/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/e2e", - "version": "7.1.0-pre.0", + "version": "7.2.0-pre.0", "description": "Grafana End-to-End Test Library", "keywords": [ "cli", @@ -45,7 +45,7 @@ "types": "src/index.ts", "dependencies": { "@cypress/webpack-preprocessor": "4.1.3", - "@grafana/e2e-selectors": "7.1.0-pre.0", + "@grafana/e2e-selectors": "7.2.0-pre.0", "@grafana/tsconfig": "^1.0.0-rc1", "@mochajs/json-file-reporter": "^1.2.0", "blink-diff": "1.0.13", diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index fe9165133b422..159a832b11de1 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/runtime", - "version": "7.1.0-pre.0", + "version": "7.2.0-pre.0", "description": "Grafana Runtime Library", "keywords": [ "grafana", @@ -23,8 +23,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@grafana/data": "7.1.0-pre.0", - "@grafana/ui": "7.1.0-pre.0", + "@grafana/data": "7.2.0-pre.0", + "@grafana/ui": "7.2.0-pre.0", "systemjs": "0.20.19", "systemjs-plugin-css": "0.1.37" }, @@ -32,9 +32,9 @@ "@grafana/tsconfig": "^1.0.0-rc1", "@rollup/plugin-commonjs": "11.0.2", "@rollup/plugin-node-resolve": "7.1.1", + "@types/jest": "23.3.14", "@types/rollup-plugin-visualizer": "2.6.0", "@types/systemjs": "^0.20.6", - "@types/jest": "23.3.14", "lodash": "4.17.15", "pretty-format": "25.1.0", "rollup": "2.0.6", diff --git a/packages/grafana-toolkit/package.json b/packages/grafana-toolkit/package.json index 332f27c261871..311fd40a4e517 100644 --- a/packages/grafana-toolkit/package.json +++ b/packages/grafana-toolkit/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/toolkit", - "version": "7.1.0-pre.0", + "version": "7.2.0-pre.0", "description": "Grafana Toolkit", "keywords": [ "grafana", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 415eeb2cd4b14..b4edcb6179436 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/ui", - "version": "7.1.0-pre.0", + "version": "7.2.0-pre.0", "description": "Grafana Components Library", "keywords": [ "grafana", @@ -28,8 +28,8 @@ }, "dependencies": { "@emotion/core": "^10.0.27", - "@grafana/data": "7.1.0-pre.0", - "@grafana/e2e-selectors": "7.1.0-pre.0", + "@grafana/data": "7.2.0-pre.0", + "@grafana/e2e-selectors": "7.2.0-pre.0", "@grafana/slate-react": "0.22.9-grafana", "@grafana/tsconfig": "^1.0.0-rc1", "@iconscout/react-unicons": "^1.0.0", @@ -47,9 +47,8 @@ "immutable": "3.8.2", "jquery": "3.5.1", "lodash": "4.17.15", - "monaco-editor": "0.20.0", - "react-monaco-editor": "0.36.0", "moment": "2.24.0", + "monaco-editor": "0.20.0", "papaparse": "4.6.3", "rc-cascader": "1.0.1", "rc-drawer": "3.1.3", @@ -63,6 +62,7 @@ "react-dom": "16.12.0", "react-highlight-words": "0.16.0", "react-hook-form": "5.1.3", + "react-monaco-editor": "0.36.0", "react-popper": "1.3.3", "react-storybook-addon-props-combinations": "1.1.0", "react-table": "7.0.0", diff --git a/packages/jaeger-ui-components/package.json b/packages/jaeger-ui-components/package.json index 79326d56895c0..165583b495d36 100644 --- a/packages/jaeger-ui-components/package.json +++ b/packages/jaeger-ui-components/package.json @@ -1,6 +1,6 @@ { "name": "@jaegertracing/jaeger-ui-components", - "version": "7.1.0-pre.0", + "version": "7.2.0-pre.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -14,7 +14,7 @@ "typescript": "3.9.3" }, "dependencies": { - "@grafana/data": "7.1.0-pre.0", + "@grafana/data": "7.2.0-pre.0", "@types/classnames": "^2.2.7", "@types/deep-freeze": "^0.1.1", "@types/hoist-non-react-statics": "^3.3.1", diff --git a/plugins-bundled/internal/input-datasource/package.json b/plugins-bundled/internal/input-datasource/package.json index 2ad6e97a01aa1..86332fd302eee 100644 --- a/plugins-bundled/internal/input-datasource/package.json +++ b/plugins-bundled/internal/input-datasource/package.json @@ -1,6 +1,6 @@ { "name": "@grafana-plugins/input-datasource", - "version": "7.1.0-pre.0", + "version": "7.2.0-pre.0", "description": "Input Datasource", "private": true, "repository": { @@ -16,9 +16,9 @@ "author": "Grafana Labs", "license": "Apache-2.0", "devDependencies": { - "@grafana/data": "7.1.0-pre.0", - "@grafana/toolkit": "7.1.0-pre.0", - "@grafana/ui": "7.1.0-pre.0" + "@grafana/data": "7.2.0-pre.0", + "@grafana/toolkit": "7.2.0-pre.0", + "@grafana/ui": "7.2.0-pre.0" }, "volta": { "node": "12.16.2" From 81095335c491e44addd529206cc629c995d222db Mon Sep 17 00:00:00 2001 From: Andrej Ocenas Date: Wed, 1 Jul 2020 14:26:37 +0200 Subject: [PATCH 0002/1228] docs: 7.1 beta what's new draft (#25961) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add whats new draft article * Update docs/sources/guides/whats-new-in-v7-1.md Co-authored-by: Sofia Papagiannaki * Add elastic internal links * Docs: more about vault in whats new * docs: monthly reports * Apply suggestions from code review Co-authored-by: Sofia Papagiannaki * Remove unified explore mention * updated what's new * Update link * Add search image link * Add 7.1 article to the menu * Fix typo Co-authored-by: Sofia Papagiannaki Co-authored-by: Leonard Gram Co-authored-by: Torkel Ödegaard --- docs/sources/guides/whats-new-in-v7-1.md | 94 ++++++++++++++++++++++++ docs/sources/menu.yaml | 2 + 2 files changed, 96 insertions(+) create mode 100644 docs/sources/guides/whats-new-in-v7-1.md diff --git a/docs/sources/guides/whats-new-in-v7-1.md b/docs/sources/guides/whats-new-in-v7-1.md new file mode 100644 index 0000000000000..cc46a7ac8ad23 --- /dev/null +++ b/docs/sources/guides/whats-new-in-v7-1.md @@ -0,0 +1,94 @@ ++++ +title = "What's New in Grafana v7.1" +description = "Feature and improvement highlights for Grafana v7.1" +keywords = ["grafana", "new", "documentation", "7.1", "release notes"] +type = "docs" +[menu.docs] +name = "Version 7.1" +identifier = "v7.1" +parent = "whatsnew" +weight = -16 ++++ + +# What's new in Grafana v7.1 + +This topic includes the release notes for the Grafana v7.1, which is currently in beta. For all details, read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md). + +The main highlights are: + +- [**Query history search**]({{< relref "#query-history-search" >}}) +- [**Provisioning of apps**]({{< relref "#provisioning-of-apps" >}}) +- [**Azure Monitor Datasource**]({{< relref "#azure-monitor-datasource" >}}) +- [**Influx Datasource**]({{< relref "#influx-datasource" >}}) +- [**Deep linking for Google Cloud Monitoring (formerly named Google Stackdriver) datasource**]({{< relref "#deep-linking-for-google-cloud-monitoring-formerly-named-google-stackdriver-datasource" >}}) +- [**Transforms**]({{< relref "#transforms" >}}) +- [**Stat panel text mode**]({{< relref "#stat-panel-text-mode" >}}) +- [**Grafana Enterprise features**]({{< relref "#grafana-enterprise-features" >}}) + - [**Support for HashiCorp Vault**]({{< relref "#support-for-hashicorp-vault" >}}) + - [**Internal links for Elastic**]({{< relref "#internal-links-for-elastic" >}}) + +## Query history search + +In Grafana v 7.1 we are introducing search functionality in Query history. You can search across queries and your comments. It is especially useful in combination with a time filter and data source filter. Read more about Query history [here]({{}}). + +{{< docs-imagebox img="/img/docs/v71/query_history_search.gif" max-width="800px" caption="Query history search" >}} + +## Provisioning of apps + +Grafana v7.1 adds support for provisioning of app plugins. This allows app plugins to be configured and enabled/disabled using configuration files. Read more about provisioning of app plugins [here]({{ < relref "../administration/provisioning.md#plugins" >}}). + +## Azure Monitor Datasource + +Support for multiple dimensions has been added to all services in the Azure Monitor datasource. This means you can now group by more than one dimension with time series queries. With the Kusto based services, Log Analytics and Application Insights Analytics, you can also select multiple metrics as well as multiple dimensions. + +Additionally, the “Raw Edit” mode for Application Insights Analytics has been replaced with a new service in the drop down for the datasource and is called “Insights Analytics”. The new query editor behaves in the same way as Log Analytics. + +## Influx Datasource + +Support for Flux and Influx v2 has been added. + +## Deep linking for Google Cloud Monitoring (formerly named Google Stackdriver) datasource + +A new feature in Grafana 7.1 is [deep linking from Grafana panels to the Metrics Explorer in Gooogle Cloud Console]({{}}). Click on a time series in the panel to see a context menu with a link to View in Metrics explorer in Google Cloud Console. Clicking that link opens the Metrics explorer in the Monitoring Google Cloud Console and runs the query from the Grafana panel there. + +## Internal links for Elastic + +You can now create links in Elastic configuration that point to another datasource similar to existing feature in +Loki. This allows you to link traceID from your logs to tracing data source in Grafana. + +## Transformations + +We have added a new **Merge on time** transform that can combine many time series or table results. Unlike the join transform this combines the result into one table even when the time values does not align / match. + +## Stat panel text mode + +The [stat panel]({{}}) has a new **Text mode** option to control what text to show. + +By default, the Stat panel displays: + +- Just the value for a single series or field. +- Both the value and name for multiple series or fields. + +You can use the Text mode option to control what text the panel renders. If the value is not important, only name and color is, then change the `Text mode` to **Name**. The value will still be used to determine color and is displayed in a tooltip. + +{{< docs-imagebox img="/img/docs/v71/stat-panel-text-modes.png" max-width="1025px" caption="Stat panel" >}} + +## Grafana Enterprise features + +General features are included in the Grafana Enterprise edition software. + +### Support for HashiCorp Vault + +You can now use HashiCorp Vault to get secrets for configuration and provisioning of Grafana Enterprise. Learn more about this feature [here]({{}}). + +### Support for monthly in reports + +With Grafana Enterprise 7.1 you can configure reports to be generated on a [monthly schedule]({{}}). + +## Upgrading + +See [upgrade notes]({{}}). + +## Changelog + +Check out [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) for a complete list of new features, changes, and bug fixes. diff --git a/docs/sources/menu.yaml b/docs/sources/menu.yaml index ec02031f65c0e..e66b2fae2ba65 100644 --- a/docs/sources/menu.yaml +++ b/docs/sources/menu.yaml @@ -256,6 +256,8 @@ - name: What's new in Grafana link: /whatsnew/ children: + - name: Version 7.1 + link: /guides/whats-new-in-v7-1/ - name: Version 7.0 link: /guides/whats-new-in-v7-0/ - name: Version 6.7 From 039367336c0fdf8ea86e0e2137bc2465658eb7a7 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Wed, 1 Jul 2020 15:02:01 +0200 Subject: [PATCH 0003/1228] Chore: added change log for 7.1.0-beta1. (#25978) --- CHANGELOG.md | 81 ++++++++++++++++++++++++++++++++ packages/grafana-ui/CHANGELOG.md | 7 +++ 2 files changed, 88 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29d95eb726c48..cba577323b870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,84 @@ +# 7.1.0-beta 1 (2020-07-01) + +### Features / Enhancements +* **Alerting**: Adds support for multiple URLs in Alertmanager notifier. [#24196](https://github.com/grafana/grafana/pull/24196), [@alistarle](https://github.com/alistarle) +* **Alerting**: updating the victorops alerter to handle the no_data alert type. [#23761](https://github.com/grafana/grafana/pull/23761), [@rrusso1982](https://github.com/rrusso1982) +* **Azure**: Application Insights metrics to Frame and support multiple query dimensions. [#25849](https://github.com/grafana/grafana/pull/25849), [@kylebrandt](https://github.com/kylebrandt) +* **Azure**: Multiple dimension support for Azure Monitor Service. [#25947](https://github.com/grafana/grafana/pull/25947), [@kylebrandt](https://github.com/kylebrandt) +* **Azure**: Split Insights into two services. [#25410](https://github.com/grafana/grafana/pull/25410), [@kylebrandt](https://github.com/kylebrandt) +* **Backend plugins**: Refactor to allow shared contract between core and external backend plugins. [#25472](https://github.com/grafana/grafana/pull/25472), [@marefr](https://github.com/marefr) +* **Branding**: Use AppTitle as document title. [#25271](https://github.com/grafana/grafana/pull/25271), [@benrubson](https://github.com/benrubson) +* **Chore**: upgrade to typescript 3.9.3. [#25154](https://github.com/grafana/grafana/pull/25154), [@ryantxu](https://github.com/ryantxu) +* **CloudWatch**: Add Route53 DNSQueries metric and dimension. [#25125](https://github.com/grafana/grafana/pull/25125), [@erkolson](https://github.com/erkolson) +* **CloudWatch**: Added AWS DataSync metrics and dimensions. [#25054](https://github.com/grafana/grafana/pull/25054), [@ilyastoli](https://github.com/ilyastoli) +* **CloudWatch**: Added AWS MediaStore metrics and dimensions. [#25492](https://github.com/grafana/grafana/pull/25492), [@ilyastoli](https://github.com/ilyastoli) +* **CloudWatch**: Added AWS RoboMaker metrics and dimensions. [#25090](https://github.com/grafana/grafana/pull/25090), [@ilyastoli](https://github.com/ilyastoli) +* **CloudWatch**: Added AWS SDKMetrics metrics and dimensions. [#25150](https://github.com/grafana/grafana/pull/25150), [@ilyastoli](https://github.com/ilyastoli) +* **CloudWatch**: Added AWS ServiceCatalog metrics and dimensions. [#25812](https://github.com/grafana/grafana/pull/25812), [@ilyastoli](https://github.com/ilyastoli) +* **CloudWatch**: Added AWS WAFV2 metrics. [#24048](https://github.com/grafana/grafana/pull/24048), [@mikkokupsu](https://github.com/mikkokupsu) +* **Dashboards**: Make path to default dashboard configurable. [#25595](https://github.com/grafana/grafana/pull/25595), [@bergquist](https://github.com/bergquist) +* **Elastic**: Internal data links. [#25942](https://github.com/grafana/grafana/pull/25942), [@aocenas](https://github.com/aocenas) +* **Elasticsearch**: Add support for template variable in date histogram min_doc_count. [#21064](https://github.com/grafana/grafana/pull/21064), [@faxm0dem](https://github.com/faxm0dem) +* **Elasticsearch**: Adds cumulative sum aggregation support. [#24820](https://github.com/grafana/grafana/pull/24820), [@retzkek](https://github.com/retzkek) +* **Elasticsearch**: Support using a variable for histogram and terms min doc count. [#25392](https://github.com/grafana/grafana/pull/25392), [@marefr](https://github.com/marefr) +* **Explore/Loki**: Show results of instant queries only in table and time series only in graph. [#25845](https://github.com/grafana/grafana/pull/25845), [@ivanahuckova](https://github.com/ivanahuckova) +* **Explore**: Remove legend formatting when switching from panel to Explore. [#25848](https://github.com/grafana/grafana/pull/25848), [@ivanahuckova](https://github.com/ivanahuckova) +* **Footer**: Add back footer to login page. [#25656](https://github.com/grafana/grafana/pull/25656), [@torkelo](https://github.com/torkelo) +* **ForgottenPassword**: Move view to login screen. [#25366](https://github.com/grafana/grafana/pull/25366), [@tskarhed](https://github.com/tskarhed) +* **Gauge**: Hide orientation option in panel options. [#25511](https://github.com/grafana/grafana/pull/25511), [@torkelo](https://github.com/torkelo) +* **Grafana-UI**: Add FileUpload. [#25835](https://github.com/grafana/grafana/pull/25835), [@Clarity-89](https://github.com/Clarity-89) +* **GraphPanel**: Make legend values clickable series toggles. [#25581](https://github.com/grafana/grafana/pull/25581), [@hshoff](https://github.com/hshoff) +* **Influx**: Support flux in the influx datasource. [#25308](https://github.com/grafana/grafana/pull/25308), [@ryantxu](https://github.com/ryantxu) +* **Migration**: Select org. [#24739](https://github.com/grafana/grafana/pull/24739), [@tskarhed](https://github.com/tskarhed) +* **Migration**: Settings forms. [#24741](https://github.com/grafana/grafana/pull/24741), [@tskarhed](https://github.com/tskarhed) +* **Panel Inspect**: use Monaco editor for json display. [#25251](https://github.com/grafana/grafana/pull/25251), [@ryantxu](https://github.com/ryantxu) +* **Panel edit**: Clicking twice on a visualization closes the VizPicker. [#25739](https://github.com/grafana/grafana/pull/25739), [@peterholmberg](https://github.com/peterholmberg) +* **PanelInspect**: Update UI for Data display options. [#25478](https://github.com/grafana/grafana/pull/25478), [@tskarhed](https://github.com/tskarhed) +* **Plugins**: move jaeger trace type to grafana data. [#25403](https://github.com/grafana/grafana/pull/25403), [@zoltanbedi](https://github.com/zoltanbedi) +* **Provisioning**: Adds support for enabling app plugins. [#25649](https://github.com/grafana/grafana/pull/25649), [@marefr](https://github.com/marefr) +* **Provisioning**: Use folders structure from the file system to create desired folders in dashboard provisioning. [#23117](https://github.com/grafana/grafana/pull/23117), [@nabokihms](https://github.com/nabokihms) +* **Query history**: Add keyboard shortcut support for commenting. [#24736](https://github.com/grafana/grafana/pull/24736), [@ivanahuckova](https://github.com/ivanahuckova) +* **Query history**: Add search for query history and starred queries. [#25747](https://github.com/grafana/grafana/pull/25747), [@ivanahuckova](https://github.com/ivanahuckova) +* **Rich history**: Updates for default settings and starred queries deletion. [#25732](https://github.com/grafana/grafana/pull/25732), [@ivanahuckova](https://github.com/ivanahuckova) +* **Search**: support URL query params. [#25541](https://github.com/grafana/grafana/pull/25541), [@Clarity-89](https://github.com/Clarity-89) +* **Stackdriver**: Deep linking from Grafana panels to the Metrics Explorer. [#25858](https://github.com/grafana/grafana/pull/25858), [@papagian](https://github.com/papagian) +* **Stackdriver**: Rename Stackdriver to Google Cloud Monitoring. [#25807](https://github.com/grafana/grafana/pull/25807), [@papagian](https://github.com/papagian) +* **StatPanel**: Option showing name instead of value and more. [#25676](https://github.com/grafana/grafana/pull/25676), [@torkelo](https://github.com/torkelo) +* **Switch**: Deprecate checked prop in favor of value. [#25862](https://github.com/grafana/grafana/pull/25862), [@tskarhed](https://github.com/tskarhed) +* **Table**: Adds adhoc filtering. [#25467](https://github.com/grafana/grafana/pull/25467), [@hugohaggmark](https://github.com/hugohaggmark) +* **Teams**: Add index for permission check. [#25736](https://github.com/grafana/grafana/pull/25736), [@sakjur](https://github.com/sakjur) +* **Template variable filters**: Hide overflowing text. [#25801](https://github.com/grafana/grafana/pull/25801), [@tskarhed](https://github.com/tskarhed) +* **Templating**: Add bult in __user {name, id, login, email} variable to templating system. [#23378](https://github.com/grafana/grafana/pull/23378), [@aidanmountford](https://github.com/aidanmountford) +* **Templating**: removes old Angular variable system and featureToggle. [#24779](https://github.com/grafana/grafana/pull/24779), [@hugohaggmark](https://github.com/hugohaggmark) +* **TextPanel**: Adds proper editor for markdown and html. [#25618](https://github.com/grafana/grafana/pull/25618), [@hugohaggmark](https://github.com/hugohaggmark) +* **TextPanel**: Removes Angular Text Panel. [#25504](https://github.com/grafana/grafana/pull/25504), [@hugohaggmark](https://github.com/hugohaggmark) +* **TextPanel**: Removes text mode. [#25589](https://github.com/grafana/grafana/pull/25589), [@hugohaggmark](https://github.com/hugohaggmark) +* **TimeZone**: unify the time zone pickers to one that can rule them all. [#24803](https://github.com/grafana/grafana/pull/24803), [@mckn](https://github.com/mckn) +* **Transform**: added merge transform that will merge multiple series/tables into one table. [#25490](https://github.com/grafana/grafana/pull/25490), [@mckn](https://github.com/mckn) +* **Units**: add base-pascals and rotational speed units. [#22879](https://github.com/grafana/grafana/pull/22879), [@sakjur](https://github.com/sakjur) +* **Units**: add new unit for duration, it is optimized for displaying days, hours, minutes and seconds. [#24175](https://github.com/grafana/grafana/pull/24175), [@pabigot](https://github.com/pabigot) +* **Variables**: enables cancel for slow query variables queries. [#24430](https://github.com/grafana/grafana/pull/24430), [@hugohaggmark](https://github.com/hugohaggmark) +* **switches default value for security settings**. [#25175](https://github.com/grafana/grafana/pull/25175), [@bergquist](https://github.com/bergquist) +* **Reporting:** add monthly schedule option. (Enterprise) + +### Bug Fixes +* **DatatLinks**: Fix open in new tab state mismatch. [#25826](https://github.com/grafana/grafana/pull/25826), [@tskarhed](https://github.com/tskarhed) +* **Explore/Loki**: Fix field type in table for instant queries. [#25907](https://github.com/grafana/grafana/pull/25907), [@ivanahuckova](https://github.com/ivanahuckova) +* **Explore/Loki**: Fix scrolling of context when leaving context window. [#25838](https://github.com/grafana/grafana/pull/25838), [@ivanahuckova](https://github.com/ivanahuckova) +* **Explore/SQL data sources**: Show correctly interpolated queries. [#25110](https://github.com/grafana/grafana/pull/25110), [@ivanahuckova](https://github.com/ivanahuckova) +* **Explore/Tooltip**: Fix label value in tooltip. [#25940](https://github.com/grafana/grafana/pull/25940), [@ivanahuckova](https://github.com/ivanahuckova) +* **Explore**: Fix query editors on mobile. [#25148](https://github.com/grafana/grafana/pull/25148), [@ivanahuckova](https://github.com/ivanahuckova) +* **Explore**: adds an ability to exit log row context with ESC key. [#24205](https://github.com/grafana/grafana/pull/24205), [@Estrax](https://github.com/Estrax) +* **Fix**: Value mappings match against string values. [#25929](https://github.com/grafana/grafana/pull/25929), [@peterholmberg](https://github.com/peterholmberg) +* **GraphPanel**: Fix annotations overflowing panels. [#25606](https://github.com/grafana/grafana/pull/25606), [@hshoff](https://github.com/hshoff) +* **Instrumentation**: Fix setting Jaeger tracing address through Grafana config. [#25768](https://github.com/grafana/grafana/pull/25768), [@marefr](https://github.com/marefr) +* **Prometheus**: Fix performance issue in processing of histogram labels. [#25813](https://github.com/grafana/grafana/pull/25813), [@bsherrod](https://github.com/bsherrod) +* **Provisioning**: Makes file the default dashboard provisioner type. [#24856](https://github.com/grafana/grafana/pull/24856), [@bergquist](https://github.com/bergquist) +* **Templating**: fixes variables not being interpolated after dashboard refresh. [#25698](https://github.com/grafana/grafana/pull/25698), [@hugohaggmark](https://github.com/hugohaggmark) +* **Units**: Custom unit suffix and docs for custom units. [#25710](https://github.com/grafana/grafana/pull/25710), [@torkelo](https://github.com/torkelo) +* **ValueFormats**: Fix byte-format data rates. [#25424](https://github.com/grafana/grafana/pull/25424), [@mueslo](https://github.com/mueslo) +* **Variables**: Fixes maximum call stack bug for empty value. [#25503](https://github.com/grafana/grafana/pull/25503), [@hugohaggmark](https://github.com/hugohaggmark) + # 7.0.5 (2020-06-30) ### Bug Fixes diff --git a/packages/grafana-ui/CHANGELOG.md b/packages/grafana-ui/CHANGELOG.md index 4157d3063d584..a57e6578c90d4 100644 --- a/packages/grafana-ui/CHANGELOG.md +++ b/packages/grafana-ui/CHANGELOG.md @@ -1,3 +1,10 @@ +# 7.1.0-beta1 (2020-07-01) + +### Features / Enhancements +* **Grafana-UI**: Add FileUpload. [#25835](https://github.com/grafana/grafana/pull/25835), [@Clarity-89](https://github.com/Clarity-89) +* **Switch**: Deprecate checked prop in favor of value. [#25862](https://github.com/grafana/grafana/pull/25862), [@tskarhed](https://github.com/tskarhed) + + # 7.0.4 (2020-06-25) ### Features / Enhancements From bcaa42fbb3ff28eb289d62c43145f8f1484180a7 Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki Date: Wed, 1 Jul 2020 18:23:18 +0300 Subject: [PATCH 0004/1228] Fix build-in plugins failing to load in windows (#25982) --- pkg/plugins/frontend_plugin.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/plugins/frontend_plugin.go b/pkg/plugins/frontend_plugin.go index e28d0a2106a99..89464a3013878 100644 --- a/pkg/plugins/frontend_plugin.go +++ b/pkg/plugins/frontend_plugin.go @@ -3,6 +3,7 @@ package plugins import ( "net/url" "path" + "path/filepath" "strings" "github.com/grafana/grafana/pkg/setting" @@ -63,7 +64,9 @@ func (fp *FrontendPluginBase) handleModuleDefaults() { // Previously there was an assumption that the plugin directory // should be public/app/plugins// // However this can be an issue if the plugin directory should be renamed to something else - currentDir := path.Base(fp.PluginDir) + currentDir := filepath.Base(fp.PluginDir) + // use path package for the following statements + // because these are not file paths fp.Module = path.Join("app/plugins", fp.Type, currentDir, "module") fp.BaseUrl = path.Join("public/app/plugins", fp.Type, currentDir) } From dda231009f37915a09f0a4e5b016361c974ff5ce Mon Sep 17 00:00:00 2001 From: Alexander Tymchuk Date: Wed, 1 Jul 2020 18:28:06 +0300 Subject: [PATCH 0005/1228] docs: remove a section on PhantomJS debugging in e2e.md (#25951) --- contribute/style-guides/e2e.md | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/contribute/style-guides/e2e.md b/contribute/style-guides/e2e.md index be6bcfeb48453..f8ffefe8d2b7e 100644 --- a/contribute/style-guides/e2e.md +++ b/contribute/style-guides/e2e.md @@ -66,8 +66,8 @@ The next step is to create a `Page` representation in our e2e test framework to ```typescript export const Login = { - url: "/login", // used when called from Login.visit() - username: "Username input field", // used when called from Login.username().type('Hello World') + url: '/login', // used when called from Login.visit() + username: 'Username input field', // used when called from Login.username().type('Hello World') }; ``` @@ -186,24 +186,3 @@ describe('List test', () => { }); }); ``` - -## Debugging PhantomJS image rendering - -### Common Error - -The most common error with PhantomJs image rendering is when a PR introduces an import that has functionality that's not supported by PhantomJs. To quickly identify which new import causes this you can use a tool like `es-check`. - -1. Run > `npx es-check es5 './public/build/*.js'` -2. Check the output for files that break es5 compatibility. -3. Lazy load the failing imports if possible. - -### Debugging - -There is no easy or comprehensive way to debug PhantomJS smoke test (image rendering) failures. However, PhantomJS exposes remote debugging interface which can give you a sense of what is going wrong in the smoke test. Before performing the steps described below make sure your local Grafana instance is running: - -1. Go to `tools/phantomjs` directory -2. Execute `phantomjs` binary against `render.js` file: `./phantomjs --remote-debugger-port=9009 --remote-debugger-autorun=yes ./render.js url="http://localhost:3000"` -3. In your browser navigate to `http://localhost:9009/` -4. Select `http://localhost:3000/login` from the list. You will get access to Webkit's inspector to see the console's output from the smoke test. - -The method described above is not perfect, but is helpful to evaluate smoke tests breaking due to bundle errors. From 55406053dc076d9e53e430278d4471427033badf Mon Sep 17 00:00:00 2001 From: Mohit Nain Date: Wed, 1 Jul 2020 21:17:49 +0530 Subject: [PATCH 0006/1228] Docs: Correction for repeat panels introduction (#25985) --- docs/sources/variables/repeat-panels-or-rows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/variables/repeat-panels-or-rows.md b/docs/sources/variables/repeat-panels-or-rows.md index 981fbdcb01eb3..f4162981bd687 100644 --- a/docs/sources/variables/repeat-panels-or-rows.md +++ b/docs/sources/variables/repeat-panels-or-rows.md @@ -10,7 +10,7 @@ weight = 400 # Repeat panels or rows -Grafana lets you create dynamic dashboards using _template variables_. Any variables in your queries expand to the current value of the variable before the query is sent to the database. Variables let you reuse a single dashboard for all your services. +Grafana lets you create dynamic dashboards using _template variables_. All variables in your queries expand to the current value of the variable before the query is sent to the database. Variables let you reuse a single dashboard for all your services. Template variables can be very useful to dynamically change your queries across a whole dashboard. If you want Grafana to dynamically create new panels or rows based on what values you have selected, you can use the *Repeat* feature. From b1b44d01da22b970d82874203c1c05522540a358 Mon Sep 17 00:00:00 2001 From: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Date: Wed, 1 Jul 2020 09:08:46 -0700 Subject: [PATCH 0007/1228] Docs: Update organization_roles.md (#25912) * Update organization_roles.md * Update docs/sources/permissions/organization_roles.md Co-authored-by: Marcus Efraimsson Co-authored-by: Marcus Efraimsson --- .../sources/permissions/organization_roles.md | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/docs/sources/permissions/organization_roles.md b/docs/sources/permissions/organization_roles.md index 5998c681be544..2e701b732a74b 100644 --- a/docs/sources/permissions/organization_roles.md +++ b/docs/sources/permissions/organization_roles.md @@ -1,6 +1,6 @@ +++ -title = "Organization Roles" -description = "Grafana Organization Roles Guide " +title = "Organization roles" +description = "Grafana organization roles guide " keywords = ["grafana", "configuration", "documentation", "organization", "roles", "permissions"] type = "docs" [menu.docs] @@ -10,32 +10,42 @@ parent = "permissions" weight = 30 +++ -# Organization Roles +# Organization roles Users can belong to one or more organizations. A user's organization membership is tied to a role that defines what the user is allowed to do in that organization. -## Admin Role +## Admin role Can do everything scoped to the organization. For example: -- Add and Edit data sources. -- Add and Edit organization users and teams. -- Configure App plugins and set org settings. +- Can add, edit, and delete data sources. +- Can add and edit users and teams in organizations. +- Can configure app plugins and organization settings. +- Can do everything allowed by the Editor role. -## Editor Role +## Editor role -- Can create and modify dashboards and alert rules. This can be disabled on specific folders and dashboards. -- **Cannot** create or edit data sources nor invite new users. +- Can view, add, and edit dashboards, panels, and alert rules in dashboards they have access to. This can be disabled on specific folders and dashboards. +- Can create, update, or delete playlists. +- Can access Explore. +- Cannot add, edit, or delete data sources. +- Cannot add, edit, or delete alert notification channels. +- Cannot manage other organizations, users, and teams. -This role can be tweaked via Grafana server setting [editors_can_admin]({{< relref "../administration/configuration.md#editors_can_admin" >}}). If you set this to true users -with **Editor** can also administrate dashboards, folders and teams they create. Useful for enabling self organizing teams. +This role can be tweaked via Grafana server setting [editors_can_admin]({{< relref "../administration/configuration.md#editors_can_admin" >}}). If you set this to `true`, then users +with the Editor role can also administrate dashboards, folders and teams they create. This is especially useful for enabling self-organizing teams to administer their own dashboards. -## Viewer Role +## Viewer role -- View any dashboard. This can be disabled on specific folders and dashboards. -- **Cannot** create or edit dashboards nor data sources. +- Can view any dashboard they have access to. This can be disabled on specific folders and dashboards. +- Cannot add, edit, or delete data sources. +- Cannot add, edit, or delete dashboards or panels. +- Cannot create, update, or delete playlists. +- Cannot add, edit, or delete alert notification channels. +- Cannot access Explore. +- Cannot manage other organizations, users, and teams. -This role can be tweaked via Grafana server setting [viewers_can_edit]({{< relref "../administration/configuration.md#viewers-can-edit" >}}). If you set this to true users -with **Viewer** can also make transient dashboard edits, meaning they can modify panels and queries but not save the changes (nor create new dashboards). -Useful for public Grafana installations where you want anonymous users to be able to edit panels and queries but not save or create new dashboards. +This role can be tweaked via Grafana server setting [viewers_can_edit]({{< relref "../administration/configuration.md#viewers-can-edit" >}}). If you set this to `true`, then users +with the Viewer role can also make transient dashboard edits, meaning they can modify panels and queries but not save the changes (nor create new dashboards). +This is especially useful for public Grafana installations where you want anonymous users to be able to edit panels and queries but not save or create new dashboards. From bbdcd59c503806e785d61383a1aa508ce2084f03 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Wed, 1 Jul 2020 18:49:55 +0200 Subject: [PATCH 0008/1228] Chore: updated testing in latest.json to 7.1.0-beta1. (#25988) --- latest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latest.json b/latest.json index 2bfb4dd314fcd..1e11931a0e2e4 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { "stable": "7.0.5", - "testing": "7.0.5" + "testing": "7.1.0-beta1" } From b5ca2381bc9f4b7c5747a6d11149b6d02973ec2c Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Wed, 1 Jul 2020 13:02:53 -0400 Subject: [PATCH 0009/1228] provide license token directly via plugin environment (#25987) * provide license token directly via plugin environment --- pkg/models/licensing.go | 2 ++ pkg/plugins/backendplugin/manager.go | 6 +++++- pkg/plugins/backendplugin/manager_test.go | 10 ++++++++-- pkg/services/licensing/oss.go | 4 ++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/pkg/models/licensing.go b/pkg/models/licensing.go index a1440de5f6da1..aadc0e8b3bb72 100644 --- a/pkg/models/licensing.go +++ b/pkg/models/licensing.go @@ -16,4 +16,6 @@ type Licensing interface { LicenseURL(user *SignedInUser) string StateInfo() string + + TokenRaw() string } diff --git a/pkg/plugins/backendplugin/manager.go b/pkg/plugins/backendplugin/manager.go index b58ae5bf2b3b3..fe47dc77ad624 100644 --- a/pkg/plugins/backendplugin/manager.go +++ b/pkg/plugins/backendplugin/manager.go @@ -100,7 +100,11 @@ func (m *manager) Register(pluginID string, factory PluginFactoryFunc) error { } if m.License.HasLicense() { - hostEnv = append(hostEnv, fmt.Sprintf("GF_ENTERPRISE_LICENSE_PATH=%s", m.Cfg.EnterpriseLicensePath)) + hostEnv = append( + hostEnv, + fmt.Sprintf("GF_ENTERPRISE_LICENSE_PATH=%s", m.Cfg.EnterpriseLicensePath), + fmt.Sprintf("GF_ENTERPRISE_LICENSE_TEXT=%s", m.License.TokenRaw()), + ) } env := pluginSettings.ToEnv("GF_PLUGIN", hostEnv) diff --git a/pkg/plugins/backendplugin/manager_test.go b/pkg/plugins/backendplugin/manager_test.go index ec62a1778ff28..28fb8a737cc5b 100644 --- a/pkg/plugins/backendplugin/manager_test.go +++ b/pkg/plugins/backendplugin/manager_test.go @@ -251,6 +251,7 @@ func TestManager(t *testing.T) { t.Run("Plugin registration scenario when Grafana is licensed", func(t *testing.T) { ctx.license.edition = "Enterprise" ctx.license.hasLicense = true + ctx.license.tokenRaw = "testtoken" ctx.cfg.BuildVersion = "7.0.0" ctx.cfg.EnterpriseLicensePath = "/license.txt" @@ -258,8 +259,8 @@ func TestManager(t *testing.T) { require.NoError(t, err) t.Run("Should provide expected host environment variables", func(t *testing.T) { - require.Len(t, ctx.env, 3) - require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Enterprise", "GF_ENTERPRISE_LICENSE_PATH=/license.txt"}, ctx.env) + require.Len(t, ctx.env, 4) + require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Enterprise", "GF_ENTERPRISE_LICENSE_PATH=/license.txt", "GF_ENTERPRISE_LICENSE_TEXT=testtoken"}, ctx.env) }) }) }) @@ -383,6 +384,7 @@ func (tp *testPlugin) CallResource(ctx context.Context, req *backend.CallResourc type testLicensingService struct { edition string hasLicense bool + tokenRaw string } func (t *testLicensingService) HasLicense() bool { @@ -408,3 +410,7 @@ func (t *testLicensingService) LicenseURL(user *models.SignedInUser) string { func (t *testLicensingService) HasValidLicense() bool { return false } + +func (t *testLicensingService) TokenRaw() string { + return t.tokenRaw +} diff --git a/pkg/services/licensing/oss.go b/pkg/services/licensing/oss.go index a903567d95a00..0e9678400367d 100644 --- a/pkg/services/licensing/oss.go +++ b/pkg/services/licensing/oss.go @@ -56,3 +56,7 @@ func (l *OSSLicensingService) Init() error { func (*OSSLicensingService) HasValidLicense() bool { return false } + +func (*OSSLicensingService) TokenRaw() string { + return "" +} From c9751707c5630ba721bd45a7bdf4fdd66f422d02 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Wed, 1 Jul 2020 21:04:06 +0200 Subject: [PATCH 0010/1228] Fix href to datasources for NoDataSourceCallToAction in Explore (#25991) --- public/app/features/explore/NoDataSourceCallToAction.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/explore/NoDataSourceCallToAction.tsx b/public/app/features/explore/NoDataSourceCallToAction.tsx index e7409f8d852b9..b3d837d14d684 100644 --- a/public/app/features/explore/NoDataSourceCallToAction.tsx +++ b/public/app/features/explore/NoDataSourceCallToAction.tsx @@ -23,7 +23,7 @@ export const NoDataSourceCallToAction = () => { ); const ctaElement = ( - + Add data source ); From 73e82af4df77931acedcce5a73e6a2267826f1d2 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 1 Jul 2020 12:52:36 -0700 Subject: [PATCH 0011/1228] Value Mappings: remove unused operator property from interface (#25989) --- .../src/field/displayProcessor.test.ts | 12 +++---- .../grafana-data/src/types/valueMapping.ts | 5 ++- .../src/utils/valueMappings.test.ts | 32 +++++++++---------- .../ValueMappingsEditor.test.tsx | 22 +++++-------- .../ValueMappingsEditor.tsx | 1 - .../grafana-ui/src/utils/standardEditors.tsx | 1 + 6 files changed, 32 insertions(+), 41 deletions(-) diff --git a/packages/grafana-data/src/field/displayProcessor.test.ts b/packages/grafana-data/src/field/displayProcessor.test.ts index 5064bc52f1ea8..8136cc8c2f490 100644 --- a/packages/grafana-data/src/field/displayProcessor.test.ts +++ b/packages/grafana-data/src/field/displayProcessor.test.ts @@ -152,8 +152,8 @@ describe('Format value', () => { it('should return formatted value if there are no matching value mappings', () => { const valueMappings: ValueMapping[] = [ - { id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, - { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' }, + { id: 0, text: 'elva', type: MappingType.ValueToText, value: '11' }, + { id: 1, text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' }, ]; const value = '10'; const instance = getDisplayProcessorFromConfig({ decimals: 1, mappings: valueMappings }); @@ -186,8 +186,8 @@ describe('Format value', () => { it('should return mapped value if there are matching value mappings', () => { const valueMappings: ValueMapping[] = [ - { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, - { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, + { id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, + { id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' }, ]; const value = '11'; const instance = getDisplayProcessorFromConfig({ decimals: 1, mappings: valueMappings }); @@ -196,9 +196,7 @@ describe('Format value', () => { }); it('should return mapped value and leave numeric value in tact if value mapping maps to empty string', () => { - const valueMappings: ValueMapping[] = [ - { id: 1, operator: '', text: '', type: MappingType.ValueToText, value: '1' }, - ]; + const valueMappings: ValueMapping[] = [{ id: 1, text: '', type: MappingType.ValueToText, value: '1' }]; const value = '1'; const instance = getDisplayProcessorFromConfig({ decimals: 1, mappings: valueMappings }); diff --git a/packages/grafana-data/src/types/valueMapping.ts b/packages/grafana-data/src/types/valueMapping.ts index 46b4e1377cf10..3a2b61a070314 100644 --- a/packages/grafana-data/src/types/valueMapping.ts +++ b/packages/grafana-data/src/types/valueMapping.ts @@ -4,9 +4,8 @@ export enum MappingType { } interface BaseMap { - id: number; - operator: string; - text: string; + id: number; // this could/should just be the array index + text: string; // the final display value type: MappingType; } diff --git a/packages/grafana-data/src/utils/valueMappings.test.ts b/packages/grafana-data/src/utils/valueMappings.test.ts index f1f465ee84986..eee48dbe5e3d6 100644 --- a/packages/grafana-data/src/utils/valueMappings.test.ts +++ b/packages/grafana-data/src/utils/valueMappings.test.ts @@ -11,8 +11,8 @@ describe('Format value with value mappings', () => { it('should return undefined with no matching valuemappings', () => { const valueMappings: ValueMapping[] = [ - { id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, - { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' }, + { id: 0, text: 'elva', type: MappingType.ValueToText, value: '11' }, + { id: 1, text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' }, ]; const value = '10'; @@ -21,8 +21,8 @@ describe('Format value with value mappings', () => { it('should return first matching mapping with lowest id', () => { const valueMappings: ValueMapping[] = [ - { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, - { id: 1, operator: '', text: 'tio', type: MappingType.ValueToText, value: '10' }, + { id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, + { id: 1, text: 'tio', type: MappingType.ValueToText, value: '10' }, ]; const value = '10'; @@ -31,8 +31,8 @@ describe('Format value with value mappings', () => { it('should return if value is null and value to text mapping value is null', () => { const valueMappings: ValueMapping[] = [ - { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, - { id: 1, operator: '', text: '', type: MappingType.ValueToText, value: 'null' }, + { id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, + { id: 1, text: '', type: MappingType.ValueToText, value: 'null' }, ]; const value = null; @@ -41,8 +41,8 @@ describe('Format value with value mappings', () => { it('should return if value is null and range to text mapping from and to is null', () => { const valueMappings: ValueMapping[] = [ - { id: 0, operator: '', text: '', type: MappingType.RangeToText, from: 'null', to: 'null' }, - { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, + { id: 0, text: '', type: MappingType.RangeToText, from: 'null', to: 'null' }, + { id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' }, ]; const value = null; @@ -51,8 +51,8 @@ describe('Format value with value mappings', () => { it('should return rangeToText mapping where value equals to', () => { const valueMappings: ValueMapping[] = [ - { id: 0, operator: '', text: '1-10', type: MappingType.RangeToText, from: '1', to: '10' }, - { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, + { id: 0, text: '1-10', type: MappingType.RangeToText, from: '1', to: '10' }, + { id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' }, ]; const value = '10'; @@ -61,8 +61,8 @@ describe('Format value with value mappings', () => { it('should return rangeToText mapping where value equals from', () => { const valueMappings: ValueMapping[] = [ - { id: 0, operator: '', text: '10-20', type: MappingType.RangeToText, from: '10', to: '20' }, - { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, + { id: 0, text: '10-20', type: MappingType.RangeToText, from: '10', to: '20' }, + { id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' }, ]; const value = '10'; @@ -71,8 +71,8 @@ describe('Format value with value mappings', () => { it('should return rangeToText mapping where value is between from and to', () => { const valueMappings: ValueMapping[] = [ - { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, - { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' }, + { id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, + { id: 1, text: 'elva', type: MappingType.ValueToText, value: '11' }, ]; const value = '10'; @@ -81,8 +81,8 @@ describe('Format value with value mappings', () => { it('should map value text to mapping', () => { const valueMappings: ValueMapping[] = [ - { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, - { id: 1, operator: '', text: 'ELVA', type: MappingType.ValueToText, value: 'elva' }, + { id: 0, text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' }, + { id: 1, text: 'ELVA', type: MappingType.ValueToText, value: 'elva' }, ]; const value = 'elva'; diff --git a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.test.tsx b/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.test.tsx index 33b22f7574705..56bcf01153d53 100644 --- a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.test.tsx +++ b/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.test.tsx @@ -12,8 +12,8 @@ const setup = (spy?: any, propOverrides?: object) => { } }, valueMappings: [ - { id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' }, - { id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' }, + { id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' }, + { id: 2, type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' }, ], }; @@ -35,9 +35,7 @@ describe('On remove mapping', () => { const remove = wrapper.find('button[aria-label="ValueMappingsEditor remove button"]'); remove.at(0).simulate('click'); - expect(onChangeSpy).toBeCalledWith([ - { id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' }, - ]); + expect(onChangeSpy).toBeCalledWith([{ id: 2, type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' }]); }); it('should remove mapping at index 1', () => { @@ -47,9 +45,7 @@ describe('On remove mapping', () => { const remove = wrapper.find('button[aria-label="ValueMappingsEditor remove button"]'); remove.at(1).simulate('click'); - expect(onChangeSpy).toBeCalledWith([ - { id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' }, - ]); + expect(onChangeSpy).toBeCalledWith([{ id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' }]); }); }); @@ -62,9 +58,9 @@ describe('Next id to add', () => { add.at(0).simulate('click'); expect(onChangeSpy).toBeCalledWith([ - { id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' }, - { id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' }, - { id: 3, operator: '', type: MappingType.ValueToText, from: '', to: '', text: '' }, + { id: 1, type: MappingType.ValueToText, value: '20', text: 'Ok' }, + { id: 2, type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' }, + { id: 3, type: MappingType.ValueToText, from: '', to: '', text: '' }, ]); }); @@ -73,8 +69,6 @@ describe('Next id to add', () => { const wrapper = setup(onChangeSpy, { valueMappings: [] }); const add = wrapper.find('*[aria-label="ValueMappingsEditor add mapping button"]'); add.at(0).simulate('click'); - expect(onChangeSpy).toBeCalledWith([ - { id: 0, operator: '', type: MappingType.ValueToText, from: '', to: '', text: '' }, - ]); + expect(onChangeSpy).toBeCalledWith([{ id: 0, type: MappingType.ValueToText, from: '', to: '', text: '' }]); }); }); diff --git a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx b/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx index ee838b5c4813a..d1fc93f82177c 100644 --- a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx +++ b/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.tsx @@ -15,7 +15,6 @@ export const ValueMappingsEditor: React.FC = ({ valueMappings, onChange, type: MappingType.ValueToText, from: '', to: '', - operator: '', text: '', }; const id = update && update.length > 0 ? Math.max(...update.map(v => v.id)) + 1 : 0; diff --git a/packages/grafana-ui/src/utils/standardEditors.tsx b/packages/grafana-ui/src/utils/standardEditors.tsx index 56bfa3289bff2..ac66c10ba931f 100644 --- a/packages/grafana-ui/src/utils/standardEditors.tsx +++ b/packages/grafana-ui/src/utils/standardEditors.tsx @@ -155,6 +155,7 @@ export const getStandardFieldConfigs = () => { id: 'mappings', path: 'mappings', name: 'Value mappings', + description: 'Modify the display text based on input value', editor: standardEditorsRegistry.get('mappings').editor as any, override: standardEditorsRegistry.get('mappings').editor as any, From 5789f80e14fab219890166af75193f00c733ef9c Mon Sep 17 00:00:00 2001 From: Sebastian Widmer Date: Wed, 1 Jul 2020 22:19:36 +0200 Subject: [PATCH 0012/1228] Loki: Allow aliasing Loki queries in dashboard (#25706) * Loki: Add Legend field to query editor * Loki: Basic test for legend field * Loki: Mention legend is only for metric queries * Loki: Fix absolute timerange never updating --- .../loki/components/LokiQueryEditor.test.tsx | 66 ++++++++++ .../loki/components/LokiQueryEditor.tsx | 114 +++++++++++++---- .../LokiQueryEditor.test.tsx.snap | 115 ++++++++++++++++++ .../datasource/loki/result_transformer.ts | 4 +- 4 files changed, 271 insertions(+), 28 deletions(-) create mode 100644 public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx create mode 100644 public/app/plugins/datasource/loki/components/__snapshots__/LokiQueryEditor.test.tsx.snap diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx new file mode 100644 index 0000000000000..f117ec6f50591 --- /dev/null +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { toUtc } from '@grafana/data'; + +import { LokiQueryEditor } from './LokiQueryEditor'; +import { LokiDatasource } from '../datasource'; +import { LokiQuery } from '../types'; + +const createMockRequestRange = (from: string, to: string) => { + return { + request: { + range: { + from: toUtc(from, 'YYYY-MM-DD'), + to: toUtc(to, 'YYYY-MM-DD'), + }, + }, + }; +}; + +const setup = (propOverrides?: object) => { + const datasourceMock: unknown = {}; + const datasource: LokiDatasource = datasourceMock as LokiDatasource; + const onRunQuery = jest.fn(); + const onChange = jest.fn(); + + const query: LokiQuery = { + expr: '', + refId: 'A', + legendFormat: 'My Legend', + }; + + const data = createMockRequestRange('2020-01-01', '2020-01-02'); + + const props: any = { + datasource, + onChange, + onRunQuery, + query, + data, + }; + + Object.assign(props, propOverrides); + + const wrapper = shallow(); + const instance = wrapper.instance() as LokiQueryEditor; + + return { + instance, + wrapper, + }; +}; + +describe('Render LokiQueryEditor with legend', () => { + it('should render', () => { + const { wrapper } = setup(); + expect(wrapper).toMatchSnapshot(); + }); + + it('should update absolute timerange', () => { + const { wrapper } = setup(); + wrapper.setProps({ + data: createMockRequestRange('2019-01-01', '2020-01-02'), + }); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx index b8d4757885068..7782269f853d5 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx @@ -1,45 +1,105 @@ // Libraries -import React, { memo } from 'react'; +import React, { PureComponent } from 'react'; // Types -import { AbsoluteTimeRange, QueryEditorProps } from '@grafana/data'; +import { AbsoluteTimeRange, QueryEditorProps, PanelData } from '@grafana/data'; +import { InlineFormLabel } from '@grafana/ui'; import { LokiDatasource } from '../datasource'; import { LokiQuery } from '../types'; import { LokiQueryField } from './LokiQueryField'; type Props = QueryEditorProps; -export const LokiQueryEditor = memo(function LokiQueryEditor(props: Props) { - const { query, data, datasource, onChange, onRunQuery } = props; +interface State { + legendFormat: string; +} - let absolute: AbsoluteTimeRange; +export class LokiQueryEditor extends PureComponent { + // Query target to be modified and used for queries + query: LokiQuery; - if (data && data.request) { - const { range } = data.request; - absolute = { - from: range.from.valueOf(), - to: range.to.valueOf(), + constructor(props: Props) { + super(props); + // Use default query to prevent undefined input values + const defaultQuery: Partial = { expr: '', legendFormat: '' }; + const query = Object.assign({}, defaultQuery, props.query); + this.query = query; + // Query target properties that are fully controlled inputs + this.state = { + // Fully controlled text inputs + legendFormat: query.legendFormat, }; - } else { - absolute = { + } + + calcAbsoluteRange = (data: PanelData): AbsoluteTimeRange => { + if (data && data.request) { + const { range } = data.request; + return { + from: range.from.valueOf(), + to: range.to.valueOf(), + }; + } + + return { from: Date.now() - 10000, to: Date.now(), }; - } + }; + + onFieldChange = (query: LokiQuery, override?: any) => { + this.query.expr = query.expr; + }; - return ( -
- -
- ); -}); + onLegendChange = (e: React.SyntheticEvent) => { + const legendFormat = e.currentTarget.value; + this.query.legendFormat = legendFormat; + this.setState({ legendFormat }); + }; + + onRunQuery = () => { + const { query } = this; + this.props.onChange(query); + this.props.onRunQuery(); + }; + + render() { + const { datasource, query, data } = this.props; + const { legendFormat } = this.state; + + return ( +
+ + +
+
+ + Legend + + +
+
+
+ ); + } +} export default LokiQueryEditor; diff --git a/public/app/plugins/datasource/loki/components/__snapshots__/LokiQueryEditor.test.tsx.snap b/public/app/plugins/datasource/loki/components/__snapshots__/LokiQueryEditor.test.tsx.snap new file mode 100644 index 0000000000000..d7d39fcb2409b --- /dev/null +++ b/public/app/plugins/datasource/loki/components/__snapshots__/LokiQueryEditor.test.tsx.snap @@ -0,0 +1,115 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Render LokiQueryEditor with legend should render 1`] = ` +
+ +
+
+ + Legend + + +
+
+
+`; + +exports[`Render LokiQueryEditor with legend should update absolute timerange 1`] = ` +
+ +
+
+ + Legend + + +
+
+
+`; diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index 4ae03bd1d64db..8c2ba7409da64 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -143,8 +143,10 @@ function createUid(ts: string, labelsString: string, line: string): string { } function lokiMatrixToTimeSeries(matrixResult: LokiMatrixResult, options: TransformerOptions): TimeSeries { + const name = createMetricLabel(matrixResult.metric, options); return { - target: createMetricLabel(matrixResult.metric, options), + target: name, + title: name, datapoints: lokiPointsToTimeseriesPoints(matrixResult.values, options), tags: matrixResult.metric, meta: options.meta, From 90a5a85eb1f1b67a7abfe2d079cc78767c9c6c5a Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 1 Jul 2020 22:15:41 -0700 Subject: [PATCH 0013/1228] Panel Loading: spin clockwise, not counter clockwise (#25998) * spin clockwise * spin clockwise --- .../dashboard/dashgrid/PanelHeader/PanelHeader.tsx | 2 +- public/sass/mixins/_animations.scss | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx index ab009e4e7ca42..ca92498b372a8 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx @@ -95,7 +95,7 @@ export class PanelHeader extends Component { return (
- +
); diff --git a/public/sass/mixins/_animations.scss b/public/sass/mixins/_animations.scss index 7104303400cfc..cf98ad30140f4 100644 --- a/public/sass/mixins/_animations.scss +++ b/public/sass/mixins/_animations.scss @@ -35,15 +35,15 @@ } } -@keyframes spin-counter-clock { +@keyframes spin-clockwise { 0% { - transform: rotate(359deg); + transform: rotate(0deg) scaleX(-1); // scaleX flips the `sync` icon so arrows point the correct way } 100% { - transform: rotate(0deg); + transform: rotate(359deg) scaleX(-1); } } -.spin-counter-clock { - animation: spin-counter-clock 3s infinite linear; +.spin-clockwise { + animation: spin-clockwise 3s infinite linear; } From 9746e1f6af9718c98446e8a62f5697a25f8963b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 2 Jul 2020 07:59:18 +0200 Subject: [PATCH 0014/1228] PanelEdit: Remove field label for single item categories (#25990) * FieldConfig: Remove field label for single item categories * Update public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx Co-authored-by: Alex Khomenko Co-authored-by: Alex Khomenko --- .../components/PanelEditor/FieldConfigEditor.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx b/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx index 43b19ecb1ec29..feed3102f5986 100644 --- a/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, ReactNode } from 'react'; import cloneDeep from 'lodash/cloneDeep'; import { DataFrame, @@ -171,12 +171,17 @@ export const DefaultFieldConfigEditor: React.FC = ({ data, onChange, conf : undefined : (defaults as any)[item.path]; - const label = ( + let label: ReactNode | undefined = ( ); + // hide label if there is only one item and category name is same as item, name + if (categoryItemCount === 1 && item.category?.[0] === item.name) { + label = undefined; + } + return ( Date: Thu, 2 Jul 2020 09:07:52 +0300 Subject: [PATCH 0015/1228] Update whats-new-in-v7-1.md (#25979) --- docs/sources/guides/whats-new-in-v7-1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/guides/whats-new-in-v7-1.md b/docs/sources/guides/whats-new-in-v7-1.md index cc46a7ac8ad23..89215230a732f 100644 --- a/docs/sources/guides/whats-new-in-v7-1.md +++ b/docs/sources/guides/whats-new-in-v7-1.md @@ -49,7 +49,7 @@ Support for Flux and Influx v2 has been added. ## Deep linking for Google Cloud Monitoring (formerly named Google Stackdriver) datasource -A new feature in Grafana 7.1 is [deep linking from Grafana panels to the Metrics Explorer in Gooogle Cloud Console]({{}}). Click on a time series in the panel to see a context menu with a link to View in Metrics explorer in Google Cloud Console. Clicking that link opens the Metrics explorer in the Monitoring Google Cloud Console and runs the query from the Grafana panel there. +A new feature in Grafana 7.1 is [deep linking from Grafana panels to the Metrics Explorer in Gooogle Cloud Console]({{}}). Click on a time series in the panel to see a context menu with a link to View in Metrics explorer in Google Cloud Console. Clicking that link opens the Metrics explorer in the Monitoring Google Cloud Console and runs the query from the Grafana panel there. ## Internal links for Elastic From ab172f09b25265cd010f1fd5daa6c3f1dfb4a6c5 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Thu, 2 Jul 2020 08:53:10 +0200 Subject: [PATCH 0016/1228] Docs: Add Query history search documentation (#25946) * Update docs for search in query history * Update * Update docs/sources/features/explore/index.md * Update docs/sources/features/explore/index.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/features/explore/index.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/features/explore/index.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/features/explore/index.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Update docs/sources/features/explore/index.md * Update docs/sources/features/explore/index.md Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> --- docs/sources/features/explore/index.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/sources/features/explore/index.md b/docs/sources/features/explore/index.md index 7cc14dd8a9bde..c1fd03d8d3510 100755 --- a/docs/sources/features/explore/index.md +++ b/docs/sources/features/explore/index.md @@ -84,7 +84,7 @@ By default, query history shows you the most recent queries. You can sort your h Filter query history in Query history and Starred tab by data source name: -1. Click the **Filter queries for specific data source(s)** field +1. Click the **Filter queries for specific data source(s)** field. 2. Select the data source for which you would like to filter your history. You can select multiple data sources. In **Query history** tab it is also possible to filter queries by date using the slider: @@ -95,6 +95,13 @@ In **Query history** tab it is also possible to filter queries by date using the > Note: If you are in split mode, filters are applied only to your currently active panel. +### Search in query history + +You can search in your history across queries and your comments. Search is possible for queries in the Query history tab and Starred tab. + +1. Click the **Search queries** field. +1. Type the term you are searching for into search field. + ### Query history settings You can customize the query history in the Settings tab. Options are described in the table below. @@ -103,7 +110,7 @@ You can customize the query history in the Settings tab. Options are described i | ------------------------------------------------------------- | --------------------------------------- | | Period of time for which Grafana will save your query history | 1 week | | Change the default active tab | Query history tab | -| Only show queries for datasource currently active in Explore | False | +| Only show queries for data source currently active in Explore | True | | Clear query history | Permanently deletes all stored queries. | > Note: Query history settings are global, and applied to both panels in split mode. @@ -199,6 +206,7 @@ While in Live tail view new logs will come from the bottom of the screen and wil {{< docs-imagebox img="/img/docs/v64/explore_live_tailing.gif" class="docs-image--no-shadow" caption="Explore Live tailing in action" >}} ## Tracing integration + > Only available in Grafana v7.0+. You can visualize traces from tracing data sources in explore. Data sources currently supported: @@ -229,12 +237,13 @@ Shows condensed view or the trace timeline. Drag your mouse over the minimap to {{< docs-imagebox img="/img/docs/v70/explore-trace-view-timeline.png" class="docs-image--no-shadow" caption="Screenshot of the trace view timeline" >}} Shows list of spans within the trace. Each span row consists of these components: + - Expand children button: Expands or collapses all the children spans of selected span. - Service name: Name of the service logged the span. - Operation name: Name of the operation that this span represents. - Span duration bar: Visual representation of the operation duration within the trace. -Clicking anywhere on the span row will show span details. +Clicking anywhere on the span row shows span details. ##### Span details From 9e47114c45bac0acf921c6811b40967e025dc917 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Thu, 2 Jul 2020 10:33:45 +0300 Subject: [PATCH 0017/1228] Forgot password: Fix styling (#26002) --- .../core/components/ForgottenPassword/ForgottenPassword.tsx | 2 +- public/app/core/components/Login/LoginLayout.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx b/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx index e0c1eb4b82b2c..39069dd376d10 100644 --- a/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx +++ b/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx @@ -45,7 +45,7 @@ export const ForgottenPassword: FC = () => { Reset password diff --git a/public/app/core/components/Login/LoginLayout.tsx b/public/app/core/components/Login/LoginLayout.tsx index 8a00e39bff4e7..e0ccd7f86cf3f 100644 --- a/public/app/core/components/Login/LoginLayout.tsx +++ b/public/app/core/components/Login/LoginLayout.tsx @@ -49,10 +49,11 @@ export const getLoginStyles = (theme: GrafanaTheme) => { min-height: 100vh; background-position: center; background-repeat: no-repeat; + background-color: ${theme.palette.black}; min-width: 100%; margin-left: 0; - background-color: $black; display: flex; + flex-direction: column; align-items: center; justify-content: center; `, @@ -76,7 +77,7 @@ export const getLoginStyles = (theme: GrafanaTheme) => { text-align: center; `, mainTitle: css` - font-size: '32px'; + font-size: 32px; `, subTitle: css` font-size: ${theme.typography.size.md}; From 085b2f3dbf85c7a4ce03d675215ab1d8ab6428ad Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 2 Jul 2020 01:42:44 -0700 Subject: [PATCH 0018/1228] Monaco: check suggestions against current word (#25992) * trigger on current word * proper index * test suggestsions * test suggestsions * fix test --- .../src/components/Monaco/suggestions.test.ts | 38 +++++++++ .../src/components/Monaco/suggestions.ts | 77 +++++++++++-------- 2 files changed, 84 insertions(+), 31 deletions(-) create mode 100644 packages/grafana-ui/src/components/Monaco/suggestions.test.ts diff --git a/packages/grafana-ui/src/components/Monaco/suggestions.test.ts b/packages/grafana-ui/src/components/Monaco/suggestions.test.ts new file mode 100644 index 0000000000000..61be3eb69d59b --- /dev/null +++ b/packages/grafana-ui/src/components/Monaco/suggestions.test.ts @@ -0,0 +1,38 @@ +import { findInsertIndex } from './suggestions'; + +describe('Check suggestion index', () => { + it('find last $ sign', () => { + const line = ' hello $123'; + const { index, prefix } = findInsertIndex(line); + expect(index).toEqual(line.indexOf('$')); + expect(prefix).toEqual('$123'); + }); + + it('insert into empty line', () => { + const line = ''; + const { index, prefix } = findInsertIndex(line); + expect(index).toEqual(0); + expect(prefix).toEqual(''); + }); + + it('insert new word', () => { + const line = 'this is a new '; + const { index, prefix } = findInsertIndex(line); + expect(index).toEqual(line.length); + expect(prefix).toEqual(''); + }); + + it('complte a simple word', () => { + const line = 'SELECT * FROM tab'; + const { index, prefix } = findInsertIndex(line); + expect(index).toEqual(line.lastIndexOf(' ') + 1); + expect(prefix).toEqual('tab'); + }); + + it('complete a quoted word', () => { + const line = 'SELECT "hello", "wo'; + const { index, prefix } = findInsertIndex(line); + expect(index).toEqual(line.lastIndexOf('"') + 1); + expect(prefix).toEqual('wo'); + }); +}); diff --git a/packages/grafana-ui/src/components/Monaco/suggestions.ts b/packages/grafana-ui/src/components/Monaco/suggestions.ts index 1070789943bd0..dc79f4675573d 100644 --- a/packages/grafana-ui/src/components/Monaco/suggestions.ts +++ b/packages/grafana-ui/src/components/Monaco/suggestions.ts @@ -2,6 +2,33 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { CodeEditorSuggestionItem, CodeEditorSuggestionItemKind, CodeEditorSuggestionProvider } from './types'; +/** + * @internal -- only exported for tests + */ +export function findInsertIndex(line: string): { index: number; prefix: string } { + for (let i = line.length - 1; i > 0; i--) { + const ch = line.charAt(i); + if (ch === '$') { + return { + index: i, + prefix: line.substring(i), + }; + } + + // Keep these seperators + if (ch === ' ' || ch === '\t' || ch === '"' || ch === "'") { + return { + index: i + 1, + prefix: line.substring(i + 1), + }; + } + } + return { + index: 0, + prefix: line, + }; +} + function getCompletionItems( prefix: string, suggestions: CodeEditorSuggestionItem[], @@ -53,51 +80,39 @@ export function registerSuggestions( triggerCharacters: ['$'], provideCompletionItems: (model, position, context) => { + const range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: position.column, + endColumn: position.column, + }; + + // Simple check if this was triggered by pressing `$` if (context.triggerCharacter === '$') { - const range = { - startLineNumber: position.lineNumber, - endLineNumber: position.lineNumber, - startColumn: position.column - 1, - endColumn: position.column, - }; + range.startColumn = position.column - 1; return { suggestions: getCompletionItems('$', getSuggestions(), range), }; } - // find out if we are completing a property in the 'dependencies' object. - const lineText = model.getValueInRange({ + // Find the replacement region + const currentLine = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column, }); - const idx = lineText.lastIndexOf('$'); - if (idx >= 0) { - const range = { - startLineNumber: position.lineNumber, - endLineNumber: position.lineNumber, - startColumn: idx, // the last $ we found - endColumn: position.column, - }; - return { - suggestions: getCompletionItems(lineText.substr(idx), getSuggestions(), range), - }; - } + const { index, prefix } = findInsertIndex(currentLine); + range.startColumn = index + 1; - // Empty line that asked for suggestion - if (lineText.trim().length < 1) { - return { - suggestions: getCompletionItems('', getSuggestions(), { - startLineNumber: position.lineNumber, - endLineNumber: position.lineNumber, - startColumn: position.column, - endColumn: position.column, - }), - }; + const suggestions = getCompletionItems(prefix, getSuggestions(), range); + if (suggestions.length) { + // NOTE, this will replace any language provided suggestions + return { suggestions }; } - // console.log('complete?', lineText, context); + + // Default language suggestions return undefined; }, }); From 3e9e2db384361e847d15842c489bf93f33ab2164 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Thu, 2 Jul 2020 11:28:37 +0200 Subject: [PATCH 0019/1228] CircleCI: Upgrade build pipeline tool (#26006) Signed-off-by: Arve Knudsen --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ebacf0aba3307..d878d72bd8891 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -56,7 +56,7 @@ commands: - run: name: "Install Grafana build pipeline tool" command: | - VERSION=0.4.17 + VERSION=0.4.18 curl -fLO https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v${VERSION}/grabpl chmod +x grabpl mv grabpl /tmp From c3d4e69a3229906f5c6d028a7ee48152590d1c1b Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Thu, 2 Jul 2020 14:17:42 +0300 Subject: [PATCH 0020/1228] Grafana UI: Make FileUpload button size customizable (#26013) --- .../components/FileUpload/FileUpload.story.tsx | 4 ++++ .../src/components/FileUpload/FileUpload.tsx | 16 ++++++++++++---- .../grafana-ui/src/utils/storybook/useSize.ts | 7 +++++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 packages/grafana-ui/src/utils/storybook/useSize.ts diff --git a/packages/grafana-ui/src/components/FileUpload/FileUpload.story.tsx b/packages/grafana-ui/src/components/FileUpload/FileUpload.story.tsx index d3e44096404b3..398295d3e437a 100644 --- a/packages/grafana-ui/src/components/FileUpload/FileUpload.story.tsx +++ b/packages/grafana-ui/src/components/FileUpload/FileUpload.story.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { FileUpload } from './FileUpload'; import mdx from './FileUpload.mdx'; +import { useSize } from '../../utils/storybook/useSize'; +import { ComponentSize } from '../../types/size'; export default { title: 'Forms/FileUpload', @@ -15,8 +17,10 @@ export default { }; export const single = () => { + const size = useSize(); return ( console.log('file', currentTarget?.files && currentTarget.files[0])} /> ); diff --git a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx index 776fdec5f4518..5525001112d1a 100644 --- a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx +++ b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx @@ -3,12 +3,14 @@ import { GrafanaTheme } from '@grafana/data'; import { css, cx } from 'emotion'; import { getFormStyles, Icon } from '../index'; import { stylesFactory, useTheme } from '../../themes'; +import { ComponentSize } from '../../types/size'; export interface Props { onFileUpload: (event: FormEvent) => void; /** Accepted file extensions */ accept?: string; className?: string; + size?: ComponentSize; } function trimFileName(fileName: string) { @@ -24,9 +26,15 @@ function trimFileName(fileName: string) { return `${file.substring(0, nameLength)}...${extension}`; } -export const FileUpload: FC = ({ onFileUpload, className, children = 'Upload file', accept = '*' }) => { +export const FileUpload: FC = ({ + onFileUpload, + className, + children = 'Upload file', + accept = '*', + size = 'md', +}) => { const theme = useTheme(); - const style = getStyles(theme); + const style = getStyles(theme, size); const [fileName, setFileName] = useState(''); const onChange = useCallback((event: FormEvent) => { @@ -60,8 +68,8 @@ export const FileUpload: FC = ({ onFileUpload, className, children = 'Upl ); }; -const getStyles = stylesFactory((theme: GrafanaTheme) => { - const buttonFormStyle = getFormStyles(theme, { variant: 'primary', invalid: false, size: 'md' }).button.button; +const getStyles = stylesFactory((theme: GrafanaTheme, size: ComponentSize) => { + const buttonFormStyle = getFormStyles(theme, { variant: 'primary', invalid: false, size }).button.button; return { fileUpload: css` display: none; diff --git a/packages/grafana-ui/src/utils/storybook/useSize.ts b/packages/grafana-ui/src/utils/storybook/useSize.ts new file mode 100644 index 0000000000000..d7a5d4e80646e --- /dev/null +++ b/packages/grafana-ui/src/utils/storybook/useSize.ts @@ -0,0 +1,7 @@ +import { select } from '@storybook/addon-knobs'; +import { ComponentSize } from '../../types/size'; + +export function useSize(size: ComponentSize = 'md') { + const sizes = ['xs', 'sm', 'md', 'lg']; + return select('Size', sizes, size); +} From 2d4bcbeff617bb921348536c652fecc410960875 Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki Date: Thu, 2 Jul 2020 14:29:37 +0300 Subject: [PATCH 0021/1228] Small fix in provisioning docs (#26004) * Small fix in provisioning docs * Update docs/sources/administration/provisioning.md Co-authored-by: Arve Knudsen Co-authored-by: Arve Knudsen --- docs/sources/administration/provisioning.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md index 75a0eb868da4d..4c5010dcd0300 100644 --- a/docs/sources/administration/provisioning.md +++ b/docs/sources/administration/provisioning.md @@ -327,11 +327,11 @@ providers: updateIntervalSeconds: 30 options: path: /etc/dashboards - foldersFromFileStructure: true + foldersFromFilesStructure: true ``` `server` and `application` will become new folders in Grafana menu. -> **Note.** `folder` and `folderUid` options should be empty or missing to make `foldersFromFileStructure` works. +> **Note.** `folder` and `folderUid` options should be empty or missing to make `foldersFromFilesStructure` work. ## Alert Notification Channels From 3ef06a0c8872945ee867e90cc0b2b9c88a83f1ef Mon Sep 17 00:00:00 2001 From: Dhananjay <45629183+gdhananjay@users.noreply.github.com> Date: Thu, 2 Jul 2020 18:54:36 +0530 Subject: [PATCH 0022/1228] Cloudwatch: Add Support for external ID in assume role (#23685) Co-authored by: Arve Knudsen --- .../features/datasources/cloudwatch.md | 1 + go.mod | 1 + go.sum | 7 + pkg/tsdb/cloudwatch/cloudwatch.go | 1 + pkg/tsdb/cloudwatch/credentials.go | 45 +- pkg/tsdb/cloudwatch/credentials_test.go | 121 ++++- pkg/tsdb/cloudwatch/metric_find_query.go | 2 +- pkg/tsdb/cloudwatch/mock_stsiface/stsapi.go | 436 ++++++++++++++++++ .../components/ConfigEditor.test.tsx | 1 + .../cloudwatch/components/ConfigEditor.tsx | 17 + .../plugins/datasource/cloudwatch/types.ts | 1 + 11 files changed, 602 insertions(+), 31 deletions(-) create mode 100644 pkg/tsdb/cloudwatch/mock_stsiface/stsapi.go diff --git a/docs/sources/features/datasources/cloudwatch.md b/docs/sources/features/datasources/cloudwatch.md index d3a7286ae58e2..a8623b1702722 100755 --- a/docs/sources/features/datasources/cloudwatch.md +++ b/docs/sources/features/datasources/cloudwatch.md @@ -34,6 +34,7 @@ build dashboards or use Explore with CloudWatch metrics and CloudWatch Logs. | _Auth Provider_ | Specify the provider to get credentials. | | _Credentials_ profile name | Specify the name of the profile to use (if you use `~/.aws/credentials` file), leave blank for default. | | _Assume Role Arn_ | Specify the ARN of the role to assume | +| _External ID_ | If you are assuming a role in another account, that has been created with an external ID, specify the exterrnal ID here. | ## Authentication diff --git a/go.mod b/go.mod index fe0585e63da0d..9898e7e8a7599 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/go-sql-driver/mysql v1.5.0 github.com/go-stack/stack v1.8.0 github.com/gobwas/glob v0.2.3 + github.com/golang/mock v1.4.3 github.com/golang/protobuf v1.4.0 github.com/google/go-cmp v0.4.0 github.com/gorilla/websocket v1.4.1 diff --git a/go.sum b/go.sum index 7da750ff1a712..69a9229faa8c1 100644 --- a/go.sum +++ b/go.sum @@ -108,7 +108,10 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -437,6 +440,7 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -450,6 +454,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190802220118-1d1727260058/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= @@ -526,6 +531,8 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index ff858076e3005..92df32b3c08ba 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/pkg/tsdb/cloudwatch/cloudwatch.go @@ -32,6 +32,7 @@ type DatasourceInfo struct { Region string AuthType string AssumeRoleArn string + ExternalID string Namespace string AccessKey string diff --git a/pkg/tsdb/cloudwatch/credentials.go b/pkg/tsdb/cloudwatch/credentials.go index 3a1a901f40c3c..d66fa4e9e7f4e 100644 --- a/pkg/tsdb/cloudwatch/credentials.go +++ b/pkg/tsdb/cloudwatch/credentials.go @@ -7,6 +7,7 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/client" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds" @@ -18,6 +19,7 @@ import ( "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/sts" + "github.com/aws/aws-sdk-go/service/sts/stsiface" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" ) @@ -30,7 +32,25 @@ type cache struct { var awsCredentialCache = make(map[string]cache) var credentialCacheLock sync.RWMutex -func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { +// Session factory. +// Stubbable by tests. +var newSession = func(cfgs ...*aws.Config) (*session.Session, error) { + return session.NewSession(cfgs...) +} + +// STS service factory. +// Stubbable by tests. +var newSTSService = func(p client.ConfigProvider, cfgs ...*aws.Config) stsiface.STSAPI { + return sts.New(p, cfgs...) +} + +// EC2Metadata service factory. +// Stubbable by tests. +var newEC2Metadata = func(p client.ConfigProvider, cfgs ...*aws.Config) *ec2metadata.EC2Metadata { + return ec2metadata.New(p, cfgs...) +} + +func getCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { cacheKey := fmt.Sprintf("%s:%s:%s:%s", dsInfo.AuthType, dsInfo.AccessKey, dsInfo.Profile, dsInfo.AssumeRoleArn) credentialCacheLock.RLock() if _, ok := awsCredentialCache[cacheKey]; ok { @@ -53,8 +73,11 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { RoleSessionName: aws.String("GrafanaSession"), DurationSeconds: aws.Int64(900), } + if dsInfo.ExternalID != "" { + params.ExternalId = aws.String(dsInfo.ExternalID) + } - stsSess, err := session.NewSession() + stsSess, err := newSession() if err != nil { return nil, err } @@ -70,11 +93,11 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { Credentials: stsCreds, } - sess, err := session.NewSession(stsConfig) + sess, err := newSession(stsConfig) if err != nil { return nil, err } - svc := sts.New(sess, stsConfig) + svc := newSTSService(sess, stsConfig) resp, err := svc.AssumeRole(params) if err != nil { return nil, err @@ -91,7 +114,7 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { expiration = &e } - sess, err := session.NewSession() + sess, err := newSession() if err != nil { return nil, err } @@ -123,7 +146,7 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) { } func webIdentityProvider(sess *session.Session) credentials.Provider { - svc := sts.New(sess) + svc := newSTSService(sess) roleARN := os.Getenv("AWS_ROLE_ARN") tokenFilepath := os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE") @@ -152,7 +175,7 @@ func ecsCredProvider(sess *session.Session, uri string) credentials.Provider { } func ec2RoleProvider(sess *session.Session) credentials.Provider { - return &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute} + return &ec2rolecreds.EC2RoleProvider{Client: newEC2Metadata(sess), ExpiryWindow: 5 * time.Minute} } func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo { @@ -167,6 +190,7 @@ func retrieveDsInfo(datasource *models.DataSource, region string) *DatasourceInf authType := datasource.JsonData.Get("authType").MustString() assumeRoleArn := datasource.JsonData.Get("assumeRoleArn").MustString() + externalID := datasource.JsonData.Get("externalId").MustString() decrypted := datasource.DecryptedValues() accessKey := decrypted["accessKey"] secretKey := decrypted["secretKey"] @@ -176,6 +200,7 @@ func retrieveDsInfo(datasource *models.DataSource, region string) *DatasourceInf Profile: datasource.Database, AuthType: authType, AssumeRoleArn: assumeRoleArn, + ExternalID: externalID, AccessKey: accessKey, SecretKey: secretKey, } @@ -184,7 +209,7 @@ func retrieveDsInfo(datasource *models.DataSource, region string) *DatasourceInf } func getAwsConfig(dsInfo *DatasourceInfo) (*aws.Config, error) { - creds, err := GetCredentials(dsInfo) + creds, err := getCredentials(dsInfo) if err != nil { return nil, err } @@ -204,7 +229,7 @@ func (e *CloudWatchExecutor) getClient(region string) (*cloudwatch.CloudWatch, e return nil, err } - sess, err := session.NewSession(cfg) + sess, err := newSession(cfg) if err != nil { return nil, err } @@ -224,7 +249,7 @@ func retrieveLogsClient(datasourceInfo *DatasourceInfo) (*cloudwatchlogs.CloudWa return nil, err } - sess, err := session.NewSession(cfg) + sess, err := newSession(cfg) if err != nil { return nil, err } diff --git a/pkg/tsdb/cloudwatch/credentials_test.go b/pkg/tsdb/cloudwatch/credentials_test.go index a0b51ec466956..e3d5f200ba764 100644 --- a/pkg/tsdb/cloudwatch/credentials_test.go +++ b/pkg/tsdb/cloudwatch/credentials_test.go @@ -4,39 +4,120 @@ import ( "os" "testing" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/client" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds" + "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" - . "github.com/smartystreets/goconvey/convey" + "github.com/aws/aws-sdk-go/service/sts" + "github.com/aws/aws-sdk-go/service/sts/stsiface" + "github.com/golang/mock/gomock" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/mock_stsiface" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestECSCredProvider(t *testing.T) { - Convey("Running in an ECS container task", t, func() { - defer os.Clearenv() - os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/abc/123") - - sess, _ := session.NewSession() - provider := remoteCredProvider(sess) + os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/abc/123") + t.Cleanup(func() { + os.Unsetenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") + }) - So(provider, ShouldNotBeNil) + sess, err := session.NewSession() + require.NoError(t, err) + provider := remoteCredProvider(sess) + require.NotNil(t, provider) - ecsProvider, ok := provider.(*endpointcreds.Provider) - So(ecsProvider, ShouldNotBeNil) - So(ok, ShouldBeTrue) + ecsProvider, ok := provider.(*endpointcreds.Provider) + require.NotNil(t, ecsProvider) + require.True(t, ok) - So(ecsProvider.Client.Endpoint, ShouldEqual, "http://169.254.170.2/abc/123") - }) + assert.Equal(t, "http://169.254.170.2/abc/123", ecsProvider.Client.Endpoint) } func TestDefaultEC2RoleProvider(t *testing.T) { - Convey("Running outside an ECS container task", t, func() { - sess, _ := session.NewSession() - provider := remoteCredProvider(sess) + sess, err := session.NewSession() + require.NoError(t, err) + provider := remoteCredProvider(sess) + require.NotNil(t, provider) + + ec2Provider, ok := provider.(*ec2rolecreds.EC2RoleProvider) + require.NotNil(t, ec2Provider) + require.True(t, ok) +} + +func TestGetCredentials_ARNAuthType(t *testing.T) { + ctrl := gomock.NewController(t) + var stsMock *mock_stsiface.MockSTSAPI + + origNewSession := newSession + origNewSTSService := newSTSService + origNewEC2Metadata := newEC2Metadata + t.Cleanup(func() { + newSession = origNewSession + newSTSService = origNewSTSService + newEC2Metadata = origNewEC2Metadata + }) + newSession = func(cfgs ...*aws.Config) (*session.Session, error) { + return &session.Session{}, nil + } + newSTSService = func(p client.ConfigProvider, cfgs ...*aws.Config) stsiface.STSAPI { + return stsMock + } + newEC2Metadata = func(p client.ConfigProvider, cfgs ...*aws.Config) *ec2metadata.EC2Metadata { + return nil + } + + t.Run("Without external ID", func(t *testing.T) { + stsMock = mock_stsiface.NewMockSTSAPI(ctrl) + stsMock. + EXPECT(). + AssumeRole(gomock.Eq(&sts.AssumeRoleInput{ + RoleArn: aws.String(""), + DurationSeconds: aws.Int64(900), + RoleSessionName: aws.String("GrafanaSession"), + })). + Return(&sts.AssumeRoleOutput{ + Credentials: &sts.Credentials{ + AccessKeyId: aws.String("id"), + SecretAccessKey: aws.String("secret"), + SessionToken: aws.String("token"), + }, + }, nil). + Times(1) + + creds, err := getCredentials(&DatasourceInfo{ + AuthType: "arn", + }) + require.NoError(t, err) + require.NotNil(t, creds) + }) - So(provider, ShouldNotBeNil) + t.Run("With external ID", func(t *testing.T) { + stsMock = mock_stsiface.NewMockSTSAPI(ctrl) + stsMock. + EXPECT(). + AssumeRole(gomock.Eq(&sts.AssumeRoleInput{ + RoleArn: aws.String(""), + DurationSeconds: aws.Int64(900), + RoleSessionName: aws.String("GrafanaSession"), + ExternalId: aws.String("external-id"), + })). + Return(&sts.AssumeRoleOutput{ + Credentials: &sts.Credentials{ + AccessKeyId: aws.String("id"), + SecretAccessKey: aws.String("secret"), + SessionToken: aws.String("token"), + }, + }, nil). + Times(1) - ec2Provider, ok := provider.(*ec2rolecreds.EC2RoleProvider) - So(ec2Provider, ShouldNotBeNil) - So(ok, ShouldBeTrue) + creds, err := getCredentials(&DatasourceInfo{ + AuthType: "arn", + ExternalID: "external-id", + }) + require.NoError(t, err) + require.NotNil(t, creds) }) } diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 22e157f26f3e8..825d2ae12e87b 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -740,7 +740,7 @@ func (e *CloudWatchExecutor) resourceGroupsGetResources(region string, filters [ } func getAllMetrics(cwData *DatasourceInfo) (cloudwatch.ListMetricsOutput, error) { - creds, err := GetCredentials(cwData) + creds, err := getCredentials(cwData) if err != nil { return cloudwatch.ListMetricsOutput{}, err } diff --git a/pkg/tsdb/cloudwatch/mock_stsiface/stsapi.go b/pkg/tsdb/cloudwatch/mock_stsiface/stsapi.go new file mode 100644 index 0000000000000..f7937852a911c --- /dev/null +++ b/pkg/tsdb/cloudwatch/mock_stsiface/stsapi.go @@ -0,0 +1,436 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/aws/aws-sdk-go/service/sts/stsiface (interfaces: STSAPI) + +// Package mock_stsiface is a generated GoMock package. +package mock_stsiface + +import ( + context "context" + request "github.com/aws/aws-sdk-go/aws/request" + sts "github.com/aws/aws-sdk-go/service/sts" + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockSTSAPI is a mock of STSAPI interface +type MockSTSAPI struct { + ctrl *gomock.Controller + recorder *MockSTSAPIMockRecorder +} + +// MockSTSAPIMockRecorder is the mock recorder for MockSTSAPI +type MockSTSAPIMockRecorder struct { + mock *MockSTSAPI +} + +// NewMockSTSAPI creates a new mock instance +func NewMockSTSAPI(ctrl *gomock.Controller) *MockSTSAPI { + mock := &MockSTSAPI{ctrl: ctrl} + mock.recorder = &MockSTSAPIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockSTSAPI) EXPECT() *MockSTSAPIMockRecorder { + return m.recorder +} + +// AssumeRole mocks base method +func (m *MockSTSAPI) AssumeRole(arg0 *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssumeRole", arg0) + ret0, _ := ret[0].(*sts.AssumeRoleOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AssumeRole indicates an expected call of AssumeRole +func (mr *MockSTSAPIMockRecorder) AssumeRole(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRole", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRole), arg0) +} + +// AssumeRoleRequest mocks base method +func (m *MockSTSAPI) AssumeRoleRequest(arg0 *sts.AssumeRoleInput) (*request.Request, *sts.AssumeRoleOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssumeRoleRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*sts.AssumeRoleOutput) + return ret0, ret1 +} + +// AssumeRoleRequest indicates an expected call of AssumeRoleRequest +func (mr *MockSTSAPIMockRecorder) AssumeRoleRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleRequest", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleRequest), arg0) +} + +// AssumeRoleWithContext mocks base method +func (m *MockSTSAPI) AssumeRoleWithContext(arg0 context.Context, arg1 *sts.AssumeRoleInput, arg2 ...request.Option) (*sts.AssumeRoleOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AssumeRoleWithContext", varargs...) + ret0, _ := ret[0].(*sts.AssumeRoleOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AssumeRoleWithContext indicates an expected call of AssumeRoleWithContext +func (mr *MockSTSAPIMockRecorder) AssumeRoleWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithContext", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithContext), varargs...) +} + +// AssumeRoleWithSAML mocks base method +func (m *MockSTSAPI) AssumeRoleWithSAML(arg0 *sts.AssumeRoleWithSAMLInput) (*sts.AssumeRoleWithSAMLOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssumeRoleWithSAML", arg0) + ret0, _ := ret[0].(*sts.AssumeRoleWithSAMLOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AssumeRoleWithSAML indicates an expected call of AssumeRoleWithSAML +func (mr *MockSTSAPIMockRecorder) AssumeRoleWithSAML(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithSAML", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithSAML), arg0) +} + +// AssumeRoleWithSAMLRequest mocks base method +func (m *MockSTSAPI) AssumeRoleWithSAMLRequest(arg0 *sts.AssumeRoleWithSAMLInput) (*request.Request, *sts.AssumeRoleWithSAMLOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssumeRoleWithSAMLRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*sts.AssumeRoleWithSAMLOutput) + return ret0, ret1 +} + +// AssumeRoleWithSAMLRequest indicates an expected call of AssumeRoleWithSAMLRequest +func (mr *MockSTSAPIMockRecorder) AssumeRoleWithSAMLRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithSAMLRequest", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithSAMLRequest), arg0) +} + +// AssumeRoleWithSAMLWithContext mocks base method +func (m *MockSTSAPI) AssumeRoleWithSAMLWithContext(arg0 context.Context, arg1 *sts.AssumeRoleWithSAMLInput, arg2 ...request.Option) (*sts.AssumeRoleWithSAMLOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AssumeRoleWithSAMLWithContext", varargs...) + ret0, _ := ret[0].(*sts.AssumeRoleWithSAMLOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AssumeRoleWithSAMLWithContext indicates an expected call of AssumeRoleWithSAMLWithContext +func (mr *MockSTSAPIMockRecorder) AssumeRoleWithSAMLWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithSAMLWithContext", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithSAMLWithContext), varargs...) +} + +// AssumeRoleWithWebIdentity mocks base method +func (m *MockSTSAPI) AssumeRoleWithWebIdentity(arg0 *sts.AssumeRoleWithWebIdentityInput) (*sts.AssumeRoleWithWebIdentityOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssumeRoleWithWebIdentity", arg0) + ret0, _ := ret[0].(*sts.AssumeRoleWithWebIdentityOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AssumeRoleWithWebIdentity indicates an expected call of AssumeRoleWithWebIdentity +func (mr *MockSTSAPIMockRecorder) AssumeRoleWithWebIdentity(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithWebIdentity", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithWebIdentity), arg0) +} + +// AssumeRoleWithWebIdentityRequest mocks base method +func (m *MockSTSAPI) AssumeRoleWithWebIdentityRequest(arg0 *sts.AssumeRoleWithWebIdentityInput) (*request.Request, *sts.AssumeRoleWithWebIdentityOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssumeRoleWithWebIdentityRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*sts.AssumeRoleWithWebIdentityOutput) + return ret0, ret1 +} + +// AssumeRoleWithWebIdentityRequest indicates an expected call of AssumeRoleWithWebIdentityRequest +func (mr *MockSTSAPIMockRecorder) AssumeRoleWithWebIdentityRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithWebIdentityRequest", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithWebIdentityRequest), arg0) +} + +// AssumeRoleWithWebIdentityWithContext mocks base method +func (m *MockSTSAPI) AssumeRoleWithWebIdentityWithContext(arg0 context.Context, arg1 *sts.AssumeRoleWithWebIdentityInput, arg2 ...request.Option) (*sts.AssumeRoleWithWebIdentityOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AssumeRoleWithWebIdentityWithContext", varargs...) + ret0, _ := ret[0].(*sts.AssumeRoleWithWebIdentityOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AssumeRoleWithWebIdentityWithContext indicates an expected call of AssumeRoleWithWebIdentityWithContext +func (mr *MockSTSAPIMockRecorder) AssumeRoleWithWebIdentityWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssumeRoleWithWebIdentityWithContext", reflect.TypeOf((*MockSTSAPI)(nil).AssumeRoleWithWebIdentityWithContext), varargs...) +} + +// DecodeAuthorizationMessage mocks base method +func (m *MockSTSAPI) DecodeAuthorizationMessage(arg0 *sts.DecodeAuthorizationMessageInput) (*sts.DecodeAuthorizationMessageOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DecodeAuthorizationMessage", arg0) + ret0, _ := ret[0].(*sts.DecodeAuthorizationMessageOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DecodeAuthorizationMessage indicates an expected call of DecodeAuthorizationMessage +func (mr *MockSTSAPIMockRecorder) DecodeAuthorizationMessage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeAuthorizationMessage", reflect.TypeOf((*MockSTSAPI)(nil).DecodeAuthorizationMessage), arg0) +} + +// DecodeAuthorizationMessageRequest mocks base method +func (m *MockSTSAPI) DecodeAuthorizationMessageRequest(arg0 *sts.DecodeAuthorizationMessageInput) (*request.Request, *sts.DecodeAuthorizationMessageOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DecodeAuthorizationMessageRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*sts.DecodeAuthorizationMessageOutput) + return ret0, ret1 +} + +// DecodeAuthorizationMessageRequest indicates an expected call of DecodeAuthorizationMessageRequest +func (mr *MockSTSAPIMockRecorder) DecodeAuthorizationMessageRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeAuthorizationMessageRequest", reflect.TypeOf((*MockSTSAPI)(nil).DecodeAuthorizationMessageRequest), arg0) +} + +// DecodeAuthorizationMessageWithContext mocks base method +func (m *MockSTSAPI) DecodeAuthorizationMessageWithContext(arg0 context.Context, arg1 *sts.DecodeAuthorizationMessageInput, arg2 ...request.Option) (*sts.DecodeAuthorizationMessageOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DecodeAuthorizationMessageWithContext", varargs...) + ret0, _ := ret[0].(*sts.DecodeAuthorizationMessageOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DecodeAuthorizationMessageWithContext indicates an expected call of DecodeAuthorizationMessageWithContext +func (mr *MockSTSAPIMockRecorder) DecodeAuthorizationMessageWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeAuthorizationMessageWithContext", reflect.TypeOf((*MockSTSAPI)(nil).DecodeAuthorizationMessageWithContext), varargs...) +} + +// GetAccessKeyInfo mocks base method +func (m *MockSTSAPI) GetAccessKeyInfo(arg0 *sts.GetAccessKeyInfoInput) (*sts.GetAccessKeyInfoOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccessKeyInfo", arg0) + ret0, _ := ret[0].(*sts.GetAccessKeyInfoOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAccessKeyInfo indicates an expected call of GetAccessKeyInfo +func (mr *MockSTSAPIMockRecorder) GetAccessKeyInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessKeyInfo", reflect.TypeOf((*MockSTSAPI)(nil).GetAccessKeyInfo), arg0) +} + +// GetAccessKeyInfoRequest mocks base method +func (m *MockSTSAPI) GetAccessKeyInfoRequest(arg0 *sts.GetAccessKeyInfoInput) (*request.Request, *sts.GetAccessKeyInfoOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccessKeyInfoRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*sts.GetAccessKeyInfoOutput) + return ret0, ret1 +} + +// GetAccessKeyInfoRequest indicates an expected call of GetAccessKeyInfoRequest +func (mr *MockSTSAPIMockRecorder) GetAccessKeyInfoRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessKeyInfoRequest", reflect.TypeOf((*MockSTSAPI)(nil).GetAccessKeyInfoRequest), arg0) +} + +// GetAccessKeyInfoWithContext mocks base method +func (m *MockSTSAPI) GetAccessKeyInfoWithContext(arg0 context.Context, arg1 *sts.GetAccessKeyInfoInput, arg2 ...request.Option) (*sts.GetAccessKeyInfoOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetAccessKeyInfoWithContext", varargs...) + ret0, _ := ret[0].(*sts.GetAccessKeyInfoOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAccessKeyInfoWithContext indicates an expected call of GetAccessKeyInfoWithContext +func (mr *MockSTSAPIMockRecorder) GetAccessKeyInfoWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessKeyInfoWithContext", reflect.TypeOf((*MockSTSAPI)(nil).GetAccessKeyInfoWithContext), varargs...) +} + +// GetCallerIdentity mocks base method +func (m *MockSTSAPI) GetCallerIdentity(arg0 *sts.GetCallerIdentityInput) (*sts.GetCallerIdentityOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCallerIdentity", arg0) + ret0, _ := ret[0].(*sts.GetCallerIdentityOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCallerIdentity indicates an expected call of GetCallerIdentity +func (mr *MockSTSAPIMockRecorder) GetCallerIdentity(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCallerIdentity", reflect.TypeOf((*MockSTSAPI)(nil).GetCallerIdentity), arg0) +} + +// GetCallerIdentityRequest mocks base method +func (m *MockSTSAPI) GetCallerIdentityRequest(arg0 *sts.GetCallerIdentityInput) (*request.Request, *sts.GetCallerIdentityOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCallerIdentityRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*sts.GetCallerIdentityOutput) + return ret0, ret1 +} + +// GetCallerIdentityRequest indicates an expected call of GetCallerIdentityRequest +func (mr *MockSTSAPIMockRecorder) GetCallerIdentityRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCallerIdentityRequest", reflect.TypeOf((*MockSTSAPI)(nil).GetCallerIdentityRequest), arg0) +} + +// GetCallerIdentityWithContext mocks base method +func (m *MockSTSAPI) GetCallerIdentityWithContext(arg0 context.Context, arg1 *sts.GetCallerIdentityInput, arg2 ...request.Option) (*sts.GetCallerIdentityOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetCallerIdentityWithContext", varargs...) + ret0, _ := ret[0].(*sts.GetCallerIdentityOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCallerIdentityWithContext indicates an expected call of GetCallerIdentityWithContext +func (mr *MockSTSAPIMockRecorder) GetCallerIdentityWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCallerIdentityWithContext", reflect.TypeOf((*MockSTSAPI)(nil).GetCallerIdentityWithContext), varargs...) +} + +// GetFederationToken mocks base method +func (m *MockSTSAPI) GetFederationToken(arg0 *sts.GetFederationTokenInput) (*sts.GetFederationTokenOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFederationToken", arg0) + ret0, _ := ret[0].(*sts.GetFederationTokenOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFederationToken indicates an expected call of GetFederationToken +func (mr *MockSTSAPIMockRecorder) GetFederationToken(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFederationToken", reflect.TypeOf((*MockSTSAPI)(nil).GetFederationToken), arg0) +} + +// GetFederationTokenRequest mocks base method +func (m *MockSTSAPI) GetFederationTokenRequest(arg0 *sts.GetFederationTokenInput) (*request.Request, *sts.GetFederationTokenOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFederationTokenRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*sts.GetFederationTokenOutput) + return ret0, ret1 +} + +// GetFederationTokenRequest indicates an expected call of GetFederationTokenRequest +func (mr *MockSTSAPIMockRecorder) GetFederationTokenRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFederationTokenRequest", reflect.TypeOf((*MockSTSAPI)(nil).GetFederationTokenRequest), arg0) +} + +// GetFederationTokenWithContext mocks base method +func (m *MockSTSAPI) GetFederationTokenWithContext(arg0 context.Context, arg1 *sts.GetFederationTokenInput, arg2 ...request.Option) (*sts.GetFederationTokenOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetFederationTokenWithContext", varargs...) + ret0, _ := ret[0].(*sts.GetFederationTokenOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFederationTokenWithContext indicates an expected call of GetFederationTokenWithContext +func (mr *MockSTSAPIMockRecorder) GetFederationTokenWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFederationTokenWithContext", reflect.TypeOf((*MockSTSAPI)(nil).GetFederationTokenWithContext), varargs...) +} + +// GetSessionToken mocks base method +func (m *MockSTSAPI) GetSessionToken(arg0 *sts.GetSessionTokenInput) (*sts.GetSessionTokenOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSessionToken", arg0) + ret0, _ := ret[0].(*sts.GetSessionTokenOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSessionToken indicates an expected call of GetSessionToken +func (mr *MockSTSAPIMockRecorder) GetSessionToken(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSessionToken", reflect.TypeOf((*MockSTSAPI)(nil).GetSessionToken), arg0) +} + +// GetSessionTokenRequest mocks base method +func (m *MockSTSAPI) GetSessionTokenRequest(arg0 *sts.GetSessionTokenInput) (*request.Request, *sts.GetSessionTokenOutput) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSessionTokenRequest", arg0) + ret0, _ := ret[0].(*request.Request) + ret1, _ := ret[1].(*sts.GetSessionTokenOutput) + return ret0, ret1 +} + +// GetSessionTokenRequest indicates an expected call of GetSessionTokenRequest +func (mr *MockSTSAPIMockRecorder) GetSessionTokenRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSessionTokenRequest", reflect.TypeOf((*MockSTSAPI)(nil).GetSessionTokenRequest), arg0) +} + +// GetSessionTokenWithContext mocks base method +func (m *MockSTSAPI) GetSessionTokenWithContext(arg0 context.Context, arg1 *sts.GetSessionTokenInput, arg2 ...request.Option) (*sts.GetSessionTokenOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetSessionTokenWithContext", varargs...) + ret0, _ := ret[0].(*sts.GetSessionTokenOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSessionTokenWithContext indicates an expected call of GetSessionTokenWithContext +func (mr *MockSTSAPIMockRecorder) GetSessionTokenWithContext(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSessionTokenWithContext", reflect.TypeOf((*MockSTSAPI)(nil).GetSessionTokenWithContext), varargs...) +} diff --git a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx index 560035a1a9157..5f510208a8b3d 100644 --- a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx @@ -42,6 +42,7 @@ const setup = (propOverrides?: object) => { }, jsonData: { assumeRoleArn: '', + externalId: '', database: '', customMetricsNamespaces: '', authType: 'keys', diff --git a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx index cd36315f3f304..89cb8bc6d1940 100644 --- a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx @@ -130,6 +130,7 @@ export class ConfigEditor extends PureComponent { onChange={option => { if (options.jsonData.authType === 'arn' && option.value !== 'arn') { delete this.props.options.jsonData.assumeRoleArn; + delete this.props.options.jsonData.externalId; } onUpdateDatasourceJsonDataOptionSelect(this.props, 'authType')(option); }} @@ -239,6 +240,22 @@ export class ConfigEditor extends PureComponent { /> +
+ + External ID + +
+ +
+
)}
diff --git a/public/app/plugins/datasource/cloudwatch/types.ts b/public/app/plugins/datasource/cloudwatch/types.ts index 8bbb9bf911da5..5bab1b6957d09 100644 --- a/public/app/plugins/datasource/cloudwatch/types.ts +++ b/public/app/plugins/datasource/cloudwatch/types.ts @@ -59,6 +59,7 @@ export type SelectableStrings = Array>; export interface CloudWatchJsonData extends DataSourceJsonData { timeField?: string; assumeRoleArn?: string; + externalId?: string; database?: string; customMetricsNamespaces?: string; } From e25c6db7e98ebe64cfe5db3208e6bcef88b6cd88 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 2 Jul 2020 15:43:12 +0200 Subject: [PATCH 0023/1228] Docker: Make sure to create default plugin provisioning directory (#26017) --- packaging/docker/Dockerfile | 1 + packaging/docker/ubuntu.Dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile index fc84033f4b1f1..fed434719fd4c 100644 --- a/packaging/docker/Dockerfile +++ b/packaging/docker/Dockerfile @@ -49,6 +49,7 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \ mkdir -p "$GF_PATHS_PROVISIONING/datasources" \ "$GF_PATHS_PROVISIONING/dashboards" \ "$GF_PATHS_PROVISIONING/notifiers" \ + "$GF_PATHS_PROVISIONING/plugins" \ "$GF_PATHS_LOGS" \ "$GF_PATHS_PLUGINS" \ "$GF_PATHS_DATA" && \ diff --git a/packaging/docker/ubuntu.Dockerfile b/packaging/docker/ubuntu.Dockerfile index dbeef849d6cd0..c474db5919e09 100644 --- a/packaging/docker/ubuntu.Dockerfile +++ b/packaging/docker/ubuntu.Dockerfile @@ -39,6 +39,7 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \ mkdir -p "$GF_PATHS_PROVISIONING/datasources" \ "$GF_PATHS_PROVISIONING/dashboards" \ "$GF_PATHS_PROVISIONING/notifiers" \ + "$GF_PATHS_PROVISIONING/plugins" \ "$GF_PATHS_LOGS" \ "$GF_PATHS_PLUGINS" \ "$GF_PATHS_DATA" && \ From 66a00ee5c948dd48b39e850e0b38f7463fd765e1 Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki Date: Thu, 2 Jul 2020 16:48:30 +0300 Subject: [PATCH 0024/1228] Chore: added changelog for 7.1.0-beta2 (#26015) * Chore: added changelog for 7.1.0-beta2 * Update Changelog.md --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cba577323b870..c2672cecf4c88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 7.1.0-beta 2 (2020-07-02) + +### Features / Enhancements +* **Loki**: Allow aliasing Loki queries in dashboard. [#25706](https://github.com/grafana/grafana/pull/25706), [@bastjan](https://github.com/bastjan) + +### Bug Fixes +* **Explore**: Fix href when jumping from Explore to Add data source. [#25991](https://github.com/grafana/grafana/pull/25991), [@ivanahuckova](https://github.com/ivanahuckova) +* **Fix**: Build-in plugins failed to load in windows. [#25982](https://github.com/grafana/grafana/pull/25982), [@papagian](https://github.com/papagian) + # 7.1.0-beta 1 (2020-07-01) ### Features / Enhancements From 3720c2563843a083d77dbda81f71c5d67f758123 Mon Sep 17 00:00:00 2001 From: Maksim Nabokikh <32434187+nabokihms@users.noreply.github.com> Date: Thu, 2 Jul 2020 18:29:10 +0400 Subject: [PATCH 0025/1228] grafana-cli: Add ability to read password from stdin to reset admin password (#26016) * grafana-cli: Add ability to read password from stdin to reset admin password Signed-off-by: m.nabokikh Co-authored-by: Arve Knudsen --- pkg/cmd/grafana-cli/commands/commands.go | 7 +++++++ .../commands/reset_password_command.go | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/grafana-cli/commands/commands.go b/pkg/cmd/grafana-cli/commands/commands.go index 269a0c917fb2e..61c8dd38d05a5 100644 --- a/pkg/cmd/grafana-cli/commands/commands.go +++ b/pkg/cmd/grafana-cli/commands/commands.go @@ -111,6 +111,13 @@ var adminCommands = []*cli.Command{ Name: "reset-admin-password", Usage: "reset-admin-password ", Action: runDbCommand(resetPasswordCommand), + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "password-from-stdin", + Usage: "Read the password from stdin", + Value: false, + }, + }, }, { Name: "data-migration", diff --git a/pkg/cmd/grafana-cli/commands/reset_password_command.go b/pkg/cmd/grafana-cli/commands/reset_password_command.go index 8b99f85814d91..1faa6ba908850 100644 --- a/pkg/cmd/grafana-cli/commands/reset_password_command.go +++ b/pkg/cmd/grafana-cli/commands/reset_password_command.go @@ -1,7 +1,9 @@ package commands import ( + "bufio" "fmt" + "os" "github.com/fatih/color" "github.com/grafana/grafana/pkg/bus" @@ -16,7 +18,22 @@ import ( const AdminUserId = 1 func resetPasswordCommand(c utils.CommandLine, sqlStore *sqlstore.SqlStore) error { - newPassword := c.Args().First() + newPassword := "" + + if c.Bool("password-from-stdin") { + logger.Infof("New Password: ") + + scanner := bufio.NewScanner(os.Stdin) + if ok := scanner.Scan(); !ok { + if err := scanner.Err(); err != nil { + return fmt.Errorf("can't read password from stdin: %w", err) + } + return fmt.Errorf("can't read password from stdin") + } + newPassword = scanner.Text() + } else { + newPassword = c.Args().First() + } password := models.Password(newPassword) if password.IsWeak() { From b765b4130f8f29356fd9f6755660608f8818aff4 Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki Date: Thu, 2 Jul 2020 17:49:42 +0300 Subject: [PATCH 0026/1228] Chore: updated testing in latest.json to 7.1.0-beta2 (#26018) --- latest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/latest.json b/latest.json index 1e11931a0e2e4..3998cdbff30f4 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { "stable": "7.0.5", - "testing": "7.1.0-beta1" + "testing": "7.1.0-beta2" } From 45bbee2dea4bedb21568768b328dc5434b70610d Mon Sep 17 00:00:00 2001 From: ChrisDGH <61090940+ChrisDGH@users.noreply.github.com> Date: Thu, 2 Jul 2020 15:03:03 -0400 Subject: [PATCH 0027/1228] Update _index.md --- docs/sources/developers/plugins/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/developers/plugins/_index.md b/docs/sources/developers/plugins/_index.md index 025fe98ed7a99..0bfe60d81b8ad 100644 --- a/docs/sources/developers/plugins/_index.md +++ b/docs/sources/developers/plugins/_index.md @@ -1,5 +1,5 @@ +++ -title = "Build a plugin" +title = "Build a plugin." type = "docs" +++ From 02a46a5d613b0797d4452fd916ec33c64e2af934 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 3 Jul 2020 08:53:54 +0200 Subject: [PATCH 0028/1228] Migrate: Error page (404) (#26010) * first things * simple migration and remove angular part --- .../core/components/ErrorPage/ErrorPage.tsx | 90 +++++++++++++++++++ public/app/core/controllers/all.ts | 1 - public/app/core/controllers/error_ctrl.ts | 24 ----- public/app/core/reducers/navModel.ts | 17 +++- public/app/partials/error.html | 50 ----------- public/app/routes/ReactContainer.tsx | 2 +- public/app/routes/routes.ts | 8 +- 7 files changed, 112 insertions(+), 80 deletions(-) create mode 100644 public/app/core/components/ErrorPage/ErrorPage.tsx delete mode 100644 public/app/core/controllers/error_ctrl.ts delete mode 100644 public/app/partials/error.html diff --git a/public/app/core/components/ErrorPage/ErrorPage.tsx b/public/app/core/components/ErrorPage/ErrorPage.tsx new file mode 100644 index 0000000000000..c9740f40eb794 --- /dev/null +++ b/public/app/core/components/ErrorPage/ErrorPage.tsx @@ -0,0 +1,90 @@ +import React, { PureComponent } from 'react'; +import { connect, MapStateToProps } from 'react-redux'; +import { NavModel } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { Icon } from '@grafana/ui'; +import Page from '../Page/Page'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { StoreState } from 'app/types'; + +interface ConnectedProps { + navModel: NavModel; +} + +interface OwnProps {} + +type Props = ConnectedProps; + +export class ErrorPage extends PureComponent { + render() { + const { navModel } = this.props; + return ( + + +
+
+
+
+
+

100%

+

80%

+

60%

+

40%

+

20%

+

0%

+
+
+ graph +
+

Then

+

Now

+
+
+
+
+
+
+

current

+
+
+ +
+
+

Chances you are on the page you are looking for.

+

0%

+
+
+

Sorry for the inconvenience

+

+ Please go back to your{' '} + + home dashboard + {' '} + and try again. +

+

+ If the error persists, seek help on the{' '} + + community site + + . +

+
+
+
+
+
+
+
+
+ ); + } +} + +const mapStateToProps: MapStateToProps = state => { + return { + navModel: getNavModel(state.navIndex, 'not-found'), + }; +}; + +export default connect(mapStateToProps)(ErrorPage); diff --git a/public/app/core/controllers/all.ts b/public/app/core/controllers/all.ts index ab658d75acf11..f7f9f4c0486e9 100644 --- a/public/app/core/controllers/all.ts +++ b/public/app/core/controllers/all.ts @@ -1,5 +1,4 @@ import './invited_ctrl'; import './signup_ctrl'; import './reset_password_ctrl'; -import './error_ctrl'; import './json_editor_ctrl'; diff --git a/public/app/core/controllers/error_ctrl.ts b/public/app/core/controllers/error_ctrl.ts deleted file mode 100644 index 780b0ecfce087..0000000000000 --- a/public/app/core/controllers/error_ctrl.ts +++ /dev/null @@ -1,24 +0,0 @@ -import config from 'app/core/config'; -import coreModule from '../core_module'; -import appEvents from 'app/core/app_events'; -import { CoreEvents } from 'app/types'; - -export class ErrorCtrl { - /** @ngInject */ - constructor($scope: any, contextSrv: any, navModelSrv: any) { - $scope.navModel = navModelSrv.getNotFoundNav(); - $scope.appSubUrl = config.appSubUrl; - - if (!contextSrv.isSignedIn) { - appEvents.emit(CoreEvents.toggleSidemenuHidden); - } - - $scope.$on('destroy', () => { - if (!contextSrv.isSignedIn) { - appEvents.emit(CoreEvents.toggleSidemenuHidden); - } - }); - } -} - -coreModule.controller('ErrorCtrl', ErrorCtrl); diff --git a/public/app/core/reducers/navModel.ts b/public/app/core/reducers/navModel.ts index 2c89a509e9a03..d974e82efc7e1 100644 --- a/public/app/core/reducers/navModel.ts +++ b/public/app/core/reducers/navModel.ts @@ -1,5 +1,5 @@ import { AnyAction, createAction } from '@reduxjs/toolkit'; -import { NavIndex, NavModelItem } from '@grafana/data'; +import { NavIndex, NavModel, NavModelItem } from '@grafana/data'; import config from 'app/core/config'; @@ -21,6 +21,21 @@ function buildNavIndex(navIndex: NavIndex, children: NavModelItem[], parentItem? buildNavIndex(navIndex, node.children, node); } } + + navIndex['not-found'] = { ...buildWarningNav('Page not found', '404 Error').node }; +} + +function buildWarningNav(text: string, subTitle?: string): NavModel { + const node = { + text, + subTitle, + icon: 'exclamation-triangle', + }; + return { + breadcrumbs: [node], + node: node, + main: node, + }; } export const initialState: NavIndex = {}; diff --git a/public/app/partials/error.html b/public/app/partials/error.html deleted file mode 100644 index c25addeba5167..0000000000000 --- a/public/app/partials/error.html +++ /dev/null @@ -1,50 +0,0 @@ - - -
-
-
-
-
-

100%

-

80%

-

60%

-

40%

-

20%

-

0%

-
-
- -
-

Then

-

Now

-
-
-
-
-
-
-

current

-
-
- -
-
-

Chances you are on the page you are looking for.

-

0%

-
-
-

Sorry for the inconvenience

-

Please go back to your home dashboard and try again.

-

- If the error persists, seek help on the - community site. -

-
-
-
-
- -
-
- -
diff --git a/public/app/routes/ReactContainer.tsx b/public/app/routes/ReactContainer.tsx index b5336eb46979f..75dc116551fbe 100644 --- a/public/app/routes/ReactContainer.tsx +++ b/public/app/routes/ReactContainer.tsx @@ -63,7 +63,7 @@ export function reactContainer( $rootScope: $rootScope, $scope: scope, $contextSrv: contextSrv, - routeInfo: $route.current.$$route.routeInfo, + routeInfo: $route.current.$$route?.routeInfo, }; document.body.classList.add('is-react'); diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index 4280e818f5873..bf54da8fb7781 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -538,9 +538,11 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati reloadOnSearch: false, }) .otherwise({ - templateUrl: 'public/app/partials/error.html', - controller: 'ErrorCtrl', - reloadOnSearch: false, + template: '', + resolve: { + component: () => + SafeDynamicImport(import(/* webpackChunkName: "ErrorPage" */ 'app/core/components/ErrorPage/ErrorPage')), + }, }); applyRouteRegistrationHandlers($routeProvider); From b7792de16da66deb89ba673f5f018fdadd25b8ea Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 3 Jul 2020 00:07:17 -0700 Subject: [PATCH 0029/1228] grafana/data: do not bundle rxjs (#26039) --- packages/grafana-data/rollup.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-data/rollup.config.ts b/packages/grafana-data/rollup.config.ts index dc5e576457b1a..68c5461fd47d6 100644 --- a/packages/grafana-data/rollup.config.ts +++ b/packages/grafana-data/rollup.config.ts @@ -21,7 +21,7 @@ const buildCjsPackage = ({ env }) => { globals: {}, }, ], - external: ['lodash', 'apache-arrow'], // Use Lodash & arrow from grafana + external: ['lodash', 'rxjs', 'apache-arrow'], // Use Lodash, rxjs & arrow from grafana plugins: [ json({ include: ['../../node_modules/moment-timezone/data/packed/latest.json'], From 8b4665536178209ff4493f367dd0ea119d1d8dff Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Fri, 3 Jul 2020 09:10:59 +0200 Subject: [PATCH 0030/1228] Elastic: Fix displaying of correct log message (#26020) * Fix default field, remove redundant line field check * Add comments --- public/app/core/logs_model.ts | 5 +---- public/app/plugins/datasource/loki/result_transformer.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index bb06277de65f6..ba0b3438a2fa6 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -319,10 +319,7 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi // Find the fields we care about and collect all labels const allSeries: LogFields[] = logSeries.map(series => { const fieldCache = new FieldCache(series); - - const stringField = fieldCache.hasFieldNamed('line') - ? fieldCache.getFieldByName('line') - : fieldCache.getFirstFieldOfType(FieldType.string); + const stringField = fieldCache.getFirstFieldOfType(FieldType.string); if (stringField?.labels) { allLabels.push(stringField.labels); } diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index 8c2ba7409da64..c8ea38fd9ec4e 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -78,7 +78,7 @@ function constructDataFrame( refId, fields: [ { name: 'ts', type: FieldType.time, config: { displayName: 'Time' }, values: times }, // Time - { name: 'line', type: FieldType.string, config: {}, values: lines, labels }, // Line + { name: 'line', type: FieldType.string, config: {}, values: lines, labels }, // Line - needs to be the first field with string type { name: 'id', type: FieldType.string, config: {}, values: uids }, { name: 'tsNs', type: FieldType.time, config: { displayName: 'Time ns' }, values: timesNs }, // Time ], From dec76b4556d5cba805178a57fe652bb2bb8f457d Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Fri, 3 Jul 2020 03:14:44 -0400 Subject: [PATCH 0031/1228] Graph panel: Move Stacking and null values before Hover tooltip options (#26037) --- .../panels/visualizations/graph-panel.md | 22 +++---- .../app/plugins/panel/graph/tab_display.html | 64 +++++++++---------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/docs/sources/panels/visualizations/graph-panel.md b/docs/sources/panels/visualizations/graph-panel.md index fd75ab981309c..1059df67ad5cc 100644 --- a/docs/sources/panels/visualizations/graph-panel.md +++ b/docs/sources/panels/visualizations/graph-panel.md @@ -32,6 +32,17 @@ Use these settings to refine your visualization. - **Points -** Display points for values. - **Point radius -** Controls how large the points are. +### Stacking and null value + +- **Stack -** Each series is stacked on top of another. +- **Percent -** Available when **Stack** is selected. Each series is drawn as a percentage of the total of all series. +- **Null value -** How null values are displayed. _This is a very important setting._ See note below. + - **connected -** If there is a gap in the series, meaning a null value or values, then the line will skip the gap and connect to the next non-null value. + - **null -** (default) If there is a gap in the series, meaning a null value, then the line in the graph will be broken and show the gap. + - **null as zero -** If there is a gap in the series, meaning a null value, then it will be displayed as a zero value in the graph panel. + +> **Note:** If you are monitoring a server's CPU load and the load reaches 100%, then the server will lock up and the agent sending statistics will not be able to collect the load statistic. This leads to a gap in the metrics and having the default as _null_ means Grafana will show the gaps and indicate that something is wrong. If this is set to _connected_, then it would be easy to miss this signal. + ### Hover tooltip Use these settings to change the appearance of the tooltip that appears when you hover your cursor over the graph visualization. @@ -44,17 +55,6 @@ Use these settings to change the appearance of the tooltip that appears when you - **Increasing -** The series in the hover tooltip are sorted by value and in increasing order, with the lowest value at the top of the list. - **Decreasing -** The series in the hover tooltip are sorted by value and in decreasing order, with the highest value at the top of the list. -### Stacking and null value - -- **Stack -** Each series is stacked on top of another. -- **Percent -** Available when **Stack** is selected. Each series is drawn as a percentage of the total of all series. -- **Null value -** How null values are displayed. _This is a very important setting._ See note below. - - **connected -** If there is a gap in the series, meaning a null value or values, then the line will skip the gap and connect to the next non-null value. - - **null -** (default) If there is a gap in the series, meaning a null value, then the line in the graph will be broken and show the gap. - - **null as zero -** If there is a gap in the series, meaning a null value, then it will be displayed as a zero value in the graph panel. - -> **Note:** If you are monitoring a server's CPU load and the load reaches 100%, then the server will lock up and the agent sending statistics will not be able to collect the load statistic. This leads to a gap in the metrics and having the default as _null_ means Grafana will show the gaps and indicate that something is wrong. If this is set to _connected_, then it would be easy to miss this signal. - ## Series overrides Series overrides allow a series in a graph panel to be rendered differently from the others. You can customize display options on a per-series bases or by using regex rules. For example, one series can have a thicker line width to make it stand out or be moved to the right Y-axis. diff --git a/public/app/plugins/panel/graph/tab_display.html b/public/app/plugins/panel/graph/tab_display.html index dac0887fe57b0..c17177ac5c211 100644 --- a/public/app/plugins/panel/graph/tab_display.html +++ b/public/app/plugins/panel/graph/tab_display.html @@ -81,6 +81,38 @@
+
+
Stacking and null value
+ + + + +
+ +
+ +
+
+
+
Hover tooltip
@@ -117,35 +149,3 @@
Hover tooltip
- -
-
Stacking and null value
- - - - -
- -
- -
-
-
From b06d2cf30f5dc4e690263025d69852c2e1853962 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Fri, 3 Jul 2020 09:24:36 +0200 Subject: [PATCH 0032/1228] AdminUsersTable: Fix width (#26019) --- .../app/features/admin/UserListAdminPage.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/public/app/features/admin/UserListAdminPage.tsx b/public/app/features/admin/UserListAdminPage.tsx index 8c676cccdf4eb..6c27070a7d1dd 100644 --- a/public/app/features/admin/UserListAdminPage.tsx +++ b/public/app/features/admin/UserListAdminPage.tsx @@ -99,14 +99,20 @@ const renderUser = (user: UserDTO) => { - - {user.login} + + + {user.login} + - - {user.email} + + + {user.email} + - - {user.name} + + + {user.name} + {user.lastSeenAtAge && {user.lastSeenAtAge}} From 634d8d60d6e4a10e7400c97ee82ef15b6c298096 Mon Sep 17 00:00:00 2001 From: Steven Vachon Date: Fri, 3 Jul 2020 04:22:56 -0400 Subject: [PATCH 0033/1228] @grafana/e2e: close options panel before interacting with the query form (#26036) ... it's logically better, but the real reason is to appease Cypress which was cause a consistent request error for a single plugin (datadog-datasource). An error which could not be reproduced manually. --- packages/grafana-e2e/src/flows/addPanel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grafana-e2e/src/flows/addPanel.ts b/packages/grafana-e2e/src/flows/addPanel.ts index 298439c167056..8e4b403ba7dd6 100644 --- a/packages/grafana-e2e/src/flows/addPanel.ts +++ b/packages/grafana-e2e/src/flows/addPanel.ts @@ -68,6 +68,8 @@ export const addPanel = (config?: Partial): any => .click(); closeOptionsGroup('type'); + closeOptions(); + queriesForm(fullConfig); e2e().wait('@chartData'); @@ -77,8 +79,6 @@ export const addPanel = (config?: Partial): any => //e2e.components.Panels.Panel.containerByTitle(panelTitle).find('.panel-content').contains('No data'); //e2e.components.QueryEditorRow.actionButton('Disable/enable query').click(); - closeOptions(); - e2e() .get('button[title="Apply changes and go back to dashboard"]') .click(); From 3acc2a6ac2e91fe485fb88c48dd71b0560e62324 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 3 Jul 2020 01:23:32 -0700 Subject: [PATCH 0034/1228] Table: JSON Cell should try to convert strings to JSON (#26024) --- .../src/components/Table/JSONViewCell.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/grafana-ui/src/components/Table/JSONViewCell.tsx b/packages/grafana-ui/src/components/Table/JSONViewCell.tsx index b642be28efcf8..7784f54041300 100644 --- a/packages/grafana-ui/src/components/Table/JSONViewCell.tsx +++ b/packages/grafana-ui/src/components/Table/JSONViewCell.tsx @@ -3,6 +3,7 @@ import { css, cx } from 'emotion'; import { TableCellProps } from './types'; import { Tooltip } from '../Tooltip/Tooltip'; import { JSONFormatter } from '../JSONFormatter/JSONFormatter'; +import { isString } from 'lodash'; export const JSONViewCell: FC = props => { const { field, cell, tableStyles } = props; @@ -16,8 +17,16 @@ export const JSONViewCell: FC = props => { font-family: monospace; `; - const displayValue = JSON.stringify(cell.value); - const content = ; + let value = cell.value; + let displayValue = value; + if (isString(value)) { + try { + value = JSON.parse(value); + } catch {} // ignore errors + } else { + displayValue = JSON.stringify(value); + } + const content = ; return (
From 66460ae740f04d2f167618931bcc04847a1196c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 3 Jul 2020 14:58:29 +0200 Subject: [PATCH 0035/1228] InfluxDB: Fixed new group by dropdown now showing (#26031) --- public/app/plugins/datasource/influxdb/query_builder.ts | 6 ++++++ public/app/plugins/datasource/influxdb/query_ctrl.ts | 2 ++ .../datasource/influxdb/specs/query_builder.test.ts | 9 +++++++++ 3 files changed, 17 insertions(+) diff --git a/public/app/plugins/datasource/influxdb/query_builder.ts b/public/app/plugins/datasource/influxdb/query_builder.ts index 49e17ab39ba37..023dec10e4052 100644 --- a/public/app/plugins/datasource/influxdb/query_builder.ts +++ b/public/app/plugins/datasource/influxdb/query_builder.ts @@ -90,6 +90,12 @@ export class InfluxQueryBuilder { if (tag.key === withKey) { return memo; } + + // value operators not supported in these types of queries + if (tag.operator === '>' || tag.operator === '<') { + return memo; + } + memo.push(renderTagCondition(tag, memo.length)); return memo; }, diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts index e68d5830ee855..edfabdb9d0925 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.ts +++ b/public/app/plugins/datasource/influxdb/query_ctrl.ts @@ -167,6 +167,7 @@ export class InfluxQueryCtrl extends QueryCtrl { const plusButton = this.uiSegmentSrv.newPlusButton(); this.groupBySegment.value = plusButton.value; this.groupBySegment.html = plusButton.html; + this.groupBySegment.fake = true; this.panelCtrl.refresh(); } @@ -308,6 +309,7 @@ export class InfluxQueryCtrl extends QueryCtrl { if (segment.type === 'condition') { return Promise.resolve([this.uiSegmentSrv.newSegment('AND'), this.uiSegmentSrv.newSegment('OR')]); } + if (segment.type === 'operator') { const nextValue = this.tagSegments[index + 1].value; if (/^\/.*\/$/.test(nextValue)) { diff --git a/public/app/plugins/datasource/influxdb/specs/query_builder.test.ts b/public/app/plugins/datasource/influxdb/specs/query_builder.test.ts index d8917946eb531..6543d5a77d5a0 100644 --- a/public/app/plugins/datasource/influxdb/specs/query_builder.test.ts +++ b/public/app/plugins/datasource/influxdb/specs/query_builder.test.ts @@ -32,6 +32,15 @@ describe('InfluxQueryBuilder', () => { expect(query).toBe('SHOW TAG KEYS WHERE "host" = \'se1\''); }); + it('should ignore condition if operator is a value operator', () => { + const builder = new InfluxQueryBuilder({ + measurement: '', + tags: [{ key: 'value', value: '10', operator: '>' }], + }); + const query = builder.buildExploreQuery('TAG_KEYS'); + expect(query).toBe('SHOW TAG KEYS'); + }); + it('should have no conditions in measurement query for query with no tags', () => { const builder = new InfluxQueryBuilder({ measurement: '', tags: [] }); const query = builder.buildExploreQuery('MEASUREMENTS'); From 081f954a2b538edd6af56d42dba1b580f71a191e Mon Sep 17 00:00:00 2001 From: David Date: Fri, 3 Jul 2020 15:04:57 +0200 Subject: [PATCH 0036/1228] Explore: Don't run queries on datasource change (#26033) - more and more datasources are having long-running queries, automatically triggering is becoming more of a burden than a help. - some datasource queries might actually cost money, so running queries should be explicit. --- public/app/features/explore/state/actions.test.ts | 4 +++- public/app/features/explore/state/actions.ts | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/public/app/features/explore/state/actions.test.ts b/public/app/features/explore/state/actions.test.ts index 492be9b5d4232..c8c8d6b175083 100644 --- a/public/app/features/explore/state/actions.test.ts +++ b/public/app/features/explore/state/actions.test.ts @@ -259,7 +259,7 @@ describe('changing datasource', () => { jest.spyOn(Actions, 'importQueries').mockImplementationOnce(() => jest.fn); jest.spyOn(Actions, 'loadDatasource').mockImplementationOnce(() => jest.fn); - jest.spyOn(Actions, 'runQueries').mockImplementationOnce(() => jest.fn); + const runQueriesAction = jest.spyOn(Actions, 'runQueries').mockImplementationOnce(() => jest.fn); const dispatchedActions = await thunkTester(initialState) .givenThunk(changeDatasource) .whenThunkIsDispatched(exploreId, name); @@ -272,6 +272,8 @@ describe('changing datasource', () => { mode: ExploreMode.Logs, }), ]); + // Don't run queries just on datasource change + expect(runQueriesAction).toHaveBeenCalledTimes(0); }); }); diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 809d507a65746..b4db347e723c0 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -157,7 +157,6 @@ export function changeDatasource(exploreId: ExploreId, datasourceName: string): } await dispatch(loadDatasource(exploreId, newDataSourceInstance, orgId)); - dispatch(runQueries(exploreId)); }; } @@ -265,11 +264,12 @@ export function loadExploreDatasourcesAndSetDatasource( exploreId: ExploreId, datasourceName: string ): ThunkResult { - return dispatch => { + return async dispatch => { const exploreDatasources = getExploreDatasources(); if (exploreDatasources.length >= 1) { - dispatch(changeDatasource(exploreId, datasourceName)); + await dispatch(changeDatasource(exploreId, datasourceName)); + dispatch(runQueries(exploreId)); } else { dispatch(loadDatasourceMissingAction({ exploreId })); } From 26852ca788b83a4c757d5495dd0b57103f314acb Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Fri, 3 Jul 2020 15:47:46 +0200 Subject: [PATCH 0037/1228] Instrument dashboard versions and annotation count (#26044) --- pkg/infra/metrics/metrics.go | 20 ++++++++++++++++++++ pkg/infra/usagestats/usage_stats.go | 4 ++++ pkg/infra/usagestats/usage_stats_test.go | 4 ++++ pkg/models/stats.go | 2 ++ pkg/services/sqlstore/stats.go | 2 ++ 5 files changed, 32 insertions(+) diff --git a/pkg/infra/metrics/metrics.go b/pkg/infra/metrics/metrics.go index f4bf21d66cdfe..4f51e4f36b633 100644 --- a/pkg/infra/metrics/metrics.go +++ b/pkg/infra/metrics/metrics.go @@ -159,6 +159,12 @@ var ( // StatsTotalDataSources is a metric total number of defined datasources, labeled by pluginId StatsTotalDataSources *prometheus.GaugeVec + // StatsTotalAnnotations is a metric of total number of annotations stored in Grafana. + StatsTotalAnnotations prometheus.Gauge + + // StatsTotalDashboardVersions is a metric of total number of dashboard versions stored in Grafana. + StatsTotalDashboardVersions prometheus.Gauge + // grafanaBuildVersion is a metric with a constant '1' value labeled by version, revision, branch, and goversion from which Grafana was built grafanaBuildVersion *prometheus.GaugeVec @@ -483,6 +489,18 @@ func init() { Help: "A metric with a constant '1' value labeled by pluginId, pluginType and version from which Grafana plugin was built", Namespace: ExporterName, }, []string{"plugin_id", "plugin_type", "version"}) + + StatsTotalDashboardVersions = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "stat_totals_dashboard_versions", + Help: "total amount of dashboard versions in the database", + Namespace: ExporterName, + }) + + StatsTotalAnnotations = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "stat_totals_annotations", + Help: "total amount of annotations in the database", + Namespace: ExporterName, + }) } // SetBuildInformation sets the build information for this binary @@ -550,6 +568,8 @@ func initMetricVars() { StatsTotalDataSources, grafanaBuildVersion, grafanaPluginBuildInfoDesc, + StatsTotalDashboardVersions, + StatsTotalAnnotations, ) } diff --git a/pkg/infra/usagestats/usage_stats.go b/pkg/infra/usagestats/usage_stats.go index 6d22e55d03c2c..36f5c4ebe8eb9 100644 --- a/pkg/infra/usagestats/usage_stats.go +++ b/pkg/infra/usagestats/usage_stats.go @@ -61,6 +61,8 @@ func (uss *UsageStatsService) sendUsageStats(oauthProviders map[string]bool) { metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots metrics["stats.teams.count"] = statsQuery.Result.Teams metrics["stats.total_auth_token.count"] = statsQuery.Result.AuthTokens + metrics["stats.dashboard_versions.count"] = statsQuery.Result.DashboardVersions + metrics["stats.annotations.count"] = statsQuery.Result.Annotations metrics["stats.valid_license.count"] = getValidLicenseCount(uss.License.HasValidLicense()) metrics["stats.edition.oss.count"] = getOssEditionCount() metrics["stats.edition.enterprise.count"] = getEnterpriseEditionCount() @@ -212,6 +214,8 @@ func (uss *UsageStatsService) updateTotalStats() { metrics.StatsTotalActiveEditors.Set(float64(statsQuery.Result.ActiveEditors)) metrics.StatsTotalAdmins.Set(float64(statsQuery.Result.Admins)) metrics.StatsTotalActiveAdmins.Set(float64(statsQuery.Result.ActiveAdmins)) + metrics.StatsTotalDashboardVersions.Set(float64(statsQuery.Result.DashboardVersions)) + metrics.StatsTotalAnnotations.Set(float64(statsQuery.Result.Annotations)) dsStats := models.GetDataSourceStatsQuery{} if err := uss.Bus.Dispatch(&dsStats); err != nil { diff --git a/pkg/infra/usagestats/usage_stats_test.go b/pkg/infra/usagestats/usage_stats_test.go index afa49360cc44a..917ccd6d3a2d2 100644 --- a/pkg/infra/usagestats/usage_stats_test.go +++ b/pkg/infra/usagestats/usage_stats_test.go @@ -50,6 +50,8 @@ func TestMetrics(t *testing.T) { Snapshots: 13, Teams: 14, AuthTokens: 15, + DashboardVersions: 16, + Annotations: 17, } getSystemStatsQuery = query return nil @@ -238,6 +240,8 @@ func TestMetrics(t *testing.T) { So(metrics.Get("stats.teams.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Teams) So(metrics.Get("stats.total_auth_token.count").MustInt64(), ShouldEqual, 15) So(metrics.Get("stats.avg_auth_token_per_user.count").MustInt64(), ShouldEqual, 5) + So(metrics.Get("stats.dashboard_versions.count").MustInt64(), ShouldEqual, 16) + So(metrics.Get("stats.annotations.count").MustInt64(), ShouldEqual, 17) So(metrics.Get("stats.ds."+models.DS_ES+".count").MustInt(), ShouldEqual, 9) So(metrics.Get("stats.ds."+models.DS_PROMETHEUS+".count").MustInt(), ShouldEqual, 10) diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 498cdd5c0bf26..05df818256649 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -16,6 +16,8 @@ type SystemStats struct { Folders int64 ProvisionedDashboards int64 AuthTokens int64 + DashboardVersions int64 + Annotations int64 Admins int Editors int diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 6f80a8017e619..685a0783ee4e3 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -75,6 +75,8 @@ func GetSystemStats(query *models.GetSystemStatsQuery) error { sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_provisioning") + `) AS provisioned_dashboards,`) sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_snapshot") + `) AS snapshots,`) + sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("dashboard_version") + `) AS dashboard_versions,`) + sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("annotation") + `) AS annotations,`) sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("team") + `) AS teams,`) sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("user_auth_token") + `) AS auth_tokens,`) From c0762b6ddc60dc2c251cd5c1428a031448c357fe Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 3 Jul 2020 08:49:29 -0700 Subject: [PATCH 0038/1228] Chore: reduce null check errors/warnigns (#25223) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix a few null errors * more * flip logic * merge master * more * fewer changes * processor Co-authored-by: Torkel Ödegaard --- .../src/dataframe/DataFrameView.ts | 6 +-- .../datasource/mixed/MixedDataSource.ts | 4 +- public/app/plugins/panel/alertlist/module.ts | 6 +-- .../plugins/panel/annolist/AnnoListPanel.tsx | 45 +++++++++---------- .../plugins/panel/bargauge/BarGaugePanel.tsx | 9 +++- .../app/plugins/panel/graph/Legend/Legend.tsx | 25 ++++++----- .../panel/graph/Legend/LegendSeriesItem.tsx | 4 +- public/app/plugins/panel/singlestat/module.ts | 9 ++-- 8 files changed, 60 insertions(+), 48 deletions(-) diff --git a/packages/grafana-data/src/dataframe/DataFrameView.ts b/packages/grafana-data/src/dataframe/DataFrameView.ts index 0568bbc690c5f..b96055813c9a0 100644 --- a/packages/grafana-data/src/dataframe/DataFrameView.ts +++ b/packages/grafana-data/src/dataframe/DataFrameView.ts @@ -53,15 +53,15 @@ export class DataFrameView extends FunctionalVector { * Helper function to return the {@link DisplayProcessor} for a given field column. * @param colIndex - the field column index for the data frame. */ - getFieldDisplayProcessor(colIndex: number): DisplayProcessor | null { + getFieldDisplayProcessor(colIndex: number): DisplayProcessor | undefined { if (!this.dataFrame || !this.dataFrame.fields) { - return null; + return undefined; } const field = this.dataFrame.fields[colIndex]; if (!field || !field.display) { - return null; + return undefined; } return field.display; diff --git a/public/app/plugins/datasource/mixed/MixedDataSource.ts b/public/app/plugins/datasource/mixed/MixedDataSource.ts index 1f171e83a296f..3fe232cc8b870 100644 --- a/public/app/plugins/datasource/mixed/MixedDataSource.ts +++ b/public/app/plugins/datasource/mixed/MixedDataSource.ts @@ -40,9 +40,9 @@ export class MixedDatasource extends DataSourceApi { const mixed: BatchedQueries[] = []; for (const key in sets) { const targets = sets[key]; - const dsName = targets[0].datasource; + const dsName: string | undefined = targets[0].datasource; mixed.push({ - datasource: getDataSourceSrv().get(dsName), + datasource: getDataSourceSrv().get(dsName, request.scopedVars), targets, }); } diff --git a/public/app/plugins/panel/alertlist/module.ts b/public/app/plugins/panel/alertlist/module.ts index d3faa5ca1f0e5..f165508f71a6c 100644 --- a/public/app/plugins/panel/alertlist/module.ts +++ b/public/app/plugins/panel/alertlist/module.ts @@ -90,10 +90,10 @@ class AlertListPanel extends PanelCtrl { if (this.panel.show === 'current') { getAlertsPromise = this.getCurrentAlertState(); - } - - if (this.panel.show === 'changes') { + } else if (this.panel.show === 'changes') { getAlertsPromise = this.getStateChanges(); + } else { + getAlertsPromise = Promise.resolve(); } getAlertsPromise.then(() => { diff --git a/public/app/plugins/panel/annolist/AnnoListPanel.tsx b/public/app/plugins/panel/annolist/AnnoListPanel.tsx index 5f7e9b3151072..63342c6b4aaa5 100644 --- a/public/app/plugins/panel/annolist/AnnoListPanel.tsx +++ b/public/app/plugins/panel/annolist/AnnoListPanel.tsx @@ -4,20 +4,17 @@ import React, { PureComponent } from 'react'; import { AnnoOptions } from './types'; import { AnnotationEvent, AppEvents, dateTime, DurationUnit, PanelProps } from '@grafana/data'; import { Tooltip } from '@grafana/ui'; -import { getBackendSrv } from '@grafana/runtime'; +import { getBackendSrv, getLocationSrv } from '@grafana/runtime'; import { AbstractList } from '@grafana/ui/src/components/List/AbstractList'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import appEvents from 'app/core/app_events'; - -import { updateLocation } from 'app/core/actions'; -import { store } from 'app/store/store'; import { css, cx } from 'emotion'; interface UserInfo { - id: number; - login: string; - email: string; + id?: number; + login?: string; + email?: string; } interface Props extends PanelProps {} @@ -108,6 +105,10 @@ export class AnnoListPanel extends PureComponent { onAnnoClick = (e: React.SyntheticEvent, anno: AnnotationEvent) => { e.stopPropagation(); + if (!anno.time) { + return; + } + const { options } = this.props; const dashboardSrv = getDashboardSrv(); const current = dashboardSrv.getCurrent(); @@ -122,12 +123,10 @@ export class AnnoListPanel extends PureComponent { } if (current.id === anno.dashboardId) { - store.dispatch( - updateLocation({ - query: params, - partial: true, - }) - ); + getLocationSrv().update({ + query: params, + partial: true, + }); return; } @@ -136,12 +135,10 @@ export class AnnoListPanel extends PureComponent { .then((res: any[]) => { if (res && res.length && res[0].id === anno.dashboardId) { const dash = res[0]; - store.dispatch( - updateLocation({ - query: params, - path: dash.url, - }) - ); + getLocationSrv().update({ + query: params, + path: dash.url, + }); return; } appEvents.emit(AppEvents.alertWarning, ['Unknown Dashboard: ' + anno.dashboardId]); @@ -164,7 +161,7 @@ export class AnnoListPanel extends PureComponent { return t.add(incr, unit as DurationUnit).valueOf(); } - onTagClick = (e: React.SyntheticEvent, tag: string, remove: boolean) => { + onTagClick = (e: React.SyntheticEvent, tag: string, remove?: boolean) => { e.stopPropagation(); const queryTags = remove ? this.state.queryTags.filter(item => item !== tag) : [...this.state.queryTags, tag]; @@ -188,7 +185,7 @@ export class AnnoListPanel extends PureComponent { }); }; - renderTags = (tags: string[], remove: boolean): JSX.Element | null => { + renderTags = (tags?: string[], remove?: boolean): JSX.Element | null => { if (!tags || !tags.length) { return null; } @@ -197,7 +194,7 @@ export class AnnoListPanel extends PureComponent { {tags.map(tag => { return ( this.onTagClick(e, tag, remove)} className="pointer"> - + ); })} @@ -251,7 +248,9 @@ export class AnnoListPanel extends PureComponent { {showTags && this.renderTags(anno.tags, false)} - {showTime && {dashboard.formatDate(anno.time)}} + + {showTime && anno.time && {dashboard.formatDate(anno.time)}} +
); diff --git a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx index 4768c3ba40a00..50e9a37eb8c77 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx @@ -6,6 +6,7 @@ import { getFieldDisplayValues, PanelProps, FieldConfig, + DisplayProcessor, DisplayValue, } from '@grafana/data'; import { BarGauge, DataLinksContextMenu, VizRepeater, VizRepeaterRenderValueProps } from '@grafana/ui'; @@ -13,6 +14,7 @@ import { BarGauge, DataLinksContextMenu, VizRepeater, VizRepeaterRenderValueProp import { config } from 'app/core/config'; import { BarGaugeOptions } from './types'; import { DataLinksContextMenuApi } from '@grafana/ui/src/components/DataLinks/DataLinksContextMenu'; +import { isNumber } from 'lodash'; export class BarGaugePanel extends PureComponent> { renderComponent = ( @@ -24,6 +26,11 @@ export class BarGaugePanel extends PureComponent> { const { field, display, view, colIndex } = value; const { openMenu, targetClassName } = menuProps; + let processor: DisplayProcessor | undefined = undefined; + if (view && isNumber(colIndex)) { + processor = view!.getFieldDisplayProcessor(colIndex as number); + } + return ( > { height={height} orientation={orientation} field={field} - display={view?.getFieldDisplayProcessor(colIndex)} + display={processor} theme={config.theme} itemSpacing={this.getItemSpacing()} displayMode={options.displayMode} diff --git a/public/app/plugins/panel/graph/Legend/Legend.tsx b/public/app/plugins/panel/graph/Legend/Legend.tsx index f4cd97a7b59b1..eb8832fc79835 100644 --- a/public/app/plugins/panel/graph/Legend/Legend.tsx +++ b/public/app/plugins/panel/graph/Legend/Legend.tsx @@ -286,18 +286,19 @@ class LegendTable extends PureComponent> { - {seriesList.map((series, i) => ( -