From 7db848f153f083b5efafc4c3bae177eeafb99f8b Mon Sep 17 00:00:00 2001 From: bugficks Date: Tue, 15 Jan 2019 13:29:56 +0100 Subject: [PATCH 001/119] [Feature request] MySQL SSL CA in datasource connector https://github.com/grafana/grafana/issues/8570 --- pkg/tsdb/mysql/mysql.go | 44 ++++++++++++ .../datasource/mysql/partials/config.html | 68 ++++++++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index 35b03e489a0..e713b87e265 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -6,6 +6,10 @@ import ( "reflect" "strconv" "strings" + "errors" + + "crypto/x509" + "crypto/tls" "github.com/go-sql-driver/mysql" "github.com/go-xorm/core" @@ -32,6 +36,46 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin datasource.Url, datasource.Database, ) + + var tlsSkipVerify, tlsAuth, tlsAuthWithCACert bool + if datasource.JsonData != nil { + tlsAuth = datasource.JsonData.Get("tlsAuth").MustBool(false) + tlsAuthWithCACert = datasource.JsonData.Get("tlsAuthWithCACert").MustBool(false) + tlsSkipVerify = datasource.JsonData.Get("tlsSkipVerify").MustBool(false) + } + + if tlsAuth || tlsAuthWithCACert { + + secureJsonData := datasource.SecureJsonData.Decrypt() + tlsConfig := tls.Config { + InsecureSkipVerify: tlsSkipVerify, + } + + if tlsAuthWithCACert && len(secureJsonData["tlsCACert"]) > 0 { + + caPool := x509.NewCertPool() + if ok := caPool.AppendCertsFromPEM([]byte(secureJsonData["tlsCACert"])); !ok { + return nil, errors.New("Failed to parse TLS CA PEM certificate") + } + + tlsConfig.RootCAs = caPool + } + + if tlsAuth { + certs, err := tls.X509KeyPair([]byte(secureJsonData["tlsClientCert"]), []byte(secureJsonData["tlsClientKey"])) + if err != nil { + return nil, err + } + clientCert := make([]tls.Certificate, 0, 1) + clientCert = append(clientCert, certs) + + tlsConfig.Certificates = clientCert + } + + mysql.RegisterTLSConfig(datasource.Name, &tlsConfig) + cnnstr += "&tls=" + datasource.Name + } + logger.Debug("getEngine", "connection", cnnstr) config := tsdb.SqlQueryEndpointConfiguration{ diff --git a/public/app/plugins/datasource/mysql/partials/config.html b/public/app/plugins/datasource/mysql/partials/config.html index a35633c626a..5f3ba5c1286 100644 --- a/public/app/plugins/datasource/mysql/partials/config.html +++ b/public/app/plugins/datasource/mysql/partials/config.html @@ -1,4 +1,3 @@ -

MySQL Connection

@@ -22,6 +21,72 @@
+ +
+
+ + +
+
+ +
+
+ +
+
+
TLS Auth Details
+ TLS Certs are encrypted and stored in the Grafana database. +
+
+
+
+ +
+
+ +
+ +
+ + reset +
+
+
+ +
+
+
+ +
+
+ +
+
+ + reset +
+
+ +
+
+ +
+
+ +
+
+ + reset +
+
+
+
Connection limits @@ -84,4 +149,3 @@

- From f31fe495e977cd9fe1c585e25221a423ff9a7c71 Mon Sep 17 00:00:00 2001 From: bugficks Date: Tue, 15 Jan 2019 13:54:25 +0100 Subject: [PATCH 002/119] fix go fmt --- pkg/tsdb/mysql/mysql.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index e713b87e265..82e7cac27f0 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -2,14 +2,14 @@ package mysql import ( "database/sql" + "errors" "fmt" "reflect" "strconv" "strings" - "errors" - "crypto/x509" "crypto/tls" + "crypto/x509" "github.com/go-sql-driver/mysql" "github.com/go-xorm/core" @@ -47,7 +47,7 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin if tlsAuth || tlsAuthWithCACert { secureJsonData := datasource.SecureJsonData.Decrypt() - tlsConfig := tls.Config { + tlsConfig := tls.Config{ InsecureSkipVerify: tlsSkipVerify, } From 7df5e3cebf06b39c0007bca76c9e86254fc7bc5a Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 28 Jan 2019 19:37:19 +0100 Subject: [PATCH 003/119] extract tls auth settings directive from datasource http settings directive --- public/app/features/all.ts | 1 + .../datasources/partials/http_settings.html | 52 +------------- .../partials/tls_auth_settings.html | 62 +++++++++++++++++ .../settings/TlsAuthSettingsCtrl.ts | 10 +++ .../datasource/mysql/partials/config.html | 68 +++---------------- 5 files changed, 84 insertions(+), 109 deletions(-) create mode 100644 public/app/features/datasources/partials/tls_auth_settings.html create mode 100644 public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts diff --git a/public/app/features/all.ts b/public/app/features/all.ts index 83146596ea0..d5e684e4a4e 100644 --- a/public/app/features/all.ts +++ b/public/app/features/all.ts @@ -12,3 +12,4 @@ import './manage-dashboards'; import './teams/CreateTeamCtrl'; import './profile/all'; import './datasources/settings/HttpSettingsCtrl'; +import './datasources/settings/TlsAuthSettingsCtrl'; diff --git a/public/app/features/datasources/partials/http_settings.html b/public/app/features/datasources/partials/http_settings.html index 521e2d3cdc6..b6f2c4fc0dd 100644 --- a/public/app/features/datasources/partials/http_settings.html +++ b/public/app/features/datasources/partials/http_settings.html @@ -101,53 +101,5 @@ -
-
-
TLS Auth Details
- TLS Certs are encrypted and stored in the Grafana database. -
-
-
-
- -
-
- -
- -
- - reset -
-
-
- -
-
-
- -
-
- -
-
- - reset -
-
- -
-
- -
-
- -
-
- - reset -
-
-
-
- + + \ No newline at end of file diff --git a/public/app/features/datasources/partials/tls_auth_settings.html b/public/app/features/datasources/partials/tls_auth_settings.html new file mode 100644 index 00000000000..c852e8ec70c --- /dev/null +++ b/public/app/features/datasources/partials/tls_auth_settings.html @@ -0,0 +1,62 @@ +
+
+
TLS Auth Details
+ TLS Certs are encrypted and stored in the Grafana database. +
+
+
+
+
+ +
+ +
+ + reset +
+
+
+ +
+
+
+
+ +
+
+ + reset +
+
+ +
+
+
+ +
+
+ + reset +
+
+
+
diff --git a/public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts b/public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts new file mode 100644 index 00000000000..7c21fab404c --- /dev/null +++ b/public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts @@ -0,0 +1,10 @@ +import { coreModule } from 'app/core/core'; + +coreModule.directive('datasourceTlsAuthSettings', () => { + return { + scope: { + current: '=', + }, + templateUrl: 'public/app/features/datasources/partials/tls_auth_settings.html', + }; +}); diff --git a/public/app/plugins/datasource/mysql/partials/config.html b/public/app/plugins/datasource/mysql/partials/config.html index 5f3ba5c1286..8221a06e1ee 100644 --- a/public/app/plugins/datasource/mysql/partials/config.html +++ b/public/app/plugins/datasource/mysql/partials/config.html @@ -24,70 +24,20 @@
- - + +
- +
-
-
-
TLS Auth Details
- TLS Certs are encrypted and stored in the Grafana database. -
-
-
-
- -
-
- -
- -
- - reset -
-
-
- -
-
-
- -
-
- -
-
- - reset -
-
- -
-
- -
-
- -
-
- - reset -
-
-
-
- + + Connection limits From f157c19e16cdc970542867ec77eb5a61fe5f11ad Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 28 Jan 2019 19:38:56 +0100 Subject: [PATCH 004/119] extract parsing of datasource tls config to method --- pkg/models/datasource_cache.go | 48 +++++++++++++++++++++------------- pkg/tsdb/mysql/mysql.go | 43 ++++-------------------------- 2 files changed, 35 insertions(+), 56 deletions(-) diff --git a/pkg/models/datasource_cache.go b/pkg/models/datasource_cache.go index 66ba66e4d39..1c895514ace 100644 --- a/pkg/models/datasource_cache.go +++ b/pkg/models/datasource_cache.go @@ -46,19 +46,16 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { return t.Transport, nil } - var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool - if ds.JsonData != nil { - tlsClientAuth = ds.JsonData.Get("tlsAuth").MustBool(false) - tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false) - tlsSkipVerify = ds.JsonData.Get("tlsSkipVerify").MustBool(false) + tlsConfig, err := ds.GetTLSConfig() + if err != nil { + return nil, err } + tlsConfig.Renegotiation = tls.RenegotiateFreelyAsClient + transport := &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: tlsSkipVerify, - Renegotiation: tls.RenegotiateFreelyAsClient, - }, - Proxy: http.ProxyFromEnvironment, + TLSClientConfig: tlsConfig, + Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, @@ -70,6 +67,26 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { IdleConnTimeout: 90 * time.Second, } + ptc.cache[ds.Id] = cachedTransport{ + Transport: transport, + updated: ds.Updated, + } + + return transport, nil +} + +func (ds *DataSource) GetTLSConfig() (*tls.Config, error) { + var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool + if ds.JsonData != nil { + tlsClientAuth = ds.JsonData.Get("tlsAuth").MustBool(false) + tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false) + tlsSkipVerify = ds.JsonData.Get("tlsSkipVerify").MustBool(false) + } + + tlsConfig := &tls.Config{ + InsecureSkipVerify: tlsSkipVerify, + } + if tlsClientAuth || tlsAuthWithCACert { decrypted := ds.SecureJsonData.Decrypt() if tlsAuthWithCACert && len(decrypted["tlsCACert"]) > 0 { @@ -78,7 +95,7 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { if !ok { return nil, errors.New("Failed to parse TLS CA PEM certificate") } - transport.TLSClientConfig.RootCAs = caPool + tlsConfig.RootCAs = caPool } if tlsClientAuth { @@ -86,14 +103,9 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { if err != nil { return nil, err } - transport.TLSClientConfig.Certificates = []tls.Certificate{cert} + tlsConfig.Certificates = []tls.Certificate{cert} } } - ptc.cache[ds.Id] = cachedTransport{ - Transport: transport, - updated: ds.Updated, - } - - return transport, nil + return tlsConfig, nil } diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index 82e7cac27f0..d451150f1de 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -2,15 +2,11 @@ package mysql import ( "database/sql" - "errors" "fmt" "reflect" "strconv" "strings" - "crypto/tls" - "crypto/x509" - "github.com/go-sql-driver/mysql" "github.com/go-xorm/core" "github.com/grafana/grafana/pkg/log" @@ -37,42 +33,13 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin datasource.Database, ) - var tlsSkipVerify, tlsAuth, tlsAuthWithCACert bool - if datasource.JsonData != nil { - tlsAuth = datasource.JsonData.Get("tlsAuth").MustBool(false) - tlsAuthWithCACert = datasource.JsonData.Get("tlsAuthWithCACert").MustBool(false) - tlsSkipVerify = datasource.JsonData.Get("tlsSkipVerify").MustBool(false) + tlsConfig, err := datasource.GetTLSConfig() + if err != nil { + return nil, err } - if tlsAuth || tlsAuthWithCACert { - - secureJsonData := datasource.SecureJsonData.Decrypt() - tlsConfig := tls.Config{ - InsecureSkipVerify: tlsSkipVerify, - } - - if tlsAuthWithCACert && len(secureJsonData["tlsCACert"]) > 0 { - - caPool := x509.NewCertPool() - if ok := caPool.AppendCertsFromPEM([]byte(secureJsonData["tlsCACert"])); !ok { - return nil, errors.New("Failed to parse TLS CA PEM certificate") - } - - tlsConfig.RootCAs = caPool - } - - if tlsAuth { - certs, err := tls.X509KeyPair([]byte(secureJsonData["tlsClientCert"]), []byte(secureJsonData["tlsClientKey"])) - if err != nil { - return nil, err - } - clientCert := make([]tls.Certificate, 0, 1) - clientCert = append(clientCert, certs) - - tlsConfig.Certificates = clientCert - } - - mysql.RegisterTLSConfig(datasource.Name, &tlsConfig) + if tlsConfig.RootCAs != nil || len(tlsConfig.Certificates) > 0 { + mysql.RegisterTLSConfig(datasource.Name, tlsConfig) cnnstr += "&tls=" + datasource.Name } From ce2209585c1e1e885e1f02c29d004e8abde61c84 Mon Sep 17 00:00:00 2001 From: corpglory-dev Date: Fri, 1 Feb 2019 14:32:40 +0300 Subject: [PATCH 005/119] Remove version.ts --- .../config_ctrl.ts | 2 +- .../version.test.ts | 53 ------------------- .../version.ts | 34 ------------ 3 files changed, 1 insertion(+), 88 deletions(-) delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/version.test.ts delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/version.ts diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/config_ctrl.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/config_ctrl.ts index 98fe5a87a56..4ee5c94fad6 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/config_ctrl.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/config_ctrl.ts @@ -1,6 +1,6 @@ import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource'; import config from 'app/core/config'; -import { isVersionGtOrEq } from './version'; +import { isVersionGtOrEq } from 'app/core/utils/version'; export class AzureMonitorConfigCtrl { static templateUrl = 'public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/config.html'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.test.ts deleted file mode 100644 index 17a6ce9bb0b..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { SemVersion, isVersionGtOrEq } from './version'; - -describe('SemVersion', () => { - let version = '1.0.0-alpha.1'; - - describe('parsing', () => { - it('should parse version properly', () => { - const semver = new SemVersion(version); - expect(semver.major).toBe(1); - expect(semver.minor).toBe(0); - expect(semver.patch).toBe(0); - expect(semver.meta).toBe('alpha.1'); - }); - }); - - describe('comparing', () => { - beforeEach(() => { - version = '3.4.5'; - }); - - it('should detect greater version properly', () => { - const semver = new SemVersion(version); - const cases = [ - { value: '3.4.5', expected: true }, - { value: '3.4.4', expected: true }, - { value: '3.4.6', expected: false }, - { value: '4', expected: false }, - { value: '3.5', expected: false }, - ]; - cases.forEach(testCase => { - expect(semver.isGtOrEq(testCase.value)).toBe(testCase.expected); - }); - }); - }); - - describe('isVersionGtOrEq', () => { - it('should compare versions properly (a >= b)', () => { - const cases = [ - { values: ['3.4.5', '3.4.5'], expected: true }, - { values: ['3.4.5', '3.4.4'], expected: true }, - { values: ['3.4.5', '3.4.6'], expected: false }, - { values: ['3.4', '3.4.0'], expected: true }, - { values: ['3', '3.0.0'], expected: true }, - { values: ['3.1.1-beta1', '3.1'], expected: true }, - { values: ['3.4.5', '4'], expected: false }, - { values: ['3.4.5', '3.5'], expected: false }, - ]; - cases.forEach(testCase => { - expect(isVersionGtOrEq(testCase.values[0], testCase.values[1])).toBe(testCase.expected); - }); - }); - }); -}); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.ts deleted file mode 100644 index 1131e1d2ab8..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.ts +++ /dev/null @@ -1,34 +0,0 @@ -import _ from 'lodash'; - -const versionPattern = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z\.]+))?/; - -export class SemVersion { - major: number; - minor: number; - patch: number; - meta: string; - - constructor(version: string) { - const match = versionPattern.exec(version); - if (match) { - this.major = Number(match[1]); - this.minor = Number(match[2] || 0); - this.patch = Number(match[3] || 0); - this.meta = match[4]; - } - } - - isGtOrEq(version: string): boolean { - const compared = new SemVersion(version); - return !(this.major < compared.major || this.minor < compared.minor || this.patch < compared.patch); - } - - isValid(): boolean { - return _.isNumber(this.major); - } -} - -export function isVersionGtOrEq(a: string, b: string): boolean { - const aSemver = new SemVersion(a); - return aSemver.isGtOrEq(b); -} From cf60ae79c31d6c0745b47e77e62e2d0605a19b73 Mon Sep 17 00:00:00 2001 From: corpglory-dev Date: Fri, 1 Feb 2019 14:47:17 +0300 Subject: [PATCH 006/119] Move prism to app/features/explore --- .../editor => features/explore}/slate-plugins/prism/index.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename public/app/{plugins/datasource/grafana-azure-monitor-datasource/editor => features/explore}/slate-plugins/prism/index.tsx (100%) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/prism/index.tsx b/public/app/features/explore/slate-plugins/prism/index.tsx similarity index 100% rename from public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/prism/index.tsx rename to public/app/features/explore/slate-plugins/prism/index.tsx From bdd59de877f677d8831f64d438a146408097faed Mon Sep 17 00:00:00 2001 From: corpglory-dev Date: Fri, 1 Feb 2019 14:47:33 +0300 Subject: [PATCH 007/119] Remove newline && runner plugins --- .../editor/slate-plugins/newline.ts | 35 ------------------- .../editor/slate-plugins/runner.ts | 14 -------- 2 files changed, 49 deletions(-) delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/newline.ts delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/runner.ts diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/newline.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/newline.ts deleted file mode 100644 index d484d93a542..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/newline.ts +++ /dev/null @@ -1,35 +0,0 @@ -function getIndent(text) { - let offset = text.length - text.trimLeft().length; - if (offset) { - let indent = text[0]; - while (--offset) { - indent += text[0]; - } - return indent; - } - return ''; -} - -export default function NewlinePlugin() { - return { - onKeyDown(event, change) { - const { value } = change; - if (!value.isCollapsed) { - return undefined; - } - - if (event.key === 'Enter' && !event.shiftKey) { - event.preventDefault(); - - const { startBlock } = value; - const currentLineText = startBlock.text; - const indent = getIndent(currentLineText); - - return change - .splitBlock() - .insertText(indent) - .focus(); - } - }, - }; -} diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/runner.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/runner.ts deleted file mode 100644 index 068bd9f0ad1..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/runner.ts +++ /dev/null @@ -1,14 +0,0 @@ -export default function RunnerPlugin({ handler }) { - return { - onKeyDown(event) { - // Handle enter - if (handler && event.key === 'Enter' && event.shiftKey) { - // Submit on Enter - event.preventDefault(); - handler(event); - return true; - } - return undefined; - }, - }; -} From 9a3f4def98fcb89855ec278c924f1897d1b7357e Mon Sep 17 00:00:00 2001 From: corpglory-dev Date: Fri, 1 Feb 2019 14:49:04 +0300 Subject: [PATCH 008/119] Use slate-plugins from app/features/explore --- .../editor/query_field.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx index 1c883a40c31..f93912f069e 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx @@ -1,12 +1,9 @@ -import PluginPrism from './slate-plugins/prism'; -// import PluginPrism from 'slate-prism'; -// import Prism from 'prismjs'; +import PluginPrism from 'app/features/explore/slate-plugins/prism'; import BracesPlugin from 'app/features/explore/slate-plugins/braces'; import ClearPlugin from 'app/features/explore/slate-plugins/clear'; -// Custom plugins (new line on Enter and run on Shift+Enter) -import NewlinePlugin from './slate-plugins/newline'; -import RunnerPlugin from './slate-plugins/runner'; +import NewlinePlugin from 'app/features/explore/slate-plugins/newline'; +import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; import Typeahead from './typeahead'; From 6d03766acecab8914d344a929708a22470838a43 Mon Sep 17 00:00:00 2001 From: corpglory-dev Date: Fri, 1 Feb 2019 14:54:38 +0300 Subject: [PATCH 009/119] Remove extra newline --- .../grafana-azure-monitor-datasource/editor/query_field.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx index f93912f069e..400126f7e55 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx @@ -1,5 +1,4 @@ import PluginPrism from 'app/features/explore/slate-plugins/prism'; - import BracesPlugin from 'app/features/explore/slate-plugins/braces'; import ClearPlugin from 'app/features/explore/slate-plugins/clear'; import NewlinePlugin from 'app/features/explore/slate-plugins/newline'; From 1f3fafb198fc47e143e81afaa5bf497e1059f401 Mon Sep 17 00:00:00 2001 From: Paresh Date: Sun, 3 Feb 2019 13:07:33 -0600 Subject: [PATCH 010/119] mssql: pass timerange for template variable queries --- .../app/plugins/datasource/mssql/datasource.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/mssql/datasource.ts b/public/app/plugins/datasource/mssql/datasource.ts index 23aa5504d3e..1ede9cc3d1e 100644 --- a/public/app/plugins/datasource/mssql/datasource.ts +++ b/public/app/plugins/datasource/mssql/datasource.ts @@ -107,13 +107,24 @@ export class MssqlDatasource { format: 'table', }; + const data = { + queries: [interpolatedQuery], + }; + + if (optionalOptions && optionalOptions.range) { + if (optionalOptions.range.from) { + data['from'] = optionalOptions.range.from.valueOf().toString(); + } + if (optionalOptions.range.to) { + data['to'] = optionalOptions.range.to.valueOf().toString(); + } + } + return this.backendSrv .datasourceRequest({ url: '/api/tsdb/query', method: 'POST', - data: { - queries: [interpolatedQuery], - }, + data: data, }) .then(data => this.responseParser.parseMetricFindQueryResult(refId, data)); } From f2d2712a9547ac8bcd6ef8b69f85d41d2d19dd51 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 30 Jan 2019 14:21:51 +0300 Subject: [PATCH 011/119] azuremonitor: add more builtin functions and operators --- .../editor/KustoQueryField.tsx | 26 +- .../editor/editor_component.tsx | 2 +- .../editor/kusto.ts | 114 ------ .../editor/kusto/kusto.ts | 355 ++++++++++++++++++ 4 files changed, 379 insertions(+), 118 deletions(-) delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto.ts create mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx index 849cf62efe0..fa79d4bdb99 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx @@ -6,7 +6,7 @@ import QueryField from './query_field'; import debounce from 'app/features/explore/utils/debounce'; import { getNextCharacter } from 'app/features/explore/utils/dom'; -import { FUNCTIONS, KEYWORDS } from './kusto'; +import { KEYWORDS, functionTokens, operatorTokens, grafanaMacros } from './kusto/kusto'; // import '../sass/editor.base.scss'; @@ -260,10 +260,20 @@ export default class KustoQueryField extends QueryField { label: 'Keywords', items: KEYWORDS.map(wrapText) }, + { + prefixMatch: true, + label: 'Operators', + items: operatorTokens.map((s: any) => { s.type = 'function'; return s; }) + }, { prefixMatch: true, label: 'Functions', - items: FUNCTIONS.map((s: any) => { s.type = 'function'; return s; }) + items: functionTokens.map((s: any) => { s.type = 'function'; return s; }) + }, + { + prefixMatch: true, + label: 'Macros', + items: grafanaMacros.map((s: any) => { s.type = 'function'; return s; }) } ]; } @@ -276,10 +286,20 @@ export default class KustoQueryField extends QueryField { label: 'Keywords', items: KEYWORDS.map(wrapText) }, + { + prefixMatch: true, + label: 'Operators', + items: operatorTokens.map((s: any) => { s.type = 'function'; return s; }) + }, { prefixMatch: true, label: 'Functions', - items: FUNCTIONS.map((s: any) => { s.type = 'function'; return s; }) + items: functionTokens.map((s: any) => { s.type = 'function'; return s; }) + }, + { + prefixMatch: true, + label: 'Macros', + items: grafanaMacros.map((s: any) => { s.type = 'function'; return s; }) } ]; } diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx index da7db58567f..59e4ab12c81 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx @@ -1,5 +1,5 @@ import KustoQueryField from './KustoQueryField'; -import Kusto from './kusto'; +import Kusto from './kusto/kusto'; import React, { Component } from 'react'; import coreModule from 'app/core/core_module'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto.ts deleted file mode 100644 index 647ebb8024a..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto.ts +++ /dev/null @@ -1,114 +0,0 @@ -export const FUNCTIONS = [ - { text: 'countof', display: 'countof()', hint: '' }, - { text: 'bin', display: 'bin()', hint: '' }, - { text: 'extentid', display: 'extentid()', hint: '' }, - { text: 'extract', display: 'extract()', hint: '' }, - { text: 'extractjson', display: 'extractjson()', hint: '' }, - { text: 'floor', display: 'floor()', hint: '' }, - { text: 'iif', display: 'iif()', hint: '' }, - { text: 'isnull', display: 'isnull()', hint: '' }, - { text: 'isnotnull', display: 'isnotnull()', hint: '' }, - { text: 'notnull', display: 'notnull()', hint: '' }, - { text: 'isempty', display: 'isempty()', hint: '' }, - { text: 'isnotempty', display: 'isnotempty()', hint: '' }, - { text: 'notempty', display: 'notempty()', hint: '' }, - { text: 'now', display: 'now()', hint: '' }, - { text: 're2', display: 're2()', hint: '' }, - { text: 'strcat', display: 'strcat()', hint: '' }, - { text: 'strlen', display: 'strlen()', hint: '' }, - { text: 'toupper', display: 'toupper()', hint: '' }, - { text: 'tostring', display: 'tostring()', hint: '' }, - { text: 'count', display: 'count()', hint: '' }, - { text: 'cnt', display: 'cnt()', hint: '' }, - { text: 'sum', display: 'sum()', hint: '' }, - { text: 'min', display: 'min()', hint: '' }, - { text: 'max', display: 'max()', hint: '' }, - { text: 'avg', display: 'avg()', hint: '' }, - { - text: '$__timeFilter', - display: '$__timeFilter()', - hint: 'Macro that uses the selected timerange in Grafana to filter the query.', - }, - { - text: '$__escapeMulti', - display: '$__escapeMulti()', - hint: 'Macro to escape multi-value template variables that contain illegal characters.', - }, - { text: '$__contains', display: '$__contains()', hint: 'Macro for multi-value template variables.' }, -]; - -export const KEYWORDS = [ - 'by', - 'on', - 'contains', - 'notcontains', - 'containscs', - 'notcontainscs', - 'startswith', - 'has', - 'matches', - 'regex', - 'true', - 'false', - 'and', - 'or', - 'typeof', - 'int', - 'string', - 'date', - 'datetime', - 'time', - 'long', - 'real', - '​boolean', - 'bool', - // add some more keywords - 'where', - 'order', -]; - -// Kusto operators -// export const OPERATORS = ['+', '-', '*', '/', '>', '<', '==', '<>', '<=', '>=', '~', '!~']; - -export const DURATION = ['SECONDS', 'MINUTES', 'HOURS', 'DAYS', 'WEEKS', 'MONTHS', 'YEARS']; - -const tokenizer = { - comment: { - pattern: /(^|[^\\:])\/\/.*/, - lookbehind: true, - greedy: true, - }, - 'function-context': { - pattern: /[a-z0-9_]+\([^)]*\)?/i, - inside: {}, - }, - duration: { - pattern: new RegExp(`${DURATION.join('?|')}?`, 'i'), - alias: 'number', - }, - builtin: new RegExp(`\\b(?:${FUNCTIONS.map(f => f.text).join('|')})(?=\\s*\\()`, 'i'), - string: { - pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, - greedy: true, - }, - keyword: new RegExp(`\\b(?:${KEYWORDS.join('|')}|\\*)\\b`, 'i'), - boolean: /\b(?:true|false)\b/, - number: /\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i, - operator: /-|\+|\*|\/|>|<|==|<=?|>=?|<>|!~|~|=|\|/, - punctuation: /[{};(),.:]/, - variable: /(\[\[(.+?)\]\])|(\$(.+?))\b/, -}; - -tokenizer['function-context'].inside = { - argument: { - pattern: /[a-z0-9_]+(?=:)/i, - alias: 'symbol', - }, - duration: tokenizer.duration, - number: tokenizer.number, - builtin: tokenizer.builtin, - string: tokenizer.string, - variable: tokenizer.variable, -}; - -export default tokenizer; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts new file mode 100644 index 00000000000..e2a1142597b --- /dev/null +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/kusto/kusto.ts @@ -0,0 +1,355 @@ +/* tslint:disable:max-line-length */ +export const operatorTokens = [ + { text: "!between", hint: "Matches the input that is outside the inclusive range." }, + { text: "as", hint: "Binds a name to the operator's input tabular expression." }, + { text: "between", hint: "Matches the input that is inside the inclusive range." }, + { text: "consume", hint: "The `consume` operator consumes the tabular data stream handed to it. It is\r\nmostly used for triggering the query side-effect without actually returning\r\nthe results back to the caller." }, + { text: "count", hint: "Returns the number of records in the input record set." }, + { text: "datatable", hint: "Returns a table whose schema and values are defined in the query itself." }, + { text: "distinct", hint: "Produces a table with the distinct combination of the provided columns of the input table." }, + { text: "evaluate", hint: "Invokes a service-side query extension (plugin)." }, + { text: "extend", hint: "Create calculated columns and append them to the result set." }, + { text: "externaldata", hint: "Returns a table whose schema is defined in the query itself, and whose data is read from an external raw file." }, + { text: "facet", hint: "Returns a set of tables, one for each specified column.\r\nEach table specifies the list of values taken by its column.\r\nAn additional table can be created by using the `with` clause." }, + { text: "find", hint: "Finds rows that match a predicate across a set of tables." }, + { text: "fork", hint: "Runs multiple consumer operators in parallel." }, + { text: "getschema", hint: "Produce a table that represents a tabular schema of the input." }, + { text: "in", hint: "Filters a recordset based on the provided set of values." }, + { text: "invoke", hint: "Invokes lambda that receives the source of `invoke` as tabular parameter argument." }, + { text: "join", hint: "Merge the rows of two tables to form a new table by matching values of the specified column(s) from each table." }, + { text: "limit", hint: "Return up to the specified number of rows." }, + { text: "make-series", hint: "Create series of specified aggregated values along specified axis." }, + { text: "mvexpand", hint: "Expands multi-value array or property bag." }, + { text: "order", hint: "Sort the rows of the input table into order by one or more columns." }, + { text: "parse", hint: "Evaluates a string expression and parses its value into one or more calculated columns." }, + { text: "print", hint: "Evaluates one or more scalar expressions and inserts the results (as a single-row table with as many columns as there are expressions) into the output." }, + { text: "project", hint: "Select the columns to include, rename or drop, and insert new computed columns." }, + { text: "project-away", hint: "Select what columns to exclude from the input." }, + { text: "project-rename", hint: "Renames columns in the result output." }, + { text: "range", hint: "Generates a single-column table of values." }, + { text: "reduce", hint: "Groups a set of strings together based on values similarity." }, + { text: "render", hint: "Instructs the user agent to render the results of the query in a particular way." }, + { text: "sample", hint: "Returns up to the specified number of random rows from the input table." }, + { text: "sample-distinct", hint: "Returns a single column that contains up to the specified number of distinct values of the requested column." }, + { text: "search", hint: "The search operator provides a multi-table/multi-column search experience." }, + { text: "serialize", hint: "Marks that order of the input row set is safe for window functions usage." }, + { text: "sort", hint: "Sort the rows of the input table into order by one or more columns." }, + { text: "summarize", hint: "Produces a table that aggregates the content of the input table." }, + { text: "take", hint: "Return up to the specified number of rows." }, + { text: "top", hint: "Returns the first *N* records sorted by the specified columns." }, + { text: "top-hitters", hint: "Returns an approximation of the first *N* results (assuming skewed distribution of the input)." }, + { text: "top-nested", hint: "Produces hierarchical top results, where each level is a drill-down based on previous level values." }, + { text: "union", hint: "Takes two or more tables and returns the rows of all of them." }, + { text: "where", hint: "Filters a table to the subset of rows that satisfy a predicate." }, +]; + +export const functionTokens = [ + { text: "abs", hint: "Calculates the absolute value of the input." }, + { text: "acos", hint: "Returns the angle whose cosine is the specified number (the inverse operation of [`cos()`](cosfunction.md)) ." }, + { text: "ago", hint: "Subtracts the given timespan from the current UTC clock time." }, + { text: "any", hint: "Returns random non-empty value from the specified expression values." }, + { text: "arg_max", hint: "Finds a row in the group that maximizes *ExprToMaximize*, and returns the value of *ExprToReturn* (or `*` to return the entire row)." }, + { text: "arg_min", hint: "Finds a row in the group that minimizes *ExprToMinimize*, and returns the value of *ExprToReturn* (or `*` to return the entire row)." }, + { text: "argmax", hint: "Finds a row in the group that maximizes *ExprToMaximize*, and returns the value of *ExprToReturn* (or `*` to return the entire row)." }, + { text: "argmin", hint: "Finds a row in the group that minimizes *ExprToMinimize*, and returns the value of *ExprToReturn* (or `*` to return the entire row)." }, + { text: "array_concat", hint: "Concatenates a number of dynamic arrays to a single array." }, + { text: "array_length", hint: "Calculates the number of elements in a dynamic array." }, + { text: "array_slice", hint: "Extracts a slice of a dynamic array." }, + { text: "array_split", hint: "Splits an array to multiple arrays according to the split indices and packs the generated array in a dynamic array." }, + { text: "asin", hint: "Returns the angle whose sine is the specified number (the inverse operation of [`sin()`](sinfunction.md)) ." }, + { text: "assert", hint: "Checks for a condition; if the condition is false, outputs error messages and fails the query." }, + { text: "atan", hint: "Returns the angle whose tangent is the specified number (the inverse operation of [`tan()`](tanfunction.md)) ." }, + { text: "atan2", hint: "Calculates the angle, in radians, between the positive x-axis and the ray from the origin to the point (y, x)." }, + { text: "avg", hint: "Calculates the average of *Expr* across the group." }, + { text: "avgif", hint: "Calculates the [average](avg-aggfunction.md) of *Expr* across the group for which *Predicate* evaluates to `true`." }, + { text: "bag_keys", hint: "Enumerates all the root keys in a dynamic property-bag object." }, + { text: "base64_decodestring", hint: "Decodes a base64 string to a UTF-8 string" }, + { text: "base64_encodestring", hint: "Encodes a string as base64 string" }, + { text: "beta_cdf", hint: "Returns the standard cumulative beta distribution function." }, + { text: "beta_inv", hint: "Returns the inverse of the beta cumulative probability beta density function." }, + { text: "beta_pdf", hint: "Returns the probability density beta function." }, + { text: "bin", hint: "Rounds values down to an integer multiple of a given bin size." }, + { text: "bin_at", hint: "Rounds values down to a fixed-size \'bin\', with control over the bin's starting point.\r\n(See also [`bin function`](./binfunction.md).)" }, + { text: "bin_auto", hint: "Rounds values down to a fixed-size \'bin\', with control over the bin size and starting point provided by a query property." }, + { text: "binary_and", hint: "Returns a result of the bitwise `and` operation between two values." }, + { text: "binary_not", hint: "Returns a bitwise negation of the input value." }, + { text: "binary_or", hint: "Returns a result of the bitwise `or` operation of the two values." }, + { text: "binary_shift_left", hint: "Returns binary shift left operation on a pair of numbers." }, + { text: "binary_shift_right", hint: "Returns binary shift right operation on a pair of numbers." }, + { text: "binary_xor", hint: "Returns a result of the bitwise `xor` operation of the two values." }, + { text: "buildschema", hint: "Returns the minimal schema that admits all values of *DynamicExpr*." }, + { text: "case", hint: "Evaluates a list of predicates and returns the first result expression whose predicate is satisfied." }, + { text: "ceiling", hint: "Calculates the smallest integer greater than, or equal to, the specified numeric expression." }, + { text: "cluster", hint: "Changes the reference of the query to a remote cluster." }, + { text: "coalesce", hint: "Evaluates a list of expressions and returns the first non-null (or non-empty for string) expression." }, + { text: "cos", hint: "Returns the cosine function." }, + { text: "cot", hint: "Calculates the trigonometric cotangent of the specified angle, in radians." }, + { text: "count", hint: "Returns a count of the records per summarization group (or in total if summarization is done without grouping)." }, + { text: "countif", hint: "Returns a count of rows for which *Predicate* evaluates to `true`." }, + { text: "countof", hint: "Counts occurrences of a substring in a string. Plain string matches may overlap; regex matches do not." }, + { text: "current_principal", hint: "Returns the current principal running this query." }, + { text: "cursor_after", hint: "A predicate over the records of a table to compare their ingestion time\r\nagainst a database cursor." }, + { text: "cursor_before_or_at", hint: "A predicate over the records of a table to compare their ingestion time\r\nagainst a database cursor." }, + { text: "database", hint: "Changes the reference of the query to a specific database within the cluster scope." }, + { text: "datetime_add", hint: "Calculates a new [datetime](./scalar-data-types/datetime.md) from a specified datepart multiplied by a specified amount, added to a specified [datetime](./scalar-data-types/datetime.md)." }, + { text: "datetime_diff", hint: "Calculates calendarian difference between two [datetime](./scalar-data-types/datetime.md) values." }, + { text: "datetime_part", hint: "Extracts the requested date part as an integer value." }, + { text: "dayofmonth", hint: "Returns the integer number representing the day number of the given month" }, + { text: "dayofweek", hint: "Returns the integer number of days since the preceding Sunday, as a `timespan`." }, + { text: "dayofyear", hint: "Returns the integer number represents the day number of the given year." }, + { text: "dcount", hint: "Returns an estimate of the number of distinct values of *Expr* in the group." }, + { text: "dcount_hll", hint: "Calculates the dcount from hll results (which was generated by [hll](hll-aggfunction.md) or [hll_merge](hll-merge-aggfunction.md))." }, + { text: "dcountif", hint: "Returns an estimate of the number of distinct values of *Expr* of rows for which *Predicate* evaluates to `true`." }, + { text: "degrees", hint: "Converts angle value in radians into value in degrees, using formula `degrees = (180 / PI ) * angle_in_radians`" }, + { text: "distance", hint: "Returns the distance between two points in meters." }, + { text: "endofday", hint: "Returns the end of the day containing the date, shifted by an offset, if provided." }, + { text: "endofmonth", hint: "Returns the end of the month containing the date, shifted by an offset, if provided." }, + { text: "endofweek", hint: "Returns the end of the week containing the date, shifted by an offset, if provided." }, + { text: "endofyear", hint: "Returns the end of the year containing the date, shifted by an offset, if provided." }, + { text: "estimate_data_size", hint: "Returns an estimated data size of the selected columns of the tabular expression." }, + { text: "exp", hint: "The base-e exponential function of x, which is e raised to the power x: e^x." }, + { text: "exp10", hint: "The base-10 exponential function of x, which is 10 raised to the power x: 10^x. \r\n**Syntax**" }, + { text: "exp2", hint: "The base-2 exponential function of x, which is 2 raised to the power x: 2^x." }, + { text: "extent_id", hint: "Returns a unique identifier that identifies the data shard (\"extent\") that the current record resides in." }, + { text: "extent_tags", hint: "Returns a dynamic array with the [tags](../management/extents-overview.md#extent-tagging) of the data shard (\"extent\") that the current record resides in." }, + { text: "extract", hint: "Get a match for a [regular expression](./re2.md) from a text string." }, + { text: "extract_all", hint: "Get all matches for a [regular expression](./re2.md) from a text string." }, + { text: "extractjson", hint: "Get a specified element out of a JSON text using a path expression." }, + { text: "floor", hint: "An alias for [`bin()`](binfunction.md)." }, + { text: "format_datetime", hint: "Formats a datetime parameter based on the format pattern parameter." }, + { text: "format_timespan", hint: "Formats a timespan parameter based on the format pattern parameter." }, + { text: "gamma", hint: "Computes [gamma function](https://en.wikipedia.org/wiki/Gamma_function)" }, + { text: "getmonth", hint: "Get the month number (1-12) from a datetime." }, + { text: "gettype", hint: "Returns the runtime type of its single argument." }, + { text: "getyear", hint: "Returns the year part of the `datetime` argument." }, + { text: "hash", hint: "Returns a hash value for the input value." }, + { text: "hash_sha256", hint: "Returns a sha256 hash value for the input value." }, + { text: "hll", hint: "Calculates the Intermediate results of [dcount](dcount-aggfunction.md) across the group." }, + { text: "hll_merge", hint: "Merges hll results (scalar version of the aggregate version [`hll_merge()`](hll-merge-aggfunction.md))." }, + { text: "hourofday", hint: "Returns the integer number representing the hour number of the given date" }, + { text: "iff", hint: "Evaluates the first argument (the predicate), and returns the value of either the second or third arguments, depending on whether the predicate evaluated to `true` (second) or `false` (third)." }, + { text: "iif", hint: "Evaluates the first argument (the predicate), and returns the value of either the second or third arguments, depending on whether the predicate evaluated to `true` (second) or `false` (third)." }, + { text: "indexof", hint: "Function reports the zero-based index of the first occurrence of a specified string within input string." }, + { text: "ingestion_time", hint: "Retrieves the record's `$IngestionTime` hidden `datetime` column, or null." }, + { text: "iscolumnexists", hint: "Returns a boolean value indicating if the given string argument exists in the schema produced by the preceding tabular operator." }, + { text: "isempty", hint: "Returns `true` if the argument is an empty string or is null." }, + { text: "isfinite", hint: "Returns whether input is a finite value (is neither infinite nor NaN)." }, + { text: "isinf", hint: "Returns whether input is an infinite (positive or negative) value." }, + { text: "isnan", hint: "Returns whether input is Not-a-Number (NaN) value." }, + { text: "isnotempty", hint: "Returns `true` if the argument is not an empty string nor it is a null." }, + { text: "isnotnull", hint: "Returns `true` if the argument is not null." }, + { text: "isnull", hint: "Evaluates its sole argument and returns a `bool` value indicating if the argument evaluates to a null value." }, + { text: "log", hint: "Returns the natural logarithm function." }, + { text: "log10", hint: "Returns the common (base-10) logarithm function." }, + { text: "log2", hint: "Returns the base-2 logarithm function." }, + { text: "loggamma", hint: "Computes log of absolute value of the [gamma function](https://en.wikipedia.org/wiki/Gamma_function)" }, + { text: "make_datetime", hint: "Creates a [datetime](./scalar-data-types/datetime.md) scalar value from the specified date and time." }, + { text: "make_dictionary", hint: "Returns a `dynamic` (JSON) property-bag (dictionary) of all the values of *Expr* in the group." }, + { text: "make_string", hint: "Returns the string generated by the Unicode characters." }, + { text: "make_timespan", hint: "Creates a [timespan](./scalar-data-types/timespan.md) scalar value from the specified time period." }, + { text: "makelist", hint: "Returns a `dynamic` (JSON) array of all the values of *Expr* in the group." }, + { text: "makeset", hint: "Returns a `dynamic` (JSON) array of the set of distinct values that *Expr* takes in the group." }, + { text: "materialize", hint: "Allows caching a sub-query result during the time of query execution in a way that other subqueries can reference the partial result." }, + { text: "max", hint: "Returns the maximum value across the group." }, + { text: "max_of", hint: "Returns the maximum value of several evaluated numeric expressions." }, + { text: "merge_tdigests", hint: "Merges tdigest results (scalar version of the aggregate version [`merge_tdigests()`](merge-tdigests-aggfunction.md))." }, + { text: "min", hint: "Returns the minimum value agross the group." }, + { text: "min_of", hint: "Returns the minimum value of several evaluated numeric expressions." }, + { text: "monthofyear", hint: "Returns the integer number represents the month number of the given year." }, + { text: "next", hint: "Returns the value of a column in a row that it at some offset following the\r\ncurrent row in a [serialized row set](./windowsfunctions.md#serialized-row-set)." }, + { text: "not", hint: "Reverses the value of its `bool` argument." }, + { text: "now", hint: "Returns the current UTC clock time, optionally offset by a given timespan.\r\nThis function can be used multiple times in a statement and the clock time being referenced will be the same for all instances." }, + { text: "pack", hint: "Creates a `dynamic` object (property bag) from a list of names and values." }, + { text: "pack_all", hint: "Creates a `dynamic` object (property bag) from all the columns of the tabular expression." }, + { text: "pack_array", hint: "Packs all input values into a dynamic array." }, + { text: "parse_ipv4", hint: "Converts input to integer (signed 64-bit) number representation." }, + { text: "parse_json", hint: "Interprets a `string` as a [JSON value](https://json.org/)) and returns the value as [`dynamic`](./scalar-data-types/dynamic.md). \r\nIt is superior to using [extractjson() function](./extractjsonfunction.md)\r\nwhen you need to extract more than one element of a JSON compound object." }, + { text: "parse_path", hint: "Parses a file path `string` and returns a [`dynamic`](./scalar-data-types/dynamic.md) object that contains the following parts of the path: \r\nScheme, RootPath, DirectoryPath, DirectoryName, FileName, Extension, AlternateDataStreamName.\r\nIn addition to the simple paths with both types of slashes, supports paths with schemas (e.g. \"file://...\"), shared paths (e.g. \"\\\\shareddrive\\users...\"), long paths (e.g \"\\\\?\\C:...\"\"), alternate data streams (e.g. \"file1.exe:file2.exe\")" }, + { text: "parse_url", hint: "Parses an absolute URL `string` and returns a [`dynamic`](./scalar-data-types/dynamic.md) object contains all parts of the URL (Scheme, Host, Port, Path, Username, Password, Query Parameters, Fragment)." }, + { text: "parse_urlquery", hint: "Parses a url query `string` and returns a [`dynamic`](./scalar-data-types/dynamic.md) object contains the Query parameters." }, + { text: "parse_user_agent", hint: "Interprets a user-agent string, which identifies the user's browser and provides certain system details to servers hosting the websites the user visits. The result is returned as [`dynamic`](./scalar-data-types/dynamic.md)." }, + { text: "parse_version", hint: "Converts input string representation of version to a comparable decimal number." }, + { text: "parse_xml", hint: "Interprets a `string` as a XML value, converts the value to a [JSON value](https://json.org/) and returns the value as [`dynamic`](./scalar-data-types/dynamic.md)." }, + { text: "percentile", hint: "Returns an estimate for the specified [nearest-rank percentile](#nearest-rank-percentile) of the population defined by *Expr*. \r\nThe accuracy depends on the density of population in the region of the percentile." }, + { text: "percentile_tdigest", hint: "Calculates the percentile result from tdigest results (which was generated by [tdigest](tdigest-aggfunction.md) or [merge-tdigests](merge-tdigests-aggfunction.md))" }, + { text: "percentrank_tdigest", hint: "Calculates the approximate rank of the value in a set where rank is expressed as percentage of set's size. \r\nThis function can be viewed as the inverse of the percentile." }, + { text: "pi", hint: "Returns the constant value of Pi (π)." }, + { text: "point", hint: "Returns a dynamic array representation of a point." }, + { text: "pow", hint: "Returns a result of raising to power" }, + { text: "prev", hint: "Returns the value of a column in a row that it at some offset prior to the\r\ncurrent row in a [serialized row set](./windowsfunctions.md#serialized-row-set)." }, + { text: "radians", hint: "Converts angle value in degrees into value in radians, using formula `radians = (PI / 180 ) * angle_in_degrees`" }, + { text: "rand", hint: "Returns a random number." }, + { text: "range", hint: "Generates a dynamic array holding a series of equally-spaced values." }, + { text: "repeat", hint: "Generates a dynamic array holding a series of equal values." }, + { text: "replace", hint: "Replace all regex matches with another string." }, + { text: "reverse", hint: "Function makes reverse of input string." }, + { text: "round", hint: "Returns the rounded source to the specified precision." }, + { text: "row_cumsum", hint: "Calculates the cumulative sum of a column in a [serialized row set](./windowsfunctions.md#serialized-row-set)." }, + { text: "row_number", hint: "Returns the current row's index in a [serialized row set](./windowsfunctions.md#serialized-row-set).\r\nThe row index starts by default at `1` for the first row, and is incremented by `1` for each additional row.\r\nOptionally, the row index can start at a different value than `1`.\r\nAdditionally, the row index may be reset according to some provided predicate." }, + { text: "series_add", hint: "Calculates the element-wise addition of two numeric series inputs." }, + { text: "series_decompose", hint: "Applies a decomposition transformation on a series." }, + { text: "series_decompose_anomalies", hint: "Anomaly Detection based on series decomposition (refer to [series_decompose()](series-decomposefunction.md))" }, + { text: "series_decompose_forecast", hint: "Forecast based on series decomposition." }, + { text: "series_divide", hint: "Calculates the element-wise division of two numeric series inputs." }, + { text: "series_equals", hint: "Calculates the element-wise equals (`==`) logic operation of two numeric series inputs." }, + { text: "series_fill_backward", hint: "Performs backward fill interpolation of missing values in a series." }, + { text: "series_fill_const", hint: "Replaces missing values in a series with a specified constant value." }, + { text: "series_fill_forward", hint: "Performs forward fill interpolation of missing values in a series." }, + { text: "series_fill_linear", hint: "Performs linear interpolation of missing values in a series." }, + { text: "series_fir", hint: "Applies a Finite Impulse Response filter on a series." }, + { text: "series_fit_2lines", hint: "Applies two segments linear regression on a series, returning multiple columns." }, + { text: "series_fit_2lines_dynamic", hint: "Applies two segments linear regression on a series, returning dynamic object." }, + { text: "series_fit_line", hint: "Applies linear regression on a series, returning multiple columns." }, + { text: "series_fit_line_dynamic", hint: "Applies linear regression on a series, returning dynamic object." }, + { text: "series_greater", hint: "Calculates the element-wise greater (`>`) logic operation of two numeric series inputs." }, + { text: "series_greater_equals", hint: "Calculates the element-wise greater or equals (`>=`) logic operation of two numeric series inputs." }, + { text: "series_iir", hint: "Applies a Infinite Impulse Response filter on a series." }, + { text: "series_less", hint: "Calculates the element-wise less (`<`) logic operation of two numeric series inputs." }, + { text: "series_less_equals", hint: "Calculates the element-wise less or equal (`<=`) logic operation of two numeric series inputs." }, + { text: "series_multiply", hint: "Calculates the element-wise multiplication of two numeric series inputs." }, + { text: "series_not_equals", hint: "Calculates the element-wise not equals (`!=`) logic operation of two numeric series inputs." }, + { text: "series_outliers", hint: "Scores anomaly points in a series." }, + { text: "series_periods_detect", hint: "Finds the most significant periods that exist in a time series." }, + { text: "series_periods_validate", hint: "Checks whether a time series contains periodic patterns of given lengths." }, + { text: "series_seasonal", hint: "Calculates the seasonal component of a series according to the detected or given seasonal period." }, + { text: "series_stats", hint: "Returns statistics for a series in multiple columns." }, + { text: "series_stats_dynamic", hint: "Returns statistics for a series in dynamic object." }, + { text: "series_subtract", hint: "Calculates the element-wise subtraction of two numeric series inputs." }, + { text: "sign", hint: "Sign of a numeric expression" }, + { text: "sin", hint: "Returns the sine function." }, + { text: "split", hint: "Splits a given string according to a given delimiter and returns a string array with the contained substrings." }, + { text: "sqrt", hint: "Returns the square root function." }, + { text: "startofday", hint: "Returns the start of the day containing the date, shifted by an offset, if provided." }, + { text: "startofmonth", hint: "Returns the start of the month containing the date, shifted by an offset, if provided." }, + { text: "startofweek", hint: "Returns the start of the week containing the date, shifted by an offset, if provided." }, + { text: "startofyear", hint: "Returns the start of the year containing the date, shifted by an offset, if provided." }, + { text: "stdev", hint: "Calculates the standard deviation of *Expr* across the group, considering the group as a [sample](https://en.wikipedia.org/wiki/Sample_%28statistics%29)." }, + { text: "stdevif", hint: "Calculates the [stdev](stdev-aggfunction.md) of *Expr* across the group for which *Predicate* evaluates to `true`." }, + { text: "stdevp", hint: "Calculates the standard deviation of *Expr* across the group, considering the group as a [population](https://en.wikipedia.org/wiki/Statistical_population)." }, + { text: "strcat", hint: "Concatenates between 1 and 64 arguments." }, + { text: "strcat_array", hint: "Creates a concatenated string of array values using specified delimiter." }, + { text: "strcat_delim", hint: "Concatenates between 2 and 64 arguments, with delimiter, provided as first argument." }, + { text: "strcmp", hint: "Compares two strings." }, + { text: "string_size", hint: "Returns the size, in bytes, of the input string." }, + { text: "strlen", hint: "Returns the length, in characters, of the input string." }, + { text: "strrep", hint: "Repeats given [string](./scalar-data-types/string.md) provided amount of times." }, + { text: "substring", hint: "Extracts a substring from a source string starting from some index to the end of the string." }, + { text: "sum", hint: "Calculates the sum of *Expr* across the group." }, + { text: "sumif", hint: "Returns a sum of *Expr* for which *Predicate* evaluates to `true`." }, + { text: "table", hint: "References specific table using an query-time evaluated string-expression." }, + { text: "tan", hint: "Returns the tangent function." }, + { text: "tdigest", hint: "Calculates the Intermediate results of [`percentiles()`](percentiles-aggfunction.md) across the group." }, + { text: "tdigest_merge", hint: "Merges tdigest results (scalar version of the aggregate version [`tdigest_merge()`](tdigest-merge-aggfunction.md))." }, + { text: "tobool", hint: "Converts input to boolean (signed 8-bit) representation." }, + { text: "todatetime", hint: "Converts input to [datetime](./scalar-data-types/datetime.md) scalar." }, + { text: "todecimal", hint: "Converts input to decimal number representation." }, + { text: "todouble", hint: "Converts the input to a value of type `real`. (`todouble()` and `toreal()` are synonyms.)" }, + { text: "todynamic", hint: "Interprets a `string` as a [JSON value](https://json.org/) and returns the value as [`dynamic`](./scalar-data-types/dynamic.md)." }, + { text: "toguid", hint: "Converts input to [`guid`](./scalar-data-types/guid.md) representation." }, + { text: "tohex", hint: "Converts input to a hexadecimal string." }, + { text: "toint", hint: "Converts input to integer (signed 32-bit) number representation." }, + { text: "tolong", hint: "Converts input to long (signed 64-bit) number representation." }, + { text: "tolower", hint: "Converts input string to lower case." }, + { text: "toscalar", hint: "Returns a scalar constant value of the evaluated expression." }, + { text: "tostring", hint: "Converts input to a string representation." }, + { text: "totimespan", hint: "Converts input to [timespan](./scalar-data-types/timespan.md) scalar." }, + { text: "toupper", hint: "Converts a string to upper case." }, + { text: "translate", hint: "Replaces a set of characters ('searchList') with another set of characters ('replacementList') in a given a string.\r\nThe function searches for characters in the 'searchList' and replaces them with the corresponding characters in 'replacementList'" }, + { text: "treepath", hint: "Enumerates all the path expressions that identify leaves in a dynamic object." }, + { text: "trim", hint: "Removes all leading and trailing matches of the specified regular expression." }, + { text: "trim_end", hint: "Removes trailing match of the specified regular expression." }, + { text: "trim_start", hint: "Removes leading match of the specified regular expression." }, + { text: "url_decode", hint: "The function converts encoded URL into a to regular URL representation." }, + { text: "url_encode", hint: "The function converts characters of the input URL into a format that can be transmitted over the Internet." }, + { text: "variance", hint: "Calculates the variance of *Expr* across the group, considering the group as a [sample](https://en.wikipedia.org/wiki/Sample_%28statistics%29)." }, + { text: "varianceif", hint: "Calculates the [variance](variance-aggfunction.md) of *Expr* across the group for which *Predicate* evaluates to `true`." }, + { text: "variancep", hint: "Calculates the variance of *Expr* across the group, considering the group as a [population](https://en.wikipedia.org/wiki/Statistical_population)." }, + { text: "weekofyear", hint: "Returns the integer number represents the week number." }, + { text: "welch_test", hint: "Computes the p_value of the [Welch-test function](https://en.wikipedia.org/wiki/Welch%27s_t-test)" }, + { text: "zip", hint: "The `zip` function accepts any number of `dynamic` arrays, and returns an\r\narray whose elements are each an array holding the elements of the input\r\narrays of the same index." }, +]; + +export const KEYWORDS = [ + 'by', + 'on', + 'contains', + 'notcontains', + 'containscs', + 'notcontainscs', + 'startswith', + 'has', + 'matches', + 'regex', + 'true', + 'false', + 'and', + 'or', + 'typeof', + 'int', + 'string', + 'date', + 'datetime', + 'time', + 'long', + 'real', + '​boolean', + 'bool', +]; + +export const grafanaMacros = [ + { text: '$__timeFilter', display: '$__timeFilter()', hint: 'Macro that uses the selected timerange in Grafana to filter the query.', }, + { text: '$__escapeMulti', display: '$__escapeMulti()', hint: 'Macro to escape multi-value template variables that contain illegal characters.', }, + { text: '$__contains', display: '$__contains()', hint: 'Macro for multi-value template variables.' }, +]; + +// Kusto operators +// export const OPERATORS = ['+', '-', '*', '/', '>', '<', '==', '<>', '<=', '>=', '~', '!~']; + +export const DURATION = ['SECONDS', 'MINUTES', 'HOURS', 'DAYS', 'WEEKS', 'MONTHS', 'YEARS']; + +const tokenizer = { + comment: { + pattern: /(^|[^\\:])\/\/.*/, + lookbehind: true, + greedy: true, + }, + 'function-context': { + pattern: /[a-z0-9_]+\([^)]*\)?/i, + inside: {}, + }, + duration: { + pattern: new RegExp(`${DURATION.join('?|')}?`, 'i'), + alias: 'number', + }, + builtin: new RegExp(`\\b(?:${functionTokens.map(f => f.text).join('|')})(?=\\s*\\()`, 'i'), + string: { + pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, + greedy: true, + }, + keyword: new RegExp(`\\b(?:${KEYWORDS.join('|')}|${operatorTokens.map(f => f.text).join('|')}|\\*)\\b`, 'i'), + boolean: /\b(?:true|false)\b/, + number: /\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i, + operator: /-|\+|\*|\/|>|<|==|<=?|>=?|<>|!~|~|=|\|/, + punctuation: /[{};(),.:]/, + variable: /(\[\[(.+?)\]\])|(\$(.+?))\b/, +}; + +tokenizer['function-context'].inside = { + argument: { + pattern: /[a-z0-9_]+(?=:)/i, + alias: 'symbol', + }, + duration: tokenizer.duration, + number: tokenizer.number, + builtin: tokenizer.builtin, + string: tokenizer.string, + variable: tokenizer.variable, +}; + +// console.log(tokenizer.builtin); + +export default tokenizer; + +// function escapeRegExp(str: string): string { +// return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +// } From 0c3657da7e41f4d895cbc7f32eda87695bbb25f9 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 30 Jan 2019 15:24:37 +0300 Subject: [PATCH 012/119] azuremonitor: suggest tables initially --- .../editor/KustoQueryField.tsx | 90 ++++++++++++++----- .../editor/editor_component.tsx | 8 +- .../partials/query.editor.html | 1 + .../query_ctrl.ts | 4 +- 4 files changed, 79 insertions(+), 24 deletions(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx index fa79d4bdb99..c8f96fba211 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx @@ -1,3 +1,4 @@ +import _ from 'lodash'; import Plain from 'slate-plain-serializer'; import QueryField from './query_field'; @@ -25,21 +26,43 @@ interface SuggestionGroup { skipFilter?: boolean; } +interface KustoSchema { + Databases: { + Default?: KustoDBSchema; + }; + Plugins?: any[]; +} + +interface KustoDBSchema { + Name?: string; + Functions?: any; + Tables?: any; +} + +const defaultSchema = () => ({ + Databases: { + Default: {} + } +}); + const cleanText = s => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim(); const wrapText = text => ({ text }); export default class KustoQueryField extends QueryField { fields: any; events: any; + schema: KustoSchema; constructor(props, context) { super(props, context); + this.schema = defaultSchema(); this.onTypeahead = debounce(this.onTypeahead, TYPEAHEAD_DELAY); } componentDidMount() { this.updateMenu(); + this.fetchSchema(); } onTypeahead = () => { @@ -128,7 +151,13 @@ export default class KustoQueryField extends QueryField { suggestionGroups = this._getKeywordSuggestions(); } else if (Plain.serialize(this.state.value) === '') { typeaheadContext = 'context-new'; - suggestionGroups = this._getInitialSuggestions(); + if (this.schema) { + suggestionGroups = this._getInitialSuggestions(); + } else { + this.fetchSchema(); + setTimeout(this.onTypeahead, 0); + return; + } } let results = 0; @@ -263,7 +292,7 @@ export default class KustoQueryField extends QueryField { { prefixMatch: true, label: 'Operators', - items: operatorTokens.map((s: any) => { s.type = 'function'; return s; }) + items: operatorTokens }, { prefixMatch: true, @@ -274,34 +303,46 @@ export default class KustoQueryField extends QueryField { prefixMatch: true, label: 'Macros', items: grafanaMacros.map((s: any) => { s.type = 'function'; return s; }) + }, + { + prefixMatch: true, + label: 'Tables', + items: _.map(this.schema.Databases.Default.Tables, (t: any) => ({ text: t.Name })) } ]; } private _getInitialSuggestions(): SuggestionGroup[] { - // TODO: return datbase tables as an initial suggestion return [ { prefixMatch: true, - label: 'Keywords', - items: KEYWORDS.map(wrapText) - }, - { - prefixMatch: true, - label: 'Operators', - items: operatorTokens.map((s: any) => { s.type = 'function'; return s; }) - }, - { - prefixMatch: true, - label: 'Functions', - items: functionTokens.map((s: any) => { s.type = 'function'; return s; }) - }, - { - prefixMatch: true, - label: 'Macros', - items: grafanaMacros.map((s: any) => { s.type = 'function'; return s; }) + label: 'Tables', + items: _.map(this.schema.Databases.Default.Tables, (t: any) => ({ text: t.Name })) } ]; + + // return [ + // { + // prefixMatch: true, + // label: 'Keywords', + // items: KEYWORDS.map(wrapText) + // }, + // { + // prefixMatch: true, + // label: 'Operators', + // items: operatorTokens.map((s: any) => { s.type = 'function'; return s; }) + // }, + // { + // prefixMatch: true, + // label: 'Functions', + // items: functionTokens.map((s: any) => { s.type = 'function'; return s; }) + // }, + // { + // prefixMatch: true, + // label: 'Macros', + // items: grafanaMacros.map((s: any) => { s.type = 'function'; return s; }) + // } + // ]; } private async _fetchEvents() { @@ -329,4 +370,13 @@ export default class KustoQueryField extends QueryField { // Stub this.fields = []; } + + private async fetchSchema() { + const schema = await this.props.getSchema(); + if (schema) { + this.schema = schema; + } else { + this.schema = defaultSchema(); + } + } } diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx index 59e4ab12c81..7787f029ee7 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx @@ -31,7 +31,7 @@ class Editor extends Component { }; render() { - const { request, variables } = this.props; + const { request, variables, getSchema } = this.props; const { edited, query } = this.state; return ( @@ -45,6 +45,7 @@ class Editor extends Component { placeholder="Enter a query" request={request} templateVariables={variables} + getSchema={getSchema} /> ); @@ -54,6 +55,9 @@ class Editor extends Component { coreModule.directive('kustoEditor', [ 'reactDirective', reactDirective => { - return reactDirective(Editor, ['change', 'database', 'execute', 'query', 'request', 'variables']); + return reactDirective(Editor, [ + 'change', 'database', 'execute', 'query', 'request', 'variables', + ['getSchema', { watchDepth: 'reference' }] + ]); }, ]); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html index 49f02ec8355..592fccdcda9 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html @@ -130,6 +130,7 @@ change="ctrl.onLogAnalyticsQueryChange" execute="ctrl.onLogAnalyticsQueryExecute" variables="ctrl.templateVariables" + getSchema="ctrl.getAzureLogAnalyticsSchema" /> diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts index fd42c172f11..b3aa5f9f6e9 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts @@ -304,7 +304,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { /* Azure Log Analytics */ - getWorkspaces() { + getWorkspaces = () => { return this.datasource.azureLogAnalyticsDatasource .getWorkspaces() .then(list => { @@ -316,7 +316,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { .catch(this.handleQueryCtrlError.bind(this)); } - getAzureLogAnalyticsSchema() { + getAzureLogAnalyticsSchema = () => { return this.getWorkspaces() .then(() => { return this.datasource.azureLogAnalyticsDatasource.getSchema(this.target.azureLogAnalytics.workspace); From df9ecc68162ae678a8f22acda868b06fb0433f0d Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 31 Jan 2019 17:44:00 +0300 Subject: [PATCH 013/119] azuremonitor: don't go back to dashboard if escape pressed in the editor --- public/app/core/services/keybindingSrv.ts | 18 ++++++++++++++++++ .../editor/KustoQueryField.tsx | 2 +- .../editor/query_field.tsx | 14 ++++++++++++++ public/app/routes/GrafanaCtrl.ts | 3 +++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 989746fd067..6d790baa336 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -139,6 +139,10 @@ export class KeybindingSrv { ); } + unbind(keyArg: string, keyType?: string) { + Mousetrap.unbind(keyArg, keyType); + } + showDashEditView() { const search = _.extend(this.$location.search(), { editview: 'settings' }); this.$location.search(search); @@ -293,3 +297,17 @@ export class KeybindingSrv { } coreModule.service('keybindingSrv', KeybindingSrv); + +/** + * Code below exports the service to react components + */ + +let singletonInstance: KeybindingSrv; + +export function setKeybindingSrv(instance: KeybindingSrv) { + singletonInstance = instance; +} + +export function getKeybindingSrv(): KeybindingSrv { + return singletonInstance; +} diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx index c8f96fba211..719d57b9b6a 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx @@ -61,7 +61,7 @@ export default class KustoQueryField extends QueryField { } componentDidMount() { - this.updateMenu(); + super.componentDidMount(); this.fetchSchema(); } diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx index 1c883a40c31..0acd53cabff 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx @@ -9,6 +9,7 @@ import NewlinePlugin from './slate-plugins/newline'; import RunnerPlugin from './slate-plugins/runner'; import Typeahead from './typeahead'; +import { getKeybindingSrv, KeybindingSrv } from 'app/core/services/keybindingSrv'; import { Block, Document, Text, Value } from 'slate'; import { Editor } from 'slate-react'; @@ -61,6 +62,7 @@ class QueryField extends React.Component { menuEl: any; plugins: any; resetTimer: any; + keybindingSrv: KeybindingSrv = getKeybindingSrv(); constructor(props, context) { super(props, context); @@ -90,6 +92,7 @@ class QueryField extends React.Component { } componentWillUnmount() { + this.restoreEscapeKeyBinding(); clearTimeout(this.resetTimer); } @@ -218,6 +221,7 @@ class QueryField extends React.Component { if (onBlur) { onBlur(); } + this.restoreEscapeKeyBinding(); }; handleFocus = () => { @@ -225,8 +229,18 @@ class QueryField extends React.Component { if (onFocus) { onFocus(); } + // Don't go back to dashboard if Escape pressed inside the editor. + this.removeEscapeKeyBinding(); }; + removeEscapeKeyBinding() { + this.keybindingSrv.unbind('esc', 'keydown'); + } + + restoreEscapeKeyBinding() { + this.keybindingSrv.setupGlobal(); + } + onClickItem = item => { const { suggestions } = this.state; if (!suggestions || suggestions.length === 0) { diff --git a/public/app/routes/GrafanaCtrl.ts b/public/app/routes/GrafanaCtrl.ts index 70bdf49e5e4..e50abdc0710 100644 --- a/public/app/routes/GrafanaCtrl.ts +++ b/public/app/routes/GrafanaCtrl.ts @@ -10,6 +10,7 @@ import appEvents from 'app/core/app_events'; import { BackendSrv, setBackendSrv } from 'app/core/services/backend_srv'; import { TimeSrv, setTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DatasourceSrv, setDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { KeybindingSrv, setKeybindingSrv } from 'app/core/services/keybindingSrv'; import { AngularLoader, setAngularLoader } from 'app/core/services/AngularLoader'; import { configureStore } from 'app/store/configureStore'; @@ -25,6 +26,7 @@ export class GrafanaCtrl { backendSrv: BackendSrv, timeSrv: TimeSrv, datasourceSrv: DatasourceSrv, + keybindingSrv: KeybindingSrv, angularLoader: AngularLoader ) { // make angular loader service available to react components @@ -32,6 +34,7 @@ export class GrafanaCtrl { setBackendSrv(backendSrv); setDatasourceSrv(datasourceSrv); setTimeSrv(timeSrv); + setKeybindingSrv(keybindingSrv); configureStore(); $scope.init = () => { From ad821cf6296b224d3995e1473e0db6533e1fed0c Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 31 Jan 2019 20:23:40 +0300 Subject: [PATCH 014/119] azuremonitor: where clause autocomplete --- .../editor/KustoQueryField.tsx | 49 ++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx index 719d57b9b6a..33be370ada3 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx @@ -11,7 +11,7 @@ import { KEYWORDS, functionTokens, operatorTokens, grafanaMacros } from './kusto // import '../sass/editor.base.scss'; -const TYPEAHEAD_DELAY = 500; +const TYPEAHEAD_DELAY = 100; interface Suggestion { text: string; @@ -104,12 +104,13 @@ export default class KustoQueryField extends QueryField { this._fetchFields(); return; } - } else if (modelPrefix.match(/(facet\s$)/i)) { - typeaheadContext = 'context-facet'; - if (this.fields) { - suggestionGroups = this._getKeywordSuggestions(); + } else if (modelPrefix.match(/(where\s$)/i)) { + typeaheadContext = 'context-where'; + const fullQuery = Plain.serialize(this.state.value); + const table = this.getTableFromContext(fullQuery); + if (table) { + suggestionGroups = this.getWhereSuggestions(table); } else { - this._fetchFields(); return; } } else if (modelPrefix.match(/(,\s*$)/)) { @@ -345,6 +346,35 @@ export default class KustoQueryField extends QueryField { // ]; } + private getWhereSuggestions(table: string): SuggestionGroup[] { + const tableSchema = this.schema.Databases.Default.Tables[table]; + if (tableSchema) { + return [ + { + prefixMatch: true, + label: 'Fields', + items: _.map(tableSchema.OrderedColumns, (f: any) => ({ + text: f.Name, + hint: f.Type + })) + } + ]; + } else { + return []; + } + } + + private getTableFromContext(query: string) { + const tablePattern = /^\s*(\w+)\s*|/g; + const normalizedQuery = normalizeQuery(query); + const match = tablePattern.exec(normalizedQuery); + if (match && match.length > 1 && match[0] && match[1]) { + return match[1]; + } else { + return null; + } + } + private async _fetchEvents() { // const query = 'events'; // const result = await this.request(query); @@ -380,3 +410,10 @@ export default class KustoQueryField extends QueryField { } } } + +function normalizeQuery(query: string): string { + const commentPattern = /\/\/.*$/gm; + let normalizedQuery = query.replace(commentPattern, ''); + normalizedQuery = normalizedQuery.replace('\n', ' '); + return normalizedQuery; +} From dd8ca70151672293b48267a800b2e3682e8a79c5 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 4 Feb 2019 18:51:56 +0300 Subject: [PATCH 015/119] azuremonitor: use kusto editor for App Insights --- .../app_insights/app_insights_datasource.ts | 9 +++++++ .../app_insights/response_parser.ts | 26 +++++++++++++++++++ .../editor/KustoQueryField.tsx | 14 +++++++++- .../editor/editor_component.tsx | 22 ++++++++++++---- .../partials/query.editor.html | 15 ++++++++--- .../query_ctrl.ts | 14 ++++++++++ 6 files changed, 91 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/app_insights/app_insights_datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/app_insights/app_insights_datasource.ts index 950fa73a16b..97f76d229fb 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/app_insights/app_insights_datasource.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/app_insights/app_insights_datasource.ts @@ -224,4 +224,13 @@ export default class AppInsightsDatasource { return new ResponseParser(result).parseGroupBys(); }); } + + getQuerySchema() { + const url = `${this.baseUrl}/query/schema`; + return this.doRequest(url).then(result => { + const schema = new ResponseParser(result).parseQuerySchema(); + // console.log(schema); + return schema; + }); + } } diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/app_insights/response_parser.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/app_insights/response_parser.ts index 848472cf101..fa96e4a2e3e 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/app_insights/response_parser.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/app_insights/response_parser.ts @@ -199,6 +199,32 @@ export default class ResponseParser { return ResponseParser.toTextValueList(this.results.supportedGroupBy); } + parseQuerySchema() { + const result = { + Type: 'AppInsights', + Tables: {} + }; + if (this.results && this.results.data && this.results.data.Tables) { + for (let i = 0; i < this.results.data.Tables[0].Rows.length; i++) { + const column = this.results.data.Tables[0].Rows[i]; + const columnTable = column[0]; + const columnName = column[1]; + const columnType = column[2]; + if (result.Tables[columnTable]) { + result.Tables[columnTable].OrderedColumns.push({ Name: columnName, Type: columnType }); + } else { + result.Tables[columnTable] = { + Name: columnTable, + OrderedColumns: [ + { Name: columnName, Type: columnType } + ] + }; + } + } + } + return result; + } + static toTextValueList(values) { const list: any[] = []; for (let i = 0; i < values.length; i++) { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx index 33be370ada3..09573f29047 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx @@ -402,8 +402,11 @@ export default class KustoQueryField extends QueryField { } private async fetchSchema() { - const schema = await this.props.getSchema(); + let schema = await this.props.getSchema(); if (schema) { + if (schema.Type === 'AppInsights') { + schema = castSchema(schema); + } this.schema = schema; } else { this.schema = defaultSchema(); @@ -411,6 +414,15 @@ export default class KustoQueryField extends QueryField { } } +/** + * Cast schema from App Insights to default Kusto schema + */ +function castSchema(schema) { + const defaultSchemaTemplate = defaultSchema(); + defaultSchemaTemplate.Databases.Default = schema; + return defaultSchemaTemplate; +} + function normalizeQuery(query: string): string { const commentPattern = /\/\/.*$/gm; let normalizedQuery = query.replace(commentPattern, ''); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx index 7787f029ee7..bdc85f1577d 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/editor_component.tsx @@ -4,7 +4,20 @@ import Kusto from './kusto/kusto'; import React, { Component } from 'react'; import coreModule from 'app/core/core_module'; -class Editor extends Component { +interface EditorProps { + index: number; + placeholder?: string; + change: (value: string, index: number) => void; + variables: () => string[] | string[]; + getSchema?: () => Promise; + execute?: () => void; +} + +class Editor extends Component { + static defaultProps = { + placeholder: 'Enter a query' + }; + constructor(props) { super(props); this.state = { @@ -31,7 +44,7 @@ class Editor extends Component { }; render() { - const { request, variables, getSchema } = this.props; + const { variables, getSchema, placeholder } = this.props; const { edited, query } = this.state; return ( @@ -42,8 +55,7 @@ class Editor extends Component { onQueryChange={this.onChangeQuery} prismLanguage="kusto" prismDefinition={Kusto} - placeholder="Enter a query" - request={request} + placeholder={placeholder} templateVariables={variables} getSchema={getSchema} /> @@ -56,7 +68,7 @@ coreModule.directive('kustoEditor', [ 'reactDirective', reactDirective => { return reactDirective(Editor, [ - 'change', 'database', 'execute', 'query', 'request', 'variables', + 'change', 'database', 'execute', 'query', 'variables', 'placeholder', ['getSchema', { watchDepth: 'reference' }] ]); }, diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html index 592fccdcda9..6299947b30a 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/query.editor.html @@ -124,8 +124,6 @@
-
+ +
+
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts index b3aa5f9f6e9..cee67d11ab3 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/query_ctrl.ts @@ -345,6 +345,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { } return interval; } + getAppInsightsMetricNames() { if (!this.datasource.appInsightsDatasource.isConfigured()) { return; @@ -377,6 +378,19 @@ export class AzureMonitorQueryCtrl extends QueryCtrl { .catch(this.handleQueryCtrlError.bind(this)); } + onAppInsightsQueryChange = (nextQuery: string) => { + this.target.appInsights.rawQueryString = nextQuery; + } + + onAppInsightsQueryExecute = () => { + return this.refresh(); + } + + getAppInsightsQuerySchema = () => { + return this.datasource.appInsightsDatasource.getQuerySchema() + .catch(this.handleQueryCtrlError.bind(this)); + } + getAppInsightsGroupBySegments(query) { return _.map(this.target.appInsights.groupByOptions, option => { return { text: option, value: option }; From 99ff8e68ffbd851163d539891b884fbf66da67ca Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 4 Feb 2019 19:20:18 +0300 Subject: [PATCH 016/119] azuremonitor: fix where suggestions --- .../editor/KustoQueryField.tsx | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx index 09573f29047..9b2df96fcdd 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx @@ -99,12 +99,12 @@ export default class KustoQueryField extends QueryField { if (wrapperClasses.contains('function-context')) { typeaheadContext = 'context-function'; if (this.fields) { - suggestionGroups = this._getKeywordSuggestions(); + suggestionGroups = this.getKeywordSuggestions(); } else { this._fetchFields(); return; } - } else if (modelPrefix.match(/(where\s$)/i)) { + } else if (modelPrefix.match(/(where\s(\w+\b)?$)/i)) { typeaheadContext = 'context-where'; const fullQuery = Plain.serialize(this.state.value); const table = this.getTableFromContext(fullQuery); @@ -116,7 +116,7 @@ export default class KustoQueryField extends QueryField { } else if (modelPrefix.match(/(,\s*$)/)) { typeaheadContext = 'context-multiple-fields'; if (this.fields) { - suggestionGroups = this._getKeywordSuggestions(); + suggestionGroups = this.getKeywordSuggestions(); } else { this._fetchFields(); return; @@ -124,7 +124,7 @@ export default class KustoQueryField extends QueryField { } else if (modelPrefix.match(/(from\s$)/i)) { typeaheadContext = 'context-from'; if (this.events) { - suggestionGroups = this._getKeywordSuggestions(); + suggestionGroups = this.getKeywordSuggestions(); } else { this._fetchEvents(); return; @@ -132,7 +132,7 @@ export default class KustoQueryField extends QueryField { } else if (modelPrefix.match(/(^select\s\w*$)/i)) { typeaheadContext = 'context-select'; if (this.fields) { - suggestionGroups = this._getKeywordSuggestions(); + suggestionGroups = this.getKeywordSuggestions(); } else { this._fetchFields(); return; @@ -140,16 +140,19 @@ export default class KustoQueryField extends QueryField { } else if (modelPrefix.match(/from\s\S+\s\w*$/i)) { prefix = ''; typeaheadContext = 'context-since'; - suggestionGroups = this._getKeywordSuggestions(); + suggestionGroups = this.getKeywordSuggestions(); // } else if (modelPrefix.match(/\d+\s\w*$/)) { // typeaheadContext = 'context-number'; // suggestionGroups = this._getAfterNumberSuggestions(); } else if (modelPrefix.match(/ago\b/i) || modelPrefix.match(/facet\b/i) || modelPrefix.match(/\$__timefilter\b/i)) { typeaheadContext = 'context-timeseries'; - suggestionGroups = this._getKeywordSuggestions(); + suggestionGroups = this.getKeywordSuggestions(); } else if (prefix && !wrapperClasses.contains('argument')) { + if (modelPrefix.match(/\s$/i)) { + prefix = ''; + } typeaheadContext = 'context-builtin'; - suggestionGroups = this._getKeywordSuggestions(); + suggestionGroups = this.getKeywordSuggestions(); } else if (Plain.serialize(this.state.value) === '') { typeaheadContext = 'context-new'; if (this.schema) { @@ -159,6 +162,12 @@ export default class KustoQueryField extends QueryField { setTimeout(this.onTypeahead, 0); return; } + } else { + typeaheadContext = 'context-builtin'; + if (modelPrefix.match(/\s$/i)) { + prefix = ''; + } + suggestionGroups = this.getKeywordSuggestions(); } let results = 0; @@ -178,6 +187,7 @@ export default class KustoQueryField extends QueryField { .filter(group => group.items.length > 0); // console.log('onTypeahead', selection.anchorNode, wrapperClasses, text, offset, prefix, typeaheadContext); + // console.log('onTypeahead', modelPrefix, prefix, typeaheadContext); this.setState({ typeaheadPrefix: prefix, @@ -283,7 +293,7 @@ export default class KustoQueryField extends QueryField { // ]; // } - private _getKeywordSuggestions(): SuggestionGroup[] { + private getKeywordSuggestions(): SuggestionGroup[] { return [ { prefixMatch: true, From 4b5bfd3da54e8ee681da9c6d74750714b7a882ff Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 5 Feb 2019 14:01:06 +0300 Subject: [PATCH 017/119] azuremonitor: more autocomplete suggestions for built-in functions --- .../editor/KustoQueryField.tsx | 197 ++++++++---------- 1 file changed, 89 insertions(+), 108 deletions(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx index 9b2df96fcdd..bbe34b8f46a 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx @@ -96,57 +96,51 @@ export default class KustoQueryField extends QueryField { const wrapperClasses = wrapperNode.classList; let typeaheadContext: string | null = null; + // Built-in functions if (wrapperClasses.contains('function-context')) { typeaheadContext = 'context-function'; - if (this.fields) { - suggestionGroups = this.getKeywordSuggestions(); - } else { - this._fetchFields(); - return; - } + suggestionGroups = this.getColumnSuggestions(); + + // where } else if (modelPrefix.match(/(where\s(\w+\b)?$)/i)) { typeaheadContext = 'context-where'; - const fullQuery = Plain.serialize(this.state.value); - const table = this.getTableFromContext(fullQuery); - if (table) { - suggestionGroups = this.getWhereSuggestions(table); - } else { - return; - } - } else if (modelPrefix.match(/(,\s*$)/)) { - typeaheadContext = 'context-multiple-fields'; - if (this.fields) { - suggestionGroups = this.getKeywordSuggestions(); - } else { - this._fetchFields(); - return; - } - } else if (modelPrefix.match(/(from\s$)/i)) { - typeaheadContext = 'context-from'; - if (this.events) { - suggestionGroups = this.getKeywordSuggestions(); - } else { - this._fetchEvents(); - return; - } - } else if (modelPrefix.match(/(^select\s\w*$)/i)) { - typeaheadContext = 'context-select'; - if (this.fields) { - suggestionGroups = this.getKeywordSuggestions(); - } else { - this._fetchFields(); - return; - } - } else if (modelPrefix.match(/from\s\S+\s\w*$/i)) { - prefix = ''; - typeaheadContext = 'context-since'; - suggestionGroups = this.getKeywordSuggestions(); - // } else if (modelPrefix.match(/\d+\s\w*$/)) { - // typeaheadContext = 'context-number'; - // suggestionGroups = this._getAfterNumberSuggestions(); - } else if (modelPrefix.match(/ago\b/i) || modelPrefix.match(/facet\b/i) || modelPrefix.match(/\$__timefilter\b/i)) { - typeaheadContext = 'context-timeseries'; - suggestionGroups = this.getKeywordSuggestions(); + suggestionGroups = this.getColumnSuggestions(); + + // summarize by + } else if (modelPrefix.match(/(summarize\s(\w+\b)?$)/i)) { + typeaheadContext = 'context-summarize'; + suggestionGroups = this.getFunctionSuggestions(); + } else if (modelPrefix.match(/(summarize\s(.+\s)?by\s+([^,\s]+,\s*)*([^,\s]+\b)?$)/i)) { + typeaheadContext = 'context-summarize-by'; + suggestionGroups = this.getColumnSuggestions(); + + // order by, top X by, ... by ... + } else if (modelPrefix.match(/(by\s+([^,\s]+,\s*)*([^,\s]+\b)?$)/i)) { + typeaheadContext = 'context-by'; + suggestionGroups = this.getColumnSuggestions(); + + // join + } else if (modelPrefix.match(/(on\s(.+\b)?$)/i)) { + typeaheadContext = 'context-join-on'; + suggestionGroups = this.getColumnSuggestions(); + } else if (modelPrefix.match(/(join\s+(\(\s+)?(\w+\b)?$)/i)) { + typeaheadContext = 'context-join'; + suggestionGroups = this.getTableSuggestions(); + + // distinct + } else if (modelPrefix.match(/(distinct\s(.+\b)?$)/i)) { + typeaheadContext = 'context-distinct'; + suggestionGroups = this.getColumnSuggestions(); + + // database() + } else if (modelPrefix.match(/(database\(\"(\w+)\"\)\.(.+\b)?$)/i)) { + typeaheadContext = 'context-database-table'; + const db = this.getDBFromDatabaseFunction(modelPrefix); + console.log(db); + suggestionGroups = this.getTableSuggestions(db); + prefix = prefix.replace('.', ''); + + // built-in } else if (prefix && !wrapperClasses.contains('argument')) { if (modelPrefix.match(/\s$/i)) { prefix = ''; @@ -156,7 +150,7 @@ export default class KustoQueryField extends QueryField { } else if (Plain.serialize(this.state.value) === '') { typeaheadContext = 'context-new'; if (this.schema) { - suggestionGroups = this._getInitialSuggestions(); + suggestionGroups = this.getInitialSuggestions(); } else { this.fetchSchema(); setTimeout(this.onTypeahead, 0); @@ -187,7 +181,7 @@ export default class KustoQueryField extends QueryField { .filter(group => group.items.length > 0); // console.log('onTypeahead', selection.anchorNode, wrapperClasses, text, offset, prefix, typeaheadContext); - // console.log('onTypeahead', modelPrefix, prefix, typeaheadContext); + console.log('onTypeahead', modelPrefix, prefix, typeaheadContext); this.setState({ typeaheadPrefix: prefix, @@ -293,6 +287,10 @@ export default class KustoQueryField extends QueryField { // ]; // } + private getInitialSuggestions(): SuggestionGroup[] { + return this.getTableSuggestions(); + } + private getKeywordSuggestions(): SuggestionGroup[] { return [ { @@ -323,50 +321,28 @@ export default class KustoQueryField extends QueryField { ]; } - private _getInitialSuggestions(): SuggestionGroup[] { + private getFunctionSuggestions(): SuggestionGroup[] { return [ { prefixMatch: true, - label: 'Tables', - items: _.map(this.schema.Databases.Default.Tables, (t: any) => ({ text: t.Name })) + label: 'Functions', + items: functionTokens.map((s: any) => { s.type = 'function'; return s; }) + }, + { + prefixMatch: true, + label: 'Macros', + items: grafanaMacros.map((s: any) => { s.type = 'function'; return s; }) } ]; - - // return [ - // { - // prefixMatch: true, - // label: 'Keywords', - // items: KEYWORDS.map(wrapText) - // }, - // { - // prefixMatch: true, - // label: 'Operators', - // items: operatorTokens.map((s: any) => { s.type = 'function'; return s; }) - // }, - // { - // prefixMatch: true, - // label: 'Functions', - // items: functionTokens.map((s: any) => { s.type = 'function'; return s; }) - // }, - // { - // prefixMatch: true, - // label: 'Macros', - // items: grafanaMacros.map((s: any) => { s.type = 'function'; return s; }) - // } - // ]; } - private getWhereSuggestions(table: string): SuggestionGroup[] { - const tableSchema = this.schema.Databases.Default.Tables[table]; - if (tableSchema) { + getTableSuggestions(db = 'Default'): SuggestionGroup[] { + if (this.schema.Databases[db]) { return [ { prefixMatch: true, - label: 'Fields', - items: _.map(tableSchema.OrderedColumns, (f: any) => ({ - text: f.Name, - hint: f.Type - })) + label: 'Tables', + items: _.map(this.schema.Databases[db].Tables, (t: any) => ({ text: t.Name })) } ]; } else { @@ -374,7 +350,28 @@ export default class KustoQueryField extends QueryField { } } - private getTableFromContext(query: string) { + private getColumnSuggestions(): SuggestionGroup[] { + const table = this.getTableFromContext(); + if (table) { + const tableSchema = this.schema.Databases.Default.Tables[table]; + if (tableSchema) { + return [ + { + prefixMatch: true, + label: 'Fields', + items: _.map(tableSchema.OrderedColumns, (f: any) => ({ + text: f.Name, + hint: f.Type + })) + } + ]; + } + } + return []; + } + + private getTableFromContext() { + const query = Plain.serialize(this.state.value); const tablePattern = /^\s*(\w+)\s*|/g; const normalizedQuery = normalizeQuery(query); const match = tablePattern.exec(normalizedQuery); @@ -385,30 +382,14 @@ export default class KustoQueryField extends QueryField { } } - private async _fetchEvents() { - // const query = 'events'; - // const result = await this.request(query); - - // if (result === undefined) { - // this.events = []; - // } else { - // this.events = result; - // } - // setTimeout(this.onTypeahead, 0); - - //Stub - this.events = []; - } - - private async _fetchFields() { - // const query = 'fields'; - // const result = await this.request(query); - - // this.fields = result || []; - - // setTimeout(this.onTypeahead, 0); - // Stub - this.fields = []; + private getDBFromDatabaseFunction(prefix: string) { + const databasePattern = /database\(\"(\w+)\"\)/gi; + const match = databasePattern.exec(prefix); + if (match && match.length > 1 && match[0] && match[1]) { + return match[1]; + } else { + return null; + } } private async fetchSchema() { From 0642c5269315cfa2acbb61648daf2d1de20004e6 Mon Sep 17 00:00:00 2001 From: ijin08 Date: Tue, 5 Feb 2019 12:05:02 +0100 Subject: [PATCH 018/119] created new color variables, changed primary to blue, changed success-btns to primary-btns. --- .../ColorPicker/SeriesColorPickerPopover.tsx | 4 +- .../PanelOptionsGroup/_PanelOptionsGroup.scss | 4 +- .../ThresholdsEditor/_ThresholdsEditor.scss | 2 +- .../components/EmptyListCTA/EmptyListCTA.tsx | 2 +- .../__snapshots__/EmptyListCTA.test.tsx.snap | 2 +- .../components/OrgActionBar/OrgActionBar.tsx | 2 +- .../__snapshots__/OrgActionBar.test.tsx.snap | 2 +- .../PermissionList/AddPermission.tsx | 2 +- .../SharedPreferences/SharedPreferences.tsx | 2 +- .../manage_dashboards/manage_dashboards.html | 6 +- .../app/features/admin/partials/edit_org.html | 2 +- .../features/admin/partials/edit_user.html | 8 +- .../app/features/admin/partials/new_user.html | 2 +- public/app/features/admin/partials/orgs.html | 2 +- public/app/features/admin/partials/users.html | 2 +- .../alerting/partials/notification_edit.html | 2 +- .../alerting/partials/notifications_list.html | 2 +- .../features/annotations/partials/editor.html | 8 +- .../annotations/partials/event_editor.html | 2 +- public/app/features/api-keys/ApiKeysPage.tsx | 4 +- .../__snapshots__/ApiKeysPage.test.tsx.snap | 2 +- .../AddPanelWidget/AddPanelWidget.tsx | 2 +- .../components/DashExportModal/template.html | 2 +- .../components/DashLinks/editor.html | 8 +- .../DashboardPermissions.tsx | 2 +- .../DashboardSettings/template.html | 6 +- .../components/ExportDataModal/template.html | 2 +- .../components/RowOptions/template.html | 2 +- .../SaveModals/SaveDashboardAsModalCtrl.ts | 2 +- .../SaveModals/SaveDashboardModalCtrl.ts | 4 +- .../SaveProvisionedDashboardModalCtrl.ts | 2 +- .../components/ShareModal/template.html | 2 +- .../UnsavedChangesModalCtrl.ts | 2 +- .../components/VersionHistory/template.html | 2 +- .../datasources/settings/ButtonRow.tsx | 2 +- .../__snapshots__/ButtonRow.test.tsx.snap | 4 +- .../features/folders/FolderPermissions.tsx | 2 +- .../features/folders/FolderSettingsPage.tsx | 2 +- .../FolderSettingsPage.test.tsx.snap | 4 +- .../folders/partials/create_folder.html | 2 +- .../MoveToFolderModal/template.html | 2 +- .../uploadDashboardDirective.ts | 2 +- .../partials/dashboard_import.html | 2 +- public/app/features/org/OrgProfile.tsx | 2 +- .../__snapshots__/OrgProfile.test.tsx.snap | 2 +- public/app/features/org/partials/invite.html | 2 +- public/app/features/org/partials/newOrg.html | 2 +- .../app/features/org/partials/select_org.html | 2 +- .../features/playlist/partials/playlist.html | 4 +- .../features/playlist/partials/playlists.html | 2 +- .../plugins/partials/plugin_edit.html | 4 +- .../profile/partials/change_password.html | 2 +- .../features/profile/partials/profile.html | 2 +- public/app/features/teams/TeamGroupSync.tsx | 6 +- public/app/features/teams/TeamList.tsx | 2 +- public/app/features/teams/TeamMembers.tsx | 4 +- public/app/features/teams/TeamSettings.tsx | 2 +- .../__snapshots__/TeamGroupSync.test.tsx.snap | 8 +- .../__snapshots__/TeamList.test.tsx.snap | 2 +- .../__snapshots__/TeamMembers.test.tsx.snap | 6 +- .../__snapshots__/TeamSettings.test.tsx.snap | 2 +- .../features/teams/partials/create_team.html | 2 +- .../features/templating/partials/editor.html | 8 +- public/app/features/users/UsersActionBar.tsx | 4 +- .../UsersActionBar.test.tsx.snap | 4 +- public/app/partials/confirm_modal.html | 2 +- public/app/partials/edit_json.html | 2 +- public/app/partials/login.html | 2 +- public/app/partials/reset_password.html | 6 +- public/app/partials/signup_invited.html | 2 +- public/app/partials/signup_step2.html | 2 +- public/sass/_variables.dark.scss | 87 +++++++------- public/sass/_variables.light.scss | 110 +++++++++--------- public/sass/base/_type.scss | 4 +- public/sass/components/_buttons.scss | 34 +----- public/sass/components/_navbar.scss | 2 +- .../components/_panel_gettingstarted.scss | 2 +- public/vendor/angular-ui/ui-bootstrap-tpls.js | 2 +- 78 files changed, 212 insertions(+), 243 deletions(-) diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index 3fa7a1f4a45..75727f18dcb 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -69,8 +69,8 @@ export class AxisSelector extends React.PureComponent diff --git a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss index b5b815cf57c..ddcb8971275 100644 --- a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss +++ b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss @@ -29,14 +29,14 @@ &:hover { .panel-options-group__add-circle { - background-color: $btn-success-bg; + background-color: $btn-primary-bg; color: $text-color-strong; } } } .panel-options-group__add-circle { - @include gradientBar($btn-success-bg, $btn-success-bg-hl, $text-color); + @include gradientBar($btn-primary-bg, $btn-primary-bg-hl, #fff); border-radius: 50px; width: 20px; diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss b/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss index 200adfbfd75..923244af781 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss +++ b/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss @@ -21,7 +21,7 @@ } .thresholds-row-add-button { - @include buttonBackground($btn-success-bg, $btn-success-bg-hl, $text-color); + @include buttonBackground($btn-primary-bg, $btn-primary-bg-hl, #fff); align-self: center; margin-right: 5px; diff --git a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx index d63af72ae4d..6b5c6ebb7ca 100644 --- a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx +++ b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx @@ -20,7 +20,7 @@ class EmptyListCTA extends Component { return (
{title}
- + {buttonTitle} diff --git a/public/app/core/components/EmptyListCTA/__snapshots__/EmptyListCTA.test.tsx.snap b/public/app/core/components/EmptyListCTA/__snapshots__/EmptyListCTA.test.tsx.snap index b85660bcc6f..21c2ed294b4 100644 --- a/public/app/core/components/EmptyListCTA/__snapshots__/EmptyListCTA.test.tsx.snap +++ b/public/app/core/components/EmptyListCTA/__snapshots__/EmptyListCTA.test.tsx.snap @@ -10,7 +10,7 @@ exports[`EmptyListCTA renders correctly 1`] = ` Title
diff --git a/public/app/core/components/OrgActionBar/OrgActionBar.tsx b/public/app/core/components/OrgActionBar/OrgActionBar.tsx index 8fc34a018e1..b6b2046736f 100644 --- a/public/app/core/components/OrgActionBar/OrgActionBar.tsx +++ b/public/app/core/components/OrgActionBar/OrgActionBar.tsx @@ -35,7 +35,7 @@ export default class OrgActionBar extends PureComponent { onSetLayoutMode(mode)} />
diff --git a/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap b/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap index dc53e7863ea..25de037930a 100644 --- a/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap +++ b/public/app/core/components/OrgActionBar/__snapshots__/OrgActionBar.test.tsx.snap @@ -29,7 +29,7 @@ exports[`Render should render component 1`] = ` className="page-action-bar__spacer" /> diff --git a/public/app/core/components/PermissionList/AddPermission.tsx b/public/app/core/components/PermissionList/AddPermission.tsx index 30219371257..80afcedf873 100644 --- a/public/app/core/components/PermissionList/AddPermission.tsx +++ b/public/app/core/components/PermissionList/AddPermission.tsx @@ -130,7 +130,7 @@ class AddPermissions extends Component {
-
diff --git a/public/app/core/components/SharedPreferences/SharedPreferences.tsx b/public/app/core/components/SharedPreferences/SharedPreferences.tsx index 33aca1de2aa..171e0e8109e 100644 --- a/public/app/core/components/SharedPreferences/SharedPreferences.tsx +++ b/public/app/core/components/SharedPreferences/SharedPreferences.tsx @@ -126,7 +126,7 @@ export class SharedPreferences extends PureComponent { />
-
diff --git a/public/app/core/components/manage_dashboards/manage_dashboards.html b/public/app/core/components/manage_dashboards/manage_dashboards.html index 6fbd65afaf5..6036ead3ef1 100644 --- a/public/app/core/components/manage_dashboards/manage_dashboards.html +++ b/public/app/core/components/manage_dashboards/manage_dashboards.html @@ -5,15 +5,15 @@
-
+ Dashboard - + Folder - + Import diff --git a/public/app/features/admin/partials/edit_org.html b/public/app/features/admin/partials/edit_org.html index 975d663e9b0..911181ef999 100644 --- a/public/app/features/admin/partials/edit_org.html +++ b/public/app/features/admin/partials/edit_org.html @@ -10,7 +10,7 @@
- +
diff --git a/public/app/features/admin/partials/edit_user.html b/public/app/features/admin/partials/edit_user.html index 5b0efa8bdf3..7e6457a8a76 100644 --- a/public/app/features/admin/partials/edit_user.html +++ b/public/app/features/admin/partials/edit_user.html @@ -21,7 +21,7 @@
- +
@@ -34,7 +34,7 @@
- +
@@ -46,7 +46,7 @@
- +
@@ -65,7 +65,7 @@
- +
diff --git a/public/app/features/admin/partials/new_user.html b/public/app/features/admin/partials/new_user.html index 5199d957c33..e3374d080ca 100644 --- a/public/app/features/admin/partials/new_user.html +++ b/public/app/features/admin/partials/new_user.html @@ -24,7 +24,7 @@
- +
diff --git a/public/app/features/admin/partials/orgs.html b/public/app/features/admin/partials/orgs.html index d28cf4dc967..b40aed6faab 100644 --- a/public/app/features/admin/partials/orgs.html +++ b/public/app/features/admin/partials/orgs.html @@ -3,7 +3,7 @@
- + New Org diff --git a/public/app/features/admin/partials/users.html b/public/app/features/admin/partials/users.html index 806c10851e5..08704dc0459 100644 --- a/public/app/features/admin/partials/users.html +++ b/public/app/features/admin/partials/users.html @@ -7,7 +7,7 @@
- + Add new user diff --git a/public/app/features/alerting/partials/notification_edit.html b/public/app/features/alerting/partials/notification_edit.html index b2cd2f21e4d..5e7201cdfdd 100644 --- a/public/app/features/alerting/partials/notification_edit.html +++ b/public/app/features/alerting/partials/notification_edit.html @@ -68,7 +68,7 @@
- + Back
diff --git a/public/app/features/alerting/partials/notifications_list.html b/public/app/features/alerting/partials/notifications_list.html index 246cb45b4db..6624a1d1132 100644 --- a/public/app/features/alerting/partials/notifications_list.html +++ b/public/app/features/alerting/partials/notifications_list.html @@ -7,7 +7,7 @@
- + New Channel diff --git a/public/app/features/annotations/partials/editor.html b/public/app/features/annotations/partials/editor.html index 65ee7e52bd0..9a7a8cb738a 100644 --- a/public/app/features/annotations/partials/editor.html +++ b/public/app/features/annotations/partials/editor.html @@ -9,7 +9,7 @@
@@ -48,7 +48,7 @@
There are no custom annotation queries added yet
- + Add Annotation Query @@ -105,8 +105,8 @@
- - + +
diff --git a/public/app/features/annotations/partials/event_editor.html b/public/app/features/annotations/partials/event_editor.html index 529434755f1..286decb34ce 100644 --- a/public/app/features/annotations/partials/event_editor.html +++ b/public/app/features/annotations/partials/event_editor.html @@ -26,7 +26,7 @@
- + Cancel
diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index 41b9b0c8a55..21d1ca54a66 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -169,7 +169,7 @@ export class ApiKeysPage extends PureComponent {
- +
@@ -199,7 +199,7 @@ export class ApiKeysPage extends PureComponent {
-
diff --git a/public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap b/public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap index f40894426ae..03f11f79cc3 100644 --- a/public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap +++ b/public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap @@ -137,7 +137,7 @@ exports[`Render should render CTA if there are no API keys 1`] = ` className="gf-form" > diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx index 8c1ab93cec1..dbd2fb1ffeb 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx @@ -142,7 +142,7 @@ export class AddPanelWidget extends React.Component {
- {addCopyButton} diff --git a/public/app/features/dashboard/components/DashExportModal/template.html b/public/app/features/dashboard/components/DashExportModal/template.html index 3c14c4b184d..e399d166d04 100644 --- a/public/app/features/dashboard/components/DashExportModal/template.html +++ b/public/app/features/dashboard/components/DashExportModal/template.html @@ -12,7 +12,7 @@
-
@@ -126,10 +126,10 @@ - - diff --git a/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx index ce6a866ce57..8cc26c4a1f2 100644 --- a/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx +++ b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx @@ -76,7 +76,7 @@ export class DashboardPermissions extends PureComponent {
-
diff --git a/public/app/features/dashboard/components/DashboardSettings/template.html b/public/app/features/dashboard/components/DashboardSettings/template.html index 97002f7bf92..99edc035bd5 100644 --- a/public/app/features/dashboard/components/DashboardSettings/template.html +++ b/public/app/features/dashboard/components/DashboardSettings/template.html @@ -10,7 +10,7 @@
-
-
@@ -128,7 +128,7 @@

Make Editable

-
diff --git a/public/app/features/dashboard/components/ExportDataModal/template.html b/public/app/features/dashboard/components/ExportDataModal/template.html index 8b766889c33..f59bd629e03 100644 --- a/public/app/features/dashboard/components/ExportDataModal/template.html +++ b/public/app/features/dashboard/components/ExportDataModal/template.html @@ -29,7 +29,7 @@ diff --git a/public/app/features/dashboard/components/RowOptions/template.html b/public/app/features/dashboard/components/RowOptions/template.html index 3d5c6116679..13e00b631ed 100644 --- a/public/app/features/dashboard/components/RowOptions/template.html +++ b/public/app/features/dashboard/components/RowOptions/template.html @@ -22,7 +22,7 @@
- +
diff --git a/public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts b/public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts index 6a470785fdb..60fa031f71c 100644 --- a/public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts +++ b/public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts @@ -32,7 +32,7 @@ const template = `
- + Cancel
diff --git a/public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.ts b/public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.ts index 88fba13f711..ed187befb95 100644 --- a/public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.ts +++ b/public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.ts @@ -52,8 +52,8 @@ const template = ` diff --git a/public/app/features/dashboard/components/UnsavedChangesModal/UnsavedChangesModalCtrl.ts b/public/app/features/dashboard/components/UnsavedChangesModal/UnsavedChangesModalCtrl.ts index cb83a1baa0c..b08a733d877 100644 --- a/public/app/features/dashboard/components/UnsavedChangesModal/UnsavedChangesModalCtrl.ts +++ b/public/app/features/dashboard/components/UnsavedChangesModal/UnsavedChangesModalCtrl.ts @@ -20,7 +20,7 @@ const template = `
- +
diff --git a/public/app/features/dashboard/components/VersionHistory/template.html b/public/app/features/dashboard/components/VersionHistory/template.html index 5a053c46cc6..c7e94682d28 100644 --- a/public/app/features/dashboard/components/VersionHistory/template.html +++ b/public/app/features/dashboard/components/VersionHistory/template.html @@ -64,7 +64,7 @@ Show more versions diff --git a/public/app/features/folders/FolderSettingsPage.tsx b/public/app/features/folders/FolderSettingsPage.tsx index 08bc84775dc..efd2802178f 100644 --- a/public/app/features/folders/FolderSettingsPage.tsx +++ b/public/app/features/folders/FolderSettingsPage.tsx @@ -82,7 +82,7 @@ export class FolderSettingsPage extends PureComponent { />
-
-
diff --git a/public/app/features/manage-dashboards/components/MoveToFolderModal/template.html b/public/app/features/manage-dashboards/components/MoveToFolderModal/template.html index 8a67517aa92..fd805465a55 100644 --- a/public/app/features/manage-dashboards/components/MoveToFolderModal/template.html +++ b/public/app/features/manage-dashboards/components/MoveToFolderModal/template.html @@ -26,7 +26,7 @@
- + Cancel
diff --git a/public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts b/public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts index 0c38a1247f1..44f831af0c2 100644 --- a/public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts +++ b/public/app/features/manage-dashboards/components/UploadDashboard/uploadDashboardDirective.ts @@ -4,7 +4,7 @@ import angular from 'angular'; const template = ` -
@@ -317,8 +317,8 @@
- - + +
diff --git a/public/app/features/users/UsersActionBar.tsx b/public/app/features/users/UsersActionBar.tsx index 28ed4754d01..c7ce8c6f894 100644 --- a/public/app/features/users/UsersActionBar.tsx +++ b/public/app/features/users/UsersActionBar.tsx @@ -65,12 +65,12 @@ export class UsersActionBar extends PureComponent { )}
{canInvite && ( - + Invite )} {externalUserMngLinkUrl && ( - + {externalUserMngLinkName} )} diff --git a/public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap b/public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap index e69accb011b..a73d298581e 100644 --- a/public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap +++ b/public/app/features/users/__snapshots__/UsersActionBar.test.tsx.snap @@ -105,7 +105,7 @@ exports[`Render should show external user management button 1`] = ` className="page-action-bar__spacer" /> @@ -143,7 +143,7 @@ exports[`Render should show invite button 1`] = ` className="page-action-bar__spacer" /> diff --git a/public/app/partials/confirm_modal.html b/public/app/partials/confirm_modal.html index d0b0d260f78..5d80f59a41f 100644 --- a/public/app/partials/confirm_modal.html +++ b/public/app/partials/confirm_modal.html @@ -26,7 +26,7 @@
- +
diff --git a/public/app/partials/edit_json.html b/public/app/partials/edit_json.html index 91552f95d41..fb411e662fc 100644 --- a/public/app/partials/edit_json.html +++ b/public/app/partials/edit_json.html @@ -15,7 +15,7 @@
diff --git a/public/app/partials/reset_password.html b/public/app/partials/reset_password.html index bba38af0235..085cc34d111 100644 --- a/public/app/partials/reset_password.html +++ b/public/app/partials/reset_password.html @@ -16,7 +16,7 @@
-
diff --git a/public/app/partials/signup_invited.html b/public/app/partials/signup_invited.html index c4c08c9ded8..966dba2d352 100644 --- a/public/app/partials/signup_invited.html +++ b/public/app/partials/signup_invited.html @@ -30,7 +30,7 @@
-
diff --git a/public/app/partials/signup_step2.html b/public/app/partials/signup_step2.html index b01c8160b16..5fae3563600 100644 --- a/public/app/partials/signup_step2.html +++ b/public/app/partials/signup_step2.html @@ -37,7 +37,7 @@
- diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 7b0ed869bdc..66943bb733a 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -3,6 +3,18 @@ $theme-name: dark; +// New Colors +// ------------------------- +$sapphire-faint: #041126; +$sapphire-bright: #5794F2; +$sapphire-base: #3274D9; +$sapphire-shade: #1F60C4; +$lobster-base: #E02F44; +$lobster-shade: #C4162A; +$forest-light: #96D98D; +$forest-base: #37872D; +$forest-shade: #19730E; + // Grays // ------------------------- $black: #000; @@ -30,31 +42,29 @@ $white: #fff; // Accent colors // ------------------------- $blue: #33b5e5; -$blue-dark: #005f81; $green: #299c46; -$red: #d44a3a; +$red: $lobster-base; $yellow: #ecbb13; -$pink: #ff4444; $purple: #9933cc; $variable: #32d1df; $orange: #eb7b18; $brand-primary: $orange; -$brand-success: $green; +$brand-success: $forest-base; $brand-warning: $brand-primary; -$brand-danger: $red; +$brand-danger: $lobster-base; -$query-red: #e24d42; -$query-green: #74e680; +$query-red: $lobster-base; +$query-green: $forest-light; $query-purple: #fe85fc; $query-keyword: #66d9ef; $query-orange: $orange; // Status colors // ------------------------- -$online: #10a345; +$online: $forest-base; $warn: #f79520; -$critical: #ed2e18; +$critical: $lobster-base; // Scaffolding // ------------------------- @@ -68,7 +78,6 @@ $text-color-weak: $gray-2; $text-color-faint: $dark-5; $text-color-emphasis: $gray-5; -$text-shadow-strong: 1px 1px 4px $black; $text-shadow-faint: 1px 1px 4px rgb(45, 45, 45); // gradients @@ -87,7 +96,7 @@ $edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 50%, #090909); $link-color: darken($white, 11%); $link-color-disabled: darken($link-color, 30%); $link-hover-color: $white; -$external-link-color: $blue; +$external-link-color: $sapphire-bright; // Typography // ------------------------- @@ -100,8 +109,7 @@ $hr-border-color: $dark-4; // Panel // ------------------------- $panel-bg: #212124; -$panel-border-color: $dark-1; -$panel-border: solid 1px $panel-border-color; +$panel-border: solid 1px $dark-1; $panel-header-hover-bg: $dark-4; $panel-corner: $panel-bg; @@ -110,7 +118,7 @@ $page-header-bg: linear-gradient(90deg, #292a2d, black); $page-header-shadow: inset 0px -4px 14px $dark-2; $page-header-border-color: $dark-4; -$divider-border-color: #555; +$divider-border-color: $gray-1; // Graphite Target Editor $tight-form-bg: $dark-3; @@ -153,29 +161,20 @@ $table-bg-hover: $dark-3; // Buttons // ------------------------- -$btn-primary-bg: #ff6600; -$btn-primary-bg-hl: #bc3e06; +$btn-primary-bg: $sapphire-base; +$btn-primary-bg-hl: $sapphire-shade; -$btn-secondary-bg-hl: lighten($blue-dark, 5%); -$btn-secondary-bg: $blue-dark; +$btn-secondary-bg: $sapphire-base; +$btn-secondary-bg-hl: $sapphire-shade; -$btn-success-bg: $green; -$btn-success-bg-hl: darken($green, 6%); - -$btn-warning-bg: $brand-warning; -$btn-warning-bg-hl: lighten($brand-warning, 8%); - -$btn-danger-bg: $red; -$btn-danger-bg-hl: darken($red, 8%); +$btn-danger-bg: $lobster-base; +$btn-danger-bg-hl: $lobster-shade; $btn-inverse-bg: $dark-3; $btn-inverse-bg-hl: lighten($dark-3, 4%); $btn-inverse-text-color: $link-color; $btn-inverse-text-shadow: 0px 1px 0 rgba(0, 0, 0, 0.1); -$btn-active-bg: $gray-4; -$btn-active-text-color: $blue-dark; - $btn-link-color: $gray-3; $iconContainerBackground: $black; @@ -281,11 +280,11 @@ $toolbar-bg: $input-black; // ------------------------- $warning-text-color: $warn; $error-text-color: #e84d4d; -$success-text-color: #12d95a; -$info-text-color: $blue-dark; +$success-text-color: $forest-light; +//$info-text-color: $blue-dark; $alert-error-bg: linear-gradient(90deg, #d44939, #e0603d); -$alert-success-bg: linear-gradient(90deg, #3aa655, #47b274); +$alert-success-bg: linear-gradient(90deg, $forest-base, $forest-shade); $alert-warning-bg: linear-gradient(90deg, #d44939, #e0603d); $alert-info-bg: linear-gradient(100deg, #1a4552, #00374a); @@ -317,7 +316,7 @@ $tooltipBackgroundBrand: $brand-primary; $checkboxImageUrl: '../img/checkbox.png'; // info box -$info-box-border-color: darken($blue, 12%); +$info-box-border-color: $sapphire-base; // footer $footer-link-color: $gray-2; @@ -348,8 +347,8 @@ $diff-arrow-color: $white; $diff-json-bg: $dark-4; $diff-json-fg: $gray-5; -$diff-json-added: #457740; -$diff-json-deleted: #a04338; +$diff-json-added: $sapphire-shade; +$diff-json-deleted: $lobster-shade; $diff-json-old: #a04338; $diff-json-new: #457740; @@ -360,21 +359,21 @@ $diff-json-changed-num: $text-color; $diff-json-icon: $gray-7; //Submenu -$variable-option-bg: $blue-dark; +$variable-option-bg: $sapphire-shade; //Switch Slider // ------------------------- $switch-bg: $input-bg; $switch-slider-color: $dark-2; $switch-slider-off-bg: $gray-1; -$switch-slider-on-bg: linear-gradient(90deg, $orange, $red); +$switch-slider-on-bg: linear-gradient(90deg, #eb7b18, #d44a3a); $switch-slider-shadow: 0 0 3px black; //Checkbox // ------------------------- $checkbox-bg: $dark-1; $checkbox-border: 1px solid $gray-1; -$checkbox-checked-bg: linear-gradient(0deg, $orange, $red); +$checkbox-checked-bg: linear-gradient(0deg, #eb7b18, #d44a3a); $checkbox-color: $dark-1; //Panel Edit @@ -385,24 +384,24 @@ $panel-editor-side-menu-shadow: drop-shadow(0 0 10px $black); $panel-editor-toolbar-view-bg: $input-black; $panel-editor-viz-item-shadow: 0 0 8px $dark-5; $panel-editor-viz-item-border: 1px solid $dark-5; -$panel-editor-viz-item-shadow-hover: 0 0 4px $blue; -$panel-editor-viz-item-border-hover: 1px solid $blue; +$panel-editor-viz-item-shadow-hover: 0 0 4px $sapphire-shade; +$panel-editor-viz-item-border-hover: 1px solid $sapphire-shade; $panel-editor-viz-item-bg: $input-black; $panel-editor-tabs-line-color: #e3e3e3; -$panel-editor-viz-item-bg-hover: darken($blue, 47%); +$panel-editor-viz-item-bg-hover: $sapphire-faint; $panel-editor-viz-item-bg-hover-active: darken($orange, 45%); $panel-options-group-border: none; $panel-options-group-header-bg: $gray-blue; -$panel-grid-placeholder-bg: darken($blue, 47%); -$panel-grid-placeholder-shadow: 0 0 4px $blue; +$panel-grid-placeholder-bg: $sapphire-faint; +$panel-grid-placeholder-shadow: 0 0 4px $sapphire-shade; // logs $logs-color-unkown: $gray-2; // toggle-group -$button-toggle-group-btn-active-bg: linear-gradient(90deg, $orange, $red); +$button-toggle-group-btn-active-bg: linear-gradient(90deg, #eb7b18, #d44a3a); $button-toggle-group-btn-active-shadow: inset 0 0 4px $black; $button-toggle-group-btn-seperator-border: 1px solid $page-bg; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 10c074e1481..85cb047be25 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -7,6 +7,19 @@ $theme-name: light; +// New Colors +// ------------------------- +$sapphire-faint: #F5F9FF; +$sapphire-light: #A8CAFF; +$sapphire-base: #3274D9; +$sapphire-shade: #1F60C4; +$lobster-base: #E02F44; +$lobster-shade: #C4162A; +$green-base: #37872D; +$green-shade: #19730E; +$purple-shade: #8F3BB8; +$yellow-base: #F2CC0C; + // Grays // ------------------------- $black: #000; @@ -31,32 +44,29 @@ $white: #fff; // Accent colors // ------------------------- $blue: #0083b3; -$blue-dark: #005f81; -$blue-light: #00a8e6; $green: #3aa655; -$red: #d44939; +$red: $lobster-base; $yellow: #ff851b; $orange: #ff7941; -$pink: #e671b8; $purple: #9954bb; -$variable: $blue; +$variable: $purple-shade; $brand-primary: $orange; $brand-success: $green; $brand-warning: $orange; -$brand-danger: $red; +$brand-danger: $lobster-base; -$query-red: $red; +$query-red: $lobster-base; $query-green: $green; $query-purple: $purple; $query-orange: $orange; -$query-keyword: $blue; +$query-keyword: $sapphire-base; // Status colors // ------------------------- -$online: #01a64f; +$online: $green-shade; $warn: #f79520; -$critical: #ec2128; +$critical: $lobster-shade; // Scaffolding // ------------------------- @@ -70,9 +80,7 @@ $text-color-weak: $gray-2; $text-color-faint: $gray-4; $text-color-emphasis: $dark-5; -$text-shadow-strong: none; $text-shadow-faint: none; -$textShadow: none; // gradients $brand-gradient: linear-gradient(to right, rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%); @@ -84,7 +92,7 @@ $edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%); $link-color: $gray-1; $link-color-disabled: lighten($link-color, 30%); $link-hover-color: darken($link-color, 20%); -$external-link-color: $blue-light; +$external-link-color: $sapphire-shade; // Typography // ------------------------- @@ -98,8 +106,7 @@ $hr-border-color: $dark-3 !default; // ------------------------- $panel-bg: $white; -$panel-border-color: $gray-5; -$panel-border: solid 1px $panel-border-color; +$panel-border: solid 1px $gray-5; $panel-header-hover-bg: $gray-6; $panel-corner: $gray-4; @@ -150,29 +157,20 @@ $scrollbarBorder: $gray-4; // Buttons // ------------------------- -$btn-primary-bg: $brand-primary; -$btn-primary-bg-hl: lighten($brand-primary, 8%); +$btn-primary-bg: $sapphire-base; +$btn-primary-bg-hl: $sapphire-shade; -$btn-secondary-bg: $blue; -$btn-secondary-bg-hl: lighten($blue, 4%); +$btn-secondary-bg: rgba(0,0,0,0); +$btn-secondary-bg-hl: rgba(0,0,0,0); -$btn-success-bg: lighten($green, 3%); -$btn-success-bg-hl: darken($green, 3%); - -$btn-warning-bg: lighten($orange, 3%); -$btn-warning-bg-hl: darken($orange, 3%); - -$btn-danger-bg: lighten($red, 3%); -$btn-danger-bg-hl: darken($red, 3%); +$btn-danger-bg: $lobster-base; +$btn-danger-bg-hl: $lobster-shade; $btn-inverse-bg: $gray-6; $btn-inverse-bg-hl: darken($gray-6, 5%); $btn-inverse-text-color: $gray-1; $btn-inverse-text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4); -$btn-active-bg: $white; -$btn-active-text-color: $blue; - $btn-link-color: $gray-1; $btn-divider-left: $gray-4; @@ -189,8 +187,8 @@ $input-bg-disabled: $gray-5; $input-color: $dark-3; $input-border-color: $gray-5; $input-box-shadow: none; -$input-border-focus: $blue !default; -$input-box-shadow-focus: $blue !default; +$input-border-focus: $sapphire-light !default; +$input-box-shadow-focus: $sapphire-light !default; $input-color-placeholder: $gray-4 !default; $input-label-bg: $gray-5; $input-label-border-color: $gray-5; @@ -285,14 +283,14 @@ $navbar-button-border: $gray-4; // Form states and alerts // ------------------------- $warning-text-color: lighten($orange, 10%); -$error-text-color: lighten($red, 10%); +$error-text-color: $lobster-shade; $success-text-color: lighten($green, 10%); -$info-text-color: $blue; +$info-text-color: $sapphire-shade; -$alert-error-bg: linear-gradient(90deg, #d44939, #e04d3d); -$alert-success-bg: linear-gradient(90deg, #3aa655, #47b274); -$alert-warning-bg: linear-gradient(90deg, #d44939, #e04d3d); -$alert-info-bg: $blue; +$alert-error-bg: linear-gradient(90deg, $lobster-base, $lobster-shade); +$alert-success-bg: linear-gradient(90deg, $green-base, $green-shade); +$alert-warning-bg: linear-gradient(90deg, $lobster-base, $lobster-shade); +$alert-info-bg: $sapphire-base; // popover $popover-bg: $page-bg; @@ -300,7 +298,7 @@ $popover-color: $text-color; $popover-border-color: $gray-5; $popover-shadow: 0 0 20px $white; -$popover-help-bg: $blue; +$popover-help-bg: $sapphire-base; $popover-help-color: $gray-6; $popover-error-bg: $btn-danger-bg; @@ -321,7 +319,7 @@ $tooltipBackgroundBrand: $brand-primary; $checkboxImageUrl: '../img/checkbox_white.png'; // info box -$info-box-border-color: lighten($blue, 20%); +$info-box-border-color: $sapphire-base; // footer $footer-link-color: $gray-3; @@ -332,16 +330,16 @@ $footer-link-hover: $dark-5; // json explorer $json-explorer-default-color: black; $json-explorer-string-color: green; -$json-explorer-number-color: blue; -$json-explorer-boolean-color: red; +$json-explorer-number-color: $sapphire-base; +$json-explorer-boolean-color: $lobster-base; $json-explorer-null-color: #855a00; $json-explorer-undefined-color: rgb(202, 11, 105); $json-explorer-function-color: #ff20ed; $json-explorer-rotate-time: 100ms; $json-explorer-toggler-opacity: 0.6; -$json-explorer-bracket-color: blue; +$json-explorer-bracket-color: $sapphire-base; $json-explorer-key-color: #00008b; -$json-explorer-url-color: blue; +$json-explorer-url-color: $sapphire-base; // Changelog and diff // ------------------------- @@ -355,34 +353,34 @@ $diff-arrow-color: $dark-3; $diff-group-bg: $gray-7; $diff-json-bg: $gray-5; -$diff-json-fg: $gray-2; +$diff-json-fg: $gray-1; -$diff-json-added: lighten(desaturate($green, 30%), 10%); -$diff-json-deleted: desaturate($red, 35%); +$diff-json-added: $sapphire-shade; +$diff-json-deleted: $lobster-shade; $diff-json-old: #5a372a; $diff-json-new: #664e33; -$diff-json-changed-fg: $gray-6; +$diff-json-changed-fg: $gray-7; $diff-json-changed-num: $gray-4; $diff-json-icon: $gray-4; //Submenu -$variable-option-bg: $blue-light; +$variable-option-bg: $sapphire-light; //Switch Slider // ------------------------- $switch-bg: $white; $switch-slider-color: $gray-7; $switch-slider-off-bg: $gray-5; -$switch-slider-on-bg: linear-gradient(90deg, $yellow, $red); +$switch-slider-on-bg: linear-gradient(90deg, #FF9830, #E55400); $switch-slider-shadow: 0 0 3px $dark-5; //Checkbox // ------------------------- $checkbox-bg: $gray-6; $checkbox-border: 1px solid $gray-3; -$checkbox-checked-bg: linear-gradient(0deg, $yellow, $red); +$checkbox-checked-bg: linear-gradient(0deg, #FF9830, #E55400); $checkbox-color: $gray-7; //Panel Edit @@ -393,18 +391,18 @@ $panel-editor-side-menu-shadow: drop-shadow(0 0 2px $gray-3); $panel-editor-toolbar-view-bg: $white; $panel-editor-viz-item-shadow: 0 0 4px $gray-3; $panel-editor-viz-item-border: 1px solid $gray-3; -$panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light; -$panel-editor-viz-item-border-hover: 1px solid $blue-light; +$panel-editor-viz-item-shadow-hover: 0 0 4px $sapphire-light; +$panel-editor-viz-item-border-hover: 1px solid $sapphire-light; $panel-editor-viz-item-bg: $white; $panel-editor-tabs-line-color: $dark-5; -$panel-editor-viz-item-bg-hover: lighten($blue, 62%); +$panel-editor-viz-item-bg-hover: $sapphire-faint; $panel-editor-viz-item-bg-hover-active: lighten($orange, 34%); $panel-options-group-border: none; $panel-options-group-header-bg: $gray-5; -$panel-grid-placeholder-bg: lighten($blue, 62%); -$panel-grid-placeholder-shadow: 0 0 4px $blue-light; +$panel-grid-placeholder-bg: $sapphire-faint; +$panel-grid-placeholder-shadow: 0 0 4px $sapphire-light; // logs $logs-color-unkown: $gray-5; diff --git a/public/sass/base/_type.scss b/public/sass/base/_type.scss index 1a005b0d511..e5a20a80659 100644 --- a/public/sass/base/_type.scss +++ b/public/sass/base/_type.scss @@ -59,13 +59,13 @@ a.text-error:focus { color: darken($error-text-color, 10%); } -.text-info { +/*.text-info { color: $info-text-color; } a.text-info:hover, a.text-info:focus { color: darken($info-text-color, 10%); -} +}*/ .text-success { color: $success-text-color; diff --git a/public/sass/components/_buttons.scss b/public/sass/components/_buttons.scss index 4e032d7b9d1..84e2665f582 100644 --- a/public/sass/components/_buttons.scss +++ b/public/sass/components/_buttons.scss @@ -89,35 +89,12 @@ .btn-secondary { @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl); } -// Warning appears are orange -.btn-warning { - @include buttonBackground($btn-warning-bg, $btn-warning-bg-hl); -} // Danger and error appear as red .btn-danger { @include buttonBackground($btn-danger-bg, $btn-danger-bg-hl); } -// Success appears as green -.btn-success { - @include buttonBackground($btn-success-bg, $btn-success-bg-hl); - &--processing { - @include button-outline-variant($gray-1); - @include box-shadow(none); - cursor: default; - - &:hover, - &:active, - &:active:hover, - &:focus, - &:disabled { - color: $gray-1; - background-color: transparent; - border-color: $gray-1; - } - } -} // Info appears as a neutral blue .btn-secondary { @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl); @@ -138,20 +115,15 @@ @include button-outline-variant($btn-primary-bg); } .btn-outline-secondary { - @include button-outline-variant($btn-secondary-bg); + @include button-outline-variant($btn-secondary-bg-hl); } .btn-outline-inverse { @include button-outline-variant($btn-inverse-bg); } -.btn-outline-success { - @include button-outline-variant($btn-success-bg); -} -.btn-outline-warning { - @include button-outline-variant($btn-warning-bg); -} .btn-outline-danger { - @include button-outline-variant($btn-danger-bg); + @include button-outline-variant(green); } + .btn-outline-disabled { @include button-outline-variant($gray-1); @include box-shadow(none); diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index b3733b694fc..088dd72f37b 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -158,7 +158,7 @@ } &--primary { - @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl); + @include buttonBackground($btn-primary-bg, $btn-primary-bg-hl); } } diff --git a/public/sass/components/_panel_gettingstarted.scss b/public/sass/components/_panel_gettingstarted.scss index 5bbc4ba29ca..b51bd3a9ef9 100644 --- a/public/sass/components/_panel_gettingstarted.scss +++ b/public/sass/components/_panel_gettingstarted.scss @@ -118,7 +118,7 @@ $path-position: $marker-size-half - ($path-height / 2); .progress-step-cta { @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $font-size-sm, $btn-border-radius); - @include buttonBackground($btn-success-bg, $btn-success-bg-hl); + @include buttonBackground($btn-primary-bg, $btn-primary-bg-hl); display: none; } diff --git a/public/vendor/angular-ui/ui-bootstrap-tpls.js b/public/vendor/angular-ui/ui-bootstrap-tpls.js index 87120b66ce1..ad6f3b4b4bc 100644 --- a/public/vendor/angular-ui/ui-bootstrap-tpls.js +++ b/public/vendor/angular-ui/ui-bootstrap-tpls.js @@ -1245,7 +1245,7 @@ angular.module("template/datepicker/popup.html", []).run(["$templateCache", func " \n" + " \n" + " \n" + - " \n" + + " \n" + " \n" + "\n" + ""); From 181b4f9e80fbe7a6aa39c957dc79c863dcdbf11a Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 5 Feb 2019 14:39:24 +0300 Subject: [PATCH 019/119] azuremonitor: improve autocomplete experence --- .../editor/KustoQueryField.tsx | 24 ++++++++++--------- .../editor/query_field.tsx | 6 ++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx index bbe34b8f46a..0a484794e8f 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx @@ -65,7 +65,7 @@ export default class KustoQueryField extends QueryField { this.fetchSchema(); } - onTypeahead = () => { + onTypeahead = (force = false) => { const selection = window.getSelection(); if (selection.anchorNode) { const wrapperNode = selection.anchorNode.parentElement; @@ -140,14 +140,8 @@ export default class KustoQueryField extends QueryField { suggestionGroups = this.getTableSuggestions(db); prefix = prefix.replace('.', ''); - // built-in - } else if (prefix && !wrapperClasses.contains('argument')) { - if (modelPrefix.match(/\s$/i)) { - prefix = ''; - } - typeaheadContext = 'context-builtin'; - suggestionGroups = this.getKeywordSuggestions(); - } else if (Plain.serialize(this.state.value) === '') { + // new + } else if (normalizeQuery(Plain.serialize(this.state.value)).match(/^\s*\w*$/i)) { typeaheadContext = 'context-new'; if (this.schema) { suggestionGroups = this.getInitialSuggestions(); @@ -156,7 +150,15 @@ export default class KustoQueryField extends QueryField { setTimeout(this.onTypeahead, 0); return; } - } else { + + // built-in + } else if (prefix && !wrapperClasses.contains('argument')) { + if (modelPrefix.match(/\s$/i)) { + prefix = ''; + } + typeaheadContext = 'context-builtin'; + suggestionGroups = this.getKeywordSuggestions(); + } else if (force === true) { typeaheadContext = 'context-builtin'; if (modelPrefix.match(/\s$/i)) { prefix = ''; @@ -181,7 +183,7 @@ export default class KustoQueryField extends QueryField { .filter(group => group.items.length > 0); // console.log('onTypeahead', selection.anchorNode, wrapperClasses, text, offset, prefix, typeaheadContext); - console.log('onTypeahead', modelPrefix, prefix, typeaheadContext); + // console.log('onTypeahead', prefix, typeaheadContext); this.setState({ typeaheadPrefix: prefix, diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx index 0acd53cabff..e62337c4982 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx @@ -104,11 +104,11 @@ class QueryField extends React.Component { const changed = value.document !== this.state.value.document; this.setState({ value }, () => { if (changed) { + // call typeahead only if query changed + window.requestAnimationFrame(this.onTypeahead); this.onChangeQuery(); } }); - - window.requestAnimationFrame(this.onTypeahead); }; request = (url?) => { @@ -143,7 +143,7 @@ class QueryField extends React.Component { case ' ': { if (event.ctrlKey) { event.preventDefault(); - this.onTypeahead(); + this.onTypeahead(true); return true; } break; From 4caea91164bed003b589476e77ceeae64949b568 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 6 Feb 2019 13:00:26 +0300 Subject: [PATCH 020/119] azuremonitor: fix autocomplete menu height --- .../editor/query_field.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx index e62337c4982..adab7fc5414 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx @@ -105,7 +105,7 @@ class QueryField extends React.Component { this.setState({ value }, () => { if (changed) { // call typeahead only if query changed - window.requestAnimationFrame(this.onTypeahead); + requestAnimationFrame(() => this.onTypeahead()); this.onChangeQuery(); } }); @@ -283,12 +283,18 @@ class QueryField extends React.Component { const rect = node.parentElement.getBoundingClientRect(); const scrollX = window.scrollX; const scrollY = window.scrollY; + const screenHeight = window.innerHeight; + + const menuLeft = rect.left + scrollX - 2; + const menuTop = rect.top + scrollY + rect.height + 4; + const menuHeight = screenHeight - menuTop - 10; // Write DOM requestAnimationFrame(() => { menu.style.opacity = 1; - menu.style.top = `${rect.top + scrollY + rect.height + 4}px`; - menu.style.left = `${rect.left + scrollX - 2}px`; + menu.style.top = `${menuTop}px`; + menu.style.left = `${menuLeft}px`; + menu.style.maxHeight = `${menuHeight}px`; }); } }; From e4446f0340eef3edf3e6d54f50364fdc821732cc Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 6 Feb 2019 13:52:35 +0300 Subject: [PATCH 021/119] azuremonitor: improve autocomplete UX --- .../editor/KustoQueryField.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx index 0a484794e8f..2a578176674 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/KustoQueryField.tsx @@ -65,7 +65,7 @@ export default class KustoQueryField extends QueryField { this.fetchSchema(); } - onTypeahead = (force = false) => { + onTypeahead = (force?: boolean) => { const selection = window.getSelection(); if (selection.anchorNode) { const wrapperNode = selection.anchorNode.parentElement; @@ -152,14 +152,17 @@ export default class KustoQueryField extends QueryField { } // built-in - } else if (prefix && !wrapperClasses.contains('argument')) { + } else if (prefix && !wrapperClasses.contains('argument') && !force) { + // Use only last typed word as a prefix for searching if (modelPrefix.match(/\s$/i)) { prefix = ''; + return; } + prefix = getLastWord(prefix); typeaheadContext = 'context-builtin'; suggestionGroups = this.getKeywordSuggestions(); } else if (force === true) { - typeaheadContext = 'context-builtin'; + typeaheadContext = 'context-builtin-forced'; if (modelPrefix.match(/\s$/i)) { prefix = ''; } @@ -183,7 +186,7 @@ export default class KustoQueryField extends QueryField { .filter(group => group.items.length > 0); // console.log('onTypeahead', selection.anchorNode, wrapperClasses, text, offset, prefix, typeaheadContext); - // console.log('onTypeahead', prefix, typeaheadContext); + // console.log('onTypeahead', prefix, typeaheadContext, force); this.setState({ typeaheadPrefix: prefix, @@ -422,3 +425,12 @@ function normalizeQuery(query: string): string { normalizedQuery = normalizedQuery.replace('\n', ' '); return normalizedQuery; } + +function getLastWord(str: string): string { + const lastWordPattern = /(?:.*\s)?([^\s]+\s*)$/gi; + const match = lastWordPattern.exec(str); + if (match && match.length > 1) { + return match[1]; + } + return ''; +} From 6e7941d39603aabc12df50e7f74dd27f1f95e393 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 7 Feb 2019 13:13:01 +0100 Subject: [PATCH 022/119] moves usage stats sender to new package --- pkg/infra/usagestats/service.go | 54 ++++++ pkg/infra/usagestats/usage_stats.go | 168 ++++++++++++++++ .../usagestats/usage_stats_test.go} | 21 +- pkg/metrics/metrics.go | 181 +----------------- pkg/metrics/service.go | 22 +-- pkg/metrics/settings.go | 4 - 6 files changed, 247 insertions(+), 203 deletions(-) create mode 100644 pkg/infra/usagestats/service.go create mode 100644 pkg/infra/usagestats/usage_stats.go rename pkg/{metrics/metrics_test.go => infra/usagestats/usage_stats_test.go} (94%) diff --git a/pkg/infra/usagestats/service.go b/pkg/infra/usagestats/service.go new file mode 100644 index 00000000000..f853c03302d --- /dev/null +++ b/pkg/infra/usagestats/service.go @@ -0,0 +1,54 @@ +package usagestats + +import ( + "context" + "time" + + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/social" + + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/setting" +) + +var metricsLogger log.Logger = log.New("metrics") + +func init() { + registry.RegisterService(&UsageStatsService{}) +} + +type UsageStatsService struct { + Cfg *setting.Cfg `inject:""` + TokenService *auth.UserAuthTokenService `inject:""` + Bus bus.Bus `inject:""` + + oauthProviders map[string]bool +} + +func (uss *UsageStatsService) Init() error { + + uss.oauthProviders = social.GetOAuthProviders(uss.Cfg) + return nil +} + +func (uss *UsageStatsService) Run(ctx context.Context) error { + uss.updateTotalStats() + + onceEveryDayTick := time.NewTicker(time.Hour * 24) + everyMinuteTicker := time.NewTicker(time.Minute) + defer onceEveryDayTick.Stop() + defer everyMinuteTicker.Stop() + + for { + select { + case <-onceEveryDayTick.C: + uss.sendUsageStats(uss.oauthProviders) + case <-everyMinuteTicker.C: + uss.updateTotalStats() + case <-ctx.Done(): + return ctx.Err() + } + } +} diff --git a/pkg/infra/usagestats/usage_stats.go b/pkg/infra/usagestats/usage_stats.go new file mode 100644 index 00000000000..b0dc52ccd8b --- /dev/null +++ b/pkg/infra/usagestats/usage_stats.go @@ -0,0 +1,168 @@ +package usagestats + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "runtime" + "strings" + "time" + + "github.com/grafana/grafana/pkg/metrics" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/setting" +) + +var usageStatsURL = "https://stats.grafana.org/grafana-usage-report" + +func (uss *UsageStatsService) sendUsageStats(oauthProviders map[string]bool) { + if !setting.ReportingEnabled { + return + } + + metricsLogger.Debug(fmt.Sprintf("Sending anonymous usage stats to %s", usageStatsURL)) + + version := strings.Replace(setting.BuildVersion, ".", "_", -1) + + metrics := map[string]interface{}{} + report := map[string]interface{}{ + "version": version, + "metrics": metrics, + "os": runtime.GOOS, + "arch": runtime.GOARCH, + "edition": getEdition(), + "packaging": setting.Packaging, + } + + statsQuery := models.GetSystemStatsQuery{} + if err := uss.Bus.Dispatch(&statsQuery); err != nil { + metricsLogger.Error("Failed to get system stats", "error", err) + return + } + + metrics["stats.dashboards.count"] = statsQuery.Result.Dashboards + metrics["stats.users.count"] = statsQuery.Result.Users + metrics["stats.orgs.count"] = statsQuery.Result.Orgs + metrics["stats.playlist.count"] = statsQuery.Result.Playlists + metrics["stats.plugins.apps.count"] = len(plugins.Apps) + metrics["stats.plugins.panels.count"] = len(plugins.Panels) + metrics["stats.plugins.datasources.count"] = len(plugins.DataSources) + metrics["stats.alerts.count"] = statsQuery.Result.Alerts + metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers + metrics["stats.datasources.count"] = statsQuery.Result.Datasources + metrics["stats.stars.count"] = statsQuery.Result.Stars + metrics["stats.folders.count"] = statsQuery.Result.Folders + metrics["stats.dashboard_permissions.count"] = statsQuery.Result.DashboardPermissions + metrics["stats.folder_permissions.count"] = statsQuery.Result.FolderPermissions + metrics["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards + metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots + metrics["stats.teams.count"] = statsQuery.Result.Teams + + dsStats := models.GetDataSourceStatsQuery{} + if err := uss.Bus.Dispatch(&dsStats); err != nil { + metricsLogger.Error("Failed to get datasource stats", "error", err) + return + } + + // send counters for each data source + // but ignore any custom data sources + // as sending that name could be sensitive information + dsOtherCount := 0 + for _, dsStat := range dsStats.Result { + if models.IsKnownDataSourcePlugin(dsStat.Type) { + metrics["stats.ds."+dsStat.Type+".count"] = dsStat.Count + } else { + dsOtherCount += dsStat.Count + } + } + metrics["stats.ds.other.count"] = dsOtherCount + + metrics["stats.packaging."+setting.Packaging+".count"] = 1 + + dsAccessStats := models.GetDataSourceAccessStatsQuery{} + if err := uss.Bus.Dispatch(&dsAccessStats); err != nil { + metricsLogger.Error("Failed to get datasource access stats", "error", err) + return + } + + // send access counters for each data source + // but ignore any custom data sources + // as sending that name could be sensitive information + dsAccessOtherCount := make(map[string]int64) + for _, dsAccessStat := range dsAccessStats.Result { + if dsAccessStat.Access == "" { + continue + } + + access := strings.ToLower(dsAccessStat.Access) + + if models.IsKnownDataSourcePlugin(dsAccessStat.Type) { + metrics["stats.ds_access."+dsAccessStat.Type+"."+access+".count"] = dsAccessStat.Count + } else { + old := dsAccessOtherCount[access] + dsAccessOtherCount[access] = old + dsAccessStat.Count + } + } + + for access, count := range dsAccessOtherCount { + metrics["stats.ds_access.other."+access+".count"] = count + } + + anStats := models.GetAlertNotifierUsageStatsQuery{} + if err := uss.Bus.Dispatch(&anStats); err != nil { + metricsLogger.Error("Failed to get alert notification stats", "error", err) + return + } + + for _, stats := range anStats.Result { + metrics["stats.alert_notifiers."+stats.Type+".count"] = stats.Count + } + + authTypes := map[string]bool{} + authTypes["anonymous"] = setting.AnonymousEnabled + authTypes["basic_auth"] = setting.BasicAuthEnabled + authTypes["ldap"] = setting.LdapEnabled + authTypes["auth_proxy"] = setting.AuthProxyEnabled + + for provider, enabled := range oauthProviders { + authTypes["oauth_"+provider] = enabled + } + + for authType, enabled := range authTypes { + enabledValue := 0 + if enabled { + enabledValue = 1 + } + metrics["stats.auth_enabled."+authType+".count"] = enabledValue + } + + out, _ := json.MarshalIndent(report, "", " ") + data := bytes.NewBuffer(out) + + client := http.Client{Timeout: 5 * time.Second} + go client.Post(usageStatsURL, "application/json", data) +} + +func (uss *UsageStatsService) updateTotalStats() { + statsQuery := models.GetSystemStatsQuery{} + if err := uss.Bus.Dispatch(&statsQuery); err != nil { + metricsLogger.Error("Failed to get system stats", "error", err) + return + } + + metrics.M_StatTotal_Dashboards.Set(float64(statsQuery.Result.Dashboards)) + metrics.M_StatTotal_Users.Set(float64(statsQuery.Result.Users)) + metrics.M_StatActive_Users.Set(float64(statsQuery.Result.ActiveUsers)) + metrics.M_StatTotal_Playlists.Set(float64(statsQuery.Result.Playlists)) + metrics.M_StatTotal_Orgs.Set(float64(statsQuery.Result.Orgs)) +} + +func getEdition() string { + if setting.IsEnterprise { + return "enterprise" + } else { + return "oss" + } +} diff --git a/pkg/metrics/metrics_test.go b/pkg/infra/usagestats/usage_stats_test.go similarity index 94% rename from pkg/metrics/metrics_test.go rename to pkg/infra/usagestats/usage_stats_test.go index c27d6f64b8c..dd45e96f256 100644 --- a/pkg/metrics/metrics_test.go +++ b/pkg/infra/usagestats/usage_stats_test.go @@ -1,4 +1,4 @@ -package metrics +package usagestats import ( "bytes" @@ -21,8 +21,13 @@ import ( func TestMetrics(t *testing.T) { Convey("Test send usage stats", t, func() { + uss := &UsageStatsService{ + Bus: bus.New(), + } + var getSystemStatsQuery *models.GetSystemStatsQuery - bus.AddHandler("test", func(query *models.GetSystemStatsQuery) error { + uss.Bus.AddHandler(func(query *models.GetSystemStatsQuery) error { + query.Result = &models.SystemStats{ Dashboards: 1, Datasources: 2, @@ -44,7 +49,7 @@ func TestMetrics(t *testing.T) { }) var getDataSourceStatsQuery *models.GetDataSourceStatsQuery - bus.AddHandler("test", func(query *models.GetDataSourceStatsQuery) error { + uss.Bus.AddHandler(func(query *models.GetDataSourceStatsQuery) error { query.Result = []*models.DataSourceStats{ { Type: models.DS_ES, @@ -68,7 +73,7 @@ func TestMetrics(t *testing.T) { }) var getDataSourceAccessStatsQuery *models.GetDataSourceAccessStatsQuery - bus.AddHandler("test", func(query *models.GetDataSourceAccessStatsQuery) error { + uss.Bus.AddHandler(func(query *models.GetDataSourceAccessStatsQuery) error { query.Result = []*models.DataSourceAccessStats{ { Type: models.DS_ES, @@ -116,7 +121,7 @@ func TestMetrics(t *testing.T) { }) var getAlertNotifierUsageStatsQuery *models.GetAlertNotifierUsageStatsQuery - bus.AddHandler("test", func(query *models.GetAlertNotifierUsageStatsQuery) error { + uss.Bus.AddHandler(func(query *models.GetAlertNotifierUsageStatsQuery) error { query.Result = []*models.NotifierUsageStats{ { Type: "slack", @@ -155,11 +160,11 @@ func TestMetrics(t *testing.T) { "grafana_com": true, } - sendUsageStats(oauthProviders) + uss.sendUsageStats(oauthProviders) Convey("Given reporting not enabled and sending usage stats", func() { setting.ReportingEnabled = false - sendUsageStats(oauthProviders) + uss.sendUsageStats(oauthProviders) Convey("Should not gather stats or call http endpoint", func() { So(getSystemStatsQuery, ShouldBeNil) @@ -179,7 +184,7 @@ func TestMetrics(t *testing.T) { setting.Packaging = "deb" wg.Add(1) - sendUsageStats(oauthProviders) + uss.sendUsageStats(oauthProviders) Convey("Should gather stats and call http endpoint", func() { if waitTimeout(&wg, 2*time.Second) { diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 326514a9687..718a63ee768 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -1,17 +1,8 @@ package metrics import ( - "bytes" - "encoding/json" - "net/http" "runtime" - "strings" - "time" - "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/client_golang/prometheus" ) @@ -68,23 +59,6 @@ var ( grafanaBuildVersion *prometheus.GaugeVec ) -func newCounterVecStartingAtZero(opts prometheus.CounterOpts, labels []string, labelValues ...string) *prometheus.CounterVec { - counter := prometheus.NewCounterVec(opts, labels) - - for _, label := range labelValues { - counter.WithLabelValues(label).Add(0) - } - - return counter -} - -func newCounterStartingAtZero(opts prometheus.CounterOpts, labelValues ...string) prometheus.Counter { - counter := prometheus.NewCounter(opts) - counter.Add(0) - - return counter -} - func init() { M_Instance_Start = prometheus.NewCounter(prometheus.CounterOpts{ Name: "instance_start_total", @@ -362,154 +336,19 @@ func initMetricVars() { } -func updateTotalStats() { - statsQuery := models.GetSystemStatsQuery{} - if err := bus.Dispatch(&statsQuery); err != nil { - metricsLogger.Error("Failed to get system stats", "error", err) - return +func newCounterVecStartingAtZero(opts prometheus.CounterOpts, labels []string, labelValues ...string) *prometheus.CounterVec { + counter := prometheus.NewCounterVec(opts, labels) + + for _, label := range labelValues { + counter.WithLabelValues(label).Add(0) } - M_StatTotal_Dashboards.Set(float64(statsQuery.Result.Dashboards)) - M_StatTotal_Users.Set(float64(statsQuery.Result.Users)) - M_StatActive_Users.Set(float64(statsQuery.Result.ActiveUsers)) - M_StatTotal_Playlists.Set(float64(statsQuery.Result.Playlists)) - M_StatTotal_Orgs.Set(float64(statsQuery.Result.Orgs)) + return counter } -var usageStatsURL = "https://stats.grafana.org/grafana-usage-report" +func newCounterStartingAtZero(opts prometheus.CounterOpts, labelValues ...string) prometheus.Counter { + counter := prometheus.NewCounter(opts) + counter.Add(0) -func getEdition() string { - if setting.IsEnterprise { - return "enterprise" - } else { - return "oss" - } -} - -func sendUsageStats(oauthProviders map[string]bool) { - if !setting.ReportingEnabled { - return - } - - metricsLogger.Debug("Sending anonymous usage stats to stats.grafana.org") - - version := strings.Replace(setting.BuildVersion, ".", "_", -1) - - metrics := map[string]interface{}{} - report := map[string]interface{}{ - "version": version, - "metrics": metrics, - "os": runtime.GOOS, - "arch": runtime.GOARCH, - "edition": getEdition(), - "packaging": setting.Packaging, - } - - statsQuery := models.GetSystemStatsQuery{} - if err := bus.Dispatch(&statsQuery); err != nil { - metricsLogger.Error("Failed to get system stats", "error", err) - return - } - - metrics["stats.dashboards.count"] = statsQuery.Result.Dashboards - metrics["stats.users.count"] = statsQuery.Result.Users - metrics["stats.orgs.count"] = statsQuery.Result.Orgs - metrics["stats.playlist.count"] = statsQuery.Result.Playlists - metrics["stats.plugins.apps.count"] = len(plugins.Apps) - metrics["stats.plugins.panels.count"] = len(plugins.Panels) - metrics["stats.plugins.datasources.count"] = len(plugins.DataSources) - metrics["stats.alerts.count"] = statsQuery.Result.Alerts - metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers - metrics["stats.datasources.count"] = statsQuery.Result.Datasources - metrics["stats.stars.count"] = statsQuery.Result.Stars - metrics["stats.folders.count"] = statsQuery.Result.Folders - metrics["stats.dashboard_permissions.count"] = statsQuery.Result.DashboardPermissions - metrics["stats.folder_permissions.count"] = statsQuery.Result.FolderPermissions - metrics["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards - metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots - metrics["stats.teams.count"] = statsQuery.Result.Teams - - dsStats := models.GetDataSourceStatsQuery{} - if err := bus.Dispatch(&dsStats); err != nil { - metricsLogger.Error("Failed to get datasource stats", "error", err) - return - } - - // send counters for each data source - // but ignore any custom data sources - // as sending that name could be sensitive information - dsOtherCount := 0 - for _, dsStat := range dsStats.Result { - if models.IsKnownDataSourcePlugin(dsStat.Type) { - metrics["stats.ds."+dsStat.Type+".count"] = dsStat.Count - } else { - dsOtherCount += dsStat.Count - } - } - metrics["stats.ds.other.count"] = dsOtherCount - - metrics["stats.packaging."+setting.Packaging+".count"] = 1 - - dsAccessStats := models.GetDataSourceAccessStatsQuery{} - if err := bus.Dispatch(&dsAccessStats); err != nil { - metricsLogger.Error("Failed to get datasource access stats", "error", err) - return - } - - // send access counters for each data source - // but ignore any custom data sources - // as sending that name could be sensitive information - dsAccessOtherCount := make(map[string]int64) - for _, dsAccessStat := range dsAccessStats.Result { - if dsAccessStat.Access == "" { - continue - } - - access := strings.ToLower(dsAccessStat.Access) - - if models.IsKnownDataSourcePlugin(dsAccessStat.Type) { - metrics["stats.ds_access."+dsAccessStat.Type+"."+access+".count"] = dsAccessStat.Count - } else { - old := dsAccessOtherCount[access] - dsAccessOtherCount[access] = old + dsAccessStat.Count - } - } - - for access, count := range dsAccessOtherCount { - metrics["stats.ds_access.other."+access+".count"] = count - } - - anStats := models.GetAlertNotifierUsageStatsQuery{} - if err := bus.Dispatch(&anStats); err != nil { - metricsLogger.Error("Failed to get alert notification stats", "error", err) - return - } - - for _, stats := range anStats.Result { - metrics["stats.alert_notifiers."+stats.Type+".count"] = stats.Count - } - - authTypes := map[string]bool{} - authTypes["anonymous"] = setting.AnonymousEnabled - authTypes["basic_auth"] = setting.BasicAuthEnabled - authTypes["ldap"] = setting.LdapEnabled - authTypes["auth_proxy"] = setting.AuthProxyEnabled - - for provider, enabled := range oauthProviders { - authTypes["oauth_"+provider] = enabled - } - - for authType, enabled := range authTypes { - enabledValue := 0 - if enabled { - enabledValue = 1 - } - metrics["stats.auth_enabled."+authType+".count"] = enabledValue - } - - out, _ := json.MarshalIndent(report, "", " ") - data := bytes.NewBuffer(out) - - client := http.Client{Timeout: 5 * time.Second} - go client.Post(usageStatsURL, "application/json", data) + return counter } diff --git a/pkg/metrics/service.go b/pkg/metrics/service.go index d2c0c815da9..44b83187cac 100644 --- a/pkg/metrics/service.go +++ b/pkg/metrics/service.go @@ -2,7 +2,6 @@ package metrics import ( "context" - "time" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/metrics/graphitebridge" @@ -30,7 +29,6 @@ type InternalMetricsService struct { intervalSeconds int64 graphiteCfg *graphitebridge.Config - oauthProviders map[string]bool } func (im *InternalMetricsService) Init() error { @@ -50,22 +48,6 @@ func (im *InternalMetricsService) Run(ctx context.Context) error { M_Instance_Start.Inc() - // set the total stats gauges before we publishing metrics - updateTotalStats() - - onceEveryDayTick := time.NewTicker(time.Hour * 24) - everyMinuteTicker := time.NewTicker(time.Minute) - defer onceEveryDayTick.Stop() - defer everyMinuteTicker.Stop() - - for { - select { - case <-onceEveryDayTick.C: - sendUsageStats(im.oauthProviders) - case <-everyMinuteTicker.C: - updateTotalStats() - case <-ctx.Done(): - return ctx.Err() - } - } + <-ctx.Done() + return ctx.Err() } diff --git a/pkg/metrics/settings.go b/pkg/metrics/settings.go index 18b9e78d6ff..048e4134690 100644 --- a/pkg/metrics/settings.go +++ b/pkg/metrics/settings.go @@ -5,8 +5,6 @@ import ( "strings" "time" - "github.com/grafana/grafana/pkg/social" - "github.com/grafana/grafana/pkg/metrics/graphitebridge" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/client_golang/prometheus" @@ -24,8 +22,6 @@ func (im *InternalMetricsService) readSettings() error { return fmt.Errorf("Unable to parse metrics graphite section, %v", err) } - im.oauthProviders = social.GetOAuthProviders(im.Cfg) - return nil } From b4267eafb8fb0cd78702933eb772989cb30993d6 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 7 Feb 2019 15:43:05 +0100 Subject: [PATCH 023/119] log root cause error when reading from provisioning directories --- pkg/services/provisioning/dashboards/config_reader.go | 2 +- pkg/services/provisioning/datasources/config_reader.go | 2 +- pkg/services/provisioning/notifiers/config_reader.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/services/provisioning/dashboards/config_reader.go b/pkg/services/provisioning/dashboards/config_reader.go index fa08972961d..c57ca1c55e1 100644 --- a/pkg/services/provisioning/dashboards/config_reader.go +++ b/pkg/services/provisioning/dashboards/config_reader.go @@ -59,7 +59,7 @@ func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) { files, err := ioutil.ReadDir(cr.path) if err != nil { - cr.log.Error("can't read dashboard provisioning files from directory", "path", cr.path) + cr.log.Error("can't read dashboard provisioning files from directory", "path", cr.path, "error", err) return dashboards, nil } diff --git a/pkg/services/provisioning/datasources/config_reader.go b/pkg/services/provisioning/datasources/config_reader.go index b2930c2b679..34c1418aa98 100644 --- a/pkg/services/provisioning/datasources/config_reader.go +++ b/pkg/services/provisioning/datasources/config_reader.go @@ -19,7 +19,7 @@ func (cr *configReader) readConfig(path string) ([]*DatasourcesAsConfig, error) files, err := ioutil.ReadDir(path) if err != nil { - cr.log.Error("can't read datasource provisioning files from directory", "path", path) + cr.log.Error("can't read datasource provisioning files from directory", "path", path, "error", err) return datasources, nil } diff --git a/pkg/services/provisioning/notifiers/config_reader.go b/pkg/services/provisioning/notifiers/config_reader.go index e712e8e3eff..c1b4cbf9f29 100644 --- a/pkg/services/provisioning/notifiers/config_reader.go +++ b/pkg/services/provisioning/notifiers/config_reader.go @@ -23,7 +23,7 @@ func (cr *configReader) readConfig(path string) ([]*notificationsAsConfig, error files, err := ioutil.ReadDir(path) if err != nil { - cr.log.Error("Can't read alert notification provisioning files from directory", "path", path) + cr.log.Error("Can't read alert notification provisioning files from directory", "path", path, "error", err) return notifications, nil } From 9c18aa8684d3c3f1149a234617a7f1abcceed9cb Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 7 Feb 2019 16:10:39 +0100 Subject: [PATCH 024/119] make sure to create provisioning/notifiers directory for deb and rpm packages --- packaging/deb/control/postinst | 8 ++++++-- packaging/rpm/control/postinst | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packaging/deb/control/postinst b/packaging/deb/control/postinst index 351c966a8e6..049061ac2dd 100755 --- a/packaging/deb/control/postinst +++ b/packaging/deb/control/postinst @@ -32,10 +32,14 @@ case "$1" in fi if [ ! -f $PROVISIONING_CFG_DIR ]; then - mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources + mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources $PROVISIONING_CFG_DIR/notifiers cp /usr/share/grafana/conf/provisioning/dashboards/sample.yaml $PROVISIONING_CFG_DIR/dashboards/sample.yaml cp /usr/share/grafana/conf/provisioning/datasources/sample.yaml $PROVISIONING_CFG_DIR/datasources/sample.yaml - fi + cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml + elif [ ! -f $PROVISIONING_CFG_DIR/notifiers ]; then + mkdir -p $PROVISIONING_CFG_DIR/notifiers + cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml + fi # configuration files should not be modifiable by grafana user, as this can be a security issue chown -Rh root:$GRAFANA_GROUP /etc/grafana/* diff --git a/packaging/rpm/control/postinst b/packaging/rpm/control/postinst index e75850f258e..0187fc82cc5 100755 --- a/packaging/rpm/control/postinst +++ b/packaging/rpm/control/postinst @@ -46,10 +46,14 @@ if [ $1 -eq 1 ] ; then fi if [ ! -f $PROVISIONING_CFG_DIR ]; then - mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources + mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources $PROVISIONING_CFG_DIR/notifiers cp /usr/share/grafana/conf/provisioning/dashboards/sample.yaml $PROVISIONING_CFG_DIR/dashboards/sample.yaml cp /usr/share/grafana/conf/provisioning/datasources/sample.yaml $PROVISIONING_CFG_DIR/datasources/sample.yaml - fi + cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml + elif [ ! -f $PROVISIONING_CFG_DIR/notifiers ]; then + mkdir -p $PROVISIONING_CFG_DIR/notifiers + cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml + fi # Set user permissions on /var/log/grafana, /var/lib/grafana mkdir -p /var/log/grafana /var/lib/grafana From e0809831470ab08153763d982e9aa68eba2f441c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 7 Feb 2019 16:14:11 +0100 Subject: [PATCH 025/119] Panel edit navbar poc --- .../dashboard/components/DashNav/DashNav.tsx | 57 ++++++++++------ .../dashboard/containers/DashboardPage.tsx | 3 +- public/sass/components/_navbar.scss | 65 ++++++++++++++++++- public/sass/components/_panel_editor.scss | 4 ++ 4 files changed, 107 insertions(+), 22 deletions(-) diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 297d7ca7ea7..8560b3bfbba 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -14,10 +14,11 @@ import { DashNavButton } from './DashNavButton'; import { updateLocation } from 'app/core/actions'; // Types -import { DashboardModel } from '../../state/DashboardModel'; +import { DashboardModel, PanelModel } from '../../state'; export interface Props { dashboard: DashboardModel; + fullscreenPanel?: PanelModel; editview: string; isEditing: boolean; isFullscreen: boolean; @@ -33,7 +34,6 @@ export class DashNav extends PureComponent { constructor(props: Props) { super(props); - this.playlistSrv = this.props.$injector.get('playlistSrv'); } @@ -123,16 +123,14 @@ export class DashNav extends PureComponent { }); }; - render() { - const { dashboard, isFullscreen, editview, onAddPanel } = this.props; - const { canStar, canSave, canShare, folderTitle, showSettings, isStarred } = dashboard.meta; - const { snapshot } = dashboard; + renderDashboardTitleSearchButton() { + const { dashboard } = this.props; + const folderTitle = dashboard.meta.folderTitle; const haveFolder = dashboard.meta.folderId > 0; - const snapshotUrl = snapshot && snapshot.originalUrl; return ( -
+ <> -
+ + ); + } + + renderPanelFullscreeMode() { + const { fullscreenPanel } = this.props; + + return ( +
+ +
+ + +
+
+ ); + } + + render() { + const { dashboard, onAddPanel, fullscreenPanel } = this.props; + const { canStar, canSave, canShare, showSettings, isStarred } = dashboard.meta; + const { snapshot } = dashboard; + + const snapshotUrl = snapshot && snapshot.originalUrl; + + return ( +
+ {!fullscreenPanel && this.renderDashboardTitleSearchButton()} + {fullscreenPanel && this.renderPanelFullscreeMode()} {this.playlistSrv.isPlaying && (
@@ -228,17 +256,6 @@ export class DashNav extends PureComponent {
(this.timePickerEl = element)} /> - - {(isFullscreen || editview) && ( -
- -
- )}
); } diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 27118e297b5..724f3a625c0 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -238,7 +238,7 @@ export class DashboardPage extends PureComponent { render() { const { dashboard, editview, $injector, isInitSlow, initError } = this.props; - const { isSettingsOpening, isEditing, isFullscreen, scrollTop } = this.state; + const { isSettingsOpening, isEditing, isFullscreen, scrollTop, fullscreenPanel } = this.state; if (!dashboard) { if (isInitSlow) { @@ -266,6 +266,7 @@ export class DashboardPage extends PureComponent { editview={editview} $injector={$injector} onAddPanel={this.onAddPanel} + fullscreenPanel={fullscreenPanel} />
diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index 0cfa314a985..5215af41dcc 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -1,6 +1,6 @@ .navbar { position: relative; - padding-left: 40px; + padding-left: 20px; z-index: $zindex-navbar-fixed; height: $navbarHeight; padding-right: 20px; @@ -179,3 +179,66 @@ } } } + +.navbar-edit { + display: flex; + height: $navbarHeight; + align-items: center; + padding-left: 7px; + flex-grow: 1; +} + +.navbar-edit__back-btn { + background: transparent; + border: 2px solid $white; + border-radius: 50%; + width: 34px; + height: 34px; + margin-right: 7px; + + i { + font-size: $font-size-lg; + } +} + +.navbar-edit__input-wraper { + position: relative; + display: flex; + align-items: center; + flex-grow: 1; + + &:hover { + i { + opacity: 1; + } + + .navbar-edit__input { + background: $input-bg; + flex-grow: 1; + @include form-control-focus(); + } + } + + i { + left: -25px; + position: relative; + color: $text-color-weak; + opacity: 0; + transition: 200ms opacity ease-in-out; + } +} + +.navbar-edit__input { + background: transparent; + transition: 200ms background ease-in-out; + width: auto; + font-size: $font-size-lg; + height: $gf-form-input-height; + padding: $input-padding-y $input-padding-x; + flex-grow: 1; + + &:focus { + @include form-control-focus(); + background: $input-bg; + } +} diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index b791231a242..e533681d672 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -86,6 +86,10 @@ .panel-editor-container__panel { margin: 0 $dashboard-padding; } + + .panel-title-text { + visibility: hidden; + } } .panel-editor-container__resizer { From 2be60887cad49bfeaf37ed344e4981017f295c92 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 7 Feb 2019 16:27:40 +0100 Subject: [PATCH 026/119] adds usage stats for sessions --- pkg/infra/usagestats/service.go | 8 ++++---- pkg/infra/usagestats/usage_stats.go | 9 +++++++++ pkg/infra/usagestats/usage_stats_test.go | 8 +++++++- pkg/models/stats.go | 1 + pkg/services/sqlstore/stats.go | 3 ++- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/pkg/infra/usagestats/service.go b/pkg/infra/usagestats/service.go index f853c03302d..c2bf0d06349 100644 --- a/pkg/infra/usagestats/service.go +++ b/pkg/infra/usagestats/service.go @@ -5,7 +5,7 @@ import ( "time" "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/social" "github.com/grafana/grafana/pkg/log" @@ -20,9 +20,9 @@ func init() { } type UsageStatsService struct { - Cfg *setting.Cfg `inject:""` - TokenService *auth.UserAuthTokenService `inject:""` - Bus bus.Bus `inject:""` + Cfg *setting.Cfg `inject:""` + Bus bus.Bus `inject:""` + SQLStore *sqlstore.SqlStore `inject:""` oauthProviders map[string]bool } diff --git a/pkg/infra/usagestats/usage_stats.go b/pkg/infra/usagestats/usage_stats.go index b0dc52ccd8b..b54de124335 100644 --- a/pkg/infra/usagestats/usage_stats.go +++ b/pkg/infra/usagestats/usage_stats.go @@ -59,6 +59,15 @@ func (uss *UsageStatsService) sendUsageStats(oauthProviders map[string]bool) { metrics["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots metrics["stats.teams.count"] = statsQuery.Result.Teams + metrics["stats.total_sessions.count"] = statsQuery.Result.Sessions + + userCount := statsQuery.Result.Users + avgSessionsPerUser := statsQuery.Result.Sessions + if userCount != 0 { + avgSessionsPerUser = avgSessionsPerUser / userCount + } + + metrics["stats.avg_sessions_per_user.count"] = avgSessionsPerUser 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 dd45e96f256..d343ed52b93 100644 --- a/pkg/infra/usagestats/usage_stats_test.go +++ b/pkg/infra/usagestats/usage_stats_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" ) @@ -22,7 +23,8 @@ import ( func TestMetrics(t *testing.T) { Convey("Test send usage stats", t, func() { uss := &UsageStatsService{ - Bus: bus.New(), + Bus: bus.New(), + SQLStore: sqlstore.InitTestDB(t), } var getSystemStatsQuery *models.GetSystemStatsQuery @@ -43,6 +45,7 @@ func TestMetrics(t *testing.T) { ProvisionedDashboards: 12, Snapshots: 13, Teams: 14, + Sessions: 15, } getSystemStatsQuery = query return nil @@ -226,6 +229,8 @@ func TestMetrics(t *testing.T) { So(metrics.Get("stats.provisioned_dashboards.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.ProvisionedDashboards) So(metrics.Get("stats.snapshots.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Snapshots) So(metrics.Get("stats.teams.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Teams) + So(metrics.Get("stats.total_sessions.count").MustInt64(), ShouldEqual, 15) + So(metrics.Get("stats.avg_sessions_per_user.count").MustInt64(), ShouldEqual, 5) So(metrics.Get("stats.ds."+models.DS_ES+".count").MustInt(), ShouldEqual, 9) So(metrics.Get("stats.ds."+models.DS_PROMETHEUS+".count").MustInt(), ShouldEqual, 10) @@ -251,6 +256,7 @@ func TestMetrics(t *testing.T) { So(metrics.Get("stats.auth_enabled.oauth_grafana_com.count").MustInt(), ShouldEqual, 1) So(metrics.Get("stats.packaging.deb.count").MustInt(), ShouldEqual, 1) + }) }) diff --git a/pkg/models/stats.go b/pkg/models/stats.go index d3e145dedf4..00f881f3c59 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -15,6 +15,7 @@ type SystemStats struct { FolderPermissions int64 Folders int64 ProvisionedDashboards int64 + Sessions int64 } type DataSourceStats struct { diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 2cec86e7239..4c6d6c21221 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -74,7 +74,8 @@ func GetSystemStats(query *m.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("team") + `) AS teams`) + sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("team") + `) AS teams,`) + sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("user_auth_token") + `) AS sessions`) var stats m.SystemStats _, err := x.SQL(sb.GetSqlString(), sb.params...).Get(&stats) From 229d646bfc63c4db0db0e5294581ee82ca8ddeda Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Thu, 7 Feb 2019 17:46:33 +0100 Subject: [PATCH 027/119] Persis deduplication strategy in url --- public/app/core/utils/explore.test.ts | 6 ++- public/app/core/utils/explore.ts | 5 +- public/app/features/explore/Logs.tsx | 27 +++++----- public/app/features/explore/LogsContainer.tsx | 30 +++++++++-- .../app/features/explore/state/actionTypes.ts | 11 ++++ public/app/features/explore/state/actions.ts | 54 +++++++++++++++---- public/app/features/explore/state/reducers.ts | 15 ++++-- public/app/types/explore.ts | 8 ++- 8 files changed, 121 insertions(+), 35 deletions(-) diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 1c00142c3b8..dae6554ade0 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -8,6 +8,7 @@ import { } from './explore'; import { ExploreUrlState } from 'app/types/explore'; import store from 'app/core/store'; +import { LogsDedupStrategy } from 'app/core/logs_model'; const DEFAULT_EXPLORE_STATE: ExploreUrlState = { datasource: null, @@ -17,6 +18,7 @@ const DEFAULT_EXPLORE_STATE: ExploreUrlState = { showingGraph: true, showingTable: true, showingLogs: true, + dedupStrategy: LogsDedupStrategy.none, } }; @@ -78,7 +80,7 @@ describe('state functions', () => { expect(serializeStateToUrlParam(state)).toBe( '{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' + '{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"},' + - '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}' + '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true,"dedupStrategy":"none"}}' ); }); @@ -100,7 +102,7 @@ describe('state functions', () => { }, }; expect(serializeStateToUrlParam(state, true)).toBe( - '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true]}]' + '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true,"none"]}]' ); }); }); diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index 107f411353c..1dcd66c6369 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -21,6 +21,7 @@ import { QueryIntervals, QueryOptions, } from 'app/types/explore'; +import { LogsDedupStrategy } from 'app/core/logs_model'; export const DEFAULT_RANGE = { from: 'now-6h', @@ -31,6 +32,7 @@ export const DEFAULT_UI_STATE = { showingTable: true, showingGraph: true, showingLogs: true, + dedupStrategy: LogsDedupStrategy.none, }; const MAX_HISTORY_ITEMS = 100; @@ -183,6 +185,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState { showingGraph: segment.ui[0], showingLogs: segment.ui[1], showingTable: segment.ui[2], + dedupStrategy: segment.ui[3], }; } }); @@ -204,7 +207,7 @@ export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: bo urlState.range.to, urlState.datasource, ...urlState.queries, - { ui: [!!urlState.ui.showingGraph, !!urlState.ui.showingLogs, !!urlState.ui.showingTable] }, + { ui: [!!urlState.ui.showingGraph, !!urlState.ui.showingLogs, !!urlState.ui.showingTable, urlState.ui.dedupStrategy] }, ]); } return JSON.stringify(urlState); diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index b6c903bc504..af6caee6206 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -57,14 +57,15 @@ interface Props { range?: RawTimeRange; scanning?: boolean; scanRange?: RawTimeRange; + dedupStrategy: LogsDedupStrategy; onChangeTime?: (range: RawTimeRange) => void; onClickLabel?: (label: string, value: string) => void; onStartScanning?: () => void; onStopScanning?: () => void; + onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void; } interface State { - dedup: LogsDedupStrategy; deferLogs: boolean; hiddenLogLevels: Set; renderAll: boolean; @@ -78,7 +79,6 @@ export default class Logs extends PureComponent { renderAllTimer: NodeJS.Timer; state = { - dedup: LogsDedupStrategy.none, deferLogs: true, hiddenLogLevels: new Set(), renderAll: false, @@ -111,12 +111,11 @@ export default class Logs extends PureComponent { } onChangeDedup = (dedup: LogsDedupStrategy) => { - this.setState(prevState => { - if (prevState.dedup === dedup) { - return { dedup: LogsDedupStrategy.none }; - } - return { dedup }; - }); + const { onDedupStrategyChange } = this.props; + if (this.props.dedupStrategy === dedup) { + return onDedupStrategyChange(LogsDedupStrategy.none); + } + return onDedupStrategyChange(dedup); }; onChangeLabels = (event: React.SyntheticEvent) => { @@ -171,17 +170,19 @@ export default class Logs extends PureComponent { return null; } - const { dedup, deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc } = this.state; + const { deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc, } = this.state; let { showLabels } = this.state; + const { dedupStrategy } = this.props; const hasData = data && data.rows && data.rows.length > 0; - const showDuplicates = dedup !== LogsDedupStrategy.none; + const showDuplicates = dedupStrategy !== LogsDedupStrategy.none; // Filtering const filteredData = filterLogLevels(data, hiddenLogLevels); - const dedupedData = dedupLogRows(filteredData, dedup); + const dedupedData = dedupLogRows(filteredData, dedupStrategy); const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0); const meta = [...data.meta]; - if (dedup !== LogsDedupStrategy.none) { + + if (dedupStrategy !== LogsDedupStrategy.none) { meta.push({ label: 'Dedup count', value: dedupCount, @@ -233,7 +234,7 @@ export default class Logs extends PureComponent { key={i} value={dedupType} onChange={this.onChangeDedup} - selected={dedup === dedupType} + selected={dedupStrategy === dedupType} tooltip={LogsDedupDescription[dedupType]} > {dedupType} diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 76970ef343a..a3cab0b256a 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -4,10 +4,10 @@ import { connect } from 'react-redux'; import { RawTimeRange, TimeRange } from '@grafana/ui'; import { ExploreId, ExploreItemState } from 'app/types/explore'; -import { LogsModel } from 'app/core/logs_model'; +import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; import { StoreState } from 'app/types'; -import { toggleLogs } from './state/actions'; +import { toggleLogs, changeDedupStrategy } from './state/actions'; import Logs from './Logs'; import Panel from './Panel'; @@ -25,6 +25,8 @@ interface LogsContainerProps { scanRange?: RawTimeRange; showingLogs: boolean; toggleLogs: typeof toggleLogs; + changeDedupStrategy: typeof changeDedupStrategy; + dedupStrategy: LogsDedupStrategy; } export class LogsContainer extends PureComponent { @@ -32,6 +34,10 @@ export class LogsContainer extends PureComponent { this.props.toggleLogs(this.props.exploreId); }; + handleDedupStrategyChange = (dedupStrategy: LogsDedupStrategy) => { + this.props.changeDedupStrategy(this.props.exploreId, dedupStrategy); + }; + render() { const { exploreId, @@ -45,12 +51,13 @@ export class LogsContainer extends PureComponent { range, showingLogs, scanning, - scanRange, + scanRange } = this.props; return ( { onClickLabel={onClickLabel} onStartScanning={onStartScanning} onStopScanning={onStopScanning} + onDedupStrategyChange={this.handleDedupStrategyChange} range={range} scanning={scanning} scanRange={scanRange} @@ -69,11 +77,23 @@ export class LogsContainer extends PureComponent { } } +const selectItemUIState = (itemState: ExploreItemState) => { + const { showingGraph, showingLogs, showingTable, showingStartPage, dedupStrategy } = itemState; + return { + showingGraph, + showingLogs, + showingTable, + showingStartPage, + dedupStrategy, + }; +}; function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; - const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, showingLogs, range } = item; + const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, range } = item; const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done); + const {showingLogs, dedupStrategy} = selectItemUIState(item); + // const dedup = item.dedup; return { loading, logsHighlighterExpressions, @@ -82,11 +102,13 @@ function mapStateToProps(state: StoreState, { exploreId }) { scanRange, showingLogs, range, + dedupStrategy, }; } const mapDispatchToProps = { toggleLogs, + changeDedupStrategy, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(LogsContainer)); diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index 98af5e8076e..71061607d3c 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -180,6 +180,8 @@ export interface SplitOpenPayload { itemState: ExploreItemState; } +// + export interface ToggleTablePayload { exploreId: ExploreId; } @@ -192,6 +194,10 @@ export interface ToggleLogsPayload { exploreId: ExploreId; } +export interface UpdateUIStatePayload extends Partial{ + exploreId: ExploreId; +} + export interface UpdateDatasourceInstancePayload { exploreId: ExploreId; datasourceInstance: DataSourceApi; @@ -366,6 +372,11 @@ export const splitCloseAction = noPayloadActionCreatorFactory('explore/SPLIT_CLO export const splitOpenAction = actionCreatorFactory('explore/SPLIT_OPEN').create(); export const stateSaveAction = noPayloadActionCreatorFactory('explore/STATE_SAVE').create(); +/** + * Update state of Explores UI + */ +export const updateUIStateAction = actionCreatorFactory('explore/UPDATE_UI_STATE').create(); + /** * Expand/collapse the table result viewer. When collapsed, table queries won't be run. */ diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index f6fa5c05d63..63f0bfd0350 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -67,14 +67,26 @@ import { ToggleGraphPayload, ToggleLogsPayload, ToggleTablePayload, + updateUIStateAction, } from './actionTypes'; import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory'; +import { LogsDedupStrategy } from 'app/core/logs_model'; type ThunkResult = ThunkAction; -// /** -// * Adds a query row after the row with the given index. -// */ +/** + * Updates UI state and save it to the URL + */ +const updateExploreUIState = (exploreId, uiStateFragment: Partial) => { + return dispatch => { + dispatch(updateUIStateAction({ exploreId, ...uiStateFragment })); + dispatch(stateSave()); + }; +}; + +/** + * Adds a query row after the row with the given index. + */ export function addQueryRow(exploreId: ExploreId, index: number): ActionOf { const query = generateEmptyQuery(index + 1); return addQueryRowAction({ exploreId, index, query }); @@ -669,6 +681,7 @@ export function stateSave() { showingGraph: left.showingGraph, showingLogs: left.showingLogs, showingTable: left.showingTable, + dedupStrategy: left.dedupStrategy, }, }; urlStates.left = serializeStateToUrlParam(leftUrlState, true); @@ -677,7 +690,12 @@ export function stateSave() { datasource: right.datasourceInstance.name, queries: right.queries.map(clearQueryKeys), range: right.range, - ui: { showingGraph: right.showingGraph, showingLogs: right.showingLogs, showingTable: right.showingTable }, + ui: { + showingGraph: right.showingGraph, + showingLogs: right.showingLogs, + showingTable: right.showingTable, + dedupStrategy: right.dedupStrategy, + }, }; urlStates.right = serializeStateToUrlParam(rightUrlState, true); @@ -698,22 +716,29 @@ const togglePanelActionCreator = ( | ActionCreator ) => (exploreId: ExploreId) => { return (dispatch, getState) => { - let shouldRunQueries; - dispatch(actionCreator({ exploreId })); - dispatch(stateSave()); + let shouldRunQueries, uiFragmentStateUpdate: Partial; switch (actionCreator.type) { case toggleGraphAction.type: - shouldRunQueries = getState().explore[exploreId].showingGraph; + const isShowingGraph = getState().explore[exploreId].showingGraph; + shouldRunQueries = !isShowingGraph; + uiFragmentStateUpdate = { showingGraph: !isShowingGraph }; break; case toggleLogsAction.type: - shouldRunQueries = getState().explore[exploreId].showingLogs; + const isShowingLogs = getState().explore[exploreId].showingLogs; + shouldRunQueries = !isShowingLogs; + uiFragmentStateUpdate = { showingLogs: !isShowingLogs }; break; case toggleTableAction.type: - shouldRunQueries = getState().explore[exploreId].showingTable; + const isShowingTable = getState().explore[exploreId].showingTable; + shouldRunQueries = !isShowingTable; + uiFragmentStateUpdate = { showingTable: !isShowingTable }; break; } + dispatch(actionCreator({ exploreId })); + dispatch(updateExploreUIState(exploreId, uiFragmentStateUpdate)); + if (shouldRunQueries) { dispatch(runQueries(exploreId)); } @@ -734,3 +759,12 @@ export const toggleLogs = togglePanelActionCreator(toggleLogsAction); * Expand/collapse the table result viewer. When collapsed, table queries won't be run. */ export const toggleTable = togglePanelActionCreator(toggleTableAction); + +/** + * Change logs deduplication strategy and update URL. + */ +export const changeDedupStrategy = (exploreId, dedupStrategy: LogsDedupStrategy) => { + return dispatch => { + dispatch(updateExploreUIState(exploreId, { dedupStrategy })); + }; +}; diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 76fc7d5de32..255591ee6e3 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -37,6 +37,7 @@ import { toggleLogsAction, toggleTableAction, queriesImportedAction, + updateUIStateAction, } from './actionTypes'; export const DEFAULT_RANGE = { @@ -406,6 +407,12 @@ export const itemReducer = reducerFactory({} as ExploreItemSta }; }, }) + .addMapper({ + filter: updateUIStateAction, + mapper: (state, action): ExploreItemState => { + return { ...state, ...action.payload }; + }, + }) .addMapper({ filter: toggleGraphAction, mapper: (state): ExploreItemState => { @@ -415,7 +422,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta // Discard transactions related to Graph query nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph'); } - return { ...state, queryTransactions: nextQueryTransactions, showingGraph }; + return { ...state, queryTransactions: nextQueryTransactions }; }, }) .addMapper({ @@ -427,7 +434,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta // Discard transactions related to Logs query nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs'); } - return { ...state, queryTransactions: nextQueryTransactions, showingLogs }; + return { ...state, queryTransactions: nextQueryTransactions }; }, }) .addMapper({ @@ -435,7 +442,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta mapper: (state): ExploreItemState => { const showingTable = !state.showingTable; if (showingTable) { - return { ...state, showingTable, queryTransactions: state.queryTransactions }; + return { ...state, queryTransactions: state.queryTransactions }; } // Toggle off needs discarding of table queries and results @@ -446,7 +453,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta state.queryIntervals.intervalMs ); - return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable }; + return { ...state, ...results, queryTransactions: nextQueryTransactions }; }, }) .addMapper({ diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 9c8d977c3ad..066ca226157 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -11,7 +11,7 @@ import { } from '@grafana/ui'; import { Emitter } from 'app/core/core'; -import { LogsModel } from 'app/core/logs_model'; +import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; import TableModel from 'app/core/table_model'; export interface CompletionItem { @@ -237,12 +237,18 @@ export interface ExploreItemState { * React keys for rendering of QueryRows */ queryKeys: string[]; + + /** + * Current logs deduplication strategy + */ + dedupStrategy?: LogsDedupStrategy; } export interface ExploreUIState { showingTable: boolean; showingGraph: boolean; showingLogs: boolean; + dedupStrategy?: LogsDedupStrategy; } export interface ExploreUrlState { From 0f96cf866272ef72a016b0d1c9b5225037d73a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 7 Feb 2019 19:06:51 +0100 Subject: [PATCH 028/119] slight tweaks --- public/sass/components/_navbar.scss | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index 5215af41dcc..0096810798a 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -41,7 +41,7 @@ .panel-in-fullscreen { .navbar { - padding-left: 15px; + padding-left: 20px; } .navbar-button--add-panel, @@ -190,14 +190,22 @@ .navbar-edit__back-btn { background: transparent; - border: 2px solid $white; + border: 2px solid $text-color; border-radius: 50%; width: 34px; height: 34px; - margin-right: 7px; + transition: transform 0.1s ease 0.1s; + color: $text-color; i { font-size: $font-size-lg; + position: relative; + top: 2px; + } + + &:hover { + color: $text-color-strong; + border-color: $text-color-strong; } } From 512aa62efc629a1488d4f5ced02de7e5f8b519fe Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Fri, 8 Feb 2019 11:50:05 +0100 Subject: [PATCH 029/119] Update config mock in metrics panel controller test --- public/app/features/panel/specs/metrics_panel_ctrl.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/app/features/panel/specs/metrics_panel_ctrl.test.ts b/public/app/features/panel/specs/metrics_panel_ctrl.test.ts index d647af616a9..3ee4c5165cb 100644 --- a/public/app/features/panel/specs/metrics_panel_ctrl.test.ts +++ b/public/app/features/panel/specs/metrics_panel_ctrl.test.ts @@ -1,6 +1,9 @@ jest.mock('app/core/core', () => ({})); jest.mock('app/core/config', () => { return { + bootData: { + user: {}, + }, panels: { test: { id: 'test', From 0e228d582d53b22a8f9bc1a7448bdc485b4f04e0 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 8 Feb 2019 17:20:31 +0100 Subject: [PATCH 030/119] azuremonitor: builds a query and sends it to Azure on the backend Lots of edge cases not covered and the response is not parsed. It only handles one service and will have to be refactored to handle multiple --- pkg/cmd/grafana-server/main.go | 1 + pkg/models/datasource.go | 2 +- pkg/tsdb/azuremonitor/azuremonitor.go | 251 +++++++++++++++++++++ pkg/tsdb/azuremonitor/azuremonitor_test.go | 61 +++++ pkg/tsdb/azuremonitor/types.go | 72 ++++++ 5 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 pkg/tsdb/azuremonitor/azuremonitor.go create mode 100644 pkg/tsdb/azuremonitor/azuremonitor_test.go create mode 100644 pkg/tsdb/azuremonitor/types.go diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go index 3bdaf0cc80e..d371d4e91da 100644 --- a/pkg/cmd/grafana-server/main.go +++ b/pkg/cmd/grafana-server/main.go @@ -19,6 +19,7 @@ import ( _ "github.com/grafana/grafana/pkg/services/alerting/conditions" _ "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" + _ "github.com/grafana/grafana/pkg/tsdb/azuremonitor" _ "github.com/grafana/grafana/pkg/tsdb/cloudwatch" _ "github.com/grafana/grafana/pkg/tsdb/elasticsearch" _ "github.com/grafana/grafana/pkg/tsdb/graphite" diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index e1cb185d92a..22c53dfa0dd 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -23,7 +23,7 @@ const ( DS_ACCESS_DIRECT = "direct" DS_ACCESS_PROXY = "proxy" DS_STACKDRIVER = "stackdriver" - DS_AZURE_MONITOR = "azure-monitor" + DS_AZURE_MONITOR = "grafana-azure-monitor-datasource" ) var ( diff --git a/pkg/tsdb/azuremonitor/azuremonitor.go b/pkg/tsdb/azuremonitor/azuremonitor.go new file mode 100644 index 00000000000..93fd8ed8110 --- /dev/null +++ b/pkg/tsdb/azuremonitor/azuremonitor.go @@ -0,0 +1,251 @@ +package azuremonitor + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "path" + "time" + + // "github.com/grafana/grafana/pkg/components/null" + "github.com/grafana/grafana/pkg/api/pluginproxy" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tsdb" + "github.com/opentracing/opentracing-go" + "golang.org/x/net/context/ctxhttp" +) + +var ( + slog log.Logger +) + +// AzureMonitorExecutor executes queries for the Azure Monitor datasource - all four services +type AzureMonitorExecutor struct { + httpClient *http.Client + dsInfo *models.DataSource +} + +// NewAzureMonitorExecutor initializes a http client +func NewAzureMonitorExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) { + httpClient, err := dsInfo.GetHttpClient() + if err != nil { + return nil, err + } + + return &AzureMonitorExecutor{ + httpClient: httpClient, + dsInfo: dsInfo, + }, nil +} + +func init() { + slog = log.New("tsdb.azuremonitor") + tsdb.RegisterTsdbQueryEndpoint("grafana-azure-monitor-datasource", NewAzureMonitorExecutor) +} + +// Query takes in the frontend queries, parses them into the query format +// expected by chosen Azure Monitor service (Azure Monitor, App Insights etc.) +// executes the queries against the API and parses the response into +// the right format +func (e *AzureMonitorExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { + var result *tsdb.Response + var err error + queryType := tsdbQuery.Queries[0].Model.Get("queryType").MustString("") + + switch queryType { + case "azureMonitorTimeSeriesQuery": + case "Azure Monitor": + fallthrough + default: + result, err = e.executeTimeSeriesQuery(ctx, tsdbQuery) + } + + return result, err +} + +func (e *AzureMonitorExecutor) executeTimeSeriesQuery(ctx context.Context, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { + result := &tsdb.Response{ + Results: make(map[string]*tsdb.QueryResult), + } + + queries, err := e.buildQueries(tsdbQuery) + if err != nil { + return nil, err + } + + for _, query := range queries { + queryRes, resp, err := e.executeQuery(ctx, query, tsdbQuery) + if err != nil { + return nil, err + } + err = e.parseResponse(queryRes, resp, query) + if err != nil { + queryRes.Error = err + } + result.Results[query.RefID] = queryRes + } + + return result, nil +} + +func (e *AzureMonitorExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*AzureMonitorQuery, error) { + azureMonitorQueries := []*AzureMonitorQuery{} + startTime, err := tsdbQuery.TimeRange.ParseFrom() + if err != nil { + return nil, err + } + + endTime, err := tsdbQuery.TimeRange.ParseTo() + if err != nil { + return nil, err + } + + for _, query := range tsdbQuery.Queries { + var target string + + azureMonitorTarget := query.Model.Get("azureMonitor").MustMap() + + resourceGroup := azureMonitorTarget["resourceGroup"].(string) + metricDefinition := azureMonitorTarget["metricDefinition"].(string) + resourceName := azureMonitorTarget["resourceName"].(string) + azureURL := fmt.Sprintf("resourceGroups/%s/providers/%s/%s/providers/microsoft.insights/metrics", resourceGroup, metricDefinition, resourceName) + + alias := azureMonitorTarget["alias"].(string) + + params := url.Values{} + params.Add("api-version", "2018-01-01") + params.Add("timespan", fmt.Sprintf("%v/%v", startTime.UTC().Format(time.RFC3339), endTime.UTC().Format(time.RFC3339))) + params.Add("interval", azureMonitorTarget["timeGrain"].(string)) + params.Add("aggregation", azureMonitorTarget["aggregation"].(string)) + params.Add("metricnames", azureMonitorTarget["metricName"].(string)) + target = params.Encode() + + if setting.Env == setting.DEV { + slog.Debug("Azuremonitor request", "params", params) + } + + azureMonitorQueries = append(azureMonitorQueries, &AzureMonitorQuery{ + URL: azureURL, + Target: target, + Params: params, + RefID: query.RefId, + Alias: alias, + }) + } + + return azureMonitorQueries, nil +} + +func (e *AzureMonitorExecutor) executeQuery(ctx context.Context, query *AzureMonitorQuery, tsdbQuery *tsdb.TsdbQuery) (*tsdb.QueryResult, AzureMonitorResponse, error) { + queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID} + + req, err := e.createRequest(ctx, e.dsInfo) + if err != nil { + queryResult.Error = err + return queryResult, AzureMonitorResponse{}, nil + } + + req.URL.Path = path.Join(req.URL.Path, query.URL) + req.URL.RawQuery = query.Params.Encode() + queryResult.Meta.Set("rawQuery", req.URL.RawQuery) + + span, ctx := opentracing.StartSpanFromContext(ctx, "azuremonitor query") + span.SetTag("target", query.Target) + span.SetTag("from", tsdbQuery.TimeRange.From) + span.SetTag("until", tsdbQuery.TimeRange.To) + span.SetTag("datasource_id", e.dsInfo.Id) + span.SetTag("org_id", e.dsInfo.OrgId) + + defer span.Finish() + + opentracing.GlobalTracer().Inject( + span.Context(), + opentracing.HTTPHeaders, + opentracing.HTTPHeadersCarrier(req.Header)) + + res, err := ctxhttp.Do(ctx, e.httpClient, req) + if err != nil { + queryResult.Error = err + return queryResult, AzureMonitorResponse{}, nil + } + + data, err := e.unmarshalResponse(res) + if err != nil { + queryResult.Error = err + return queryResult, AzureMonitorResponse{}, nil + } + + return queryResult, data, nil +} + +func (e *AzureMonitorExecutor) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) { + // find plugin + plugin, ok := plugins.DataSources[dsInfo.Type] + if !ok { + return nil, errors.New("Unable to find datasource plugin Azure Monitor") + } + + var azureMonitorRoute *plugins.AppPluginRoute + for _, route := range plugin.Routes { + if route.Path == "azuremonitor" { + azureMonitorRoute = route + break + } + } + + cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor") + subscriptionID := dsInfo.JsonData.Get("subscriptionId").MustString() + proxyPass := fmt.Sprintf("%s/subscriptions/%s", cloudName, subscriptionID) + + u, _ := url.Parse(dsInfo.Url) + u.Path = path.Join(u.Path, "render") + + req, err := http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { + slog.Error("Failed to create request", "error", err) + return nil, fmt.Errorf("Failed to create request. error: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion)) + + pluginproxy.ApplyRoute(ctx, req, proxyPass, azureMonitorRoute, dsInfo) + + return req, nil +} + +func (e *AzureMonitorExecutor) unmarshalResponse(res *http.Response) (AzureMonitorResponse, error) { + body, err := ioutil.ReadAll(res.Body) + defer res.Body.Close() + if err != nil { + return AzureMonitorResponse{}, err + } + + if res.StatusCode/100 != 2 { + slog.Error("Request failed", "status", res.Status, "body", string(body)) + return AzureMonitorResponse{}, fmt.Errorf(string(body)) + } + + var data AzureMonitorResponse + err = json.Unmarshal(body, &data) + if err != nil { + slog.Error("Failed to unmarshal AzureMonitor response", "error", err, "status", res.Status, "body", string(body)) + return AzureMonitorResponse{}, err + } + + return data, nil +} + +func (e *AzureMonitorExecutor) parseResponse(queryRes *tsdb.QueryResult, data AzureMonitorResponse, query *AzureMonitorQuery) error { + slog.Info("AzureMonitor", "Response", data) + + return nil +} diff --git a/pkg/tsdb/azuremonitor/azuremonitor_test.go b/pkg/tsdb/azuremonitor/azuremonitor_test.go new file mode 100644 index 00000000000..787e0ae1586 --- /dev/null +++ b/pkg/tsdb/azuremonitor/azuremonitor_test.go @@ -0,0 +1,61 @@ +package azuremonitor + +import ( + "fmt" + "testing" + "time" + + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/tsdb" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestAzureMonitor(t *testing.T) { + Convey("AzureMonitor", t, func() { + executor := &AzureMonitorExecutor{} + + Convey("Parse queries from frontend and build AzureMonitor API queries", func() { + fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local) + tsdbQuery := &tsdb.TsdbQuery{ + TimeRange: &tsdb.TimeRange{ + From: fmt.Sprintf("%v", fromStart.Unix()*1000), + To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000), + }, + Queries: []*tsdb.Query{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "azureMonitor": map[string]interface{}{ + "timeGrain": "PT1M", + "aggregation": "Average", + "resourceGroup": "grafanastaging", + "resourceName": "grafana", + "metricDefinition": "Microsoft.Compute/virtualMachines", + "metricName": "Percentage CPU", + "alias": "testalias", + "queryType": "Azure Monitor", + }, + }), + RefId: "A", + }, + }, + } + Convey("and is a normal query", func() { + queries, err := executor.buildQueries(tsdbQuery) + So(err, ShouldBeNil) + + So(len(queries), ShouldEqual, 1) + So(queries[0].RefID, ShouldEqual, "A") + So(queries[0].URL, ShouldEqual, "resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana/providers/microsoft.insights/metrics") + So(queries[0].Target, ShouldEqual, "aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z") + So(len(queries[0].Params), ShouldEqual, 5) + So(queries[0].Params["timespan"][0], ShouldEqual, "2018-03-15T13:00:00Z/2018-03-15T13:34:00Z") + So(queries[0].Params["api-version"][0], ShouldEqual, "2018-01-01") + So(queries[0].Params["aggregation"][0], ShouldEqual, "Average") + So(queries[0].Params["metricnames"][0], ShouldEqual, "Percentage CPU") + So(queries[0].Params["interval"][0], ShouldEqual, "PT1M") + So(queries[0].Alias, ShouldEqual, "testalias") + }) + }) + }) +} diff --git a/pkg/tsdb/azuremonitor/types.go b/pkg/tsdb/azuremonitor/types.go new file mode 100644 index 00000000000..fc99ede6512 --- /dev/null +++ b/pkg/tsdb/azuremonitor/types.go @@ -0,0 +1,72 @@ +package azuremonitor + +import ( + "net/url" + "time" +) + +// AzureMonitorQuery is the query for all the services as they have similar queries +// with a url, a querystring and an alias field +type AzureMonitorQuery struct { + URL string + Target string + Params url.Values + RefID string + Alias string +} + +// AzureMonitorResponse is the json response from the Azure Monitor API +type AzureMonitorResponse struct { + Cost int `json:"cost"` + Timespan string `json:"timespan"` + Interval string `json:"interval"` + Value []struct { + ID string `json:"id"` + Type string `json:"type"` + Name struct { + Value string `json:"value"` + LocalizedValue string `json:"localizedValue"` + } `json:"name"` + Unit string `json:"unit"` + Timeseries []struct { + Metadatavalues []struct { + Name struct { + Value string `json:"value"` + LocalizedValue string `json:"localizedValue"` + } `json:"name"` + Value string `json:"value"` + } `json:"metadatavalues"` + Data []struct { + TimeStamp time.Time `json:"timeStamp"` + Average float64 `json:"average"` + } `json:"data"` + } `json:"timeseries"` + } `json:"value"` + Namespace string `json:"namespace"` + Resourceregion string `json:"resourceregion"` +} + +// ApplicationInsightsResponse is the json response from the Application Insights API +type ApplicationInsightsResponse struct { + Tables []struct { + TableName string `json:"TableName"` + Columns []struct { + ColumnName string `json:"ColumnName"` + DataType string `json:"DataType"` + ColumnType string `json:"ColumnType"` + } `json:"Columns"` + Rows [][]interface{} `json:"Rows"` + } `json:"Tables"` +} + +// AzureLogAnalyticsResponse is the json response object from the Azure Log Analytics API. +type AzureLogAnalyticsResponse struct { + Tables []struct { + Name string `json:"name"` + Columns []struct { + Name string `json:"name"` + Type string `json:"type"` + } `json:"columns"` + Rows [][]interface{} `json:"rows"` + } `json:"tables"` +} From 1bc2a0af70304bee4b4a18beb5865c604cf2a942 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 8 Feb 2019 18:08:07 +0100 Subject: [PATCH 031/119] use unique datasource id when registering mysql tls config --- pkg/tsdb/mysql/mysql.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index d451150f1de..d307e12166c 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -39,8 +39,9 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin } if tlsConfig.RootCAs != nil || len(tlsConfig.Certificates) > 0 { - mysql.RegisterTLSConfig(datasource.Name, tlsConfig) - cnnstr += "&tls=" + datasource.Name + tlsConfigString := fmt.Sprintf("ds%d", datasource.Id) + mysql.RegisterTLSConfig(tlsConfigString, tlsConfig) + cnnstr += "&tls=" + tlsConfigString } logger.Debug("getEngine", "connection", cnnstr) From 10194df11270d961d97d3d084afdda2ce669c835 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 8 Feb 2019 18:15:17 +0100 Subject: [PATCH 032/119] azuremonitor: simple alerting for Azure Monitor API Lots of edge cases and functionality left to implement but a simple query works for alerting now. --- pkg/tsdb/azuremonitor/azuremonitor.go | 39 +++++++++++---- pkg/tsdb/azuremonitor/azuremonitor_test.go | 34 ++++++++++++++ .../test-data/1-azure-monitor-response.json | 47 +++++++++++++++++++ pkg/tsdb/azuremonitor/types.go | 11 +++-- .../plugin.json | 3 +- 5 files changed, 118 insertions(+), 16 deletions(-) create mode 100644 pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response.json diff --git a/pkg/tsdb/azuremonitor/azuremonitor.go b/pkg/tsdb/azuremonitor/azuremonitor.go index 93fd8ed8110..8ef959bed9c 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor.go +++ b/pkg/tsdb/azuremonitor/azuremonitor.go @@ -11,8 +11,8 @@ import ( "path" "time" - // "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/api/pluginproxy" + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/models" @@ -113,10 +113,12 @@ func (e *AzureMonitorExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Azure azureMonitorTarget := query.Model.Get("azureMonitor").MustMap() - resourceGroup := azureMonitorTarget["resourceGroup"].(string) - metricDefinition := azureMonitorTarget["metricDefinition"].(string) - resourceName := azureMonitorTarget["resourceName"].(string) - azureURL := fmt.Sprintf("resourceGroups/%s/providers/%s/%s/providers/microsoft.insights/metrics", resourceGroup, metricDefinition, resourceName) + urlComponents := make(map[string]string) + urlComponents["resourceGroup"] = azureMonitorTarget["resourceGroup"].(string) + urlComponents["metricDefinition"] = azureMonitorTarget["metricDefinition"].(string) + urlComponents["resourceName"] = azureMonitorTarget["resourceName"].(string) + + azureURL := fmt.Sprintf("resourceGroups/%s/providers/%s/%s/providers/microsoft.insights/metrics", urlComponents["resourceGroup"], urlComponents["metricDefinition"], urlComponents["resourceName"]) alias := azureMonitorTarget["alias"].(string) @@ -133,11 +135,12 @@ func (e *AzureMonitorExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Azure } azureMonitorQueries = append(azureMonitorQueries, &AzureMonitorQuery{ - URL: azureURL, - Target: target, - Params: params, - RefID: query.RefId, - Alias: alias, + URL: azureURL, + UrlComponents: urlComponents, + Target: target, + Params: params, + RefID: query.RefId, + Alias: alias, }) } @@ -247,5 +250,21 @@ func (e *AzureMonitorExecutor) unmarshalResponse(res *http.Response) (AzureMonit func (e *AzureMonitorExecutor) parseResponse(queryRes *tsdb.QueryResult, data AzureMonitorResponse, query *AzureMonitorQuery) error { slog.Info("AzureMonitor", "Response", data) + for _, series := range data.Value { + points := make([]tsdb.TimePoint, 0) + + defaultMetricName := fmt.Sprintf("%s.%s", query.UrlComponents["resourceName"], series.Name.LocalizedValue) + + for _, point := range series.Timeseries[0].Data { + value := point.Average + points = append(points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.TimeStamp).Unix())*1000)) + } + + queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ + Name: defaultMetricName, + Points: points, + }) + } + return nil } diff --git a/pkg/tsdb/azuremonitor/azuremonitor_test.go b/pkg/tsdb/azuremonitor/azuremonitor_test.go index 787e0ae1586..1b8f69aa64a 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor_test.go @@ -1,7 +1,9 @@ package azuremonitor import ( + "encoding/json" "fmt" + "io/ioutil" "testing" "time" @@ -57,5 +59,37 @@ func TestAzureMonitor(t *testing.T) { So(queries[0].Alias, ShouldEqual, "testalias") }) }) + + Convey("Parse AzureMonitor API response in the time series format", func() { + Convey("when data from query aggregated to one time series", func() { + data, err := loadTestFile("./test-data/1-azure-monitor-response.json") + So(err, ShouldBeNil) + So(data.Interval, ShouldEqual, "PT1M") + + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + query := &AzureMonitorQuery{ + UrlComponents: map[string]string{ + "resourceName": "grafana", + }, + } + err = executor.parseResponse(res, data, query) + So(err, ShouldBeNil) + + So(len(res.Series), ShouldEqual, 1) + So(res.Series[0].Name, ShouldEqual, "grafana.Percentage CPU") + So(len(res.Series[0].Points), ShouldEqual, 5) + }) + }) }) } + +func loadTestFile(path string) (AzureMonitorResponse, error) { + var data AzureMonitorResponse + + jsonBody, err := ioutil.ReadFile(path) + if err != nil { + return data, err + } + err = json.Unmarshal(jsonBody, &data) + return data, err +} diff --git a/pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response.json b/pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response.json new file mode 100644 index 00000000000..febb47f2047 --- /dev/null +++ b/pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response.json @@ -0,0 +1,47 @@ +{ + "cost": 0, + "timespan": "2019-02-08T10:13:50Z\/2019-02-08T16:13:50Z", + "interval": "PT1M", + "value": [ + { + "id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", + "type": "Microsoft.Insights\/metrics", + "name": { + "value": "Percentage CPU", + "localizedValue": "Percentage CPU" + }, + "unit": "Percent", + "timeseries": [ + { + "metadatavalues": [ + + ], + "data": [ + { + "timeStamp": "2019-02-08T10:13:00Z", + "average": 2.0875 + }, + { + "timeStamp": "2019-02-08T10:14:00Z", + "average": 2.1525 + }, + { + "timeStamp": "2019-02-08T10:15:00Z", + "average": 2.155 + }, + { + "timeStamp": "2019-02-08T10:16:00Z", + "average": 3.6925 + }, + { + "timeStamp": "2019-02-08T10:17:00Z", + "average": 2.44 + } + ] + } + ] + } + ], + "namespace": "Microsoft.Compute\/virtualMachines", + "resourceregion": "westeurope" +} diff --git a/pkg/tsdb/azuremonitor/types.go b/pkg/tsdb/azuremonitor/types.go index fc99ede6512..5b1b7255d62 100644 --- a/pkg/tsdb/azuremonitor/types.go +++ b/pkg/tsdb/azuremonitor/types.go @@ -8,11 +8,12 @@ import ( // AzureMonitorQuery is the query for all the services as they have similar queries // with a url, a querystring and an alias field type AzureMonitorQuery struct { - URL string - Target string - Params url.Values - RefID string - Alias string + URL string + UrlComponents map[string]string + Target string + Params url.Values + RefID string + Alias string } // AzureMonitorResponse is the json response from the Azure Monitor API diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/plugin.json b/public/app/plugins/datasource/grafana-azure-monitor-datasource/plugin.json index 76a56f2baaa..e4f48c581e3 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/plugin.json +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/plugin.json @@ -158,5 +158,6 @@ }, "metrics": true, - "annotations": true + "annotations": true, + "alerting": true } From bd6cefa53fc43e1a841ec13d448eefec77accb2a Mon Sep 17 00:00:00 2001 From: Nick Richards Date: Fri, 8 Feb 2019 14:51:50 -0800 Subject: [PATCH 033/119] Improve usability showing disabled lines in forms * Use gray-3 instead of gray-2 for text-color-weak in "light" theme --- public/sass/_variables.light.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 97d7a374765..0f4e15c91ec 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -56,7 +56,7 @@ $page-bg: $gray-7; $body-color: $gray-1; $text-color: $gray-1; $text-color-strong: $dark-2; -$text-color-weak: $gray-2; +$text-color-weak: $gray-3; $text-color-faint: $gray-4; $text-color-emphasis: $dark-5; From 2987a47a9b147bef3c8ebdc98ca962eac2a9cb4e Mon Sep 17 00:00:00 2001 From: Connor Patterson Date: Sat, 9 Feb 2019 13:47:08 -0500 Subject: [PATCH 034/119] Add aws ec2 api metrics for cloudwatch --- pkg/tsdb/cloudwatch/metric_find_query.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 34181d19673..8d186ba7a12 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -55,6 +55,7 @@ func init() { "AWS/DynamoDB": {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "TimeToLiveDeletedItemCount", "ThrottledRequests", "UserErrors", "WriteThrottleEvents"}, "AWS/EBS": {"VolumeReadBytes", "VolumeWriteBytes", "VolumeReadOps", "VolumeWriteOps", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeIdleTime", "VolumeQueueLength", "VolumeThroughputPercentage", "VolumeConsumedReadWriteOps", "BurstBalance"}, "AWS/EC2": {"CPUCreditUsage", "CPUCreditBalance", "CPUUtilization", "DiskReadOps", "DiskWriteOps", "DiskReadBytes", "DiskWriteBytes", "NetworkIn", "NetworkOut", "NetworkPacketsIn", "NetworkPacketsOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"}, + "AWS/EC2/API": {"ClientErrors","RequestLimitExceeded","ServerErrors","SuccessfulCalls"}, "AWS/EC2Spot": {"AvailableInstancePoolsCount", "BidsSubmittedForCapacity", "EligibleInstancePoolCount", "FulfilledCapacity", "MaxPercentCapacityAllocation", "PendingCapacity", "PercentCapacityAllocation", "TargetCapacity", "TerminatingCapacity"}, "AWS/ECS": {"CPUReservation", "MemoryReservation", "CPUUtilization", "MemoryUtilization"}, "AWS/EFS": {"BurstCreditBalance", "ClientConnections", "DataReadIOBytes", "DataWriteIOBytes", "MetadataIOBytes", "TotalIOBytes", "PermittedThroughput", "PercentIOLimit"}, @@ -133,6 +134,7 @@ func init() { "AWS/DynamoDB": {"TableName", "GlobalSecondaryIndexName", "Operation", "StreamLabel"}, "AWS/EBS": {"VolumeId"}, "AWS/EC2": {"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"}, + "AWS/EC2/API": {}, "AWS/EC2Spot": {"AvailabilityZone", "FleetRequestId", "InstanceType"}, "AWS/ECS": {"ClusterName", "ServiceName"}, "AWS/EFS": {"FileSystemId"}, From d2aed7e075207fe5da0fa1ffbf7fa151ae1c029a Mon Sep 17 00:00:00 2001 From: Connor Patterson Date: Sat, 9 Feb 2019 14:13:15 -0500 Subject: [PATCH 035/119] Fix formatting --- pkg/tsdb/cloudwatch/metric_find_query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 8d186ba7a12..ddda26dfd24 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -55,7 +55,7 @@ func init() { "AWS/DynamoDB": {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "TimeToLiveDeletedItemCount", "ThrottledRequests", "UserErrors", "WriteThrottleEvents"}, "AWS/EBS": {"VolumeReadBytes", "VolumeWriteBytes", "VolumeReadOps", "VolumeWriteOps", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeIdleTime", "VolumeQueueLength", "VolumeThroughputPercentage", "VolumeConsumedReadWriteOps", "BurstBalance"}, "AWS/EC2": {"CPUCreditUsage", "CPUCreditBalance", "CPUUtilization", "DiskReadOps", "DiskWriteOps", "DiskReadBytes", "DiskWriteBytes", "NetworkIn", "NetworkOut", "NetworkPacketsIn", "NetworkPacketsOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"}, - "AWS/EC2/API": {"ClientErrors","RequestLimitExceeded","ServerErrors","SuccessfulCalls"}, + "AWS/EC2/API": {"ClientErrors", "RequestLimitExceeded", "ServerErrors", "SuccessfulCalls"}, "AWS/EC2Spot": {"AvailableInstancePoolsCount", "BidsSubmittedForCapacity", "EligibleInstancePoolCount", "FulfilledCapacity", "MaxPercentCapacityAllocation", "PendingCapacity", "PercentCapacityAllocation", "TargetCapacity", "TerminatingCapacity"}, "AWS/ECS": {"CPUReservation", "MemoryReservation", "CPUUtilization", "MemoryUtilization"}, "AWS/EFS": {"BurstCreditBalance", "ClientConnections", "DataReadIOBytes", "DataWriteIOBytes", "MetadataIOBytes", "TotalIOBytes", "PermittedThroughput", "PercentIOLimit"}, From a5e5db20e171fb248da54ec97d1b0b071d51d46c Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sat, 9 Feb 2019 21:52:44 +0100 Subject: [PATCH 036/119] azuremonitor: add support for aggregations on backend --- pkg/tsdb/azuremonitor/azuremonitor.go | 16 ++- pkg/tsdb/azuremonitor/azuremonitor_test.go | 103 +++++++++++++++++- ...json => 1-azure-monitor-response-avg.json} | 0 .../2-azure-monitor-response-total.json | 47 ++++++++ .../3-azure-monitor-response-maximum.json | 47 ++++++++ .../4-azure-monitor-response-minimum.json | 47 ++++++++ .../5-azure-monitor-response-count.json | 47 ++++++++ pkg/tsdb/azuremonitor/types.go | 6 +- 8 files changed, 309 insertions(+), 4 deletions(-) rename pkg/tsdb/azuremonitor/test-data/{1-azure-monitor-response.json => 1-azure-monitor-response-avg.json} (100%) create mode 100644 pkg/tsdb/azuremonitor/test-data/2-azure-monitor-response-total.json create mode 100644 pkg/tsdb/azuremonitor/test-data/3-azure-monitor-response-maximum.json create mode 100644 pkg/tsdb/azuremonitor/test-data/4-azure-monitor-response-minimum.json create mode 100644 pkg/tsdb/azuremonitor/test-data/5-azure-monitor-response-count.json diff --git a/pkg/tsdb/azuremonitor/azuremonitor.go b/pkg/tsdb/azuremonitor/azuremonitor.go index 8ef959bed9c..3c6ae9b0013 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor.go +++ b/pkg/tsdb/azuremonitor/azuremonitor.go @@ -256,7 +256,21 @@ func (e *AzureMonitorExecutor) parseResponse(queryRes *tsdb.QueryResult, data Az defaultMetricName := fmt.Sprintf("%s.%s", query.UrlComponents["resourceName"], series.Name.LocalizedValue) for _, point := range series.Timeseries[0].Data { - value := point.Average + var value float64 + switch query.Params.Get("aggregation") { + case "Average": + value = point.Average + case "Total": + value = point.Total + case "Maximum": + value = point.Maximum + case "Minimum": + value = point.Minimum + case "Count": + value = point.Count + default: + value = point.Count + } points = append(points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.TimeStamp).Unix())*1000)) } diff --git a/pkg/tsdb/azuremonitor/azuremonitor_test.go b/pkg/tsdb/azuremonitor/azuremonitor_test.go index 1b8f69aa64a..73f2d83090f 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/url" "testing" "time" @@ -61,8 +62,8 @@ func TestAzureMonitor(t *testing.T) { }) Convey("Parse AzureMonitor API response in the time series format", func() { - Convey("when data from query aggregated to one time series", func() { - data, err := loadTestFile("./test-data/1-azure-monitor-response.json") + Convey("when data from query aggregated as average to one time series", func() { + data, err := loadTestFile("./test-data/1-azure-monitor-response-avg.json") So(err, ShouldBeNil) So(data.Interval, ShouldEqual, "PT1M") @@ -71,6 +72,9 @@ func TestAzureMonitor(t *testing.T) { UrlComponents: map[string]string{ "resourceName": "grafana", }, + Params: url.Values{ + "aggregation": {"Average"}, + }, } err = executor.parseResponse(res, data, query) So(err, ShouldBeNil) @@ -78,6 +82,101 @@ func TestAzureMonitor(t *testing.T) { So(len(res.Series), ShouldEqual, 1) So(res.Series[0].Name, ShouldEqual, "grafana.Percentage CPU") So(len(res.Series[0].Points), ShouldEqual, 5) + + So(res.Series[0].Points[0][0].Float64, ShouldEqual, 2.0875) + So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1549620780000) + + So(res.Series[0].Points[1][0].Float64, ShouldEqual, 2.1525) + So(res.Series[0].Points[1][1].Float64, ShouldEqual, 1549620840000) + + So(res.Series[0].Points[2][0].Float64, ShouldEqual, 2.155) + So(res.Series[0].Points[2][1].Float64, ShouldEqual, 1549620900000) + + So(res.Series[0].Points[3][0].Float64, ShouldEqual, 3.6925) + So(res.Series[0].Points[3][1].Float64, ShouldEqual, 1549620960000) + + So(res.Series[0].Points[4][0].Float64, ShouldEqual, 2.44) + So(res.Series[0].Points[4][1].Float64, ShouldEqual, 1549621020000) + }) + + Convey("when data from query aggregated as total to one time series", func() { + data, err := loadTestFile("./test-data/2-azure-monitor-response-total.json") + So(err, ShouldBeNil) + + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + query := &AzureMonitorQuery{ + UrlComponents: map[string]string{ + "resourceName": "grafana", + }, + Params: url.Values{ + "aggregation": {"Total"}, + }, + } + err = executor.parseResponse(res, data, query) + So(err, ShouldBeNil) + + So(res.Series[0].Points[0][0].Float64, ShouldEqual, 8.26) + So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1549718940000) + }) + + Convey("when data from query aggregated as maximum to one time series", func() { + data, err := loadTestFile("./test-data/3-azure-monitor-response-maximum.json") + So(err, ShouldBeNil) + + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + query := &AzureMonitorQuery{ + UrlComponents: map[string]string{ + "resourceName": "grafana", + }, + Params: url.Values{ + "aggregation": {"Maximum"}, + }, + } + err = executor.parseResponse(res, data, query) + So(err, ShouldBeNil) + + So(res.Series[0].Points[0][0].Float64, ShouldEqual, 3.07) + So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1549722360000) + }) + + Convey("when data from query aggregated as minimum to one time series", func() { + data, err := loadTestFile("./test-data/4-azure-monitor-response-minimum.json") + So(err, ShouldBeNil) + + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + query := &AzureMonitorQuery{ + UrlComponents: map[string]string{ + "resourceName": "grafana", + }, + Params: url.Values{ + "aggregation": {"Minimum"}, + }, + } + err = executor.parseResponse(res, data, query) + So(err, ShouldBeNil) + + So(res.Series[0].Points[0][0].Float64, ShouldEqual, 1.51) + So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1549723380000) + }) + + Convey("when data from query aggregated as Count to one time series", func() { + data, err := loadTestFile("./test-data/5-azure-monitor-response-count.json") + So(err, ShouldBeNil) + + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + query := &AzureMonitorQuery{ + UrlComponents: map[string]string{ + "resourceName": "grafana", + }, + Params: url.Values{ + "aggregation": {"Count"}, + }, + } + err = executor.parseResponse(res, data, query) + So(err, ShouldBeNil) + + So(res.Series[0].Points[0][0].Float64, ShouldEqual, 4) + So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1549723440000) }) }) }) diff --git a/pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response.json b/pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response-avg.json similarity index 100% rename from pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response.json rename to pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response-avg.json diff --git a/pkg/tsdb/azuremonitor/test-data/2-azure-monitor-response-total.json b/pkg/tsdb/azuremonitor/test-data/2-azure-monitor-response-total.json new file mode 100644 index 00000000000..1002bbf7d18 --- /dev/null +++ b/pkg/tsdb/azuremonitor/test-data/2-azure-monitor-response-total.json @@ -0,0 +1,47 @@ +{ + "cost": 0, + "timespan": "2019-02-09T13:29:41Z\/2019-02-09T19:29:41Z", + "interval": "PT1M", + "value": [ + { + "id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", + "type": "Microsoft.Insights\/metrics", + "name": { + "value": "Percentage CPU", + "localizedValue": "Percentage CPU" + }, + "unit": "Percent", + "timeseries": [ + { + "metadatavalues": [ + + ], + "data": [ + { + "timeStamp": "2019-02-09T13:29:00Z", + "total": 8.26 + }, + { + "timeStamp": "2019-02-09T13:30:00Z", + "total": 8.7 + }, + { + "timeStamp": "2019-02-09T13:31:00Z", + "total": 14.82 + }, + { + "timeStamp": "2019-02-09T13:32:00Z", + "total": 10.07 + }, + { + "timeStamp": "2019-02-09T13:33:00Z", + "total": 8.52 + } + ] + } + ] + } + ], + "namespace": "Microsoft.Compute\/virtualMachines", + "resourceregion": "westeurope" +} diff --git a/pkg/tsdb/azuremonitor/test-data/3-azure-monitor-response-maximum.json b/pkg/tsdb/azuremonitor/test-data/3-azure-monitor-response-maximum.json new file mode 100644 index 00000000000..3ca83c99932 --- /dev/null +++ b/pkg/tsdb/azuremonitor/test-data/3-azure-monitor-response-maximum.json @@ -0,0 +1,47 @@ +{ + "cost": 0, + "timespan": "2019-02-09T14:26:12Z\/2019-02-09T20:26:12Z", + "interval": "PT1M", + "value": [ + { + "id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", + "type": "Microsoft.Insights\/metrics", + "name": { + "value": "Percentage CPU", + "localizedValue": "Percentage CPU" + }, + "unit": "Percent", + "timeseries": [ + { + "metadatavalues": [ + + ], + "data": [ + { + "timeStamp": "2019-02-09T14:26:00Z", + "maximum": 3.07 + }, + { + "timeStamp": "2019-02-09T14:27:00Z", + "maximum": 2.92 + }, + { + "timeStamp": "2019-02-09T14:28:00Z", + "maximum": 2.87 + }, + { + "timeStamp": "2019-02-09T14:29:00Z", + "maximum": 2.27 + }, + { + "timeStamp": "2019-02-09T14:30:00Z", + "maximum": 2.52 + } + ] + } + ] + } + ], + "namespace": "Microsoft.Compute\/virtualMachines", + "resourceregion": "westeurope" +} diff --git a/pkg/tsdb/azuremonitor/test-data/4-azure-monitor-response-minimum.json b/pkg/tsdb/azuremonitor/test-data/4-azure-monitor-response-minimum.json new file mode 100644 index 00000000000..5e5f99cc498 --- /dev/null +++ b/pkg/tsdb/azuremonitor/test-data/4-azure-monitor-response-minimum.json @@ -0,0 +1,47 @@ +{ + "cost": 0, + "timespan": "2019-02-09T14:43:21Z\/2019-02-09T20:43:21Z", + "interval": "PT1M", + "value": [ + { + "id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", + "type": "Microsoft.Insights\/metrics", + "name": { + "value": "Percentage CPU", + "localizedValue": "Percentage CPU" + }, + "unit": "Percent", + "timeseries": [ + { + "metadatavalues": [ + + ], + "data": [ + { + "timeStamp": "2019-02-09T14:43:00Z", + "minimum": 1.51 + }, + { + "timeStamp": "2019-02-09T14:44:00Z", + "minimum": 2.38 + }, + { + "timeStamp": "2019-02-09T14:45:00Z", + "minimum": 1.69 + }, + { + "timeStamp": "2019-02-09T14:46:00Z", + "minimum": 2.27 + }, + { + "timeStamp": "2019-02-09T14:47:00Z", + "minimum": 1.96 + } + ] + } + ] + } + ], + "namespace": "Microsoft.Compute\/virtualMachines", + "resourceregion": "westeurope" +} diff --git a/pkg/tsdb/azuremonitor/test-data/5-azure-monitor-response-count.json b/pkg/tsdb/azuremonitor/test-data/5-azure-monitor-response-count.json new file mode 100644 index 00000000000..f024a5f2518 --- /dev/null +++ b/pkg/tsdb/azuremonitor/test-data/5-azure-monitor-response-count.json @@ -0,0 +1,47 @@ +{ + "cost": 0, + "timespan": "2019-02-09T14:44:52Z\/2019-02-09T20:44:52Z", + "interval": "PT1M", + "value": [ + { + "id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", + "type": "Microsoft.Insights\/metrics", + "name": { + "value": "Percentage CPU", + "localizedValue": "Percentage CPU" + }, + "unit": "Percent", + "timeseries": [ + { + "metadatavalues": [ + + ], + "data": [ + { + "timeStamp": "2019-02-09T14:44:00Z", + "count": 4 + }, + { + "timeStamp": "2019-02-09T14:45:00Z", + "count": 4 + }, + { + "timeStamp": "2019-02-09T14:46:00Z", + "count": 4 + }, + { + "timeStamp": "2019-02-09T14:47:00Z", + "count": 4 + }, + { + "timeStamp": "2019-02-09T14:48:00Z", + "count": 4 + } + ] + } + ] + } + ], + "namespace": "Microsoft.Compute\/virtualMachines", + "resourceregion": "westeurope" +} diff --git a/pkg/tsdb/azuremonitor/types.go b/pkg/tsdb/azuremonitor/types.go index 5b1b7255d62..b547c71f185 100644 --- a/pkg/tsdb/azuremonitor/types.go +++ b/pkg/tsdb/azuremonitor/types.go @@ -39,7 +39,11 @@ type AzureMonitorResponse struct { } `json:"metadatavalues"` Data []struct { TimeStamp time.Time `json:"timeStamp"` - Average float64 `json:"average"` + Average float64 `json:"average,omitempty"` + Total float64 `json:"total,omitempty"` + Count float64 `json:"count,omitempty"` + Maximum float64 `json:"maximum,omitempty"` + Minimum float64 `json:"minimum,omitempty"` } `json:"data"` } `json:"timeseries"` } `json:"value"` From 716db35faeb0942568968e879bd20d994a9454d3 Mon Sep 17 00:00:00 2001 From: thatsparesh <45209+thatsparesh@users.noreply.github.com> Date: Sat, 9 Feb 2019 14:56:43 -0600 Subject: [PATCH 037/119] remove unnecessary spy --- public/test/specs/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/test/specs/helpers.ts b/public/test/specs/helpers.ts index 1570c7dd9b7..b8307186540 100644 --- a/public/test/specs/helpers.ts +++ b/public/test/specs/helpers.ts @@ -143,7 +143,7 @@ export function DashboardViewStateStub(this: any) { } export function TimeSrvStub(this: any) { - this.init = sinon.spy(); + this.init = () => {}; this.time = { from: 'now-1h', to: 'now' }; this.timeRange = function(parse) { if (parse === false) { From 105879ab5dc59da377f794b4be582a4b90a7cd61 Mon Sep 17 00:00:00 2001 From: thatsparesh <45209+thatsparesh@users.noreply.github.com> Date: Sat, 9 Feb 2019 14:57:20 -0600 Subject: [PATCH 038/119] use timeSrv in metricFindQuery as timeRange --- .../plugins/datasource/mssql/datasource.ts | 14 ++---- .../datasource/mssql/specs/datasource.test.ts | 48 ++++++++++++++++++- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/public/app/plugins/datasource/mssql/datasource.ts b/public/app/plugins/datasource/mssql/datasource.ts index 1ede9cc3d1e..303cd0471d7 100644 --- a/public/app/plugins/datasource/mssql/datasource.ts +++ b/public/app/plugins/datasource/mssql/datasource.ts @@ -8,7 +8,7 @@ export class MssqlDatasource { interval: string; /** @ngInject */ - constructor(instanceSettings, private backendSrv, private $q, private templateSrv) { + constructor(instanceSettings, private backendSrv, private $q, private templateSrv, private timeSrv) { this.name = instanceSettings.name; this.id = instanceSettings.id; this.responseParser = new ResponseParser(this.$q); @@ -107,19 +107,13 @@ export class MssqlDatasource { format: 'table', }; + const range = this.timeSrv.timeRange(); const data = { queries: [interpolatedQuery], + from: range.from.valueOf().toString(), + to: range.to.valueOf().toString(), }; - if (optionalOptions && optionalOptions.range) { - if (optionalOptions.range.from) { - data['from'] = optionalOptions.range.from.valueOf().toString(); - } - if (optionalOptions.range.to) { - data['to'] = optionalOptions.range.to.valueOf().toString(); - } - } - return this.backendSrv .datasourceRequest({ url: '/api/tsdb/query', diff --git a/public/app/plugins/datasource/mssql/specs/datasource.test.ts b/public/app/plugins/datasource/mssql/specs/datasource.test.ts index 0dd496bfe59..a05848b3da8 100644 --- a/public/app/plugins/datasource/mssql/specs/datasource.test.ts +++ b/public/app/plugins/datasource/mssql/specs/datasource.test.ts @@ -1,6 +1,6 @@ import moment from 'moment'; import { MssqlDatasource } from '../datasource'; -import { TemplateSrvStub } from 'test/specs/helpers'; +import { TemplateSrvStub, TimeSrvStub } from 'test/specs/helpers'; import { CustomVariable } from 'app/features/templating/custom_variable'; import q from 'q'; @@ -8,13 +8,14 @@ describe('MSSQLDatasource', () => { const ctx: any = { backendSrv: {}, templateSrv: new TemplateSrvStub(), + timeSrv: new TimeSrvStub(), }; beforeEach(() => { ctx.$q = q; ctx.instanceSettings = { name: 'mssql' }; - ctx.ds = new MssqlDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.$q, ctx.templateSrv); + ctx.ds = new MssqlDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.$q, ctx.templateSrv, ctx.timeSrv); }); describe('When performing annotationQuery', () => { @@ -188,6 +189,49 @@ describe('MSSQLDatasource', () => { }); }); + describe('When performing metricFindQuery', () => { + let results; + const query = 'select * from atable'; + const response = { + results: { + tempvar: { + meta: { + rowCount: 1, + }, + refId: 'tempvar', + tables: [ + { + columns: [{ text: 'title' }], + rows: [['aTitle']], + }, + ], + }, + }, + }; + const time = { + from: moment(1521545610656), + to: moment(1521546251185) + }; + + beforeEach(() => { + ctx.timeSrv.setTime(time); + + ctx.backendSrv.datasourceRequest = options => { + results = options.data; + return ctx.$q.when({ data: response, status: 200 }); + }; + + return ctx.ds.metricFindQuery(query); + }); + + it('should pass timerange to datasourceRequest', () => { + expect(results.from).toBe(time.from.valueOf().toString()); + expect(results.to).toBe(time.to.valueOf().toString()); + expect(results.queries.length).toBe(1); + expect(results.queries[0].rawSql).toBe(query); + }); + }); + describe('When interpolating variables', () => { beforeEach(() => { ctx.variable = new CustomVariable({}, {}); From b816f35c41f004e652a114b0ca39263f02ee1843 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sun, 10 Feb 2019 00:23:12 +0100 Subject: [PATCH 039/119] azuremonitor: handle multi-dimensions on backend --- pkg/tsdb/azuremonitor/azuremonitor.go | 62 ++++++--- pkg/tsdb/azuremonitor/azuremonitor_test.go | 27 ++++ pkg/tsdb/azuremonitor/legend-key.go | 11 ++ .../1-azure-monitor-response-avg.json | 2 +- .../2-azure-monitor-response-total.json | 2 +- .../3-azure-monitor-response-maximum.json | 2 +- .../4-azure-monitor-response-minimum.json | 2 +- .../5-azure-monitor-response-count.json | 2 +- ...zure-monitor-response-multi-dimension.json | 128 ++++++++++++++++++ pkg/tsdb/azuremonitor/url-builder.go | 28 ++++ pkg/tsdb/azuremonitor/url-builder_test.go | 45 ++++++ 11 files changed, 287 insertions(+), 24 deletions(-) create mode 100644 pkg/tsdb/azuremonitor/legend-key.go create mode 100644 pkg/tsdb/azuremonitor/test-data/6-azure-monitor-response-multi-dimension.json create mode 100644 pkg/tsdb/azuremonitor/url-builder.go create mode 100644 pkg/tsdb/azuremonitor/url-builder_test.go diff --git a/pkg/tsdb/azuremonitor/azuremonitor.go b/pkg/tsdb/azuremonitor/azuremonitor.go index 3c6ae9b0013..ef1376f5ed5 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor.go +++ b/pkg/tsdb/azuremonitor/azuremonitor.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "path" + "strings" "time" "github.com/grafana/grafana/pkg/api/pluginproxy" @@ -24,7 +25,7 @@ import ( ) var ( - slog log.Logger + azlog log.Logger ) // AzureMonitorExecutor executes queries for the Azure Monitor datasource - all four services @@ -47,7 +48,7 @@ func NewAzureMonitorExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, } func init() { - slog = log.New("tsdb.azuremonitor") + azlog = log.New("tsdb.azuremonitor") tsdb.RegisterTsdbQueryEndpoint("grafana-azure-monitor-datasource", NewAzureMonitorExecutor) } @@ -61,7 +62,6 @@ func (e *AzureMonitorExecutor) Query(ctx context.Context, dsInfo *models.DataSou queryType := tsdbQuery.Queries[0].Model.Get("queryType").MustString("") switch queryType { - case "azureMonitorTimeSeriesQuery": case "Azure Monitor": fallthrough default: @@ -112,26 +112,39 @@ func (e *AzureMonitorExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Azure var target string azureMonitorTarget := query.Model.Get("azureMonitor").MustMap() + azlog.Debug("AzureMonitor", "target", azureMonitorTarget) urlComponents := make(map[string]string) - urlComponents["resourceGroup"] = azureMonitorTarget["resourceGroup"].(string) - urlComponents["metricDefinition"] = azureMonitorTarget["metricDefinition"].(string) - urlComponents["resourceName"] = azureMonitorTarget["resourceName"].(string) + urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"]) + urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"]) + urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"]) - azureURL := fmt.Sprintf("resourceGroups/%s/providers/%s/%s/providers/microsoft.insights/metrics", urlComponents["resourceGroup"], urlComponents["metricDefinition"], urlComponents["resourceName"]) + ub := URLBuilder{ + ResourceGroup: urlComponents["resourceGroup"], + MetricDefinition: urlComponents["metricDefinition"], + ResourceName: urlComponents["resourceName"], + } + azureURL := ub.Build() - alias := azureMonitorTarget["alias"].(string) + alias := fmt.Sprintf("%v", azureMonitorTarget["alias"]) params := url.Values{} params.Add("api-version", "2018-01-01") params.Add("timespan", fmt.Sprintf("%v/%v", startTime.UTC().Format(time.RFC3339), endTime.UTC().Format(time.RFC3339))) - params.Add("interval", azureMonitorTarget["timeGrain"].(string)) - params.Add("aggregation", azureMonitorTarget["aggregation"].(string)) - params.Add("metricnames", azureMonitorTarget["metricName"].(string)) + params.Add("interval", fmt.Sprintf("%v", azureMonitorTarget["timeGrain"])) + params.Add("aggregation", fmt.Sprintf("%v", azureMonitorTarget["aggregation"])) + params.Add("metricnames", fmt.Sprintf("%v", azureMonitorTarget["metricName"])) + + dimension := fmt.Sprintf("%v", azureMonitorTarget["dimension"]) + dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimensionFilter"])) + if azureMonitorTarget["dimension"] != nil && azureMonitorTarget["dimensionFilter"] != nil && dimensionFilter != "" { + params.Add("$filter", fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter)) + } + target = params.Encode() if setting.Env == setting.DEV { - slog.Debug("Azuremonitor request", "params", params) + azlog.Debug("Azuremonitor request", "params", params) } azureMonitorQueries = append(azureMonitorQueries, &AzureMonitorQuery{ @@ -174,6 +187,7 @@ func (e *AzureMonitorExecutor) executeQuery(ctx context.Context, query *AzureMon opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) + azlog.Debug("AzureMonitor", "Request URL", req.URL.String()) res, err := ctxhttp.Do(ctx, e.httpClient, req) if err != nil { queryResult.Error = err @@ -213,7 +227,7 @@ func (e *AzureMonitorExecutor) createRequest(ctx context.Context, dsInfo *models req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - slog.Error("Failed to create request", "error", err) + azlog.Error("Failed to create request", "error", err) return nil, fmt.Errorf("Failed to create request. error: %v", err) } @@ -233,14 +247,14 @@ func (e *AzureMonitorExecutor) unmarshalResponse(res *http.Response) (AzureMonit } if res.StatusCode/100 != 2 { - slog.Error("Request failed", "status", res.Status, "body", string(body)) + azlog.Error("Request failed", "status", res.Status, "body", string(body)) return AzureMonitorResponse{}, fmt.Errorf(string(body)) } var data AzureMonitorResponse err = json.Unmarshal(body, &data) if err != nil { - slog.Error("Failed to unmarshal AzureMonitor response", "error", err, "status", res.Status, "body", string(body)) + azlog.Error("Failed to unmarshal AzureMonitor response", "error", err, "status", res.Status, "body", string(body)) return AzureMonitorResponse{}, err } @@ -248,14 +262,24 @@ func (e *AzureMonitorExecutor) unmarshalResponse(res *http.Response) (AzureMonit } func (e *AzureMonitorExecutor) parseResponse(queryRes *tsdb.QueryResult, data AzureMonitorResponse, query *AzureMonitorQuery) error { - slog.Info("AzureMonitor", "Response", data) + azlog.Debug("AzureMonitor", "Response", data) - for _, series := range data.Value { + if len(data.Value) == 0 { + return nil + } + + for _, series := range data.Value[0].Timeseries { points := make([]tsdb.TimePoint, 0) - defaultMetricName := fmt.Sprintf("%s.%s", query.UrlComponents["resourceName"], series.Name.LocalizedValue) + metadataName := "" + metadataValue := "" + if len(series.Metadatavalues) > 0 { + metadataName = series.Metadatavalues[0].Name.LocalizedValue + metadataValue = series.Metadatavalues[0].Value + } + defaultMetricName := formatLegendKey(query.UrlComponents["resourceName"], data.Value[0].Name.LocalizedValue, metadataName, metadataValue) - for _, point := range series.Timeseries[0].Data { + for _, point := range series.Data { var value float64 switch query.Params.Get("aggregation") { case "Average": diff --git a/pkg/tsdb/azuremonitor/azuremonitor_test.go b/pkg/tsdb/azuremonitor/azuremonitor_test.go index 73f2d83090f..760fecd0630 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor_test.go @@ -178,6 +178,33 @@ func TestAzureMonitor(t *testing.T) { So(res.Series[0].Points[0][0].Float64, ShouldEqual, 4) So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1549723440000) }) + + Convey("when data from query aggregated as total and has dimension filter", func() { + data, err := loadTestFile("./test-data/6-azure-monitor-response-multi-dimension.json") + So(err, ShouldBeNil) + + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + query := &AzureMonitorQuery{ + UrlComponents: map[string]string{ + "resourceName": "grafana", + }, + Params: url.Values{ + "aggregation": {"Average"}, + }, + } + err = executor.parseResponse(res, data, query) + So(err, ShouldBeNil) + So(len(res.Series), ShouldEqual, 3) + + So(res.Series[0].Name, ShouldEqual, "grafana{blobtype=PageBlob}.Blob Count") + So(res.Series[0].Points[0][0].Float64, ShouldEqual, 3) + + So(res.Series[1].Name, ShouldEqual, "grafana{blobtype=BlockBlob}.Blob Count") + So(res.Series[1].Points[0][0].Float64, ShouldEqual, 1) + + So(res.Series[2].Name, ShouldEqual, "grafana{blobtype=Azure Data Lake Storage}.Blob Count") + So(res.Series[2].Points[0][0].Float64, ShouldEqual, 0) + }) }) }) } diff --git a/pkg/tsdb/azuremonitor/legend-key.go b/pkg/tsdb/azuremonitor/legend-key.go new file mode 100644 index 00000000000..7d5cdbbcdd3 --- /dev/null +++ b/pkg/tsdb/azuremonitor/legend-key.go @@ -0,0 +1,11 @@ +package azuremonitor + +import "fmt" + +// formatLegendKey builds the legend key or timeseries name +func formatLegendKey(resourceName string, metricName string, metadataName string, metadataValue string) string { + if len(metadataName) > 0 { + return fmt.Sprintf("%s{%s=%s}.%s", resourceName, metadataName, metadataValue, metricName) + } + return fmt.Sprintf("%s.%s", resourceName, metricName) +} diff --git a/pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response-avg.json b/pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response-avg.json index febb47f2047..5fc84f6afa6 100644 --- a/pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response-avg.json +++ b/pkg/tsdb/azuremonitor/test-data/1-azure-monitor-response-avg.json @@ -4,7 +4,7 @@ "interval": "PT1M", "value": [ { - "id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", + "id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", "type": "Microsoft.Insights\/metrics", "name": { "value": "Percentage CPU", diff --git a/pkg/tsdb/azuremonitor/test-data/2-azure-monitor-response-total.json b/pkg/tsdb/azuremonitor/test-data/2-azure-monitor-response-total.json index 1002bbf7d18..d0b22f1b02c 100644 --- a/pkg/tsdb/azuremonitor/test-data/2-azure-monitor-response-total.json +++ b/pkg/tsdb/azuremonitor/test-data/2-azure-monitor-response-total.json @@ -4,7 +4,7 @@ "interval": "PT1M", "value": [ { - "id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", + "id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", "type": "Microsoft.Insights\/metrics", "name": { "value": "Percentage CPU", diff --git a/pkg/tsdb/azuremonitor/test-data/3-azure-monitor-response-maximum.json b/pkg/tsdb/azuremonitor/test-data/3-azure-monitor-response-maximum.json index 3ca83c99932..1e46cceb2be 100644 --- a/pkg/tsdb/azuremonitor/test-data/3-azure-monitor-response-maximum.json +++ b/pkg/tsdb/azuremonitor/test-data/3-azure-monitor-response-maximum.json @@ -4,7 +4,7 @@ "interval": "PT1M", "value": [ { - "id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", + "id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", "type": "Microsoft.Insights\/metrics", "name": { "value": "Percentage CPU", diff --git a/pkg/tsdb/azuremonitor/test-data/4-azure-monitor-response-minimum.json b/pkg/tsdb/azuremonitor/test-data/4-azure-monitor-response-minimum.json index 5e5f99cc498..16310614214 100644 --- a/pkg/tsdb/azuremonitor/test-data/4-azure-monitor-response-minimum.json +++ b/pkg/tsdb/azuremonitor/test-data/4-azure-monitor-response-minimum.json @@ -4,7 +4,7 @@ "interval": "PT1M", "value": [ { - "id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", + "id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", "type": "Microsoft.Insights\/metrics", "name": { "value": "Percentage CPU", diff --git a/pkg/tsdb/azuremonitor/test-data/5-azure-monitor-response-count.json b/pkg/tsdb/azuremonitor/test-data/5-azure-monitor-response-count.json index f024a5f2518..91afc33f070 100644 --- a/pkg/tsdb/azuremonitor/test-data/5-azure-monitor-response-count.json +++ b/pkg/tsdb/azuremonitor/test-data/5-azure-monitor-response-count.json @@ -4,7 +4,7 @@ "interval": "PT1M", "value": [ { - "id": "\/subscriptions\/44693801-6ee6-49de-9b2d-9106972f9572\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", + "id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Compute\/virtualMachines\/grafana\/providers\/Microsoft.Insights\/metrics\/Percentage CPU", "type": "Microsoft.Insights\/metrics", "name": { "value": "Percentage CPU", diff --git a/pkg/tsdb/azuremonitor/test-data/6-azure-monitor-response-multi-dimension.json b/pkg/tsdb/azuremonitor/test-data/6-azure-monitor-response-multi-dimension.json new file mode 100644 index 00000000000..dddcef0e79c --- /dev/null +++ b/pkg/tsdb/azuremonitor/test-data/6-azure-monitor-response-multi-dimension.json @@ -0,0 +1,128 @@ +{ + "cost": 0, + "timespan": "2019-02-09T15:21:39Z\/2019-02-09T21:21:39Z", + "interval": "PT1H", + "value": [ + { + "id": "\/subscriptions\/xxx\/resourceGroups\/grafanastaging\/providers\/Microsoft.Storage\/storageAccounts\/grafanastaging\/blobServices\/default\/providers\/Microsoft.Insights\/metrics\/BlobCount", + "type": "Microsoft.Insights\/metrics", + "name": { + "value": "BlobCount", + "localizedValue": "Blob Count" + }, + "unit": "Count", + "timeseries": [ + { + "metadatavalues": [ + { + "name": { + "value": "blobtype", + "localizedValue": "blobtype" + }, + "value": "PageBlob" + } + ], + "data": [ + { + "timeStamp": "2019-02-09T15:21:00Z", + "average": 3 + }, + { + "timeStamp": "2019-02-09T16:21:00Z", + "average": 3 + }, + { + "timeStamp": "2019-02-09T17:21:00Z", + "average": 3 + }, + { + "timeStamp": "2019-02-09T18:21:00Z", + "average": 3 + }, + { + "timeStamp": "2019-02-09T19:21:00Z", + "average": 3 + }, + { + "timeStamp": "2019-02-09T20:21:00Z" + } + ] + }, + { + "metadatavalues": [ + { + "name": { + "value": "blobtype", + "localizedValue": "blobtype" + }, + "value": "BlockBlob" + } + ], + "data": [ + { + "timeStamp": "2019-02-09T15:21:00Z", + "average": 1 + }, + { + "timeStamp": "2019-02-09T16:21:00Z", + "average": 1 + }, + { + "timeStamp": "2019-02-09T17:21:00Z", + "average": 1 + }, + { + "timeStamp": "2019-02-09T18:21:00Z", + "average": 1 + }, + { + "timeStamp": "2019-02-09T19:21:00Z", + "average": 1 + }, + { + "timeStamp": "2019-02-09T20:21:00Z" + } + ] + }, + { + "metadatavalues": [ + { + "name": { + "value": "blobtype", + "localizedValue": "blobtype" + }, + "value": "Azure Data Lake Storage" + } + ], + "data": [ + { + "timeStamp": "2019-02-09T15:21:00Z", + "average": 0 + }, + { + "timeStamp": "2019-02-09T16:21:00Z", + "average": 0 + }, + { + "timeStamp": "2019-02-09T17:21:00Z", + "average": 0 + }, + { + "timeStamp": "2019-02-09T18:21:00Z", + "average": 0 + }, + { + "timeStamp": "2019-02-09T19:21:00Z", + "average": 0 + }, + { + "timeStamp": "2019-02-09T20:21:00Z" + } + ] + } + ] + } + ], + "namespace": "Microsoft.Storage\/storageAccounts\/blobServices", + "resourceregion": "westeurope" +} diff --git a/pkg/tsdb/azuremonitor/url-builder.go b/pkg/tsdb/azuremonitor/url-builder.go new file mode 100644 index 00000000000..1ccbbc2bf81 --- /dev/null +++ b/pkg/tsdb/azuremonitor/url-builder.go @@ -0,0 +1,28 @@ +package azuremonitor + +import ( + "fmt" + "strings" +) + +// URLBuilder builds the URL for calling the Azure Monitor API +type URLBuilder struct { + ResourceGroup string + MetricDefinition string + ResourceName string +} + +// Build checks the metric definition property to see which form of the url +// should be returned +func (ub *URLBuilder) Build() string { + + if strings.Count(ub.MetricDefinition, "/") > 1 { + rn := strings.Split(ub.ResourceName, "/") + lastIndex := strings.LastIndex(ub.MetricDefinition, "/") + service := ub.MetricDefinition[lastIndex+1:] + md := ub.MetricDefinition[0:lastIndex] + return fmt.Sprintf("resourceGroups/%s/providers/%s/%s/%s/%s/providers/microsoft.insights/metrics", ub.ResourceGroup, md, rn[0], service, rn[1]) + } + + return fmt.Sprintf("resourceGroups/%s/providers/%s/%s/providers/microsoft.insights/metrics", ub.ResourceGroup, ub.MetricDefinition, ub.ResourceName) +} diff --git a/pkg/tsdb/azuremonitor/url-builder_test.go b/pkg/tsdb/azuremonitor/url-builder_test.go new file mode 100644 index 00000000000..baf9b34d7eb --- /dev/null +++ b/pkg/tsdb/azuremonitor/url-builder_test.go @@ -0,0 +1,45 @@ +package azuremonitor + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestURLBuilder(t *testing.T) { + Convey("AzureMonitor URL Builder", t, func() { + + Convey("when metric definition is in the short form", func() { + ub := &URLBuilder{ + ResourceGroup: "rg", + MetricDefinition: "Microsoft.Compute/virtualMachines", + ResourceName: "rn", + } + + url := ub.Build() + So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics") + }) + + Convey("when metric definition is Microsoft.Storage/storageAccounts/blobServices", func() { + ub := &URLBuilder{ + ResourceGroup: "rg", + MetricDefinition: "Microsoft.Storage/storageAccounts/blobServices", + ResourceName: "rn1/default", + } + + url := ub.Build() + So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/providers/microsoft.insights/metrics") + }) + + Convey("when metric definition is Microsoft.Storage/storageAccounts/fileServices", func() { + ub := &URLBuilder{ + ResourceGroup: "rg", + MetricDefinition: "Microsoft.Storage/storageAccounts/fileServices", + ResourceName: "rn1/default", + } + + url := ub.Build() + So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/providers/microsoft.insights/metrics") + }) + }) +} From b94de101cd95b23f0266cd61e311f09ae8ad7592 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sun, 10 Feb 2019 01:18:16 +0100 Subject: [PATCH 040/119] azuremonitor: refactor azure monitor api code into own file --- .../azuremonitor/azuremonitor-datasource.go | 266 +++++++++++++++++ ...est.go => azuremonitor-datasource_test.go} | 8 +- pkg/tsdb/azuremonitor/azuremonitor.go | 274 ++---------------- 3 files changed, 288 insertions(+), 260 deletions(-) create mode 100644 pkg/tsdb/azuremonitor/azuremonitor-datasource.go rename pkg/tsdb/azuremonitor/{azuremonitor_test.go => azuremonitor-datasource_test.go} (97%) diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go new file mode 100644 index 00000000000..99e45bb58b8 --- /dev/null +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go @@ -0,0 +1,266 @@ +package azuremonitor + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "path" + "strings" + "time" + + "github.com/grafana/grafana/pkg/api/pluginproxy" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/setting" + opentracing "github.com/opentracing/opentracing-go" + "golang.org/x/net/context/ctxhttp" + + "github.com/grafana/grafana/pkg/components/null" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/tsdb" +) + +type AzureMonitorDatasource struct { + httpClient *http.Client + dsInfo *models.DataSource +} + +func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.Response, error) { + result := &tsdb.Response{ + Results: make(map[string]*tsdb.QueryResult), + } + + queries, err := e.buildQueries(originalQueries, timeRange) + if err != nil { + return nil, err + } + + for _, query := range queries { + queryRes, resp, err := e.executeQuery(ctx, query, originalQueries, timeRange) + if err != nil { + return nil, err + } + azlog.Debug("AzureMonitor", "Response", resp) + + err = e.parseResponse(queryRes, resp, query) + if err != nil { + queryRes.Error = err + } + result.Results[query.RefID] = queryRes + } + + return result, nil +} + +func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *tsdb.TimeRange) ([]*AzureMonitorQuery, error) { + azureMonitorQueries := []*AzureMonitorQuery{} + startTime, err := timeRange.ParseFrom() + if err != nil { + return nil, err + } + + endTime, err := timeRange.ParseTo() + if err != nil { + return nil, err + } + + for _, query := range queries { + var target string + + azureMonitorTarget := query.Model.Get("azureMonitor").MustMap() + azlog.Debug("AzureMonitor", "target", azureMonitorTarget) + + urlComponents := make(map[string]string) + urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"]) + urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"]) + urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"]) + + ub := URLBuilder{ + ResourceGroup: urlComponents["resourceGroup"], + MetricDefinition: urlComponents["metricDefinition"], + ResourceName: urlComponents["resourceName"], + } + azureURL := ub.Build() + + alias := fmt.Sprintf("%v", azureMonitorTarget["alias"]) + + params := url.Values{} + params.Add("api-version", "2018-01-01") + params.Add("timespan", fmt.Sprintf("%v/%v", startTime.UTC().Format(time.RFC3339), endTime.UTC().Format(time.RFC3339))) + params.Add("interval", fmt.Sprintf("%v", azureMonitorTarget["timeGrain"])) + params.Add("aggregation", fmt.Sprintf("%v", azureMonitorTarget["aggregation"])) + params.Add("metricnames", fmt.Sprintf("%v", azureMonitorTarget["metricName"])) + + dimension := fmt.Sprintf("%v", azureMonitorTarget["dimension"]) + dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimensionFilter"])) + if azureMonitorTarget["dimension"] != nil && azureMonitorTarget["dimensionFilter"] != nil && dimensionFilter != "" { + params.Add("$filter", fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter)) + } + + target = params.Encode() + + if setting.Env == setting.DEV { + azlog.Debug("Azuremonitor request", "params", params) + } + + azureMonitorQueries = append(azureMonitorQueries, &AzureMonitorQuery{ + URL: azureURL, + UrlComponents: urlComponents, + Target: target, + Params: params, + RefID: query.RefId, + Alias: alias, + }) + } + + return azureMonitorQueries, nil +} + +func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *AzureMonitorQuery, queries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.QueryResult, AzureMonitorResponse, error) { + queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID} + + req, err := e.createRequest(ctx, e.dsInfo) + if err != nil { + queryResult.Error = err + return queryResult, AzureMonitorResponse{}, nil + } + + req.URL.Path = path.Join(req.URL.Path, query.URL) + req.URL.RawQuery = query.Params.Encode() + queryResult.Meta.Set("rawQuery", req.URL.RawQuery) + + span, ctx := opentracing.StartSpanFromContext(ctx, "azuremonitor query") + span.SetTag("target", query.Target) + span.SetTag("from", timeRange.From) + span.SetTag("until", timeRange.To) + span.SetTag("datasource_id", e.dsInfo.Id) + span.SetTag("org_id", e.dsInfo.OrgId) + + defer span.Finish() + + opentracing.GlobalTracer().Inject( + span.Context(), + opentracing.HTTPHeaders, + opentracing.HTTPHeadersCarrier(req.Header)) + + azlog.Debug("AzureMonitor", "Request URL", req.URL.String()) + res, err := ctxhttp.Do(ctx, e.httpClient, req) + if err != nil { + queryResult.Error = err + return queryResult, AzureMonitorResponse{}, nil + } + + data, err := e.unmarshalResponse(res) + if err != nil { + queryResult.Error = err + return queryResult, AzureMonitorResponse{}, nil + } + + return queryResult, data, nil +} + +func (e *AzureMonitorDatasource) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) { + // find plugin + plugin, ok := plugins.DataSources[dsInfo.Type] + if !ok { + return nil, errors.New("Unable to find datasource plugin Azure Monitor") + } + + var azureMonitorRoute *plugins.AppPluginRoute + for _, route := range plugin.Routes { + if route.Path == "azuremonitor" { + azureMonitorRoute = route + break + } + } + + cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor") + subscriptionID := dsInfo.JsonData.Get("subscriptionId").MustString() + proxyPass := fmt.Sprintf("%s/subscriptions/%s", cloudName, subscriptionID) + + u, _ := url.Parse(dsInfo.Url) + u.Path = path.Join(u.Path, "render") + + req, err := http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { + azlog.Error("Failed to create request", "error", err) + return nil, fmt.Errorf("Failed to create request. error: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion)) + + pluginproxy.ApplyRoute(ctx, req, proxyPass, azureMonitorRoute, dsInfo) + + return req, nil +} + +func (e *AzureMonitorDatasource) unmarshalResponse(res *http.Response) (AzureMonitorResponse, error) { + body, err := ioutil.ReadAll(res.Body) + defer res.Body.Close() + if err != nil { + return AzureMonitorResponse{}, err + } + + if res.StatusCode/100 != 2 { + azlog.Error("Request failed", "status", res.Status, "body", string(body)) + return AzureMonitorResponse{}, fmt.Errorf(string(body)) + } + + var data AzureMonitorResponse + err = json.Unmarshal(body, &data) + if err != nil { + azlog.Error("Failed to unmarshal AzureMonitor response", "error", err, "status", res.Status, "body", string(body)) + return AzureMonitorResponse{}, err + } + + return data, nil +} + +func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, data AzureMonitorResponse, query *AzureMonitorQuery) error { + if len(data.Value) == 0 { + return nil + } + + for _, series := range data.Value[0].Timeseries { + points := make([]tsdb.TimePoint, 0) + + metadataName := "" + metadataValue := "" + if len(series.Metadatavalues) > 0 { + metadataName = series.Metadatavalues[0].Name.LocalizedValue + metadataValue = series.Metadatavalues[0].Value + } + defaultMetricName := formatLegendKey(query.UrlComponents["resourceName"], data.Value[0].Name.LocalizedValue, metadataName, metadataValue) + + for _, point := range series.Data { + var value float64 + switch query.Params.Get("aggregation") { + case "Average": + value = point.Average + case "Total": + value = point.Total + case "Maximum": + value = point.Maximum + case "Minimum": + value = point.Minimum + case "Count": + value = point.Count + default: + value = point.Count + } + points = append(points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.TimeStamp).Unix())*1000)) + } + + queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ + Name: defaultMetricName, + Points: points, + }) + } + + return nil +} diff --git a/pkg/tsdb/azuremonitor/azuremonitor_test.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go similarity index 97% rename from pkg/tsdb/azuremonitor/azuremonitor_test.go rename to pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go index 760fecd0630..331a084033f 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go @@ -14,9 +14,9 @@ import ( . "github.com/smartystreets/goconvey/convey" ) -func TestAzureMonitor(t *testing.T) { - Convey("AzureMonitor", t, func() { - executor := &AzureMonitorExecutor{} +func TestAzureMonitorDatasource(t *testing.T) { + Convey("AzureMonitorDatasource", t, func() { + executor := &AzureMonitorDatasource{} Convey("Parse queries from frontend and build AzureMonitor API queries", func() { fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local) @@ -44,7 +44,7 @@ func TestAzureMonitor(t *testing.T) { }, } Convey("and is a normal query", func() { - queries, err := executor.buildQueries(tsdbQuery) + queries, err := executor.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange) So(err, ShouldBeNil) So(len(queries), ShouldEqual, 1) diff --git a/pkg/tsdb/azuremonitor/azuremonitor.go b/pkg/tsdb/azuremonitor/azuremonitor.go index ef1376f5ed5..32d4a6f0f29 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor.go +++ b/pkg/tsdb/azuremonitor/azuremonitor.go @@ -2,26 +2,12 @@ package azuremonitor import ( "context" - "encoding/json" - "errors" "fmt" - "io/ioutil" "net/http" - "net/url" - "path" - "strings" - "time" - "github.com/grafana/grafana/pkg/api/pluginproxy" - "github.com/grafana/grafana/pkg/components/null" - "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb" - "github.com/opentracing/opentracing-go" - "golang.org/x/net/context/ctxhttp" ) var ( @@ -59,250 +45,26 @@ func init() { func (e *AzureMonitorExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { var result *tsdb.Response var err error - queryType := tsdbQuery.Queries[0].Model.Get("queryType").MustString("") - switch queryType { - case "Azure Monitor": - fallthrough - default: - result, err = e.executeTimeSeriesQuery(ctx, tsdbQuery) + azureMonitorQueries := make([]*tsdb.Query, 0) + + for _, query := range tsdbQuery.Queries { + queryType := query.Model.Get("queryType").MustString("") + + switch queryType { + case "Azure Monitor": + azureMonitorQueries = append(azureMonitorQueries, query) + default: + return nil, fmt.Errorf("Alerting not supported for %s", queryType) + } } + azDatasource := &AzureMonitorDatasource{ + httpClient: e.httpClient, + dsInfo: e.dsInfo, + } + + result, err = azDatasource.executeTimeSeriesQuery(ctx, azureMonitorQueries, tsdbQuery.TimeRange) + return result, err } - -func (e *AzureMonitorExecutor) executeTimeSeriesQuery(ctx context.Context, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) { - result := &tsdb.Response{ - Results: make(map[string]*tsdb.QueryResult), - } - - queries, err := e.buildQueries(tsdbQuery) - if err != nil { - return nil, err - } - - for _, query := range queries { - queryRes, resp, err := e.executeQuery(ctx, query, tsdbQuery) - if err != nil { - return nil, err - } - err = e.parseResponse(queryRes, resp, query) - if err != nil { - queryRes.Error = err - } - result.Results[query.RefID] = queryRes - } - - return result, nil -} - -func (e *AzureMonitorExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*AzureMonitorQuery, error) { - azureMonitorQueries := []*AzureMonitorQuery{} - startTime, err := tsdbQuery.TimeRange.ParseFrom() - if err != nil { - return nil, err - } - - endTime, err := tsdbQuery.TimeRange.ParseTo() - if err != nil { - return nil, err - } - - for _, query := range tsdbQuery.Queries { - var target string - - azureMonitorTarget := query.Model.Get("azureMonitor").MustMap() - azlog.Debug("AzureMonitor", "target", azureMonitorTarget) - - urlComponents := make(map[string]string) - urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"]) - urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"]) - urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"]) - - ub := URLBuilder{ - ResourceGroup: urlComponents["resourceGroup"], - MetricDefinition: urlComponents["metricDefinition"], - ResourceName: urlComponents["resourceName"], - } - azureURL := ub.Build() - - alias := fmt.Sprintf("%v", azureMonitorTarget["alias"]) - - params := url.Values{} - params.Add("api-version", "2018-01-01") - params.Add("timespan", fmt.Sprintf("%v/%v", startTime.UTC().Format(time.RFC3339), endTime.UTC().Format(time.RFC3339))) - params.Add("interval", fmt.Sprintf("%v", azureMonitorTarget["timeGrain"])) - params.Add("aggregation", fmt.Sprintf("%v", azureMonitorTarget["aggregation"])) - params.Add("metricnames", fmt.Sprintf("%v", azureMonitorTarget["metricName"])) - - dimension := fmt.Sprintf("%v", azureMonitorTarget["dimension"]) - dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimensionFilter"])) - if azureMonitorTarget["dimension"] != nil && azureMonitorTarget["dimensionFilter"] != nil && dimensionFilter != "" { - params.Add("$filter", fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter)) - } - - target = params.Encode() - - if setting.Env == setting.DEV { - azlog.Debug("Azuremonitor request", "params", params) - } - - azureMonitorQueries = append(azureMonitorQueries, &AzureMonitorQuery{ - URL: azureURL, - UrlComponents: urlComponents, - Target: target, - Params: params, - RefID: query.RefId, - Alias: alias, - }) - } - - return azureMonitorQueries, nil -} - -func (e *AzureMonitorExecutor) executeQuery(ctx context.Context, query *AzureMonitorQuery, tsdbQuery *tsdb.TsdbQuery) (*tsdb.QueryResult, AzureMonitorResponse, error) { - queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefID} - - req, err := e.createRequest(ctx, e.dsInfo) - if err != nil { - queryResult.Error = err - return queryResult, AzureMonitorResponse{}, nil - } - - req.URL.Path = path.Join(req.URL.Path, query.URL) - req.URL.RawQuery = query.Params.Encode() - queryResult.Meta.Set("rawQuery", req.URL.RawQuery) - - span, ctx := opentracing.StartSpanFromContext(ctx, "azuremonitor query") - span.SetTag("target", query.Target) - span.SetTag("from", tsdbQuery.TimeRange.From) - span.SetTag("until", tsdbQuery.TimeRange.To) - span.SetTag("datasource_id", e.dsInfo.Id) - span.SetTag("org_id", e.dsInfo.OrgId) - - defer span.Finish() - - opentracing.GlobalTracer().Inject( - span.Context(), - opentracing.HTTPHeaders, - opentracing.HTTPHeadersCarrier(req.Header)) - - azlog.Debug("AzureMonitor", "Request URL", req.URL.String()) - res, err := ctxhttp.Do(ctx, e.httpClient, req) - if err != nil { - queryResult.Error = err - return queryResult, AzureMonitorResponse{}, nil - } - - data, err := e.unmarshalResponse(res) - if err != nil { - queryResult.Error = err - return queryResult, AzureMonitorResponse{}, nil - } - - return queryResult, data, nil -} - -func (e *AzureMonitorExecutor) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) { - // find plugin - plugin, ok := plugins.DataSources[dsInfo.Type] - if !ok { - return nil, errors.New("Unable to find datasource plugin Azure Monitor") - } - - var azureMonitorRoute *plugins.AppPluginRoute - for _, route := range plugin.Routes { - if route.Path == "azuremonitor" { - azureMonitorRoute = route - break - } - } - - cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor") - subscriptionID := dsInfo.JsonData.Get("subscriptionId").MustString() - proxyPass := fmt.Sprintf("%s/subscriptions/%s", cloudName, subscriptionID) - - u, _ := url.Parse(dsInfo.Url) - u.Path = path.Join(u.Path, "render") - - req, err := http.NewRequest(http.MethodGet, u.String(), nil) - if err != nil { - azlog.Error("Failed to create request", "error", err) - return nil, fmt.Errorf("Failed to create request. error: %v", err) - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion)) - - pluginproxy.ApplyRoute(ctx, req, proxyPass, azureMonitorRoute, dsInfo) - - return req, nil -} - -func (e *AzureMonitorExecutor) unmarshalResponse(res *http.Response) (AzureMonitorResponse, error) { - body, err := ioutil.ReadAll(res.Body) - defer res.Body.Close() - if err != nil { - return AzureMonitorResponse{}, err - } - - if res.StatusCode/100 != 2 { - azlog.Error("Request failed", "status", res.Status, "body", string(body)) - return AzureMonitorResponse{}, fmt.Errorf(string(body)) - } - - var data AzureMonitorResponse - err = json.Unmarshal(body, &data) - if err != nil { - azlog.Error("Failed to unmarshal AzureMonitor response", "error", err, "status", res.Status, "body", string(body)) - return AzureMonitorResponse{}, err - } - - return data, nil -} - -func (e *AzureMonitorExecutor) parseResponse(queryRes *tsdb.QueryResult, data AzureMonitorResponse, query *AzureMonitorQuery) error { - azlog.Debug("AzureMonitor", "Response", data) - - if len(data.Value) == 0 { - return nil - } - - for _, series := range data.Value[0].Timeseries { - points := make([]tsdb.TimePoint, 0) - - metadataName := "" - metadataValue := "" - if len(series.Metadatavalues) > 0 { - metadataName = series.Metadatavalues[0].Name.LocalizedValue - metadataValue = series.Metadatavalues[0].Value - } - defaultMetricName := formatLegendKey(query.UrlComponents["resourceName"], data.Value[0].Name.LocalizedValue, metadataName, metadataValue) - - for _, point := range series.Data { - var value float64 - switch query.Params.Get("aggregation") { - case "Average": - value = point.Average - case "Total": - value = point.Total - case "Maximum": - value = point.Maximum - case "Minimum": - value = point.Minimum - case "Count": - value = point.Count - default: - value = point.Count - } - points = append(points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.TimeStamp).Unix())*1000)) - } - - queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ - Name: defaultMetricName, - Points: points, - }) - } - - return nil -} From 452c4f5b9be8a5a8927066dc29f2df11dedcddd9 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Sun, 10 Feb 2019 01:47:38 +0100 Subject: [PATCH 041/119] azuremonitor: add test for dimension filter --- .../azuremonitor/azuremonitor-datasource.go | 9 ++++- .../azuremonitor-datasource_test.go | 39 +++++++++++++++---- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go index 99e45bb58b8..2da97514dd8 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go @@ -24,11 +24,16 @@ import ( "github.com/grafana/grafana/pkg/tsdb" ) +// AzureMonitorDatasource calls the Azure Monitor API - one of the four API's supported type AzureMonitorDatasource struct { httpClient *http.Client dsInfo *models.DataSource } +// executeTimeSeriesQuery does the following: +// 1. build the AzureMonitor url and querystring for each query +// 2. executes each query by calling the Azure Monitor API +// 3. parses the responses for each query into the timeseries format func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.Response, error) { result := &tsdb.Response{ Results: make(map[string]*tsdb.QueryResult), @@ -95,9 +100,9 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange * params.Add("aggregation", fmt.Sprintf("%v", azureMonitorTarget["aggregation"])) params.Add("metricnames", fmt.Sprintf("%v", azureMonitorTarget["metricName"])) - dimension := fmt.Sprintf("%v", azureMonitorTarget["dimension"]) + dimension := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimension"])) dimensionFilter := strings.TrimSpace(fmt.Sprintf("%v", azureMonitorTarget["dimensionFilter"])) - if azureMonitorTarget["dimension"] != nil && azureMonitorTarget["dimensionFilter"] != nil && dimensionFilter != "" { + if azureMonitorTarget["dimension"] != nil && azureMonitorTarget["dimensionFilter"] != nil && len(dimension) > 0 && len(dimensionFilter) > 0 { params.Add("$filter", fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter)) } diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go index 331a084033f..0c95cabb3ec 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go @@ -16,7 +16,7 @@ import ( func TestAzureMonitorDatasource(t *testing.T) { Convey("AzureMonitorDatasource", t, func() { - executor := &AzureMonitorDatasource{} + datasource := &AzureMonitorDatasource{} Convey("Parse queries from frontend and build AzureMonitor API queries", func() { fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local) @@ -44,7 +44,7 @@ func TestAzureMonitorDatasource(t *testing.T) { }, } Convey("and is a normal query", func() { - queries, err := executor.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange) + queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange) So(err, ShouldBeNil) So(len(queries), ShouldEqual, 1) @@ -59,6 +59,29 @@ func TestAzureMonitorDatasource(t *testing.T) { So(queries[0].Params["interval"][0], ShouldEqual, "PT1M") So(queries[0].Alias, ShouldEqual, "testalias") }) + + Convey("and has a dimension filter", func() { + tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{ + "azureMonitor": map[string]interface{}{ + "timeGrain": "PT1M", + "aggregation": "Average", + "resourceGroup": "grafanastaging", + "resourceName": "grafana", + "metricDefinition": "Microsoft.Compute/virtualMachines", + "metricName": "Percentage CPU", + "alias": "testalias", + "queryType": "Azure Monitor", + "dimension": "blob", + "dimensionFilter": "*", + }, + }) + + queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange) + So(err, ShouldBeNil) + + So(queries[0].Target, ShouldEqual, "%24filter=blob+eq+%27%2A%27&aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z") + + }) }) Convey("Parse AzureMonitor API response in the time series format", func() { @@ -76,7 +99,7 @@ func TestAzureMonitorDatasource(t *testing.T) { "aggregation": {"Average"}, }, } - err = executor.parseResponse(res, data, query) + err = datasource.parseResponse(res, data, query) So(err, ShouldBeNil) So(len(res.Series), ShouldEqual, 1) @@ -112,7 +135,7 @@ func TestAzureMonitorDatasource(t *testing.T) { "aggregation": {"Total"}, }, } - err = executor.parseResponse(res, data, query) + err = datasource.parseResponse(res, data, query) So(err, ShouldBeNil) So(res.Series[0].Points[0][0].Float64, ShouldEqual, 8.26) @@ -132,7 +155,7 @@ func TestAzureMonitorDatasource(t *testing.T) { "aggregation": {"Maximum"}, }, } - err = executor.parseResponse(res, data, query) + err = datasource.parseResponse(res, data, query) So(err, ShouldBeNil) So(res.Series[0].Points[0][0].Float64, ShouldEqual, 3.07) @@ -152,7 +175,7 @@ func TestAzureMonitorDatasource(t *testing.T) { "aggregation": {"Minimum"}, }, } - err = executor.parseResponse(res, data, query) + err = datasource.parseResponse(res, data, query) So(err, ShouldBeNil) So(res.Series[0].Points[0][0].Float64, ShouldEqual, 1.51) @@ -172,7 +195,7 @@ func TestAzureMonitorDatasource(t *testing.T) { "aggregation": {"Count"}, }, } - err = executor.parseResponse(res, data, query) + err = datasource.parseResponse(res, data, query) So(err, ShouldBeNil) So(res.Series[0].Points[0][0].Float64, ShouldEqual, 4) @@ -192,7 +215,7 @@ func TestAzureMonitorDatasource(t *testing.T) { "aggregation": {"Average"}, }, } - err = executor.parseResponse(res, data, query) + err = datasource.parseResponse(res, data, query) So(err, ShouldBeNil) So(len(res.Series), ShouldEqual, 3) From a0729b9b50e4c7ad761fa2884c2a3b608c1c8b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 10 Feb 2019 17:05:58 +0100 Subject: [PATCH 042/119] provide time range to angular query controllers --- .../dashboard/panel_editor/QueryEditorRow.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx index eda10087d41..83ef70f62e7 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx +++ b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx @@ -7,10 +7,11 @@ import _ from 'lodash'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; import { Emitter } from 'app/core/utils/emitter'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; // Types import { PanelModel } from '../state/PanelModel'; -import { DataQuery, DataSourceApi } from '@grafana/ui'; +import { DataQuery, DataSourceApi, TimeRange } from '@grafana/ui'; interface Props { panel: PanelModel; @@ -43,8 +44,15 @@ export class QueryEditorRow extends PureComponent { componentDidMount() { this.loadDatasource(); + this.props.panel.events.on('refresh', this.onPanelRefresh); } + onPanelRefresh = () => { + if (this.state.angularScope) { + this.state.angularScope.range = getTimeSrv().timeRange(); + } + }; + getAngularQueryComponentScope(): AngularQueryComponentScope { const { panel, query } = this.props; const { datasource } = this.state; @@ -56,6 +64,7 @@ export class QueryEditorRow extends PureComponent { refresh: () => panel.refresh(), render: () => panel.render(), events: panel.events, + range: getTimeSrv().timeRange(), }; } @@ -97,6 +106,8 @@ export class QueryEditorRow extends PureComponent { } componentWillUnmount() { + this.props.panel.events.off('refresh', this.onPanelRefresh); + if (this.angularQueryEditor) { this.angularQueryEditor.destroy(); } @@ -250,4 +261,5 @@ export interface AngularQueryComponentScope { datasource: DataSourceApi; toggleEditorMode?: () => void; getCollapsedText?: () => string; + range: TimeRange; } From f38e64cc5da68bee5395004f5fb0cfd26cecaf02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 10 Feb 2019 20:01:22 +0100 Subject: [PATCH 043/119] Navbar back button, no title edit this time --- .../dashboard/components/DashNav/DashNav.tsx | 30 ++++++------- .../dashboard/containers/DashboardPage.tsx | 3 +- public/sass/components/_navbar.scss | 44 ------------------- public/sass/components/_panel_editor.scss | 4 +- 4 files changed, 18 insertions(+), 63 deletions(-) diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 8560b3bfbba..6db07b5d42e 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -9,16 +9,16 @@ import { PlaylistSrv } from 'app/features/playlist/playlist_srv'; // Components import { DashNavButton } from './DashNavButton'; +import { Tooltip } from '@grafana/ui'; // State import { updateLocation } from 'app/core/actions'; // Types -import { DashboardModel, PanelModel } from '../../state'; +import { DashboardModel } from '../../state'; export interface Props { dashboard: DashboardModel; - fullscreenPanel?: PanelModel; editview: string; isEditing: boolean; isFullscreen: boolean; @@ -133,7 +133,7 @@ export class DashNav extends PureComponent { <>
- + {!this.isInFullscreenOrSettings && } {haveFolder && {folderTitle} / } {dashboard.title} @@ -144,24 +144,24 @@ export class DashNav extends PureComponent { ); } - renderPanelFullscreeMode() { - const { fullscreenPanel } = this.props; + get isInFullscreenOrSettings() { + return this.props.editview || this.props.isFullscreen; + } + renderBackButton() { return (
- -
- - -
+ + +
); } render() { - const { dashboard, onAddPanel, fullscreenPanel } = this.props; + const { dashboard, onAddPanel } = this.props; const { canStar, canSave, canShare, showSettings, isStarred } = dashboard.meta; const { snapshot } = dashboard; @@ -169,8 +169,8 @@ export class DashNav extends PureComponent { return (
- {!fullscreenPanel && this.renderDashboardTitleSearchButton()} - {fullscreenPanel && this.renderPanelFullscreeMode()} + {this.isInFullscreenOrSettings && this.renderBackButton()} + {this.renderDashboardTitleSearchButton()} {this.playlistSrv.isPlaying && (
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 724f3a625c0..27118e297b5 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -238,7 +238,7 @@ export class DashboardPage extends PureComponent { render() { const { dashboard, editview, $injector, isInitSlow, initError } = this.props; - const { isSettingsOpening, isEditing, isFullscreen, scrollTop, fullscreenPanel } = this.state; + const { isSettingsOpening, isEditing, isFullscreen, scrollTop } = this.state; if (!dashboard) { if (isInitSlow) { @@ -266,7 +266,6 @@ export class DashboardPage extends PureComponent { editview={editview} $injector={$injector} onAddPanel={this.onAddPanel} - fullscreenPanel={fullscreenPanel} />
diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index 0096810798a..ce0fb45051e 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -47,9 +47,6 @@ .navbar-button--add-panel, .navbar-button--star, .navbar-button--tv, - .navbar-page-btn .fa-caret-down { - display: none; - } .navbar-buttons--close { display: flex; @@ -185,7 +182,6 @@ height: $navbarHeight; align-items: center; padding-left: 7px; - flex-grow: 1; } .navbar-edit__back-btn { @@ -209,44 +205,4 @@ } } -.navbar-edit__input-wraper { - position: relative; - display: flex; - align-items: center; - flex-grow: 1; - &:hover { - i { - opacity: 1; - } - - .navbar-edit__input { - background: $input-bg; - flex-grow: 1; - @include form-control-focus(); - } - } - - i { - left: -25px; - position: relative; - color: $text-color-weak; - opacity: 0; - transition: 200ms opacity ease-in-out; - } -} - -.navbar-edit__input { - background: transparent; - transition: 200ms background ease-in-out; - width: auto; - font-size: $font-size-lg; - height: $gf-form-input-height; - padding: $input-padding-y $input-padding-x; - flex-grow: 1; - - &:focus { - @include form-control-focus(); - background: $input-bg; - } -} diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index e533681d672..b1d828069fb 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -87,8 +87,8 @@ margin: 0 $dashboard-padding; } - .panel-title-text { - visibility: hidden; + .search-container { + left: 0 !important; } } From 60327953a2edf245d8fe8f1a5d394dc18931f20c Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 11 Feb 2019 01:17:37 +0100 Subject: [PATCH 044/119] azuremonitor: handles timegrain set to auto on backend --- .../azuremonitor/azuremonitor-datasource.go | 34 ++++++++++- .../azuremonitor-datasource_test.go | 20 +++++++ pkg/tsdb/azuremonitor/time-grain.go | 53 ++++++++++++++++ pkg/tsdb/azuremonitor/time-grain_test.go | 60 +++++++++++++++++++ 4 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 pkg/tsdb/azuremonitor/time-grain.go create mode 100644 pkg/tsdb/azuremonitor/time-grain_test.go diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go index 2da97514dd8..3405c3bbd1f 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go @@ -30,6 +30,11 @@ type AzureMonitorDatasource struct { dsInfo *models.DataSource } +var ( + // 1m, 5m, 15m, 30m, 1h, 6h, 12h, 1d in milliseconds + allowedIntervalsMS = []int64{60000, 300000, 900000, 1800000, 3600000, 21600000, 43200000, 86400000} +) + // executeTimeSeriesQuery does the following: // 1. build the AzureMonitor url and querystring for each query // 2. executes each query by calling the Azure Monitor API @@ -49,7 +54,7 @@ func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, ori if err != nil { return nil, err } - azlog.Debug("AzureMonitor", "Response", resp) + // azlog.Debug("AzureMonitor", "Response", resp) err = e.parseResponse(queryRes, resp, query) if err != nil { @@ -93,10 +98,20 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange * alias := fmt.Sprintf("%v", azureMonitorTarget["alias"]) + timeGrain := fmt.Sprintf("%v", azureMonitorTarget["timeGrain"]) + if timeGrain == "auto" { + autoInSeconds := e.findClosestAllowedIntervalMs(query.IntervalMs) / 1000 + tg := &TimeGrain{} + timeGrain, err = tg.createISO8601DurationFromInterval(fmt.Sprintf("%vs", autoInSeconds)) + if err != nil { + return nil, err + } + } + params := url.Values{} params.Add("api-version", "2018-01-01") params.Add("timespan", fmt.Sprintf("%v/%v", startTime.UTC().Format(time.RFC3339), endTime.UTC().Format(time.RFC3339))) - params.Add("interval", fmt.Sprintf("%v", azureMonitorTarget["timeGrain"])) + params.Add("interval", timeGrain) params.Add("aggregation", fmt.Sprintf("%v", azureMonitorTarget["aggregation"])) params.Add("metricnames", fmt.Sprintf("%v", azureMonitorTarget["metricName"])) @@ -269,3 +284,18 @@ func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, data return nil } + +func (e *AzureMonitorDatasource) findClosestAllowedIntervalMs(intervalMs int64) int64 { + closest := allowedIntervalsMS[0] + + for i, allowed := range allowedIntervalsMS { + if intervalMs > allowed { + if i+1 < len(allowedIntervalsMS) { + closest = allowedIntervalsMS[i+1] + } else { + closest = allowed + } + } + } + return closest +} diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go index 0c95cabb3ec..9aba4eb617b 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go @@ -229,6 +229,26 @@ func TestAzureMonitorDatasource(t *testing.T) { So(res.Series[2].Points[0][0].Float64, ShouldEqual, 0) }) }) + + Convey("Find closest allowed interval for auto time grain", func() { + intervals := map[string]int64{ + "3m": 180000, + "5m": 300000, + "10m": 600000, + "15m": 900000, + "1d": 86400000, + "2d": 172800000, + } + + closest := datasource.findClosestAllowedIntervalMs(intervals["3m"]) + So(closest, ShouldEqual, intervals["5m"]) + + closest = datasource.findClosestAllowedIntervalMs(intervals["10m"]) + So(closest, ShouldEqual, intervals["15m"]) + + closest = datasource.findClosestAllowedIntervalMs(intervals["2d"]) + So(closest, ShouldEqual, intervals["1d"]) + }) }) } diff --git a/pkg/tsdb/azuremonitor/time-grain.go b/pkg/tsdb/azuremonitor/time-grain.go new file mode 100644 index 00000000000..22da2872bab --- /dev/null +++ b/pkg/tsdb/azuremonitor/time-grain.go @@ -0,0 +1,53 @@ +package azuremonitor + +import ( + "fmt" + "strconv" + "strings" +) + +// TimeGrain handles convertions between +// the ISO 8601 Duration format (PT1H), Kbn units (1h) and Time Grains (1 hour) +// Also handles using the automatic Grafana interval to calculate a ISO 8601 Duration. +type TimeGrain struct{} + +var ( + smallTimeUnits = []string{"hour", "minute", "h", "m"} +) + +func (tg *TimeGrain) createISO8601DurationFromInterval(interval string) (string, error) { + if strings.Contains(interval, "ms") { + return "PT1M", nil + } + + timeValueString := interval[0 : len(interval)-1] + timeValue, err := strconv.Atoi(timeValueString) + if err != nil { + return "", fmt.Errorf("Could not parse interval %v to an ISO 8061 duration", interval) + } + + unit := interval[len(interval)-1:] + + if unit == "s" { + toMinutes := (timeValue * 60) % 60 + + // mimumum interval is 1m for Azure Monitor + if toMinutes < 1 { + toMinutes = 1 + } + + return tg.createISO8601Duration(toMinutes, "m"), nil + } + + return tg.createISO8601Duration(timeValue, unit), nil +} + +func (tg *TimeGrain) createISO8601Duration(timeValue int, timeUnit string) string { + for _, smallTimeUnit := range smallTimeUnits { + if timeUnit == smallTimeUnit { + return fmt.Sprintf("PT%v%v", timeValue, strings.ToUpper(timeUnit[0:1])) + } + } + + return fmt.Sprintf("P%v%v", timeValue, strings.ToUpper(timeUnit[0:1])) +} diff --git a/pkg/tsdb/azuremonitor/time-grain_test.go b/pkg/tsdb/azuremonitor/time-grain_test.go new file mode 100644 index 00000000000..be8d0b10a0c --- /dev/null +++ b/pkg/tsdb/azuremonitor/time-grain_test.go @@ -0,0 +1,60 @@ +package azuremonitor + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestTimeGrain(t *testing.T) { + Convey("TimeGrain", t, func() { + tgc := &TimeGrain{} + + Convey("create ISO 8601 Duration", func() { + Convey("when given a time unit smaller than a day", func() { + minuteKbnDuration := tgc.createISO8601Duration(1, "m") + hourKbnDuration := tgc.createISO8601Duration(2, "h") + minuteDuration := tgc.createISO8601Duration(1, "minute") + hourDuration := tgc.createISO8601Duration(2, "hour") + + Convey("should convert it to a time duration", func() { + So(minuteKbnDuration, ShouldEqual, "PT1M") + So(hourKbnDuration, ShouldEqual, "PT2H") + + So(minuteDuration, ShouldEqual, "PT1M") + So(hourDuration, ShouldEqual, "PT2H") + }) + }) + + Convey("when given the day time unit", func() { + kbnDuration := tgc.createISO8601Duration(1, "d") + duration := tgc.createISO8601Duration(2, "day") + + Convey("should convert it to a date duration", func() { + So(kbnDuration, ShouldEqual, "P1D") + So(duration, ShouldEqual, "P2D") + }) + }) + }) + + Convey("create ISO 8601 Duration from Grafana interval", func() { + Convey("and interval is less than a minute", func() { + durationMS, _ := tgc.createISO8601DurationFromInterval("100ms") + durationS, _ := tgc.createISO8601DurationFromInterval("59s") + Convey("should be rounded up to a minute as is the minimum interval for Azure Monitor", func() { + So(durationMS, ShouldEqual, "PT1M") + So(durationS, ShouldEqual, "PT1M") + }) + }) + + Convey("and interval is more than a minute", func() { + durationM, _ := tgc.createISO8601DurationFromInterval("10m") + durationD, _ := tgc.createISO8601DurationFromInterval("2d") + Convey("should be rounded up to a minute as is the minimum interval for Azure Monitor", func() { + So(durationM, ShouldEqual, "PT10M") + So(durationD, ShouldEqual, "P2D") + }) + }) + }) + }) +} From d6904ba9b412254fd82106f3bd0b6ad754f61107 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 11 Feb 2019 01:22:15 +0100 Subject: [PATCH 045/119] azuremonitor: small refactoring --- pkg/tsdb/azuremonitor/azuremonitor-datasource.go | 11 +++++++++++ pkg/tsdb/azuremonitor/legend-key.go | 11 ----------- 2 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 pkg/tsdb/azuremonitor/legend-key.go diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go index 3405c3bbd1f..079910e1b66 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go @@ -285,6 +285,9 @@ func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, data return nil } +// findClosestAllowedIntervalMs is used for the auto time grain setting. +// It finds the closest time grain from the list of allowed time grains for Azure Monitor +// using the Grafana interval in milliseconds func (e *AzureMonitorDatasource) findClosestAllowedIntervalMs(intervalMs int64) int64 { closest := allowedIntervalsMS[0] @@ -299,3 +302,11 @@ func (e *AzureMonitorDatasource) findClosestAllowedIntervalMs(intervalMs int64) } return closest } + +// formatLegendKey builds the legend key or timeseries name +func formatLegendKey(resourceName string, metricName string, metadataName string, metadataValue string) string { + if len(metadataName) > 0 { + return fmt.Sprintf("%s{%s=%s}.%s", resourceName, metadataName, metadataValue, metricName) + } + return fmt.Sprintf("%s.%s", resourceName, metricName) +} diff --git a/pkg/tsdb/azuremonitor/legend-key.go b/pkg/tsdb/azuremonitor/legend-key.go deleted file mode 100644 index 7d5cdbbcdd3..00000000000 --- a/pkg/tsdb/azuremonitor/legend-key.go +++ /dev/null @@ -1,11 +0,0 @@ -package azuremonitor - -import "fmt" - -// formatLegendKey builds the legend key or timeseries name -func formatLegendKey(resourceName string, metricName string, metadataName string, metadataValue string) string { - if len(metadataName) > 0 { - return fmt.Sprintf("%s{%s=%s}.%s", resourceName, metadataName, metadataValue, metricName) - } - return fmt.Sprintf("%s.%s", resourceName, metricName) -} From 41217ea110875c703a50e047041464f0bb9bebbc Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 08:39:33 +0100 Subject: [PATCH 046/119] changelog: add notes about closing #13324 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a603fbb1a78..38d76762d4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * **Cloudwatch**: Add `resource_arns` template variable query function [#8207](https://github.com/grafana/grafana/issues/8207), thx [@jeroenvollenbrock](https://github.com/jeroenvollenbrock) * **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson) * **Cloudwatch**: Add AWS RDS ServerlessDatabaseCapacity metric [#15265](https://github.com/grafana/grafana/pull/15265), thx [@larsjoergensen](https://github.com/larsjoergensen) +* **MSSQL**: Timerange are now passed for template variable queries [#13324](https://github.com/grafana/grafana/issues/13324), thx [@thatsparesh](https://github.com/thatsparesh) * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) From 7b761f0a2864d0619817ca90942b91294f613354 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 08:44:04 +0100 Subject: [PATCH 047/119] changelog: add notes about closing #15189 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38d76762d4b..79c52786898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) +### 6.6.0-beta1 fixes + +* **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189) + # 6.0.0-beta1 (2019-01-30) ### New Features From 06972144d20b2125034e51db7c58573231d17e8a Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 08:48:03 +0100 Subject: [PATCH 048/119] changelog: add notes about closing #14233 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79c52786898..a5664a4205a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * **Stackdriver**: Template variables in filters using globbing format [#15182](https://github.com/grafana/grafana/issues/15182) * **Cloudwatch**: Add `resource_arns` template variable query function [#8207](https://github.com/grafana/grafana/issues/8207), thx [@jeroenvollenbrock](https://github.com/jeroenvollenbrock) * **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson) +* **Cloudwatch**: Add AWS/EC2/API metrics [#14233](https://github.com/grafana/grafana/issues/14233), thx [@tcpatterson](https://github.com/tcpatterson) * **Cloudwatch**: Add AWS RDS ServerlessDatabaseCapacity metric [#15265](https://github.com/grafana/grafana/pull/15265), thx [@larsjoergensen](https://github.com/larsjoergensen) * **MSSQL**: Timerange are now passed for template variable queries [#13324](https://github.com/grafana/grafana/issues/13324), thx [@thatsparesh](https://github.com/thatsparesh) * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) From be11da5b31892127d817cbc982a470f54602e813 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 08:49:10 +0100 Subject: [PATCH 049/119] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5664a4205a..b48766e97a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) -### 6.6.0-beta1 fixes +### 6.0.0-beta1 fixes * **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189) From 5eea85a3a3287b7916c7fb5e97db9d86bdba5add Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 09:06:20 +0100 Subject: [PATCH 050/119] changelog: add notes about closing #8570 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b48766e97a3..30f4961343b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson) * **Cloudwatch**: Add AWS/EC2/API metrics [#14233](https://github.com/grafana/grafana/issues/14233), thx [@tcpatterson](https://github.com/tcpatterson) * **Cloudwatch**: Add AWS RDS ServerlessDatabaseCapacity metric [#15265](https://github.com/grafana/grafana/pull/15265), thx [@larsjoergensen](https://github.com/larsjoergensen) +* **MySQL**: Adds datasource SSL CA/client certificates support [#8570](https://github.com/grafana/grafana/issues/8570), thx [@bugficks](https://github.com/bugficks) * **MSSQL**: Timerange are now passed for template variable queries [#13324](https://github.com/grafana/grafana/issues/13324), thx [@thatsparesh](https://github.com/thatsparesh) * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) From 9565e48f03535966bef099f4a19a3f6e418221c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 09:34:14 +0100 Subject: [PATCH 051/119] Fixed issue where double clicking on back button closes sidemenu --- public/app/core/components/sidemenu/SideMenu.test.tsx | 10 ++++++++++ public/app/core/components/sidemenu/SideMenu.tsx | 7 +++++++ public/app/core/reducers/location.ts | 2 ++ public/app/store/configureStore.ts | 4 ++-- public/app/types/location.ts | 1 + public/sass/components/_search.scss | 2 +- 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/public/app/core/components/sidemenu/SideMenu.test.tsx b/public/app/core/components/sidemenu/SideMenu.test.tsx index 2a262adca5a..2286787d777 100644 --- a/public/app/core/components/sidemenu/SideMenu.test.tsx +++ b/public/app/core/components/sidemenu/SideMenu.test.tsx @@ -8,6 +8,16 @@ jest.mock('../../app_events', () => ({ emit: jest.fn(), })); +jest.mock('app/store/store', () => ({ + store: { + getState: jest.fn().mockReturnValue({ + location: { + lastUpdated: 0, + } + }) + } +})); + jest.mock('app/core/services/context_srv', () => ({ contextSrv: { sidemenu: true, diff --git a/public/app/core/components/sidemenu/SideMenu.tsx b/public/app/core/components/sidemenu/SideMenu.tsx index fd3e0d95564..29ef0fed069 100644 --- a/public/app/core/components/sidemenu/SideMenu.tsx +++ b/public/app/core/components/sidemenu/SideMenu.tsx @@ -3,9 +3,16 @@ import appEvents from '../../app_events'; import { contextSrv } from 'app/core/services/context_srv'; import TopSection from './TopSection'; import BottomSection from './BottomSection'; +import { store } from 'app/store/store'; export class SideMenu extends PureComponent { toggleSideMenu = () => { + // ignore if we just made a location change, stops hiding sidemenu on double clicks of back button + const timeSinceLocationChanged = new Date().getTime() - store.getState().location.lastUpdated; + if (timeSinceLocationChanged < 1000) { + return; + } + contextSrv.toggleSideMenu(); appEvents.emit('toggle-sidemenu'); }; diff --git a/public/app/core/reducers/location.ts b/public/app/core/reducers/location.ts index c038ab53c9f..dff1ac8f5c1 100644 --- a/public/app/core/reducers/location.ts +++ b/public/app/core/reducers/location.ts @@ -9,6 +9,7 @@ export const initialState: LocationState = { query: {}, routeParams: {}, replace: false, + lastUpdated: 0, }; export const locationReducer = (state = initialState, action: Action): LocationState => { @@ -28,6 +29,7 @@ export const locationReducer = (state = initialState, action: Action): LocationS query: { ...query }, routeParams: routeParams || state.routeParams, replace: replace === true, + lastUpdated: new Date().getTime(), }; } } diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index e2c33523271..2638587e96d 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -1,6 +1,6 @@ import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; import thunk from 'redux-thunk'; -import { createLogger } from 'redux-logger'; +// import { createLogger } from 'redux-logger'; import sharedReducers from 'app/core/reducers'; import alertingReducers from 'app/features/alerting/state/reducers'; import teamsReducers from 'app/features/teams/state/reducers'; @@ -41,7 +41,7 @@ export function configureStore() { if (process.env.NODE_ENV !== 'production') { // DEV builds we had the logger middleware - setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger())))); + setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk)))); } else { setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk)))); } diff --git a/public/app/types/location.ts b/public/app/types/location.ts index a47ef05d2be..4730f9d6ed7 100644 --- a/public/app/types/location.ts +++ b/public/app/types/location.ts @@ -15,6 +15,7 @@ export interface LocationState { query: UrlQueryMap; routeParams: UrlQueryMap; replace: boolean; + lastUpdated: number; } export type UrlQueryValue = string | number | boolean | string[] | number[] | boolean[]; diff --git a/public/sass/components/_search.scss b/public/sass/components/_search.scss index daad8fd10da..eba03283510 100644 --- a/public/sass/components/_search.scss +++ b/public/sass/components/_search.scss @@ -21,9 +21,9 @@ // Search .search-field-wrapper { width: 100%; + height: $navbarHeight; display: flex; background-color: $navbarBackground; - box-shadow: $navbarShadow; position: relative; & > input { From 58e57a1669e8c2f2d0f2036ffed68d61f5069de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 09:46:56 +0100 Subject: [PATCH 052/119] removed unused directive --- public/app/core/core.ts | 1 - public/app/core/directives/dash_class.ts | 39 ------------------------ 2 files changed, 40 deletions(-) delete mode 100644 public/app/core/directives/dash_class.ts diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 1f289fc4b27..80987b8fc88 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -1,4 +1,3 @@ -import './directives/dash_class'; import './directives/dropdown_typeahead'; import './directives/autofill_event_fix'; import './directives/metric_segment'; diff --git a/public/app/core/directives/dash_class.ts b/public/app/core/directives/dash_class.ts deleted file mode 100644 index 1fb93d29cf3..00000000000 --- a/public/app/core/directives/dash_class.ts +++ /dev/null @@ -1,39 +0,0 @@ -import $ from 'jquery'; -import _ from 'lodash'; -import coreModule from '../core_module'; - -/** @ngInject */ -function dashClass($timeout) { - return { - link: ($scope, elem) => { - const body = $('body'); - - $scope.ctrl.dashboard.events.on('view-mode-changed', panel => { - console.log('view-mode-changed', panel.fullscreen); - if (panel.fullscreen) { - body.addClass('panel-in-fullscreen'); - } else { - $timeout(() => { - body.removeClass('panel-in-fullscreen'); - }); - } - }); - - body.toggleClass('panel-in-fullscreen', $scope.ctrl.dashboard.meta.fullscreen === true); - - $scope.$watch('ctrl.dashboardViewState.state.editview', newValue => { - if (newValue) { - elem.toggleClass('dashboard-page--settings-opening', _.isString(newValue)); - setTimeout(() => { - elem.toggleClass('dashboard-page--settings-open', _.isString(newValue)); - }, 10); - } else { - elem.removeClass('dashboard-page--settings-opening'); - elem.removeClass('dashboard-page--settings-open'); - } - }); - }, - }; -} - -coreModule.directive('dashClass', dashClass); From 9e0c79522898f20059e50269a05bc3bf47526494 Mon Sep 17 00:00:00 2001 From: ijin08 Date: Mon, 11 Feb 2019 09:54:14 +0100 Subject: [PATCH 053/119] set secondary to new blue --- public/sass/_variables.dark.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 07f65bb5b37..d10f44ca99f 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -159,8 +159,8 @@ $table-bg-hover: $dark-3; $btn-primary-bg: #ff6600; $btn-primary-bg-hl: #bc3e06; -$btn-secondary-bg-hl: lighten($blue-dark, 5%); -$btn-secondary-bg: $blue-dark; +$btn-secondary-bg-hl: $sapphire-base; +$btn-secondary-bg: $sapphire-shade; $btn-secondary-bg: $sapphire-base; $btn-secondary-bg-hl: $sapphire-shade; From 3bf0a5ffc68ff395300e8191443d3bd999bcf97d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 10:01:43 +0100 Subject: [PATCH 054/119] Fixed issue with logs graph not showing level names --- public/app/core/logs_model.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index abcd5563bd0..2cde5379448 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -340,6 +340,11 @@ export function makeSeriesForLogs(rows: LogRowModel[], intervalMs: number): Time return a[1] - b[1]; }); - return { datapoints: series.datapoints, target: series.alias, color: series.color }; + return { + datapoints: series.datapoints, + target: series.alias, + alias: series.alias, + color: series.color + }; }); } From 1cff59731c487145729bc873c6c7c4a6fa4c041f Mon Sep 17 00:00:00 2001 From: ijin08 Date: Mon, 11 Feb 2019 10:25:33 +0100 Subject: [PATCH 055/119] added old green to dark-theme --- public/sass/_variables.dark.scss | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index d10f44ca99f..de53a3d6058 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -14,6 +14,9 @@ $lobster-shade: #C4162A; $forest-light: #96D98D; $forest-base: #37872D; $forest-shade: #19730E; +$green-base: #299C46; +$green-shade: #23843B; + // Grays // ------------------------- @@ -46,7 +49,7 @@ $variable: #32d1df; $orange: #eb7b18; $brand-primary: $orange; -$brand-success: $forest-base; +$brand-success: $green-base; $brand-warning: $brand-primary; $brand-danger: $lobster-base; @@ -58,7 +61,7 @@ $query-orange: $orange; // Status colors // ------------------------- -$online: $forest-base; +$online: $green-base; $warn: #f79520; $critical: $lobster-base; @@ -156,8 +159,8 @@ $table-bg-hover: $dark-3; // Buttons // ------------------------- -$btn-primary-bg: #ff6600; -$btn-primary-bg-hl: #bc3e06; +$btn-primary-bg: $green-base; +$btn-primary-bg-hl: $green-shade; $btn-secondary-bg-hl: $sapphire-base; $btn-secondary-bg: $sapphire-shade; @@ -269,10 +272,10 @@ $error-text-color: #e84d4d; $success-text-color: $forest-light; //$info-text-color: $blue-dark; -$alert-error-bg: linear-gradient(90deg, #d44939, #e0603d); -$alert-success-bg: linear-gradient(90deg, $forest-base, $forest-shade); -$alert-warning-bg: linear-gradient(90deg, #d44939, #e0603d); -$alert-info-bg: linear-gradient(100deg, #1a4552, #00374a); +$alert-error-bg: linear-gradient(90deg, $lobster-base, $lobster-shade); +$alert-success-bg: linear-gradient(90deg, $green-base, $green-shade); +$alert-warning-bg: linear-gradient(90deg, $lobster-base, $lobster-shade); +$alert-info-bg: linear-gradient(100deg, $sapphire-base, $sapphire-shade); // popover $popover-bg: $page-bg; From e75e69a709c8fa4d4818f761f3c99edc21ede66e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 10:27:04 +0100 Subject: [PATCH 056/119] Commented out the Loki dashboard query editor --- .../loki/components/LokiQueryEditor.tsx | 85 ++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx index a1b9e7a5df9..14fe046e098 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx @@ -2,61 +2,65 @@ import React, { PureComponent } from 'react'; // Components -import { Select, SelectOptionItem } from '@grafana/ui'; +// import { Select, SelectOptionItem } from '@grafana/ui'; // Types import { QueryEditorProps } from '@grafana/ui/src/types'; import { LokiDatasource } from '../datasource'; import { LokiQuery } from '../types'; -import { LokiQueryField } from './LokiQueryField'; +// import { LokiQueryField } from './LokiQueryField'; type Props = QueryEditorProps; -interface State { - query: LokiQuery; -} +// interface State { +// query: LokiQuery; +// } export class LokiQueryEditor extends PureComponent { - state: State = { - query: this.props.query, - }; - - onRunQuery = () => { - const { query } = this.state; - - this.props.onChange(query); - this.props.onRunQuery(); - }; - - onFieldChange = (query: LokiQuery, override?) => { - this.setState({ - query: { - ...this.state.query, - expr: query.expr, - }, - }); - }; - - onFormatChanged = (option: SelectOptionItem) => { - this.props.onChange({ - ...this.state.query, - resultFormat: option.value, - }); - }; + // state: State = { + // query: this.props.query, + // }; + // + // onRunQuery = () => { + // const { query } = this.state; + // + // this.props.onChange(query); + // this.props.onRunQuery(); + // }; + // + // onFieldChange = (query: LokiQuery, override?) => { + // this.setState({ + // query: { + // ...this.state.query, + // expr: query.expr, + // }, + // }); + // }; + // + // onFormatChanged = (option: SelectOptionItem) => { + // this.props.onChange({ + // ...this.state.query, + // resultFormat: option.value, + // }); + // }; render() { - const { query } = this.state; - const { datasource } = this.props; - const formatOptions: SelectOptionItem[] = [ - { label: 'Time Series', value: 'time_series' }, - { label: 'Table', value: 'table' }, - ]; - - query.resultFormat = query.resultFormat || 'time_series'; - const currentFormat = formatOptions.find(item => item.value === query.resultFormat); + // const { query } = this.state; + // const { datasource } = this.props; + // const formatOptions: SelectOptionItem[] = [ + // { label: 'Time Series', value: 'time_series' }, + // { label: 'Table', value: 'table' }, + // ]; + // + // query.resultFormat = query.resultFormat || 'time_series'; + // const currentFormat = formatOptions.find(item => item.value === query.resultFormat); return (
+
+
Loki is currently not supported as dashboard data source. We are working on it!
+
+ {/* {
+ */}
); } From 2a2b242eb0b140de11f34079aca925e6403e9f2f Mon Sep 17 00:00:00 2001 From: ijin08 Date: Mon, 11 Feb 2019 10:27:58 +0100 Subject: [PATCH 057/119] removed extra semi-colon --- .../src/components/PanelOptionsGroup/_PanelOptionsGroup.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss index 9af18675553..882d96b3d97 100644 --- a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss +++ b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss @@ -29,7 +29,7 @@ &:hover { .panel-options-group__add-circle { - background-color: $btn-primary-bg;; + background-color: $btn-primary-bg; color: $white; } } From 2c8c4729a8ad13a44b7fdef1c570cc940777fb10 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 11 Feb 2019 10:47:03 +0100 Subject: [PATCH 058/119] changelog: adds note about closing #15288 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f4961343b..e6971f7952c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * **MSSQL**: Timerange are now passed for template variable queries [#13324](https://github.com/grafana/grafana/issues/13324), thx [@thatsparesh](https://github.com/thatsparesh) * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) +* **Login**: Anonymous usage stats for token auth [#15288](https://github.com/grafana/grafana/issues/15288) ### 6.0.0-beta1 fixes From 784d4fb70d676cf3f57fda39631e9c5bc451bdc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 10:57:32 +0100 Subject: [PATCH 059/119] Update README.md --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 658f1e34257..2cb8bfee306 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,18 @@ Grafana is an open source, feature rich metrics dashboard and graph editor for Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB. +![](https://www.grafanacon.org/2019/images/grafanacon_la_nav-logo.png) + +Join us Feb 25-26 in Los Angeles, California for GrafanaCon - a two-day event with talks focused on Grafana and the surrounding open source monitoring ecosystem. Get deep dives into Loki, the Explore workflow and all of the new features of Grafana 6, plus participate in hands on workshops to help you get the most out of your data. + +Time is running out - grab your ticket now! http://grafanacon.org + + ## Installation -Head to [docs.grafana.org](http://docs.grafana.org/installation/) and [download](https://grafana.com/get) -the latest release. - -If you have any problems please read the [troubleshooting guide](http://docs.grafana.org/installation/troubleshooting/). +Head to [docs.grafana.org](http://docs.grafana.org/installation/) for documentation or [download](https://grafana.com/get) to get the latest release. ## Documentation & Support Be sure to read the [getting started guide](http://docs.grafana.org/guides/gettingstarted/) and the other feature guides. From f39fef2a027879d5c7385fc8b8e5515a938a6487 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 11:03:24 +0100 Subject: [PATCH 060/119] Clear visualization picker search on picker close --- package.json | 1 + public/app/core/components/Animations/FadeIn.tsx | 10 ++++++++-- .../dashboard/panel_editor/VisualizationTab.tsx | 6 +++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5ac751ced3f..fae51a1d856 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@types/react-dom": "^16.0.9", "@types/react-grid-layout": "^0.16.6", "@types/react-select": "^2.0.4", + "@types/react-transition-group": "^2.0.15", "@types/react-virtualized": "^9.18.12", "angular-mocks": "1.6.6", "autoprefixer": "^6.4.0", diff --git a/public/app/core/components/Animations/FadeIn.tsx b/public/app/core/components/Animations/FadeIn.tsx index ea9a92d5f0f..d667b54261e 100644 --- a/public/app/core/components/Animations/FadeIn.tsx +++ b/public/app/core/components/Animations/FadeIn.tsx @@ -1,11 +1,12 @@ import React, { FC } from 'react'; -import Transition from 'react-transition-group/Transition'; +import Transition, { ExitHandler } from 'react-transition-group/Transition'; interface Props { duration: number; children: JSX.Element; in: boolean; unmountOnExit?: boolean; + onExited?: ExitHandler; } export const FadeIn: FC = props => { @@ -22,7 +23,12 @@ export const FadeIn: FC = props => { }; return ( - + {state => (
{ } } + clearQuery = () => { + this.setState({ searchQuery: '' }); + }; + onPanelOptionsChanged = (options: any) => { this.props.panel.updateOptions(options); this.forceUpdate(); @@ -241,7 +245,7 @@ export class VisualizationTab extends PureComponent { setScrollTop={this.setScrollTop} > <> - + Date: Mon, 11 Feb 2019 10:50:32 +0100 Subject: [PATCH 061/119] renames usage state name for auth token as noted, sessions might not be a good name for this metrics. while devices would be a better name for users I think we should align the name with the code as much as possible. The ui listing all auth_tokens per user should probarbly say "devices" instead --- pkg/infra/usagestats/usage_stats.go | 8 ++++---- pkg/infra/usagestats/usage_stats_test.go | 6 +++--- pkg/models/stats.go | 2 +- pkg/services/sqlstore/stats.go | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/infra/usagestats/usage_stats.go b/pkg/infra/usagestats/usage_stats.go index b54de124335..9d7501b7765 100644 --- a/pkg/infra/usagestats/usage_stats.go +++ b/pkg/infra/usagestats/usage_stats.go @@ -59,15 +59,15 @@ func (uss *UsageStatsService) sendUsageStats(oauthProviders map[string]bool) { metrics["stats.provisioned_dashboards.count"] = statsQuery.Result.ProvisionedDashboards metrics["stats.snapshots.count"] = statsQuery.Result.Snapshots metrics["stats.teams.count"] = statsQuery.Result.Teams - metrics["stats.total_sessions.count"] = statsQuery.Result.Sessions + metrics["stats.total_auth_token.count"] = statsQuery.Result.AuthTokens userCount := statsQuery.Result.Users - avgSessionsPerUser := statsQuery.Result.Sessions + avgAuthTokensPerUser := statsQuery.Result.AuthTokens if userCount != 0 { - avgSessionsPerUser = avgSessionsPerUser / userCount + avgAuthTokensPerUser = avgAuthTokensPerUser / userCount } - metrics["stats.avg_sessions_per_user.count"] = avgSessionsPerUser + metrics["stats.avg_auth_token_per_user.count"] = avgAuthTokensPerUser 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 d343ed52b93..ea5b95d6ef0 100644 --- a/pkg/infra/usagestats/usage_stats_test.go +++ b/pkg/infra/usagestats/usage_stats_test.go @@ -45,7 +45,7 @@ func TestMetrics(t *testing.T) { ProvisionedDashboards: 12, Snapshots: 13, Teams: 14, - Sessions: 15, + AuthTokens: 15, } getSystemStatsQuery = query return nil @@ -229,8 +229,8 @@ func TestMetrics(t *testing.T) { So(metrics.Get("stats.provisioned_dashboards.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.ProvisionedDashboards) So(metrics.Get("stats.snapshots.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Snapshots) So(metrics.Get("stats.teams.count").MustInt(), ShouldEqual, getSystemStatsQuery.Result.Teams) - So(metrics.Get("stats.total_sessions.count").MustInt64(), ShouldEqual, 15) - So(metrics.Get("stats.avg_sessions_per_user.count").MustInt64(), ShouldEqual, 5) + 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.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 00f881f3c59..0edd204ec03 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -15,7 +15,7 @@ type SystemStats struct { FolderPermissions int64 Folders int64 ProvisionedDashboards int64 - Sessions int64 + AuthTokens int64 } type DataSourceStats struct { diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 4c6d6c21221..2b7c35a4b4a 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -75,7 +75,7 @@ func GetSystemStats(query *m.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("team") + `) AS teams,`) - sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("user_auth_token") + `) AS sessions`) + sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("user_auth_token") + `) AS auth_tokens`) var stats m.SystemStats _, err := x.SQL(sb.GetSqlString(), sb.params...).Get(&stats) From f73f0e69e047e2312c0b39843a11d6329e2f301a Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 11:11:21 +0100 Subject: [PATCH 062/119] should be able to navigate to folder with only uid --- public/app/routes/routes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index e0029cf2464..4c9c5fd5304 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -150,8 +150,8 @@ export function setupAngularRoutes($routeProvider, $locationProvider) { controllerAs: 'ctrl', }) .when('/dashboards/f/:uid', { - templateUrl: 'public/app/features/dashboard/partials/folder_dashboards.html', - controller: 'FolderDashboardsCtrl', + templateUrl: 'public/app/features/folders/partials/folder_dashboards.html', + controller: FolderDashboardsCtrl, controllerAs: 'ctrl', }) .when('/explore', { From 217eb6310e157ff2d1f479993acabf31e27615b9 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 11 Feb 2019 11:17:23 +0100 Subject: [PATCH 063/119] make sure notifiers dir exists for provisioning in docker --- Dockerfile | 1 + packaging/docker/Dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index c3e59c8048e..9f07dc79c1d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,6 +64,7 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \ useradd -r -u $GF_UID -g grafana grafana && \ mkdir -p "$GF_PATHS_PROVISIONING/datasources" \ "$GF_PATHS_PROVISIONING/dashboards" \ + "$GF_PATHS_PROVISIONING/notifiers" \ "$GF_PATHS_LOGS" \ "$GF_PATHS_PLUGINS" \ "$GF_PATHS_DATA" && \ diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile index d4f2f2aa7a3..d783cb14377 100644 --- a/packaging/docker/Dockerfile +++ b/packaging/docker/Dockerfile @@ -39,6 +39,7 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \ useradd -r -u $GF_UID -g grafana grafana && \ mkdir -p "$GF_PATHS_PROVISIONING/datasources" \ "$GF_PATHS_PROVISIONING/dashboards" \ + "$GF_PATHS_PROVISIONING/notifiers" \ "$GF_PATHS_LOGS" \ "$GF_PATHS_PLUGINS" \ "$GF_PATHS_DATA" && \ From b780b6377ae2cbc7f22b7245fb3c7b7145e7527a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 11:17:23 +0100 Subject: [PATCH 064/119] Fixed missing time axis on graph due to width not being passed --- public/app/features/explore/Explore.tsx | 1 + public/app/features/explore/Logs.tsx | 3 +++ public/app/features/explore/LogsContainer.tsx | 3 +++ 3 files changed, 7 insertions(+) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index a28776d813a..aca8f033fb3 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -220,6 +220,7 @@ export class Explore extends React.PureComponent { {supportsTable && } {supportsLogs && ( { range, scanning, scanRange, + width, } = this.props; if (!data) { @@ -215,6 +217,7 @@ export default class Logs extends PureComponent { { @@ -46,6 +47,7 @@ export class LogsContainer extends PureComponent { showingLogs, scanning, scanRange, + width, } = this.props; return ( @@ -63,6 +65,7 @@ export class LogsContainer extends PureComponent { range={range} scanning={scanning} scanRange={scanRange} + width={width} /> ); From a54484638d9c940d0b74fb836832ea7da59d621a Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 11 Feb 2019 11:25:51 +0100 Subject: [PATCH 065/119] interval: make the FormatDuration function public A useful function that was ported from kbn.ts and can be used to convert milliseconds into a kbn unit --- pkg/tsdb/interval.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/tsdb/interval.go b/pkg/tsdb/interval.go index fd6adee39d7..7819ef3ecad 100644 --- a/pkg/tsdb/interval.go +++ b/pkg/tsdb/interval.go @@ -59,11 +59,11 @@ func (ic *intervalCalculator) Calculate(timerange *TimeRange, minInterval time.D interval := time.Duration((to - from) / defaultRes) if interval < minInterval { - return Interval{Text: formatDuration(minInterval), Value: minInterval} + return Interval{Text: FormatDuration(minInterval), Value: minInterval} } rounded := roundInterval(interval) - return Interval{Text: formatDuration(rounded), Value: rounded} + return Interval{Text: FormatDuration(rounded), Value: rounded} } func GetIntervalFrom(dsInfo *models.DataSource, queryModel *simplejson.Json, defaultInterval time.Duration) (time.Duration, error) { @@ -89,7 +89,8 @@ func GetIntervalFrom(dsInfo *models.DataSource, queryModel *simplejson.Json, def return parsedInterval, nil } -func formatDuration(inter time.Duration) string { +// FormatDuration converts a duration into the kbn format e.g. 1m 2h or 3d +func FormatDuration(inter time.Duration) string { if inter >= year { return fmt.Sprintf("%dy", inter/year) } From 7ce18ec4f79c529316fcb8d101e2aa2fa22be5b9 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 11 Feb 2019 11:30:41 +0100 Subject: [PATCH 066/119] extract notifiers folder creation to new if statement --- packaging/deb/control/postinst | 7 ++++--- packaging/rpm/control/postinst | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packaging/deb/control/postinst b/packaging/deb/control/postinst index 049061ac2dd..93fa276854c 100755 --- a/packaging/deb/control/postinst +++ b/packaging/deb/control/postinst @@ -32,11 +32,12 @@ case "$1" in fi if [ ! -f $PROVISIONING_CFG_DIR ]; then - mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources $PROVISIONING_CFG_DIR/notifiers + mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources cp /usr/share/grafana/conf/provisioning/dashboards/sample.yaml $PROVISIONING_CFG_DIR/dashboards/sample.yaml cp /usr/share/grafana/conf/provisioning/datasources/sample.yaml $PROVISIONING_CFG_DIR/datasources/sample.yaml - cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml - elif [ ! -f $PROVISIONING_CFG_DIR/notifiers ]; then + fi + + if [ ! -f $PROVISIONING_CFG_DIR/notifiers ]; then mkdir -p $PROVISIONING_CFG_DIR/notifiers cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml fi diff --git a/packaging/rpm/control/postinst b/packaging/rpm/control/postinst index 0187fc82cc5..fe4429fd0d4 100755 --- a/packaging/rpm/control/postinst +++ b/packaging/rpm/control/postinst @@ -46,11 +46,12 @@ if [ $1 -eq 1 ] ; then fi if [ ! -f $PROVISIONING_CFG_DIR ]; then - mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources $PROVISIONING_CFG_DIR/notifiers + mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources cp /usr/share/grafana/conf/provisioning/dashboards/sample.yaml $PROVISIONING_CFG_DIR/dashboards/sample.yaml cp /usr/share/grafana/conf/provisioning/datasources/sample.yaml $PROVISIONING_CFG_DIR/datasources/sample.yaml - cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml - elif [ ! -f $PROVISIONING_CFG_DIR/notifiers ]; then + fi + + if [ ! -f $PROVISIONING_CFG_DIR/notifiers ]; then mkdir -p $PROVISIONING_CFG_DIR/notifiers cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml fi From 5dc864b47f7f2bc06a341f301ba0b12f804cfdbe Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 11 Feb 2019 11:36:34 +0100 Subject: [PATCH 067/119] fixes invalid folder check -f check if a file exists. -d checks if the dir exists --- packaging/deb/control/postinst | 4 ++-- packaging/rpm/control/postinst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packaging/deb/control/postinst b/packaging/deb/control/postinst index 93fa276854c..957f8aef307 100755 --- a/packaging/deb/control/postinst +++ b/packaging/deb/control/postinst @@ -31,13 +31,13 @@ case "$1" in cp /usr/share/grafana/conf/ldap.toml /etc/grafana/ldap.toml fi - if [ ! -f $PROVISIONING_CFG_DIR ]; then + if [ ! -d $PROVISIONING_CFG_DIR ]; then mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources cp /usr/share/grafana/conf/provisioning/dashboards/sample.yaml $PROVISIONING_CFG_DIR/dashboards/sample.yaml cp /usr/share/grafana/conf/provisioning/datasources/sample.yaml $PROVISIONING_CFG_DIR/datasources/sample.yaml fi - if [ ! -f $PROVISIONING_CFG_DIR/notifiers ]; then + if [ ! -d $PROVISIONING_CFG_DIR/notifiers ]; then mkdir -p $PROVISIONING_CFG_DIR/notifiers cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml fi diff --git a/packaging/rpm/control/postinst b/packaging/rpm/control/postinst index fe4429fd0d4..cd679838487 100755 --- a/packaging/rpm/control/postinst +++ b/packaging/rpm/control/postinst @@ -45,13 +45,13 @@ if [ $1 -eq 1 ] ; then cp /usr/share/grafana/conf/ldap.toml /etc/grafana/ldap.toml fi - if [ ! -f $PROVISIONING_CFG_DIR ]; then + if [ ! -d $PROVISIONING_CFG_DIR ]; then mkdir -p $PROVISIONING_CFG_DIR/dashboards $PROVISIONING_CFG_DIR/datasources cp /usr/share/grafana/conf/provisioning/dashboards/sample.yaml $PROVISIONING_CFG_DIR/dashboards/sample.yaml cp /usr/share/grafana/conf/provisioning/datasources/sample.yaml $PROVISIONING_CFG_DIR/datasources/sample.yaml fi - if [ ! -f $PROVISIONING_CFG_DIR/notifiers ]; then + if [ ! -d $PROVISIONING_CFG_DIR/notifiers ]; then mkdir -p $PROVISIONING_CFG_DIR/notifiers cp /usr/share/grafana/conf/provisioning/notifiers/sample.yaml $PROVISIONING_CFG_DIR/notifiers/sample.yaml fi From 9485c678279762acee88bf6f2434fa0db99102cc Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 11:47:12 +0100 Subject: [PATCH 068/119] Fix plugin loading failure message not being displayed --- .../features/dashboard/dashgrid/DashboardPanel.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 2d794bec4d4..b9c56e36382 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -68,7 +68,7 @@ export class DashboardPanel extends PureComponent { // handle plugin loading & changing of plugin type if (!this.state.plugin || this.state.plugin.id !== pluginId) { - const plugin = config.panels[pluginId] || getPanelPluginNotFound(pluginId); + let plugin = config.panels[pluginId] || getPanelPluginNotFound(pluginId); // remember if this is from an angular panel const fromAngularPanel = this.state.angularPanel != null; @@ -81,10 +81,15 @@ export class DashboardPanel extends PureComponent { } if (plugin.exports) { - this.setState({ plugin: plugin, angularPanel: null }); + this.setState({ plugin, angularPanel: null }); } else { - plugin.exports = await importPluginModule(plugin.module); - this.setState({ plugin: plugin, angularPanel: null }); + try { + plugin.exports = await importPluginModule(plugin.module); + } catch (e) { + plugin = getPanelPluginNotFound(pluginId); + } + + this.setState({ plugin, angularPanel: null }); } } } From c9fbd43231fc715ee2b37ad6e8a6aaa16cb1ec68 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 11:59:48 +0100 Subject: [PATCH 069/119] Review changes --- public/app/features/explore/LogsContainer.tsx | 2 +- public/app/features/explore/state/actionTypes.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index a3cab0b256a..f64fec5e70e 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -93,7 +93,7 @@ function mapStateToProps(state: StoreState, { exploreId }) { const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, range } = item; const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done); const {showingLogs, dedupStrategy} = selectItemUIState(item); - // const dedup = item.dedup; + return { loading, logsHighlighterExpressions, diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index 71061607d3c..d54a8754c3d 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -180,8 +180,6 @@ export interface SplitOpenPayload { itemState: ExploreItemState; } -// - export interface ToggleTablePayload { exploreId: ExploreId; } @@ -373,7 +371,7 @@ export const splitOpenAction = actionCreatorFactory('explore/S export const stateSaveAction = noPayloadActionCreatorFactory('explore/STATE_SAVE').create(); /** - * Update state of Explores UI + * Update state of Explores UI elements (panels visiblity and deduplication strategy) */ export const updateUIStateAction = actionCreatorFactory('explore/UPDATE_UI_STATE').create(); From 37a73b6b35e44189d14a32d6b19a3e5973fb7349 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 12:04:27 +0100 Subject: [PATCH 070/119] make sure graphite takes dashboard timezone into consideration --- .../plugins/datasource/graphite/datasource.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index a89c0ea0034..8a720cb3f35 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -31,8 +31,8 @@ export function GraphiteDatasource(this: any, instanceSettings, $q, backendSrv, this.query = function(options) { const graphOptions = { - from: this.translateTime(options.rangeRaw.from, false), - until: this.translateTime(options.rangeRaw.to, true), + from: this.translateTime(options.rangeRaw.from, false, options.timezone), + until: this.translateTime(options.rangeRaw.to, true, options.timezone), targets: options.targets, format: options.format, cacheTimeout: options.cacheTimeout || this.cacheTimeout, @@ -165,9 +165,9 @@ export function GraphiteDatasource(this: any, instanceSettings, $q, backendSrv, method: 'GET', url: '/events/get_data?from=' + - this.translateTime(options.range.from, false) + + this.translateTime(options.range.from, false, options.timezone) + '&until=' + - this.translateTime(options.range.to, true) + + this.translateTime(options.range.to, true, options.timezone) + tags, }); } catch (err) { @@ -179,7 +179,7 @@ export function GraphiteDatasource(this: any, instanceSettings, $q, backendSrv, return templateSrv.variableExists(target.target); }; - this.translateTime = (date, roundUp) => { + this.translateTime = (date, roundUp, timezone) => { if (_.isString(date)) { if (date === 'now') { return 'now'; @@ -189,7 +189,7 @@ export function GraphiteDatasource(this: any, instanceSettings, $q, backendSrv, date = date.replace('M', 'mon'); return date; } - date = dateMath.parse(date, roundUp); + date = dateMath.parse(date, roundUp, timezone); } // graphite' s from filter is exclusive @@ -255,8 +255,8 @@ export function GraphiteDatasource(this: any, instanceSettings, $q, backendSrv, }; if (options.range) { - httpOptions.params.from = this.translateTime(options.range.from, false); - httpOptions.params.until = this.translateTime(options.range.to, true); + httpOptions.params.from = this.translateTime(options.range.from, false, options.timezone); + httpOptions.params.until = this.translateTime(options.range.to, true, options.timezone); } return this.doGraphiteRequest(httpOptions).then(results => { @@ -280,8 +280,8 @@ export function GraphiteDatasource(this: any, instanceSettings, $q, backendSrv, }; if (options.range) { - httpOptions.params.from = this.translateTime(options.range.from, false); - httpOptions.params.until = this.translateTime(options.range.to, true); + httpOptions.params.from = this.translateTime(options.range.from, false, options.timezone); + httpOptions.params.until = this.translateTime(options.range.to, true, options.timezone); } return this.doGraphiteRequest(httpOptions).then(results => { @@ -305,8 +305,8 @@ export function GraphiteDatasource(this: any, instanceSettings, $q, backendSrv, }; if (options.range) { - httpOptions.params.from = this.translateTime(options.range.from, false); - httpOptions.params.until = this.translateTime(options.range.to, true); + httpOptions.params.from = this.translateTime(options.range.from, false, options.timezone); + httpOptions.params.until = this.translateTime(options.range.to, true, options.timezone); } return this.doGraphiteRequest(httpOptions).then(results => { @@ -343,8 +343,8 @@ export function GraphiteDatasource(this: any, instanceSettings, $q, backendSrv, httpOptions.params.limit = options.limit; } if (options.range) { - httpOptions.params.from = this.translateTime(options.range.from, false); - httpOptions.params.until = this.translateTime(options.range.to, true); + httpOptions.params.from = this.translateTime(options.range.from, false, options.timezone); + httpOptions.params.until = this.translateTime(options.range.to, true, options.timezone); } return this.doGraphiteRequest(httpOptions).then(results => { @@ -379,8 +379,8 @@ export function GraphiteDatasource(this: any, instanceSettings, $q, backendSrv, httpOptions.params.limit = options.limit; } if (options.range) { - httpOptions.params.from = this.translateTime(options.range.from, false); - httpOptions.params.until = this.translateTime(options.range.to, true); + httpOptions.params.from = this.translateTime(options.range.from, false, options.timezone); + httpOptions.params.until = this.translateTime(options.range.to, true, options.timezone); } return this.doGraphiteRequest(httpOptions).then(results => { From cee2e4788bc7963373262fb634faa14f8e49a7b9 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 12:56:37 +0100 Subject: [PATCH 071/119] Do not read store state from toggle panelaction creator --- .../app/features/explore/GraphContainer.tsx | 2 +- public/app/features/explore/LogsContainer.tsx | 2 +- .../app/features/explore/TableContainer.tsx | 2 +- public/app/features/explore/state/actions.ts | 28 +++++++++++-------- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/public/app/features/explore/GraphContainer.tsx b/public/app/features/explore/GraphContainer.tsx index 3950d89c11f..92aac41367c 100644 --- a/public/app/features/explore/GraphContainer.tsx +++ b/public/app/features/explore/GraphContainer.tsx @@ -25,7 +25,7 @@ interface GraphContainerProps { export class GraphContainer extends PureComponent { onClickGraphButton = () => { - this.props.toggleGraph(this.props.exploreId); + this.props.toggleGraph(this.props.exploreId, this.props.showingGraph); }; onChangeTime = (timeRange: TimeRange) => { diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 183af5b28fa..190c1c43b5a 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -32,7 +32,7 @@ interface LogsContainerProps { export class LogsContainer extends PureComponent { onClickLogsButton = () => { - this.props.toggleLogs(this.props.exploreId); + this.props.toggleLogs(this.props.exploreId, this.props.showingLogs); }; handleDedupStrategyChange = (dedupStrategy: LogsDedupStrategy) => { diff --git a/public/app/features/explore/TableContainer.tsx b/public/app/features/explore/TableContainer.tsx index f386e5ab99b..e41d4a1eecb 100644 --- a/public/app/features/explore/TableContainer.tsx +++ b/public/app/features/explore/TableContainer.tsx @@ -21,7 +21,7 @@ interface TableContainerProps { export class TableContainer extends PureComponent { onClickTableButton = () => { - this.props.toggleTable(this.props.exploreId); + this.props.toggleTable(this.props.exploreId, this.props.showingTable); }; render() { diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 63f0bfd0350..8c5ed661851 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -714,25 +714,20 @@ const togglePanelActionCreator = ( | ActionCreator | ActionCreator | ActionCreator -) => (exploreId: ExploreId) => { - return (dispatch, getState) => { - let shouldRunQueries, uiFragmentStateUpdate: Partial; +) => (exploreId: ExploreId, isPanelVisible: boolean) => { + return (dispatch) => { + let uiFragmentStateUpdate: Partial; + const shouldRunQueries = !isPanelVisible; switch (actionCreator.type) { case toggleGraphAction.type: - const isShowingGraph = getState().explore[exploreId].showingGraph; - shouldRunQueries = !isShowingGraph; - uiFragmentStateUpdate = { showingGraph: !isShowingGraph }; + uiFragmentStateUpdate = { showingGraph: !isPanelVisible }; break; case toggleLogsAction.type: - const isShowingLogs = getState().explore[exploreId].showingLogs; - shouldRunQueries = !isShowingLogs; - uiFragmentStateUpdate = { showingLogs: !isShowingLogs }; + uiFragmentStateUpdate = { showingLogs: !isPanelVisible }; break; case toggleTableAction.type: - const isShowingTable = getState().explore[exploreId].showingTable; - shouldRunQueries = !isShowingTable; - uiFragmentStateUpdate = { showingTable: !isShowingTable }; + uiFragmentStateUpdate = { showingTable: !isPanelVisible }; break; } @@ -768,3 +763,12 @@ export const changeDedupStrategy = (exploreId, dedupStrategy: LogsDedupStrategy) dispatch(updateExploreUIState(exploreId, { dedupStrategy })); }; }; + +/** + * Change logs deduplication strategy and update URL. + */ +export const hiddenLogLe = (exploreId, dedupStrategy: LogsDedupStrategy) => { + return dispatch => { + dispatch(updateExploreUIState(exploreId, { dedupStrategy })); + }; +}; From 519dfd0899570999eb656063eacc678dfa9587a1 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 13:11:56 +0100 Subject: [PATCH 072/119] make sure influx takes dashboard timezone into consideration --- public/app/plugins/datasource/influxdb/datasource.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 8f1904dbc55..4e4c3feaebd 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -127,7 +127,7 @@ export default class InfluxDatasource { }); } - const timeFilter = this.getTimeFilter({ rangeRaw: options.rangeRaw }); + const timeFilter = this.getTimeFilter({ rangeRaw: options.rangeRaw, timezone: options.timezone }); let query = options.annotation.query.replace('$timeFilter', timeFilter); query = this.templateSrv.replace(query, null, 'regex'); @@ -184,7 +184,7 @@ export default class InfluxDatasource { } if (options && options.range) { - const timeFilter = this.getTimeFilter({ rangeRaw: options.range }); + const timeFilter = this.getTimeFilter({ rangeRaw: options.range, timezone: options.timezone }); query = query.replace('$timeFilter', timeFilter); } @@ -291,8 +291,8 @@ export default class InfluxDatasource { } getTimeFilter(options) { - const from = this.getInfluxTime(options.rangeRaw.from, false); - const until = this.getInfluxTime(options.rangeRaw.to, true); + const from = this.getInfluxTime(options.rangeRaw.from, false, options.timezone); + const until = this.getInfluxTime(options.rangeRaw.to, true, options.timezone); const fromIsAbsolute = from[from.length - 1] === 'ms'; if (until === 'now()' && !fromIsAbsolute) { @@ -302,7 +302,7 @@ export default class InfluxDatasource { return 'time >= ' + from + ' and time <= ' + until; } - getInfluxTime(date, roundUp) { + getInfluxTime(date, roundUp, timezone) { if (_.isString(date)) { if (date === 'now') { return 'now()'; @@ -314,7 +314,7 @@ export default class InfluxDatasource { const unit = parts[2]; return 'now() - ' + amount + unit; } - date = dateMath.parse(date, roundUp); + date = dateMath.parse(date, roundUp, timezone); } return date.valueOf() + 'ms'; From b9c36e5301cc80893c808e349cdcb5e97a66e156 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 13:13:38 +0100 Subject: [PATCH 073/119] make sure opentsdb takes dashboard timezone into consideration --- public/app/plugins/datasource/opentsdb/datasource.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/opentsdb/datasource.ts b/public/app/plugins/datasource/opentsdb/datasource.ts index 772f2aa7ff9..bab86a04765 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.ts +++ b/public/app/plugins/datasource/opentsdb/datasource.ts @@ -33,8 +33,8 @@ export default class OpenTsDatasource { // Called once per panel (graph) query(options) { - const start = this.convertToTSDBTime(options.rangeRaw.from, false); - const end = this.convertToTSDBTime(options.rangeRaw.to, true); + const start = this.convertToTSDBTime(options.rangeRaw.from, false, options.timezone); + const end = this.convertToTSDBTime(options.rangeRaw.to, true, options.timezone); const qs = []; _.each(options.targets, target => { @@ -86,8 +86,8 @@ export default class OpenTsDatasource { } annotationQuery(options) { - const start = this.convertToTSDBTime(options.rangeRaw.from, false); - const end = this.convertToTSDBTime(options.rangeRaw.to, true); + const start = this.convertToTSDBTime(options.rangeRaw.from, false, options.timezone); + const end = this.convertToTSDBTime(options.rangeRaw.to, true, options.timezone); const qs = []; const eventList = []; @@ -484,12 +484,12 @@ export default class OpenTsDatasource { }); } - convertToTSDBTime(date, roundUp) { + convertToTSDBTime(date, roundUp, timezone) { if (date === 'now') { return null; } - date = dateMath.parse(date, roundUp); + date = dateMath.parse(date, roundUp, timezone); return date.valueOf(); } } From 85780eb30cd1be80c602d98ea615baab1c91f438 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 13:18:16 +0100 Subject: [PATCH 074/119] Remove not related code --- public/app/features/explore/state/actions.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index 8c5ed661851..b84a0534836 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -715,7 +715,7 @@ const togglePanelActionCreator = ( | ActionCreator | ActionCreator ) => (exploreId: ExploreId, isPanelVisible: boolean) => { - return (dispatch) => { + return dispatch => { let uiFragmentStateUpdate: Partial; const shouldRunQueries = !isPanelVisible; @@ -763,12 +763,3 @@ export const changeDedupStrategy = (exploreId, dedupStrategy: LogsDedupStrategy) dispatch(updateExploreUIState(exploreId, { dedupStrategy })); }; }; - -/** - * Change logs deduplication strategy and update URL. - */ -export const hiddenLogLe = (exploreId, dedupStrategy: LogsDedupStrategy) => { - return dispatch => { - dispatch(updateExploreUIState(exploreId, { dedupStrategy })); - }; -}; From b14958edef29dc4dbe6bd0d1153343a24b2047f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 13:24:02 +0100 Subject: [PATCH 075/119] Minor style fixes --- public/sass/components/_alerts.scss | 2 +- public/sass/components/_navbar.scss | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/public/sass/components/_alerts.scss b/public/sass/components/_alerts.scss index 710c4d1ec0f..dc98cba82bd 100644 --- a/public/sass/components/_alerts.scss +++ b/public/sass/components/_alerts.scss @@ -16,6 +16,7 @@ border-radius: $border-radius; display: flex; flex-direction: row; + align-items: center; } // Alternate styles @@ -62,7 +63,6 @@ .alert-title { font-weight: $font-weight-semi-bold; - padding-bottom: 2px; } .alert-icon { diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss index be52167fde1..2bf16bd2d43 100644 --- a/public/sass/components/_navbar.scss +++ b/public/sass/components/_navbar.scss @@ -117,7 +117,8 @@ .navbar-button { @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl, $btn-inverse-text-color, $btn-inverse-text-shadow); - display: inline-block; + display: flex; + align-items: center; font-weight: $btn-font-weight; padding: 6px 11px; line-height: 16px; From 0b74860f55890d27125b22ff97112b682e4b7f6f Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 11 Feb 2019 13:27:08 +0100 Subject: [PATCH 076/119] azuremonitor: fix auto interval calculation on backend Not needed for alerting (as the query intervalms will always be 0) but needed later when being called from the frontend) --- .../azuremonitor/azuremonitor-datasource.go | 8 +++---- .../azuremonitor-datasource_test.go | 6 ++--- pkg/tsdb/azuremonitor/time-grain.go | 23 +++++++++---------- pkg/tsdb/azuremonitor/time-grain_test.go | 21 +++++++++++++---- pkg/tsdb/azuremonitor/url-builder.go | 6 ++--- pkg/tsdb/azuremonitor/url-builder_test.go | 6 ++--- pkg/tsdb/interval_test.go | 10 ++++---- 7 files changed, 45 insertions(+), 35 deletions(-) diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go index 079910e1b66..5def94aebf6 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go @@ -89,7 +89,7 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange * urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"]) urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"]) - ub := URLBuilder{ + ub := urlBuilder{ ResourceGroup: urlComponents["resourceGroup"], MetricDefinition: urlComponents["metricDefinition"], ResourceName: urlComponents["resourceName"], @@ -100,9 +100,9 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange * timeGrain := fmt.Sprintf("%v", azureMonitorTarget["timeGrain"]) if timeGrain == "auto" { - autoInSeconds := e.findClosestAllowedIntervalMs(query.IntervalMs) / 1000 + autoInterval := e.findClosestAllowedIntervalMS(query.IntervalMs) tg := &TimeGrain{} - timeGrain, err = tg.createISO8601DurationFromInterval(fmt.Sprintf("%vs", autoInSeconds)) + timeGrain, err = tg.createISO8601DurationFromIntervalMS(autoInterval) if err != nil { return nil, err } @@ -288,7 +288,7 @@ func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, data // findClosestAllowedIntervalMs is used for the auto time grain setting. // It finds the closest time grain from the list of allowed time grains for Azure Monitor // using the Grafana interval in milliseconds -func (e *AzureMonitorDatasource) findClosestAllowedIntervalMs(intervalMs int64) int64 { +func (e *AzureMonitorDatasource) findClosestAllowedIntervalMS(intervalMs int64) int64 { closest := allowedIntervalsMS[0] for i, allowed := range allowedIntervalsMS { diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go index 9aba4eb617b..b8d1d6cc266 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource_test.go @@ -240,13 +240,13 @@ func TestAzureMonitorDatasource(t *testing.T) { "2d": 172800000, } - closest := datasource.findClosestAllowedIntervalMs(intervals["3m"]) + closest := datasource.findClosestAllowedIntervalMS(intervals["3m"]) So(closest, ShouldEqual, intervals["5m"]) - closest = datasource.findClosestAllowedIntervalMs(intervals["10m"]) + closest = datasource.findClosestAllowedIntervalMS(intervals["10m"]) So(closest, ShouldEqual, intervals["15m"]) - closest = datasource.findClosestAllowedIntervalMs(intervals["2d"]) + closest = datasource.findClosestAllowedIntervalMS(intervals["2d"]) So(closest, ShouldEqual, intervals["1d"]) }) }) diff --git a/pkg/tsdb/azuremonitor/time-grain.go b/pkg/tsdb/azuremonitor/time-grain.go index 22da2872bab..e6a15aef64f 100644 --- a/pkg/tsdb/azuremonitor/time-grain.go +++ b/pkg/tsdb/azuremonitor/time-grain.go @@ -4,6 +4,9 @@ import ( "fmt" "strconv" "strings" + "time" + + "github.com/grafana/grafana/pkg/tsdb" ) // TimeGrain handles convertions between @@ -15,28 +18,24 @@ var ( smallTimeUnits = []string{"hour", "minute", "h", "m"} ) -func (tg *TimeGrain) createISO8601DurationFromInterval(interval string) (string, error) { - if strings.Contains(interval, "ms") { +func (tg *TimeGrain) createISO8601DurationFromIntervalMS(interval int64) (string, error) { + formatted := tsdb.FormatDuration(time.Duration(interval) * time.Millisecond) + + if strings.Contains(formatted, "ms") { return "PT1M", nil } - timeValueString := interval[0 : len(interval)-1] + timeValueString := formatted[0 : len(formatted)-1] timeValue, err := strconv.Atoi(timeValueString) if err != nil { return "", fmt.Errorf("Could not parse interval %v to an ISO 8061 duration", interval) } - unit := interval[len(interval)-1:] - - if unit == "s" { - toMinutes := (timeValue * 60) % 60 + unit := formatted[len(formatted)-1:] + if unit == "s" && timeValue < 60 { // mimumum interval is 1m for Azure Monitor - if toMinutes < 1 { - toMinutes = 1 - } - - return tg.createISO8601Duration(toMinutes, "m"), nil + return "PT1M", nil } return tg.createISO8601Duration(timeValue, unit), nil diff --git a/pkg/tsdb/azuremonitor/time-grain_test.go b/pkg/tsdb/azuremonitor/time-grain_test.go index be8d0b10a0c..2df3c92b0ff 100644 --- a/pkg/tsdb/azuremonitor/time-grain_test.go +++ b/pkg/tsdb/azuremonitor/time-grain_test.go @@ -37,10 +37,14 @@ func TestTimeGrain(t *testing.T) { }) }) - Convey("create ISO 8601 Duration from Grafana interval", func() { + Convey("create ISO 8601 Duration from Grafana interval in milliseconds", func() { Convey("and interval is less than a minute", func() { - durationMS, _ := tgc.createISO8601DurationFromInterval("100ms") - durationS, _ := tgc.createISO8601DurationFromInterval("59s") + durationMS, err := tgc.createISO8601DurationFromIntervalMS(100) + So(err, ShouldBeNil) + + durationS, err := tgc.createISO8601DurationFromIntervalMS(59999) + So(err, ShouldBeNil) + Convey("should be rounded up to a minute as is the minimum interval for Azure Monitor", func() { So(durationMS, ShouldEqual, "PT1M") So(durationS, ShouldEqual, "PT1M") @@ -48,8 +52,15 @@ func TestTimeGrain(t *testing.T) { }) Convey("and interval is more than a minute", func() { - durationM, _ := tgc.createISO8601DurationFromInterval("10m") - durationD, _ := tgc.createISO8601DurationFromInterval("2d") + intervals := map[string]int64{ + "10m": 600000, + "2d": 172800000, + } + durationM, err := tgc.createISO8601DurationFromIntervalMS(intervals["10m"]) + So(err, ShouldBeNil) + durationD, err := tgc.createISO8601DurationFromIntervalMS(intervals["2d"]) + So(err, ShouldBeNil) + Convey("should be rounded up to a minute as is the minimum interval for Azure Monitor", func() { So(durationM, ShouldEqual, "PT10M") So(durationD, ShouldEqual, "P2D") diff --git a/pkg/tsdb/azuremonitor/url-builder.go b/pkg/tsdb/azuremonitor/url-builder.go index 1ccbbc2bf81..c252048f517 100644 --- a/pkg/tsdb/azuremonitor/url-builder.go +++ b/pkg/tsdb/azuremonitor/url-builder.go @@ -5,8 +5,8 @@ import ( "strings" ) -// URLBuilder builds the URL for calling the Azure Monitor API -type URLBuilder struct { +// urlBuilder builds the URL for calling the Azure Monitor API +type urlBuilder struct { ResourceGroup string MetricDefinition string ResourceName string @@ -14,7 +14,7 @@ type URLBuilder struct { // Build checks the metric definition property to see which form of the url // should be returned -func (ub *URLBuilder) Build() string { +func (ub *urlBuilder) Build() string { if strings.Count(ub.MetricDefinition, "/") > 1 { rn := strings.Split(ub.ResourceName, "/") diff --git a/pkg/tsdb/azuremonitor/url-builder_test.go b/pkg/tsdb/azuremonitor/url-builder_test.go index baf9b34d7eb..85c4f81bc83 100644 --- a/pkg/tsdb/azuremonitor/url-builder_test.go +++ b/pkg/tsdb/azuremonitor/url-builder_test.go @@ -10,7 +10,7 @@ func TestURLBuilder(t *testing.T) { Convey("AzureMonitor URL Builder", t, func() { Convey("when metric definition is in the short form", func() { - ub := &URLBuilder{ + ub := &urlBuilder{ ResourceGroup: "rg", MetricDefinition: "Microsoft.Compute/virtualMachines", ResourceName: "rn", @@ -21,7 +21,7 @@ func TestURLBuilder(t *testing.T) { }) Convey("when metric definition is Microsoft.Storage/storageAccounts/blobServices", func() { - ub := &URLBuilder{ + ub := &urlBuilder{ ResourceGroup: "rg", MetricDefinition: "Microsoft.Storage/storageAccounts/blobServices", ResourceName: "rn1/default", @@ -32,7 +32,7 @@ func TestURLBuilder(t *testing.T) { }) Convey("when metric definition is Microsoft.Storage/storageAccounts/fileServices", func() { - ub := &URLBuilder{ + ub := &urlBuilder{ ResourceGroup: "rg", MetricDefinition: "Microsoft.Storage/storageAccounts/fileServices", ResourceName: "rn1/default", diff --git a/pkg/tsdb/interval_test.go b/pkg/tsdb/interval_test.go index 941b08dd554..4cd3fcea532 100644 --- a/pkg/tsdb/interval_test.go +++ b/pkg/tsdb/interval_test.go @@ -51,11 +51,11 @@ func TestInterval(t *testing.T) { }) Convey("Format value", func() { - So(formatDuration(time.Second*61), ShouldEqual, "1m") - So(formatDuration(time.Millisecond*30), ShouldEqual, "30ms") - So(formatDuration(time.Hour*23), ShouldEqual, "23h") - So(formatDuration(time.Hour*24), ShouldEqual, "1d") - So(formatDuration(time.Hour*24*367), ShouldEqual, "1y") + So(FormatDuration(time.Second*61), ShouldEqual, "1m") + So(FormatDuration(time.Millisecond*30), ShouldEqual, "30ms") + So(FormatDuration(time.Hour*23), ShouldEqual, "23h") + So(FormatDuration(time.Hour*24), ShouldEqual, "1d") + So(FormatDuration(time.Hour*24*367), ShouldEqual, "1y") }) }) } From 13f21fffc4500b70afa6fd9aad2379f208968681 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 11 Feb 2019 13:35:37 +0100 Subject: [PATCH 077/119] changelog: adds note about closing #15295 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6971f7952c..065e035128f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) * **Login**: Anonymous usage stats for token auth [#15288](https://github.com/grafana/grafana/issues/15288) +* **Alerting**: Fixes crash bug when alert notifier folders are missing [#15295](https://github.com/grafana/grafana/issues/15295) ### 6.0.0-beta1 fixes From a1cd550df4a2a7aaa85ba0b7b31ad92437e58739 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 13:42:05 +0100 Subject: [PATCH 078/119] revert ds_proxy timeout and implement dataproxy timeout correctly --- pkg/api/pluginproxy/ds_proxy.go | 2 +- pkg/models/datasource_cache.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index a0ad96a6977..b1950998297 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -54,7 +54,7 @@ func NewDataSourceProxy(ds *m.DataSource, plugin *plugins.DataSourcePlugin, ctx func newHTTPClient() httpClient { return &http.Client{ - Timeout: time.Duration(setting.DataProxyTimeout) * time.Second, + Timeout: 30 * time.Second, Transport: &http.Transport{Proxy: http.ProxyFromEnvironment}, } } diff --git a/pkg/models/datasource_cache.go b/pkg/models/datasource_cache.go index 1c895514ace..864adb4a5a6 100644 --- a/pkg/models/datasource_cache.go +++ b/pkg/models/datasource_cache.go @@ -8,6 +8,8 @@ import ( "net/http" "sync" "time" + + "github.com/grafana/grafana/pkg/setting" ) type proxyTransportCache struct { @@ -57,7 +59,7 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) { TLSClientConfig: tlsConfig, Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ - Timeout: 30 * time.Second, + Timeout: time.Duration(setting.DataProxyTimeout) * time.Second, KeepAlive: 30 * time.Second, DualStack: true, }).Dial, From 9570394c49079e2935215f2f29019df835a2eef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 13:42:37 +0100 Subject: [PATCH 079/119] minor style update --- public/sass/_variables.light.scss | 8 ++++---- public/sass/components/_timepicker.scss | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 4298c50369f..782354bba19 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -153,11 +153,11 @@ $table-bg-hover: $gray-5; // Buttons // ------------------------- -$btn-primary-bg: $sapphire-base; -$btn-primary-bg-hl: $sapphire-shade; +$btn-primary-bg: $green-base; +$btn-primary-bg-hl: $green-shade; -$btn-secondary-bg: rgba(0,0,0,0); -$btn-secondary-bg-hl: rgba(0,0,0,0); +$btn-secondary-bg: $sapphire-base; +$btn-secondary-bg-hl: $sapphire-shade; $btn-danger-bg: $lobster-base; $btn-danger-bg-hl: $lobster-shade; diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index 6f075c4d92e..d25d85b3d74 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -10,6 +10,10 @@ .gf-timepicker-nav-btn { text-overflow: ellipsis; overflow: hidden; + + .fa-clock-o { + margin-right: 4px; + } } .gf-timepicker-dropdown { @@ -48,7 +52,6 @@ } .gf-timepicker-utc { - background-color: $tight-form-func-bg; color: $orange; font-size: 75%; padding: 3px; From 77ba73449131126368eb491dc1e61982e8919a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 13:53:07 +0100 Subject: [PATCH 080/119] Fixed issue with light theme introduced by #15333 --- public/sass/_variables.light.scss | 2 +- public/sass/components/_query_editor.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 0f4e15c91ec..97d7a374765 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -56,7 +56,7 @@ $page-bg: $gray-7; $body-color: $gray-1; $text-color: $gray-1; $text-color-strong: $dark-2; -$text-color-weak: $gray-3; +$text-color-weak: $gray-2; $text-color-faint: $gray-4; $text-color-emphasis: $dark-5; diff --git a/public/sass/components/_query_editor.scss b/public/sass/components/_query_editor.scss index b57e575dc5b..6b2e93121f5 100644 --- a/public/sass/components/_query_editor.scss +++ b/public/sass/components/_query_editor.scss @@ -124,7 +124,7 @@ input[type='text'].tight-form-func-param { &--disabled { .query-keyword { - color: darken($blue, 20%); + color: $text-color-weak; } } From 5e6c746c9b0eeda36481e72a1f8e52ccaf664634 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 13:57:44 +0100 Subject: [PATCH 081/119] changelog: add notes about closing #15284 --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 065e035128f..9db747d6ed5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Minor * **Pushover**: Adds support for images in pushover notifier [#10780](https://github.com/grafana/grafana/issues/10780), thx [@jpenalbae](https://github.com/jpenalbae) +* **Graphite/InfluxDB/OpenTSDB**: Fix always take dashboard timezone into consideration when handle custom time ranges [#15284](https://github.com/grafana/grafana/issues/15284) * **Stackdriver**: Template variables in filters using globbing format [#15182](https://github.com/grafana/grafana/issues/15182) * **Cloudwatch**: Add `resource_arns` template variable query function [#8207](https://github.com/grafana/grafana/issues/8207), thx [@jeroenvollenbrock](https://github.com/jeroenvollenbrock) * **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson) @@ -96,7 +97,7 @@ * **Stackdriver**: Fixes issue with data proxy and Authorization header [#14262](https://github.com/grafana/grafana/issues/14262) * **Units**: fixedUnit for Flow:l/min and mL/min [#14294](https://github.com/grafana/grafana/issues/14294), thx [@flopp999](https://github.com/flopp999). * **Logging**: Fix for issue where data proxy logged a secret when debug logging was enabled, now redacted. [#14319](https://github.com/grafana/grafana/issues/14319) -* **InfluxDB**: Add support for alerting on InfluxDB queries that use the cumulative_sum function. [#14314](https://github.com/grafana/grafana/pull/14314), thx [@nitti](https://github.com/nitti) +* TSDB**: Fix always take dashboard timezone into consideration when handle custom time ranges**: Add support for alerting on InfluxDB queries that use the cumulative_sum function. [#14314](https://github.com/grafana/grafana/pull/14314), thx [@nitti](https://github.com/nitti) * **Plugins**: Panel plugins should no receive the panel-initialized event again as usual. * **Embedded Graphs**: Iframe graph panels should now work as usual. [#14284](https://github.com/grafana/grafana/issues/14284) * **Postgres**: Improve PostgreSQL Query Editor if using different Schemas, [#14313]( @@ -1031,7 +1032,7 @@ Pull Request: [#8472](https://github.com/grafana/grafana/pull/8472) * **Docs**: Added some details about Sessions in Postgres [#7694](https://github.com/grafana/grafana/pull/7694) thx [@rickard-von-essen](https://github.com/rickard-von-essen) * **Influxdb**: Allow commas in template variables [#7681](https://github.com/grafana/grafana/issues/7681) thx [@thuck](https://github.com/thuck) * **Cloudwatch**: stop using deprecated session.New() [#7736](https://github.com/grafana/grafana/issues/7736) thx [@mtanda](https://github.com/mtanda) -* **OpenTSDB**: Pass dropcounter rate option if no max counter and no reset value or reset value as 0 is specified [#7743](https://github.com/grafana/grafana/pull/7743) thx [@r4um](https://github.com/r4um) +*TSDB**: Fix always take dashboard timezone into consideration when handle custom time ranges**: Pass dropcounter rate option if no max counter and no reset value or reset value as 0 is specified [#7743](https://github.com/grafana/grafana/pull/7743) thx [@r4um](https://github.com/r4um) * **Templating**: support full resolution for $interval variable [#7696](https://github.com/grafana/grafana/pull/7696) thx [@mtanda](https://github.com/mtanda) * **Elasticsearch**: Unique Count on string fields in ElasticSearch [#3536](https://github.com/grafana/grafana/issues/3536), thx [@pyro2927](https://github.com/pyro2927) * **Templating**: Data source template variable that refers to other variable in regex filter [#6365](https://github.com/grafana/grafana/issues/6365) thx [@rlodge](https://github.com/rlodge) From e53f41e511a92dd12eb0ee3604fb494a572f6311 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 11 Feb 2019 14:10:01 +0100 Subject: [PATCH 082/119] changelog: adds note for #15131 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9db747d6ed5..afb3fbbaab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) * **Login**: Anonymous usage stats for token auth [#15288](https://github.com/grafana/grafana/issues/15288) * **Alerting**: Fixes crash bug when alert notifier folders are missing [#15295](https://github.com/grafana/grafana/issues/15295) +* **AzureMonitor**: improve autocomplete for Log Analytics and App Insights editor [#15131](https://github.com/grafana/grafana/issues/15131) ### 6.0.0-beta1 fixes From 82e330a1c53756c11a23b14c6b9a4a23d7332ef6 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 14:01:39 +0100 Subject: [PATCH 083/119] update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afb3fbbaab4..23cf3e2167f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # 6.0.0-beta2 (unreleased) ### Minor -* **Pushover**: Adds support for images in pushover notifier [#10780](https://github.com/grafana/grafana/issues/10780), thx [@jpenalbae](https://github.com/jpenalbae) +* **Alerting**: Adds support for images in pushover notifier [#10780](https://github.com/grafana/grafana/issues/10780), thx [@jpenalbae](https://github.com/jpenalbae) * **Graphite/InfluxDB/OpenTSDB**: Fix always take dashboard timezone into consideration when handle custom time ranges [#15284](https://github.com/grafana/grafana/issues/15284) * **Stackdriver**: Template variables in filters using globbing format [#15182](https://github.com/grafana/grafana/issues/15182) * **Cloudwatch**: Add `resource_arns` template variable query function [#8207](https://github.com/grafana/grafana/issues/8207), thx [@jeroenvollenbrock](https://github.com/jeroenvollenbrock) @@ -13,12 +13,12 @@ * **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh) * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) * **Login**: Anonymous usage stats for token auth [#15288](https://github.com/grafana/grafana/issues/15288) -* **Alerting**: Fixes crash bug when alert notifier folders are missing [#15295](https://github.com/grafana/grafana/issues/15295) * **AzureMonitor**: improve autocomplete for Log Analytics and App Insights editor [#15131](https://github.com/grafana/grafana/issues/15131) ### 6.0.0-beta1 fixes * **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189) +* **Alerting**: Fixes crash bug when alert notifier folders are missing [#15295](https://github.com/grafana/grafana/issues/15295) # 6.0.0-beta1 (2019-01-30) From a7c44c2ce749c496b0be7eebfb4b6a91d9c6b224 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 14:03:45 +0100 Subject: [PATCH 084/119] changelog: add notes about closing #14432 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23cf3e2167f..ab721afe144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * **Templating**: Add json formatting to variable interpolation [#15291](https://github.com/grafana/grafana/issues/15291), thx [@mtanda](https://github.com/mtanda) * **Login**: Anonymous usage stats for token auth [#15288](https://github.com/grafana/grafana/issues/15288) * **AzureMonitor**: improve autocomplete for Log Analytics and App Insights editor [#15131](https://github.com/grafana/grafana/issues/15131) +* **LDAP**: Fix IPA/FreeIPA v4.6.4 does not allow LDAP searches with empty attributes [#14432](https://github.com/grafana/grafana/issues/14432) ### 6.0.0-beta1 fixes From 9472d7e60083b6fdd903696beb657ea34fb2b497 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 14:06:39 +0100 Subject: [PATCH 085/119] changelog: add notes about closing #15219 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab721afe144..a1919fb9e3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189) * **Alerting**: Fixes crash bug when alert notifier folders are missing [#15295](https://github.com/grafana/grafana/issues/15295) +* **Dashboard**: Fix save provisioned dashboard modal [#15219](https://github.com/grafana/grafana/pull/15219) # 6.0.0-beta1 (2019-01-30) From 63f465f0ac261ceb2d10a65cc080e67503b0e9b0 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 14:10:30 +0100 Subject: [PATCH 086/119] changelog: add notes about closing #15122 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1919fb9e3b..b8c316c403f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ * **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189) * **Alerting**: Fixes crash bug when alert notifier folders are missing [#15295](https://github.com/grafana/grafana/issues/15295) * **Dashboard**: Fix save provisioned dashboard modal [#15219](https://github.com/grafana/grafana/pull/15219) +* **Dashboard**: Fix having a long query in prometheus dashboard query editor blocks 30% of the query field when on OSX and having native scrollbars [#15122](https://github.com/grafana/grafana/issues/15122) # 6.0.0-beta1 (2019-01-30) From 1f0c7727f462bb53e3815e2240651e1b466c4386 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 14:12:25 +0100 Subject: [PATCH 087/119] changelog: add notes about closing #15222 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c316c403f..5bb307a8a36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ * **Alerting**: Fixes crash bug when alert notifier folders are missing [#15295](https://github.com/grafana/grafana/issues/15295) * **Dashboard**: Fix save provisioned dashboard modal [#15219](https://github.com/grafana/grafana/pull/15219) * **Dashboard**: Fix having a long query in prometheus dashboard query editor blocks 30% of the query field when on OSX and having native scrollbars [#15122](https://github.com/grafana/grafana/issues/15122) +* **Explore**: Fix issue with wrapping on long queries [#15222](https://github.com/grafana/grafana/issues/15222) # 6.0.0-beta1 (2019-01-30) From 757a98257d25296c75a29c5f589f517ac22d800e Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 14:13:52 +0100 Subject: [PATCH 088/119] changelog: add notes about closing #15223 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bb307a8a36..d41f3cf271c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ * **Dashboard**: Fix save provisioned dashboard modal [#15219](https://github.com/grafana/grafana/pull/15219) * **Dashboard**: Fix having a long query in prometheus dashboard query editor blocks 30% of the query field when on OSX and having native scrollbars [#15122](https://github.com/grafana/grafana/issues/15122) * **Explore**: Fix issue with wrapping on long queries [#15222](https://github.com/grafana/grafana/issues/15222) +* **Explore**: Fix cut & paste adds newline before and after selection [#15223](https://github.com/grafana/grafana/issues/15223) # 6.0.0-beta1 (2019-01-30) From 8769b7aa5757881f6960cadc61856bfab071a544 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 11 Feb 2019 14:16:05 +0100 Subject: [PATCH 089/119] changelog: add notes about closing #15258 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d41f3cf271c..6221b7bcc93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ * **Dashboard**: Fix having a long query in prometheus dashboard query editor blocks 30% of the query field when on OSX and having native scrollbars [#15122](https://github.com/grafana/grafana/issues/15122) * **Explore**: Fix issue with wrapping on long queries [#15222](https://github.com/grafana/grafana/issues/15222) * **Explore**: Fix cut & paste adds newline before and after selection [#15223](https://github.com/grafana/grafana/issues/15223) +* **Dataproxy**: Fix global datasource proxy timeout not added to correct http client [#15258](https://github.com/grafana/grafana/issues/15258) [#5699](https://github.com/grafana/grafana/issues/5699) # 6.0.0-beta1 (2019-01-30) From 56b35354c708645d32db184ff44af5870c26dee3 Mon Sep 17 00:00:00 2001 From: ijin08 Date: Mon, 11 Feb 2019 14:21:43 +0100 Subject: [PATCH 090/119] changed back to old green in light theme --- public/sass/_variables.light.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 782354bba19..bf5115cd996 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -13,6 +13,8 @@ $lobster-base: #E02F44; $lobster-shade: #C4162A; $green-base: #37872D; $green-shade: #19730E; +$green-base: #3EB15B; +$green-shade: #369B4F; $purple-shade: #8F3BB8; $yellow-base: #F2CC0C; From ac345312a46f86f520af0b5399b08df29685b1d9 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 11 Feb 2019 14:42:12 +0100 Subject: [PATCH 091/119] azuremonitor: don't use make for maps and array --- pkg/tsdb/azuremonitor/azuremonitor-datasource.go | 6 +++--- pkg/tsdb/azuremonitor/azuremonitor.go | 2 +- pkg/tsdb/azuremonitor/time-grain.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go index 5def94aebf6..cae8d8bfb73 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/azuremonitor-datasource.go @@ -41,7 +41,7 @@ var ( // 3. parses the responses for each query into the timeseries format func (e *AzureMonitorDatasource) executeTimeSeriesQuery(ctx context.Context, originalQueries []*tsdb.Query, timeRange *tsdb.TimeRange) (*tsdb.Response, error) { result := &tsdb.Response{ - Results: make(map[string]*tsdb.QueryResult), + Results: map[string]*tsdb.QueryResult{}, } queries, err := e.buildQueries(originalQueries, timeRange) @@ -84,7 +84,7 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange * azureMonitorTarget := query.Model.Get("azureMonitor").MustMap() azlog.Debug("AzureMonitor", "target", azureMonitorTarget) - urlComponents := make(map[string]string) + urlComponents := map[string]string{} urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"]) urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"]) urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"]) @@ -247,7 +247,7 @@ func (e *AzureMonitorDatasource) parseResponse(queryRes *tsdb.QueryResult, data } for _, series := range data.Value[0].Timeseries { - points := make([]tsdb.TimePoint, 0) + points := []tsdb.TimePoint{} metadataName := "" metadataValue := "" diff --git a/pkg/tsdb/azuremonitor/azuremonitor.go b/pkg/tsdb/azuremonitor/azuremonitor.go index 32d4a6f0f29..31a42d21a12 100644 --- a/pkg/tsdb/azuremonitor/azuremonitor.go +++ b/pkg/tsdb/azuremonitor/azuremonitor.go @@ -46,7 +46,7 @@ func (e *AzureMonitorExecutor) Query(ctx context.Context, dsInfo *models.DataSou var result *tsdb.Response var err error - azureMonitorQueries := make([]*tsdb.Query, 0) + var azureMonitorQueries []*tsdb.Query for _, query := range tsdbQuery.Queries { queryType := query.Model.Get("queryType").MustString("") diff --git a/pkg/tsdb/azuremonitor/time-grain.go b/pkg/tsdb/azuremonitor/time-grain.go index e6a15aef64f..425e39b6208 100644 --- a/pkg/tsdb/azuremonitor/time-grain.go +++ b/pkg/tsdb/azuremonitor/time-grain.go @@ -34,7 +34,7 @@ func (tg *TimeGrain) createISO8601DurationFromIntervalMS(interval int64) (string unit := formatted[len(formatted)-1:] if unit == "s" && timeValue < 60 { - // mimumum interval is 1m for Azure Monitor + // minimum interval is 1m for Azure Monitor return "PT1M", nil } From 962815169ec2ca191dac709de0c4c90a24ca0024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 14:58:11 +0100 Subject: [PATCH 092/119] Color tweaks --- .../PanelOptionsGroup/_PanelOptionsGroup.scss | 2 +- .../ThresholdsEditor/_ThresholdsEditor.scss | 2 +- .../components/TimePicker/template.html | 6 ++--- public/sass/_variables.dark.scss | 24 ++++++++----------- public/sass/_variables.light.scss | 20 +++++++--------- public/sass/base/_type.scss | 8 ------- public/sass/components/_buttons.scss | 2 ++ 7 files changed, 25 insertions(+), 39 deletions(-) diff --git a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss index 882d96b3d97..f8a3a408bab 100644 --- a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss +++ b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss @@ -37,7 +37,7 @@ .panel-options-group__add-circle { - @include gradientBar($btn-primary-bg, $btn-primary-bg-hl, #fff); + @include gradientBar($btn-secondary-bg, $btn-secondary-bg-hl, #fff); border-radius: 50px; width: 20px; diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss b/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss index 76f390defaf..e2cbfc372a9 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss +++ b/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss @@ -21,7 +21,7 @@ } .thresholds-row-add-button { - @include buttonBackground($btn-primary-bg, $btn-primary-bg-hl, #fff); + @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl, #fff); align-self: center; margin-right: 5px; diff --git a/public/app/features/dashboard/components/TimePicker/template.html b/public/app/features/dashboard/components/TimePicker/template.html index 168d2036a7f..481082a2cf6 100644 --- a/public/app/features/dashboard/components/TimePicker/template.html +++ b/public/app/features/dashboard/components/TimePicker/template.html @@ -48,7 +48,7 @@
-
@@ -65,7 +65,7 @@
-
@@ -81,7 +81,7 @@
- +
diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index de53a3d6058..1ed3ecf2cf7 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -6,7 +6,7 @@ $theme-name: dark; // New Colors // ------------------------- $sapphire-faint: #041126; -$sapphire-bright: #5794F2; +$sapphire-light: #5794F2; $sapphire-base: #3274D9; $sapphire-shade: #1F60C4; $lobster-base: #E02F44; @@ -96,7 +96,7 @@ $edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 50%, #090909); $link-color: darken($white, 11%); $link-color-disabled: darken($link-color, 30%); $link-hover-color: $white; -$external-link-color: $sapphire-bright; +$external-link-color: $sapphire-light; // Typography // ------------------------- @@ -159,14 +159,11 @@ $table-bg-hover: $dark-3; // Buttons // ------------------------- -$btn-primary-bg: $green-base; -$btn-primary-bg-hl: $green-shade; +$btn-primary-bg: $sapphire-base; +$btn-primary-bg-hl: $sapphire-shade; -$btn-secondary-bg-hl: $sapphire-base; -$btn-secondary-bg: $sapphire-shade; - -$btn-secondary-bg: $sapphire-base; -$btn-secondary-bg-hl: $sapphire-shade; +$btn-secondary-bg: $green-base; +$btn-secondary-bg-hl: $green-shade; $btn-danger-bg: $lobster-base; $btn-danger-bg-hl: $lobster-shade; @@ -270,7 +267,6 @@ $toolbar-bg: $input-black; $warning-text-color: $warn; $error-text-color: #e84d4d; $success-text-color: $forest-light; -//$info-text-color: $blue-dark; $alert-error-bg: linear-gradient(90deg, $lobster-base, $lobster-shade); $alert-success-bg: linear-gradient(90deg, $green-base, $green-shade); @@ -347,7 +343,7 @@ $diff-json-changed-num: $text-color; $diff-json-icon: $gray-7; //Submenu -$variable-option-bg: $sapphire-shade; +$variable-option-bg: $dropdownLinkBackgroundHover; //Switch Slider // ------------------------- @@ -370,12 +366,12 @@ $panel-editor-shadow: 0 0 20px black; $panel-editor-side-menu-shadow: drop-shadow(0 0 10px $black); $panel-editor-viz-item-shadow: 0 0 8px $dark-5; $panel-editor-viz-item-border: 1px solid $dark-5; -$panel-editor-viz-item-shadow-hover: 0 0 4px $sapphire-shade; -$panel-editor-viz-item-border-hover: 1px solid $sapphire-shade; +$panel-editor-viz-item-shadow-hover: 0 0 4px $sapphire-light; +$panel-editor-viz-item-border-hover: 1px solid $sapphire-light; $panel-editor-viz-item-bg: $input-black; $panel-editor-tabs-line-color: #e3e3e3; -$panel-editor-viz-item-bg-hover: darken($blue, 47%); +$panel-editor-viz-item-bg-hover: darken($sapphire-base, 46%); $panel-options-group-border: none; $panel-options-group-header-bg: $gray-blue; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index bf5115cd996..076bb27bc74 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -155,11 +155,11 @@ $table-bg-hover: $gray-5; // Buttons // ------------------------- -$btn-primary-bg: $green-base; -$btn-primary-bg-hl: $green-shade; +$btn-secondary-bg: $green-base; +$btn-secondary-bg-hl: $green-shade; -$btn-secondary-bg: $sapphire-base; -$btn-secondary-bg-hl: $sapphire-shade; +$btn-primary-bg: $sapphire-base; +$btn-primary-bg-hl: $sapphire-shade; $btn-danger-bg: $lobster-base; $btn-danger-bg-hl: $lobster-shade; @@ -263,7 +263,6 @@ $toolbar-bg: white; $warning-text-color: lighten($orange, 10%); $error-text-color: $lobster-shade; $success-text-color: lighten($green, 10%); -$info-text-color: $sapphire-shade; $alert-error-bg: linear-gradient(90deg, $lobster-base, $lobster-shade); $alert-success-bg: linear-gradient(90deg, $green-base, $green-shade); @@ -340,7 +339,7 @@ $diff-json-changed-num: $gray-4; $diff-json-icon: $gray-4; //Submenu -$variable-option-bg: $sapphire-light; +$variable-option-bg: $dropdownLinkBackgroundHover; //Switch Slider // ------------------------- @@ -363,14 +362,11 @@ $panel-editor-shadow: 0px 0px 8px $gray-3; $panel-editor-side-menu-shadow: drop-shadow(0 0 2px $gray-3); $panel-editor-viz-item-shadow: 0 0 4px $gray-3; $panel-editor-viz-item-border: 1px solid $gray-3; -$panel-editor-viz-item-shadow-hover: 0 0 4px $sapphire-light; -$panel-editor-viz-item-border-hover: 1px solid $sapphire-light; +$panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light; +$panel-editor-viz-item-border-hover: 1px solid $blue-light; $panel-editor-viz-item-bg: $white; $panel-editor-tabs-line-color: $dark-5; -$panel-editor-viz-item-bg-hover: lighten($blue, 62%); - - -$panel-options-group-border: none; +$panel-editor-viz-item-bg-hover: lighten($blue, 62%);$panel-options-group-border: none; $panel-options-group-header-bg: $gray-5; $panel-grid-placeholder-bg: $sapphire-faint; diff --git a/public/sass/base/_type.scss b/public/sass/base/_type.scss index e5a20a80659..9919a370a87 100644 --- a/public/sass/base/_type.scss +++ b/public/sass/base/_type.scss @@ -59,14 +59,6 @@ a.text-error:focus { color: darken($error-text-color, 10%); } -/*.text-info { - color: $info-text-color; -} -a.text-info:hover, -a.text-info:focus { - color: darken($info-text-color, 10%); -}*/ - .text-success { color: $success-text-color; } diff --git a/public/sass/components/_buttons.scss b/public/sass/components/_buttons.scss index 84e2665f582..0c1ac726690 100644 --- a/public/sass/components/_buttons.scss +++ b/public/sass/components/_buttons.scss @@ -83,9 +83,11 @@ // Set the backgrounds // ------------------------- +.btn-success, .btn-primary { @include buttonBackground($btn-primary-bg, $btn-primary-bg-hl); } + .btn-secondary { @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl); } From 4408817e65cb1d6447928ff935df8f3ab19a1f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 15:13:20 +0100 Subject: [PATCH 093/119] Fixed double page class on api keys and org details page --- public/app/features/api-keys/ApiKeysPage.tsx | 8 +- .../__snapshots__/ApiKeysPage.test.tsx.snap | 196 +++++++++--------- public/app/features/org/OrgDetailsPage.tsx | 20 +- .../OrgDetailsPage.test.tsx.snap | 28 +-- 4 files changed, 119 insertions(+), 133 deletions(-) diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index 41b9b0c8a55..f0d6fa8d267 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -107,7 +107,7 @@ export class ApiKeysPage extends PureComponent { renderEmptyList() { const { isAdding } = this.state; return ( -
+ <> {!isAdding && ( { /> )} {this.renderAddApiKeyForm()} -
+ ); } @@ -183,7 +183,7 @@ export class ApiKeysPage extends PureComponent { const { apiKeys, searchQuery } = this.props; return ( -
+ <>
-
+ ); } diff --git a/public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap b/public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap index f40894426ae..9a9daab76c3 100644 --- a/public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap +++ b/public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap @@ -35,118 +35,114 @@ exports[`Render should render CTA if there are no API keys 1`] = ` -
- - + +
-
- -
- Add API Key -
-
+ +
+ Add API Key +
+ +
-
- - Key name - - + +
+
+ + Role + + + - - - - - -
-
- -
+ Viewer + + + + +
- -
- -
+
+ +
+
+ +
+
`; diff --git a/public/app/features/org/OrgDetailsPage.tsx b/public/app/features/org/OrgDetailsPage.tsx index ee644f0006f..236558db40a 100644 --- a/public/app/features/org/OrgDetailsPage.tsx +++ b/public/app/features/org/OrgDetailsPage.tsx @@ -36,18 +36,16 @@ export class OrgDetailsPage extends PureComponent { return ( - @@ -407,6 +410,7 @@ exports[`Render should render is ready only message 1`] = ` isReadOnly={true} onDelete={[Function]} onSubmit={[Function]} + onTest={[Function]} />
- {!isLoading && ( -
- this.onOrgNameChange(name)} - onSubmit={this.onUpdateOrganization} - orgName={organization.name} - /> - -
- )} + {!isLoading && ( +
+ this.onOrgNameChange(name)} + onSubmit={this.onUpdateOrganization} + orgName={organization.name} + /> +
+ )} ); diff --git a/public/app/features/org/__snapshots__/OrgDetailsPage.test.tsx.snap b/public/app/features/org/__snapshots__/OrgDetailsPage.test.tsx.snap index 9e13a73901e..2339975ca8b 100644 --- a/public/app/features/org/__snapshots__/OrgDetailsPage.test.tsx.snap +++ b/public/app/features/org/__snapshots__/OrgDetailsPage.test.tsx.snap @@ -15,11 +15,7 @@ exports[`Render should render component 1`] = ` > -
- + /> `; @@ -39,19 +35,15 @@ exports[`Render should render organization and preferences 1`] = ` -
-
- - -
+
+ +
From 93f1a48641b9e9219fa5f8869757fcdb4d1187ae Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 11 Feb 2019 15:21:02 +0100 Subject: [PATCH 094/119] changelog: adds note for #14623 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6221b7bcc93..a82fc7050b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # 6.0.0-beta2 (unreleased) +### New Features +* **AzureMonitor**: Enable alerting by converting Azure Monitor API to Go [#14623](https://github.com/grafana/grafana/issues/14623) + ### Minor * **Alerting**: Adds support for images in pushover notifier [#10780](https://github.com/grafana/grafana/issues/10780), thx [@jpenalbae](https://github.com/jpenalbae) * **Graphite/InfluxDB/OpenTSDB**: Fix always take dashboard timezone into consideration when handle custom time ranges [#15284](https://github.com/grafana/grafana/issues/15284) From c4fa64e6dc082bdb813edb13f34652f4163b9cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 15:23:17 +0100 Subject: [PATCH 095/119] Updated lint-staged --- package.json | 2 +- yarn.lock | 286 ++++++++++++++++++++++----------------------------- 2 files changed, 122 insertions(+), 166 deletions(-) diff --git a/package.json b/package.json index fae51a1d856..2f44291a86a 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "husky": "^0.14.3", "jest": "^23.6.0", "jest-date-mock": "^1.0.6", - "lint-staged": "^6.0.0", + "lint-staged": "^8.1.3", "load-grunt-tasks": "3.5.2", "mini-css-extract-plugin": "^0.4.0", "mocha": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index df2e1cea37e..2fb4a5d3ee2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1040,6 +1040,20 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.1.3.tgz#b700d97385fa91affed60c71dfd51c67e9dad762" integrity sha512-QsYGKdhhuDFNq7bjm2r44y0mp5xW3uO3csuTPDWZc0OIiMQv+AIY5Cqwd4mJiC5N8estVl7qlvOx1hbtOuUWbw== +"@iamstarkov/listr-update-renderer@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@iamstarkov/listr-update-renderer/-/listr-update-renderer-0.4.1.tgz#d7c48092a2dcf90fd672b6c8b458649cb350c77e" + integrity sha512-IJyxQWsYDEkf8C8QthBn5N8tIUR9V9je6j3sMIpAkonaadjbvxmRC6RAhpa3RKxndhNnU2M6iNbtJwd7usQYIA== + dependencies: + chalk "^1.1.3" + cli-truncate "^0.2.1" + elegant-spinner "^1.0.1" + figures "^1.7.0" + indent-string "^3.0.0" + log-symbols "^1.0.2" + log-update "^2.3.0" + strip-ansi "^3.0.1" + "@icons/material@^0.2.4": version "0.2.4" resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8" @@ -2468,7 +2482,7 @@ ansi-colors@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== -ansi-escapes@^1.0.0, ansi-escapes@^1.1.0: +ansi-escapes@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= @@ -2525,11 +2539,6 @@ ansistyles@~0.1.3: resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539" integrity sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk= -any-observable@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.2.0.tgz#c67870058003579009083f54ac0abafb5c33d242" - integrity sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI= - any-observable@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" @@ -2548,11 +2557,6 @@ app-root-dir@^1.0.2: resolved "https://registry.yarnpkg.com/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118" integrity sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg= -app-root-path@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.1.0.tgz#98bf6599327ecea199309866e8140368fd2e646a" - integrity sha1-mL9lmTJ+zqGZMJhm6BQDaP0uZGo= - append-transform@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" @@ -4588,7 +4592,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3, chalk@~1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -4783,7 +4787,7 @@ cli-columns@^3.1.2: string-width "^2.0.0" strip-ansi "^3.0.1" -cli-cursor@^1.0.1, cli-cursor@^1.0.2: +cli-cursor@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= @@ -4797,11 +4801,6 @@ cli-cursor@^2.0.0, cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" -cli-spinners@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" - integrity sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw= - cli-table2@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/cli-table2/-/cli-table2-0.2.0.tgz#2d1ef7f218a0e786e214540562d4bd177fe32d97" @@ -5075,7 +5074,7 @@ comma-separated-tokens@^1.0.0: dependencies: trim "0.0.1" -commander@2, commander@^2.11.0, commander@^2.12.1, commander@^2.13.0, commander@^2.19.0, commander@^2.8.1, commander@^2.9.0: +commander@2, commander@^2.12.1, commander@^2.13.0, commander@^2.14.1, commander@^2.19.0, commander@^2.8.1, commander@^2.9.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== @@ -5312,7 +5311,7 @@ cosmiconfig@^4.0.0: parse-json "^4.0.0" require-from-string "^2.0.1" -cosmiconfig@^5.0.5, cosmiconfig@^5.0.7: +cosmiconfig@^5.0.2, cosmiconfig@^5.0.5, cosmiconfig@^5.0.7: version "5.0.7" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.7.tgz#39826b292ee0d78eda137dfa3173bd1c21a43b04" integrity sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA== @@ -6085,7 +6084,7 @@ debug@^3.1.0, debug@^3.2.5: dependencies: ms "^2.1.1" -debug@^4.1.0: +debug@^4.0.1, debug@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -6915,7 +6914,7 @@ escape-html@^1.0.3, escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.4, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -7129,19 +7128,6 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da" - integrity sha1-2NdrvBtVIX7RkP1t1J08d07PyNo= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" @@ -7631,6 +7617,11 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" +fn-name@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" + integrity sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc= + follow-redirects@^1.0.0, follow-redirects@^1.2.5: version "1.6.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.1.tgz#514973c44b5757368bad8bddfe52f81f015c94cb" @@ -7848,6 +7839,15 @@ fuse.js@^3.0.1, fuse.js@^3.3.0: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.3.0.tgz#1e4fe172a60687230fb54a5cb247eb96e2e7e885" integrity sha512-ESBRkGLWMuVkapqYCcNO1uqMg5qbCKkgb+VS6wsy17Rix0/cMS9kSOZoYkjH8Ko//pgJ/EEGu0GTjk2mjX2LGQ== +g-status@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/g-status/-/g-status-2.0.2.tgz#270fd32119e8fc9496f066fe5fe88e0a6bc78b97" + integrity sha512-kQoE9qH+T1AHKgSSD0Hkv98bobE90ILQcXAF4wvGgsr7uFqNvwmh8j+Lq3l0RVt3E3HjSbv2B9biEGcEtpHLCA== + dependencies: + arrify "^1.0.1" + matcher "^1.0.0" + simple-git "^1.85.0" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -9497,13 +9497,6 @@ is-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= -is-observable@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-0.2.0.tgz#b361311d83c6e5d726cabf5e250b0237106f5ae2" - integrity sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI= - dependencies: - symbol-observable "^0.2.2" - is-observable@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e" @@ -9917,11 +9910,6 @@ jest-environment-node@^23.4.0: jest-mock "^23.2.0" jest-util "^23.4.0" -jest-get-type@^21.2.0: - version "21.2.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.2.0.tgz#f6376ab9db4b60d81e39f30749c6c466f40d4a23" - integrity sha512-y2fFw3C+D0yjNSDp7ab1kcd6NUYfy3waPTlD8yWkAtiocJdBRQqNoRqVfMNxgj+IjT0V5cBIHJO0z9vuSSZ43Q== - jest-get-type@^22.1.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4" @@ -10094,16 +10082,6 @@ jest-util@^23.4.0: slash "^1.0.0" source-map "^0.6.0" -jest-validate@^21.1.0: - version "21.2.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-21.2.1.tgz#cc0cbca653cd54937ba4f2a111796774530dd3c7" - integrity sha512-k4HLI1rZQjlU+EC682RlQ6oZvLrE5SCh3brseQc24vbZTxzT/k/3urar5QMCVgjadmSO7lECeGdc6YxnM3yEGg== - dependencies: - chalk "^2.0.1" - jest-get-type "^21.2.0" - leven "^2.1.0" - pretty-format "^21.2.1" - jest-validate@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.6.0.tgz#36761f99d1ed33fcd425b4e4c5595d62b6597474" @@ -10566,51 +10544,42 @@ libnpx@^10.2.0: y18n "^4.0.0" yargs "^11.0.0" -lint-staged@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-6.1.1.tgz#cd08c4d9b8ccc2d37198d1c47ce77d22be6cf324" - integrity sha512-M/7bwLdXbeG7ZNLcasGeLMBDg60/w6obj3KOtINwJyxAxb53XGY0yH5FSZlWklEzuVbTtqtIfAajh6jYIN90AA== +lint-staged@^8.1.3: + version "8.1.3" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-8.1.3.tgz#bb069db5466c0fe16710216e633a84f2b362fa60" + integrity sha512-6TGkikL1B+6mIOuSNq2TV6oP21IhPMnV8q0cf9oYZ296ArTVNcbFh1l1pfVOHHbBIYLlziWNsQ2q45/ffmJ4AA== dependencies: - app-root-path "^2.0.0" - chalk "^2.1.0" - commander "^2.11.0" - cosmiconfig "^4.0.0" + "@iamstarkov/listr-update-renderer" "0.4.1" + chalk "^2.3.1" + commander "^2.14.1" + cosmiconfig "^5.0.2" debug "^3.1.0" dedent "^0.7.0" - execa "^0.8.0" + del "^3.0.0" + execa "^1.0.0" find-parent-dir "^0.3.0" + g-status "^2.0.2" is-glob "^4.0.0" - jest-validate "^21.1.0" - listr "^0.13.0" - lodash "^4.17.4" - log-symbols "^2.0.0" - minimatch "^3.0.0" + is-windows "^1.0.2" + listr "^0.14.2" + lodash "^4.17.5" + log-symbols "^2.2.0" + micromatch "^3.1.8" npm-which "^3.0.1" p-map "^1.1.1" path-is-inside "^1.0.2" pify "^3.0.0" - staged-git-files "1.0.0" - stringify-object "^3.2.0" + please-upgrade-node "^3.0.2" + staged-git-files "1.1.2" + string-argv "^0.0.2" + stringify-object "^3.2.2" + yup "^0.26.10" listr-silent-renderer@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4= -listr-update-renderer@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.4.0.tgz#344d980da2ca2e8b145ba305908f32ae3f4cc8a7" - integrity sha1-NE2YDaLKLosUW6MFkI8yrj9MyKc= - dependencies: - chalk "^1.1.3" - cli-truncate "^0.2.1" - elegant-spinner "^1.0.1" - figures "^1.7.0" - indent-string "^3.0.0" - log-symbols "^1.0.2" - log-update "^1.0.2" - strip-ansi "^3.0.1" - listr-update-renderer@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz#4ea8368548a7b8aecb7e06d8c95cb45ae2ede6a2" @@ -10625,16 +10594,6 @@ listr-update-renderer@^0.5.0: log-update "^2.3.0" strip-ansi "^3.0.1" -listr-verbose-renderer@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35" - integrity sha1-ggb0z21S3cWCfl/RSYng6WWTOjU= - dependencies: - chalk "^1.1.3" - cli-cursor "^1.0.2" - date-fns "^1.27.2" - figures "^1.7.0" - listr-verbose-renderer@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz#f1132167535ea4c1261102b9f28dac7cba1e03db" @@ -10645,30 +10604,7 @@ listr-verbose-renderer@^0.5.0: date-fns "^1.27.2" figures "^2.0.0" -listr@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/listr/-/listr-0.13.0.tgz#20bb0ba30bae660ee84cc0503df4be3d5623887d" - integrity sha1-ILsLowuuZg7oTMBQPfS+PVYjiH0= - dependencies: - chalk "^1.1.3" - cli-truncate "^0.2.1" - figures "^1.7.0" - indent-string "^2.1.0" - is-observable "^0.2.0" - is-promise "^2.1.0" - is-stream "^1.1.0" - listr-silent-renderer "^1.1.1" - listr-update-renderer "^0.4.0" - listr-verbose-renderer "^0.4.0" - log-symbols "^1.0.2" - log-update "^1.0.2" - ora "^0.2.3" - p-map "^1.1.1" - rxjs "^5.4.2" - stream-to-observable "^0.2.0" - strip-ansi "^3.0.1" - -listr@^0.14.1: +listr@^0.14.1, listr@^0.14.2: version "0.14.3" resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA== @@ -10949,14 +10885,6 @@ log-symbols@^2.0.0, log-symbols@^2.1.0, log-symbols@^2.2.0: dependencies: chalk "^2.0.1" -log-update@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1" - integrity sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE= - dependencies: - ansi-escapes "^1.0.0" - cli-cursor "^1.0.2" - log-update@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" @@ -11154,6 +11082,13 @@ marksy@^6.1.0: he "^1.1.1" marked "^0.3.12" +matcher@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-1.1.1.tgz#51d8301e138f840982b338b116bb0c09af62c1c2" + integrity sha512-+BmqxWIubKTRKNWx/ahnCkk3mG8m7OturVlqq6HiojGJTd5hVYbgZm6WzcYPCoB+KBT4Vd6R7WSRG2OADNaCjg== + dependencies: + escape-string-regexp "^1.0.4" + material-colors@^1.2.1: version "1.2.6" resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" @@ -12512,16 +12447,6 @@ optionator@^0.8.1: type-check "~0.3.2" wordwrap "~1.0.0" -ora@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" - integrity sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q= - dependencies: - chalk "^1.1.1" - cli-cursor "^1.0.2" - cli-spinners "^0.1.2" - object-assign "^4.0.1" - ordered-ast-traverse@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ordered-ast-traverse/-/ordered-ast-traverse-1.1.1.tgz#6843a170bc0eee8b520cc8ddc1ddd3aa30fa057c" @@ -13023,6 +12948,13 @@ pkg-up@^1.0.0: dependencies: find-up "^1.0.0" +please-upgrade-node@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz#ed320051dfcc5024fae696712c8288993595e8ac" + integrity sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ== + dependencies: + semver-compare "^1.0.0" + pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" @@ -13560,14 +13492,6 @@ pretty-error@^2.0.2, pretty-error@^2.1.1: renderkid "^2.0.1" utila "~0.4" -pretty-format@^21.2.1: - version "21.2.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-21.2.1.tgz#ae5407f3cf21066cd011aa1ba5fce7b6a2eddb36" - integrity sha512-ZdWPGYAnYfcVP8yKA3zFjCn8s4/17TeYH28MXuC8vTp0o21eXjbFGcOAXZEaDaOFJjc3h2qa7HQNHNshhvoh2A== - dependencies: - ansi-regex "^3.0.0" - ansi-styles "^3.2.0" - pretty-format@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760" @@ -13670,6 +13594,11 @@ prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, pr loose-envify "^1.3.1" object-assign "^4.1.1" +property-expr@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f" + integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g== + property-information@^5.0.0, property-information@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.0.1.tgz#c3b09f4f5750b1634c0b24205adbf78f18bdf94f" @@ -15078,7 +15007,7 @@ rx-lite@^3.1.2: resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= -rxjs@^5.4.2, rxjs@^5.5.2: +rxjs@^5.5.2: version "5.5.12" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw== @@ -15247,6 +15176,11 @@ selfsigned@^1.9.1: dependencies: node-forge "0.7.5" +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -15482,6 +15416,13 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" +simple-git@^1.85.0: + version "1.107.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.107.0.tgz#12cffaf261c14d6f450f7fdb86c21ccee968b383" + integrity sha512-t4OK1JRlp4ayKRfcW6owrWcRVLyHRUlhGd0uN6ZZTqfDq8a5XpcUdOKiGRNobHEuMtNqzp0vcJNvhYWwh5PsQA== + dependencies: + debug "^4.0.1" + simple-is@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/simple-is/-/simple-is-0.2.0.tgz#2abb75aade39deb5cc815ce10e6191164850baf0" @@ -15929,10 +15870,10 @@ stack-utils@^1.0.1: resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== -staged-git-files@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.0.0.tgz#cdb847837c1fcc52c08a872d4883cc0877668a80" - integrity sha1-zbhHg3wfzFLAioctSIPMCHdmioA= +staged-git-files@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.2.tgz#4326d33886dc9ecfa29a6193bf511ba90a46454b" + integrity sha512-0Eyrk6uXW6tg9PYkhi/V/J4zHp33aNyi2hOCmhFLqLTIhbgqWn5jlSzI+IU0VqrZq6+DbHcabQl/WP6P3BG0QA== static-extend@^0.1.1: version "0.1.2" @@ -16009,13 +15950,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= -stream-to-observable@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.2.0.tgz#59d6ea393d87c2c0ddac10aa0d561bc6ba6f0e10" - integrity sha1-WdbqOT2HwsDdrBCqDVYbxrpvDhA= - dependencies: - any-observable "^0.2.0" - strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -16026,6 +15960,11 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= +string-argv@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736" + integrity sha1-2sMECGkMIfPDYwo/86BYd73L1zY= + string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -16131,7 +16070,7 @@ stringifier@^1.3.0: traverse "^0.6.6" type-name "^2.0.1" -stringify-object@^3.2.0: +stringify-object@^3.2.2: version "3.3.0" resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== @@ -16336,11 +16275,6 @@ symbol-observable@1.0.1: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ= -symbol-observable@^0.2.2: - version "0.2.4" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" - integrity sha1-lag9smGG1q9+ehjb2XYKL4bQj0A= - symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -16358,6 +16292,11 @@ symbol.prototype.description@^1.0.0: dependencies: has-symbols "^1.0.0" +synchronous-promise@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.6.tgz#de76e0ea2b3558c1e673942e47e714a930fa64aa" + integrity sha512-TyOuWLwkmtPL49LHCX1caIwHjRzcVd62+GF6h8W/jHOeZUFHpnd2XJDVuUlaTaLPH1nuu2M69mfHr5XbQJnf/g== + systemjs-plugin-css@^0.1.36: version "0.1.37" resolved "https://registry.yarnpkg.com/systemjs-plugin-css/-/systemjs-plugin-css-0.1.37.tgz#684847252ca69b7da24a1201094c86274324e82f" @@ -16649,6 +16588,11 @@ toposort@^1.0.0: resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= + touch@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/touch/-/touch-2.0.2.tgz#ca0b2a3ae3211246a61b16ba9e6cbf1596287164" @@ -18075,6 +18019,18 @@ yeoman-generator@^2.0.5: through2 "^2.0.0" yeoman-environment "^2.0.5" +yup@^0.26.10: + version "0.26.10" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.26.10.tgz#3545839663289038faf25facfc07e11fd67c0cb1" + integrity sha512-keuNEbNSnsOTOuGCt3UJW69jDE3O4P+UHAakO7vSeFMnjaitcmlbij/a3oNb9g1Y1KvSKH/7O1R2PQ4m4TRylw== + dependencies: + "@babel/runtime" "7.0.0" + fn-name "~2.0.1" + lodash "^4.17.10" + property-expr "^1.5.0" + synchronous-promise "^2.0.5" + toposort "^2.0.2" + zip-stream@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-1.2.0.tgz#a8bc45f4c1b49699c6b90198baacaacdbcd4ba04" From e4e42fcd08c156d556b4896b323580e997edc2f7 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 11 Feb 2019 15:42:12 +0100 Subject: [PATCH 096/119] adds edition to build_info metric --- pkg/metrics/metrics.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 718a63ee768..bab2fb45127 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -3,6 +3,8 @@ package metrics import ( "runtime" + "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/client_golang/prometheus" ) @@ -282,7 +284,7 @@ func init() { Name: "build_info", Help: "A metric with a constant '1' value labeled by version, revision, branch, and goversion from which Grafana was built.", Namespace: exporterName, - }, []string{"version", "revision", "branch", "goversion"}) + }, []string{"version", "revision", "branch", "goversion", "edition"}) } // SetBuildInformation sets the build information for this binary @@ -291,8 +293,13 @@ func SetBuildInformation(version, revision, branch string) { // Once this have been released for some time we should be able to remote `M_Grafana_Version` // The reason we added a new one is that its common practice in the prometheus community // to name this metric `*_build_info` so its easy to do aggregation on all programs. + edition := "oss" + if setting.IsEnterprise { + edition = "enterprise" + } + M_Grafana_Version.WithLabelValues(version).Set(1) - grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version()).Set(1) + grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version(), edition).Set(1) } func initMetricVars() { From c332e106a24f800d673a04eddaa4e3bc55a4942e Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 11 Feb 2019 16:00:08 +0100 Subject: [PATCH 097/119] Removing default thresholds values. --- public/vendor/flot/jquery.flot.gauge.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/public/vendor/flot/jquery.flot.gauge.js b/public/vendor/flot/jquery.flot.gauge.js index d256a5db7ed..b6468d5824f 100644 --- a/public/vendor/flot/jquery.flot.gauge.js +++ b/public/vendor/flot/jquery.flot.gauge.js @@ -935,16 +935,7 @@ } }, values: [ - { - value: 50, - color: "lightgreen" - }, { - value: 80, - color: "yellow" - }, { - value: 100, - color: "red" - } + ] } } From b93cdf56fb7d5828900ad60f5f2fc42e420adf00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 16:26:02 +0100 Subject: [PATCH 098/119] Removed double page container --- public/app/features/teams/TeamList.tsx | 4 +- public/app/features/teams/TeamPages.tsx | 2 +- .../__snapshots__/TeamList.test.tsx.snap | 582 +++++++++--------- .../__snapshots__/TeamPages.test.tsx.snap | 22 +- 4 files changed, 297 insertions(+), 313 deletions(-) diff --git a/public/app/features/teams/TeamList.tsx b/public/app/features/teams/TeamList.tsx index efd279184d4..2e399b34860 100644 --- a/public/app/features/teams/TeamList.tsx +++ b/public/app/features/teams/TeamList.tsx @@ -86,7 +86,7 @@ export class TeamList extends PureComponent { const { teams, searchQuery } = this.props; return ( -
+ <>
-
+ ); } diff --git a/public/app/features/teams/TeamPages.tsx b/public/app/features/teams/TeamPages.tsx index ebbde595601..7a38197ff71 100644 --- a/public/app/features/teams/TeamPages.tsx +++ b/public/app/features/teams/TeamPages.tsx @@ -84,7 +84,7 @@ export class TeamPages extends PureComponent { return ( - {team && Object.keys(team).length !== 0 &&
{this.renderPage()}
} + {team && Object.keys(team).length !== 0 && this.renderPage()}
); diff --git a/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap index 812fe05c424..5d969cd9d83 100644 --- a/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap @@ -36,320 +36,316 @@ exports[`Render should render teams table 1`] = ` isLoading={false} >
-
- -
-
+ - - - - - - - + + + + + + + + + + + + + + + + + + + +
- - Name - - Email - - Members - + +
+ + + + + + + + + + + + + + + - - - + + + - - - - - - + + - - - - - - + + - - - - - - + + - - - - - - + + + + + - - - - - - -
+ + Name + + Email + + Members + +
+ + + + + + test-1 + + + + test-1@test.com + + + + 1 + + + -
- - - - - - - test-1 - - - - test-1@test.com - - - - 1 - - - -
- - - - - - - test-2 - - - - test-2@test.com - - - - 2 - - - -
- - - - - - - test-3 - - - - test-3@test.com - - - - 3 - - - -
- - - - - - - test-4 - - - - test-4@test.com - - - - 4 - - - -
- +
+ - - - - - - test-5 - - - - test-5@test.com - - - - 5 - - - -
-
+ + +
+ + test-3 + + + + test-3@test.com + + + + 3 + + + +
+ + + + + + test-4 + + + + test-4@test.com + + + + 4 + + + +
+ + + + + + test-5 + + + + test-5@test.com + + + + 5 + + + +
diff --git a/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap index 0c09eb3f82d..70f37cea4c5 100644 --- a/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap @@ -17,11 +17,7 @@ exports[`Render should render group sync page 1`] = ` -
- -
+
`; @@ -33,13 +29,9 @@ exports[`Render should render member page if team not empty 1`] = ` -
- -
+
`; @@ -51,11 +43,7 @@ exports[`Render should render settings and preferences page 1`] = ` -
- -
+
`; From 8e93b68e6d883c8f2bb9ee4942a3888d98afcdf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 16:38:05 +0100 Subject: [PATCH 099/119] restoring green CTA --- .../PanelOptionsGroup/_PanelOptionsGroup.scss | 3 +-- .../ThresholdsEditor/_ThresholdsEditor.scss | 2 +- .../manage_dashboards/manage_dashboards.html | 11 ++++------- .../alerting/partials/notifications_list.html | 1 - public/app/features/api-keys/ApiKeysPage.tsx | 2 +- public/app/features/playlist/partials/playlists.html | 1 - public/sass/_variables.dark.scss | 11 +++++++---- public/sass/_variables.light.scss | 11 +++++++---- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss index f8a3a408bab..4ce9c5264ea 100644 --- a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss +++ b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss @@ -36,8 +36,7 @@ } .panel-options-group__add-circle { - - @include gradientBar($btn-secondary-bg, $btn-secondary-bg-hl, #fff); + @include gradientBar($btn-success-bg, $btn-success-bg-hl, #fff); border-radius: 50px; width: 20px; diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss b/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss index e2cbfc372a9..8ef59bf08af 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss +++ b/packages/grafana-ui/src/components/ThresholdsEditor/_ThresholdsEditor.scss @@ -21,7 +21,7 @@ } .thresholds-row-add-button { - @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl, #fff); + @include buttonBackground($btn-success-bg, $btn-success-bg-hl, #fff); align-self: center; margin-right: 5px; diff --git a/public/app/core/components/manage_dashboards/manage_dashboards.html b/public/app/core/components/manage_dashboards/manage_dashboards.html index 6036ead3ef1..4ef2d7c9a66 100644 --- a/public/app/core/components/manage_dashboards/manage_dashboards.html +++ b/public/app/core/components/manage_dashboards/manage_dashboards.html @@ -6,15 +6,12 @@
- - Dashboard + New Dashboard - - - Folder + + New Folder - - + Import
diff --git a/public/app/features/alerting/partials/notifications_list.html b/public/app/features/alerting/partials/notifications_list.html index 6624a1d1132..ce4fea9ff49 100644 --- a/public/app/features/alerting/partials/notifications_list.html +++ b/public/app/features/alerting/partials/notifications_list.html @@ -8,7 +8,6 @@
- New Channel
diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index 7bed498e2ac..2627b1a6862 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -200,7 +200,7 @@ export class ApiKeysPage extends PureComponent {
diff --git a/public/app/features/playlist/partials/playlists.html b/public/app/features/playlist/partials/playlists.html index 2ec919f8157..22e41ac7104 100644 --- a/public/app/features/playlist/partials/playlists.html +++ b/public/app/features/playlist/partials/playlists.html @@ -5,7 +5,6 @@ diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 1ed3ecf2cf7..6181590985f 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -159,11 +159,14 @@ $table-bg-hover: $dark-3; // Buttons // ------------------------- -$btn-primary-bg: $sapphire-base; -$btn-primary-bg-hl: $sapphire-shade; +$btn-secondary-bg: $sapphire-base; +$btn-secondary-bg-hl: $sapphire-shade; -$btn-secondary-bg: $green-base; -$btn-secondary-bg-hl: $green-shade; +$btn-primary-bg: $green-base; +$btn-primary-bg-hl: $green-shade; + +$btn-success-bg: $green-base; +$btn-success-bg-hl: $green-shade; $btn-danger-bg: $lobster-base; $btn-danger-bg-hl: $lobster-shade; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 077c6598a4d..f0e0a535653 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -155,11 +155,14 @@ $table-bg-hover: $gray-5; // Buttons // ------------------------- -$btn-secondary-bg: $green-base; -$btn-secondary-bg-hl: $green-shade; +$btn-primary-bg: $green-base; +$btn-primary-bg-hl: $green-shade; -$btn-primary-bg: $sapphire-base; -$btn-primary-bg-hl: $sapphire-shade; +$btn-secondary-bg: $sapphire-base; +$btn-secondary-bg-hl: $sapphire-shade; + +$btn-success-bg: $green-base; +$btn-success-bg-hl: $green-shade; $btn-danger-bg: $lobster-base; $btn-danger-bg-hl: $lobster-shade; From afc2efa56ddb53334b3a4c73d3f30b3de9ad2c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 16:45:47 +0100 Subject: [PATCH 100/119] Removed plus icons --- .../DashboardPermissions/DashboardPermissions.tsx | 4 +--- public/app/features/folders/FolderPermissions.tsx | 10 ++++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx index 8cc26c4a1f2..e5fb0da71fc 100644 --- a/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx +++ b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx @@ -76,9 +76,7 @@ export class DashboardPermissions extends PureComponent {
- +
diff --git a/public/app/features/folders/FolderPermissions.tsx b/public/app/features/folders/FolderPermissions.tsx index f8c59d82130..f564991f291 100644 --- a/public/app/features/folders/FolderPermissions.tsx +++ b/public/app/features/folders/FolderPermissions.tsx @@ -73,7 +73,13 @@ export class FolderPermissions extends PureComponent { const { isAdding } = this.state; if (folder.id === 0) { - return ; + return ( + + + + + + ); } const folderInfo = { title: folder.title, url: folder.url, id: folder.id }; @@ -90,7 +96,7 @@ export class FolderPermissions extends PureComponent {
From 5195954681527f277f3ce6f4f52f7b6efe215760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 16:50:12 +0100 Subject: [PATCH 101/119] style tweak to alert --- public/sass/components/_alerts.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/sass/components/_alerts.scss b/public/sass/components/_alerts.scss index dc98cba82bd..1c4f1b7fcb7 100644 --- a/public/sass/components/_alerts.scss +++ b/public/sass/components/_alerts.scss @@ -6,7 +6,7 @@ // ------------------------- .alert { - padding: 1.25rem 2rem 1.25rem 1.5rem; + padding: 15px 20px; margin-bottom: $panel-margin / 2; text-shadow: 0 2px 0 rgba(255, 255, 255, 0.5); background: $alert-error-bg; From 1693f083cc8e35efaa9daf9bc8398c9d2c70b525 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 16:57:49 +0100 Subject: [PATCH 102/119] Move deduplication calculation from Logs component to redux selector --- package.json | 2 + public/app/core/utils/reselect.ts | 5 +++ public/app/features/explore/Logs.tsx | 16 +++---- public/app/features/explore/LogsContainer.tsx | 42 ++++++++++++++++++- .../app/features/explore/state/actionTypes.ts | 13 +++++- public/app/features/explore/state/reducers.ts | 11 +++++ public/app/types/explore.ts | 7 +++- yarn.lock | 12 ++++++ 8 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 public/app/core/utils/reselect.ts diff --git a/package.json b/package.json index fae51a1d856..22cfe33a4d0 100644 --- a/package.json +++ b/package.json @@ -151,6 +151,7 @@ "dependencies": { "@babel/polyfill": "^7.0.0", "@torkelo/react-select": "2.1.1", + "@types/reselect": "^2.2.0", "angular": "1.6.6", "angular-bindonce": "0.3.1", "angular-native-dragdrop": "1.2.2", @@ -187,6 +188,7 @@ "redux-logger": "^3.0.6", "redux-thunk": "^2.3.0", "remarkable": "^1.7.1", + "reselect": "^4.0.0", "rst2html": "github:thoward/rst2html#990cb89", "rxjs": "^6.3.3", "slate": "^0.33.4", diff --git a/public/app/core/utils/reselect.ts b/public/app/core/utils/reselect.ts new file mode 100644 index 00000000000..7c8fc7727b0 --- /dev/null +++ b/public/app/core/utils/reselect.ts @@ -0,0 +1,5 @@ +import { memoize } from 'lodash'; +import { createSelectorCreator } from 'reselect'; + +const hashFn = (...args) => args.reduce((acc, val) => acc + '-' + JSON.stringify(val), ''); +export const createLodashMemoizedSelector = createSelectorCreator(memoize, hashFn); diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index f41555b9121..0e3b3f3558e 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -9,8 +9,6 @@ import { LogsDedupDescription, LogsDedupStrategy, LogsModel, - dedupLogRows, - filterLogLevels, LogLevel, LogsMetaKind, } from 'app/core/logs_model'; @@ -51,6 +49,7 @@ function renderMetaItem(value: any, kind: LogsMetaKind) { interface Props { data?: LogsModel; + dedupedData?: LogsModel; width: number; exploreId: string; highlighterExpressions: string[]; @@ -59,16 +58,17 @@ interface Props { scanning?: boolean; scanRange?: RawTimeRange; dedupStrategy: LogsDedupStrategy; + hiddenLogLevels: Set; onChangeTime?: (range: RawTimeRange) => void; onClickLabel?: (label: string, value: string) => void; onStartScanning?: () => void; onStopScanning?: () => void; onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void; + onToggleLogLevel: (hiddenLogLevels: Set) => void; } interface State { deferLogs: boolean; - hiddenLogLevels: Set; renderAll: boolean; showLabels: boolean | null; // Tristate: null means auto showLocalTime: boolean; @@ -81,7 +81,6 @@ export default class Logs extends PureComponent { state = { deferLogs: true, - hiddenLogLevels: new Set(), renderAll: false, showLabels: null, showLocalTime: true, @@ -142,7 +141,7 @@ export default class Logs extends PureComponent { onToggleLogLevel = (rawLevel: string, hiddenRawLevels: Set) => { const hiddenLogLevels: Set = new Set(Array.from(hiddenRawLevels).map(level => LogLevel[level])); - this.setState({ hiddenLogLevels }); + this.props.onToggleLogLevel(hiddenLogLevels); }; onClickScan = (event: React.SyntheticEvent) => { @@ -166,21 +165,18 @@ export default class Logs extends PureComponent { scanning, scanRange, width, + dedupedData, } = this.props; if (!data) { return null; } - const { deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc, } = this.state; + const { deferLogs, renderAll, showLocalTime, showUtc } = this.state; let { showLabels } = this.state; const { dedupStrategy } = this.props; const hasData = data && data.rows && data.rows.length > 0; const showDuplicates = dedupStrategy !== LogsDedupStrategy.none; - - // Filtering - const filteredData = filterLogLevels(data, hiddenLogLevels); - const dedupedData = dedupLogRows(filteredData, dedupStrategy); const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0); const meta = [...data.meta]; diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 190c1c43b5a..9fd06afae9b 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -4,18 +4,21 @@ import { connect } from 'react-redux'; import { RawTimeRange, TimeRange } from '@grafana/ui'; import { ExploreId, ExploreItemState } from 'app/types/explore'; -import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; +import { LogsModel, LogsDedupStrategy, LogLevel, filterLogLevels, dedupLogRows } from 'app/core/logs_model'; import { StoreState } from 'app/types'; import { toggleLogs, changeDedupStrategy } from './state/actions'; import Logs from './Logs'; import Panel from './Panel'; +import { toggleLogLevelAction } from 'app/features/explore/state/actionTypes'; +import { createLodashMemoizedSelector } from 'app/core/utils/reselect'; interface LogsContainerProps { exploreId: ExploreId; loading: boolean; logsHighlighterExpressions?: string[]; logsResult?: LogsModel; + dedupedResult?: LogsModel; onChangeTime: (range: TimeRange) => void; onClickLabel: (key: string, value: string) => void; onStartScanning: () => void; @@ -25,8 +28,10 @@ interface LogsContainerProps { scanRange?: RawTimeRange; showingLogs: boolean; toggleLogs: typeof toggleLogs; + toggleLogLevelAction: typeof toggleLogLevelAction; changeDedupStrategy: typeof changeDedupStrategy; dedupStrategy: LogsDedupStrategy; + hiddenLogLevels: Set; width: number; } @@ -39,12 +44,21 @@ export class LogsContainer extends PureComponent { this.props.changeDedupStrategy(this.props.exploreId, dedupStrategy); }; + hangleToggleLogLevel = (hiddenLogLevels: Set) => { + const { exploreId } = this.props; + this.props.toggleLogLevelAction({ + exploreId, + hiddenLogLevels, + }); + }; + render() { const { exploreId, loading, logsHighlighterExpressions, logsResult, + dedupedResult, onChangeTime, onClickLabel, onStartScanning, @@ -54,6 +68,7 @@ export class LogsContainer extends PureComponent { scanning, scanRange, width, + hiddenLogLevels, } = this.props; return ( @@ -61,6 +76,7 @@ export class LogsContainer extends PureComponent { { onStartScanning={onStartScanning} onStopScanning={onStopScanning} onDedupStrategyChange={this.handleDedupStrategyChange} + onToggleLogLevel={this.hangleToggleLogLevel} range={range} scanning={scanning} scanRange={scanRange} width={width} + hiddenLogLevels={hiddenLogLevels} /> ); @@ -90,12 +108,29 @@ const selectItemUIState = (itemState: ExploreItemState) => { dedupStrategy, }; }; + +const logsSelector = (state: ExploreItemState) => state.logsResult; +const hiddenLogLevelsSelector = (state: ExploreItemState) => state.hiddenLogLevels; +const dedupStrategySelector = (state: ExploreItemState) => state.dedupStrategy; +const deduplicatedLogsSelector = createLodashMemoizedSelector( + logsSelector, hiddenLogLevelsSelector, dedupStrategySelector, + (logs, hiddenLogLevels, dedupStrategy) => { + if (!logs) { + return null; + } + const filteredData = filterLogLevels(logs, new Set(hiddenLogLevels)); + return dedupLogRows(filteredData, dedupStrategy); + } +); + function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, range } = item; const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done); - const {showingLogs, dedupStrategy} = selectItemUIState(item); + const { showingLogs, dedupStrategy } = selectItemUIState(item); + const hiddenLogLevels = new Set(item.hiddenLogLevels); + const dedupedResult = deduplicatedLogsSelector(item); return { loading, @@ -106,12 +141,15 @@ function mapStateToProps(state: StoreState, { exploreId }) { showingLogs, range, dedupStrategy, + hiddenLogLevels, + dedupedResult, }; } const mapDispatchToProps = { toggleLogs, changeDedupStrategy, + toggleLogLevelAction, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(LogsContainer)); diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index d54a8754c3d..c54eef97a43 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -18,6 +18,7 @@ import { ExploreUIState, } from 'app/types/explore'; import { actionCreatorFactory, noPayloadActionCreatorFactory, ActionOf } from 'app/core/redux/actionCreatorFactory'; +import { LogLevel } from 'app/core/logs_model'; /** Higher order actions * @@ -201,6 +202,11 @@ export interface UpdateDatasourceInstancePayload { datasourceInstance: DataSourceApi; } +export interface ToggleLogLevelPayload { + exploreId: ExploreId; + hiddenLogLevels: Set; +} + export interface QueriesImportedPayload { exploreId: ExploreId; queries: DataQuery[]; @@ -397,6 +403,10 @@ export const updateDatasourceInstanceAction = actionCreatorFactory( + 'explore/TOGGLE_LOG_LEVEL' +).create(); + /** * Resets state for explore. */ @@ -436,4 +446,5 @@ export type Action = | ActionOf | ActionOf | ActionOf - | ActionOf; + | ActionOf + | ActionOf; diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 255591ee6e3..db3e9a95858 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -38,6 +38,7 @@ import { toggleTableAction, queriesImportedAction, updateUIStateAction, + toggleLogLevelAction, } from './actionTypes'; export const DEFAULT_RANGE = { @@ -467,6 +468,16 @@ export const itemReducer = reducerFactory({} as ExploreItemSta }; }, }) + .addMapper({ + filter: toggleLogLevelAction, + mapper: (state, action): ExploreItemState => { + const { hiddenLogLevels } = action.payload; + return { + ...state, + hiddenLogLevels: Array.from(hiddenLogLevels) + }; + }, + }) .create(); /** diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 066ca226157..7a6af04b2ee 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -11,7 +11,7 @@ import { } from '@grafana/ui'; import { Emitter } from 'app/core/core'; -import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model'; +import { LogsModel, LogsDedupStrategy, LogLevel } from 'app/core/logs_model'; import TableModel from 'app/core/table_model'; export interface CompletionItem { @@ -242,6 +242,11 @@ export interface ExploreItemState { * Current logs deduplication strategy */ dedupStrategy?: LogsDedupStrategy; + + /** + * Currently hidden log series + */ + hiddenLogLevels?: LogLevel[]; } export interface ExploreUIState { diff --git a/yarn.lock b/yarn.lock index df2e1cea37e..3c86bdf810f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1819,6 +1819,13 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/reselect@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/reselect/-/reselect-2.2.0.tgz#c667206cfdc38190e1d379babe08865b2288575f" + integrity sha1-xmcgbP3DgZDh03m6vgiGWyKIV18= + dependencies: + reselect "*" + "@types/storybook__addon-actions@^3.4.1": version "3.4.1" resolved "https://registry.yarnpkg.com/@types/storybook__addon-actions/-/storybook__addon-actions-3.4.1.tgz#8f90d76b023b58ee794170f2fe774a3fddda2c1d" @@ -14894,6 +14901,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +reselect@*, reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" From 951e5932d4e650e160aeae2bbde850b7985dd237 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 11 Feb 2019 17:00:16 +0100 Subject: [PATCH 103/119] changelog: adds note for #15363 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a82fc7050b4..6589099e178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ * **AzureMonitor**: improve autocomplete for Log Analytics and App Insights editor [#15131](https://github.com/grafana/grafana/issues/15131) * **LDAP**: Fix IPA/FreeIPA v4.6.4 does not allow LDAP searches with empty attributes [#14432](https://github.com/grafana/grafana/issues/14432) +### Breaking changes + +* **Internal Metrics** Edition has been added to the build_info metric. This will break any Graphite queries using this metric. Edition will be a new label for the Prometheus metric. [#15363](https://github.com/grafana/grafana/pull/15363) + ### 6.0.0-beta1 fixes * **Postgres**: Fix default port not added when port not configured [#15189](https://github.com/grafana/grafana/issues/15189) From 0493d905f17f6b1b7ffbb7afbf181a91798d5bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 17:42:31 +0100 Subject: [PATCH 104/119] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6589099e178..7b97da0e81c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -# 6.0.0-beta2 (unreleased) +# 6.0.0-beta3 (unreleased) + +# 6.0.0-beta2 (2019-02-11) ### New Features * **AzureMonitor**: Enable alerting by converting Azure Monitor API to Go [#14623](https://github.com/grafana/grafana/issues/14623) From e38cfc1a7183bd30e6e3eb022077e96fe6167d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 17:43:02 +0100 Subject: [PATCH 105/119] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f44291a86a..f004ee07732 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "company": "Grafana Labs" }, "name": "grafana", - "version": "6.0.0-prebeta2", + "version": "6.0.0-pre3", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git" From fc91e1cf57a22e4a4a5cd1e99f7bee70ad3ec65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 11 Feb 2019 17:47:47 +0100 Subject: [PATCH 106/119] Fixed issue with gauge requests being cancelled --- public/app/features/dashboard/dashgrid/DataPanel.tsx | 3 +-- public/app/features/dashboard/dashgrid/PanelChrome.tsx | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 2183548000b..b81d66fa7f5 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -28,7 +28,7 @@ interface RenderProps { export interface Props { datasource: string | null; queries: any[]; - panelId?: number; + panelId: number; dashboardId?: number; isVisible?: boolean; timeRange?: TimeRange; @@ -50,7 +50,6 @@ export interface State { export class DataPanel extends Component { static defaultProps = { isVisible: true, - panelId: 1, dashboardId: 1, }; diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index b02d9479dcc..b29be4b389d 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -149,6 +149,7 @@ export class PanelChrome extends PureComponent { this.renderPanel(false, panel.snapshotData, width, height) ) : ( Date: Mon, 11 Feb 2019 21:11:19 +0100 Subject: [PATCH 107/119] Fix error caused by named colors that are not part of named colors palette --- packages/grafana-ui/src/utils/namedColorsPalette.test.ts | 8 ++++---- packages/grafana-ui/src/utils/namedColorsPalette.ts | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts index aa57b46636c..e544548aa4a 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts @@ -44,10 +44,6 @@ describe('colors', () => { }); describe('getColorFromHexRgbOrName', () => { - it('returns undefined for unknown color', () => { - expect(() => getColorFromHexRgbOrName('aruba-sunshine')).toThrow(); - }); - it('returns dark hex variant for known color if theme not specified', () => { expect(getColorFromHexRgbOrName(SemiDarkBlue.name)).toBe(SemiDarkBlue.variants.dark); }); @@ -64,5 +60,9 @@ describe('colors', () => { expect(getColorFromHexRgbOrName('rgb(0,0,0)')).toBe('rgb(0,0,0)'); expect(getColorFromHexRgbOrName('rgba(0,0,0,1)')).toBe('rgba(0,0,0,1)'); }); + + it('returns hex for named color that is not a part of named colors palette', () => { + expect(getColorFromHexRgbOrName('lime')).toBe('#00ff00'); + }); }); }); diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.ts b/packages/grafana-ui/src/utils/namedColorsPalette.ts index ee5741e794e..88ae510a6d8 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.ts @@ -1,5 +1,6 @@ import { flatten } from 'lodash'; import { GrafanaThemeType } from '../types'; +import tinycolor from 'tinycolor2'; type Hue = 'green' | 'yellow' | 'red' | 'blue' | 'orange' | 'purple'; @@ -106,7 +107,7 @@ export const getColorFromHexRgbOrName = (color: string, theme?: GrafanaThemeType const colorDefinition = getColorByName(color); if (!colorDefinition) { - throw new Error('Unknown color'); + return new tinycolor(color).toHexString(); } return theme ? colorDefinition.variants[theme] : colorDefinition.variants.dark; From a5d158c014a65fded992bab069368eb61adbe1a8 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 11 Feb 2019 21:22:53 +0100 Subject: [PATCH 108/119] Added one more test case for color resolving helper --- packages/grafana-ui/src/utils/namedColorsPalette.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts index e544548aa4a..19c7d9c84ad 100644 --- a/packages/grafana-ui/src/utils/namedColorsPalette.test.ts +++ b/packages/grafana-ui/src/utils/namedColorsPalette.test.ts @@ -44,6 +44,10 @@ describe('colors', () => { }); describe('getColorFromHexRgbOrName', () => { + it('returns black for unknown color', () => { + expect(getColorFromHexRgbOrName('aruba-sunshine')).toBe("#000000"); + }); + it('returns dark hex variant for known color if theme not specified', () => { expect(getColorFromHexRgbOrName(SemiDarkBlue.name)).toBe(SemiDarkBlue.variants.dark); }); From edd9576f1586178501b1d75726944d1df70fcc1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 12 Feb 2019 08:04:30 +0100 Subject: [PATCH 109/119] Fixed elastic5 docker compose block --- devenv/docker/blocks/elastic5/docker-compose.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/devenv/docker/blocks/elastic5/docker-compose.yaml b/devenv/docker/blocks/elastic5/docker-compose.yaml index 33a7d9855b0..3a2ef39faba 100644 --- a/devenv/docker/blocks/elastic5/docker-compose.yaml +++ b/devenv/docker/blocks/elastic5/docker-compose.yaml @@ -1,6 +1,3 @@ -# You need to run 'sysctl -w vm.max_map_count=262144' on the host machine -version: '2' -services: elasticsearch5: image: elasticsearch:5 command: elasticsearch From 3b9105e1bebb24e518548faf258576e0b30f70a8 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 12 Feb 2019 08:45:21 +0100 Subject: [PATCH 110/119] enable testing provsioned datasources closes #12164 --- .../datasources/settings/ButtonRow.test.tsx | 1 + .../datasources/settings/ButtonRow.tsx | 8 +- .../settings/DataSourceSettingsPage.tsx | 89 ++++++++++--------- .../__snapshots__/ButtonRow.test.tsx.snap | 7 ++ .../DataSourceSettingsPage.test.tsx.snap | 4 + 5 files changed, 68 insertions(+), 41 deletions(-) diff --git a/public/app/features/datasources/settings/ButtonRow.test.tsx b/public/app/features/datasources/settings/ButtonRow.test.tsx index 0acab8941ff..84b16d829d5 100644 --- a/public/app/features/datasources/settings/ButtonRow.test.tsx +++ b/public/app/features/datasources/settings/ButtonRow.test.tsx @@ -7,6 +7,7 @@ const setup = (propOverrides?: object) => { isReadOnly: true, onSubmit: jest.fn(), onDelete: jest.fn(), + onTest: jest.fn(), }; Object.assign(props, propOverrides); diff --git a/public/app/features/datasources/settings/ButtonRow.tsx b/public/app/features/datasources/settings/ButtonRow.tsx index 6b85e21405c..3e8ac060010 100644 --- a/public/app/features/datasources/settings/ButtonRow.tsx +++ b/public/app/features/datasources/settings/ButtonRow.tsx @@ -4,14 +4,20 @@ export interface Props { isReadOnly: boolean; onDelete: () => void; onSubmit: (event) => void; + onTest: (event) => void; } -const ButtonRow: FC = ({ isReadOnly, onDelete, onSubmit }) => { +const ButtonRow: FC = ({ isReadOnly, onDelete, onSubmit, onTest }) => { return (
+ {isReadOnly && ( + + )} diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx index ff840390cf5..fe1121ed73e 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx @@ -72,6 +72,12 @@ export class DataSourceSettingsPage extends PureComponent { this.testDataSource(); }; + onTest = async (evt: React.FormEvent) => { + evt.preventDefault(); + + this.testDataSource(); + }; + onDelete = () => { appEvents.emit('confirm-modal', { title: 'Delete', @@ -180,52 +186,55 @@ export class DataSourceSettingsPage extends PureComponent { return ( - {this.hasDataSource &&
-
-
- {this.isReadOnly() && this.renderIsReadOnlyMessage()} - {this.shouldRenderInfoBox() &&
{this.getInfoText()}
} + {this.hasDataSource && ( +
+
+ + {this.isReadOnly() && this.renderIsReadOnlyMessage()} + {this.shouldRenderInfoBox() &&
{this.getInfoText()}
} - setIsDefault(state)} - onNameChange={name => setDataSourceName(name)} - /> - - {dataSourceMeta.module && ( - setIsDefault(state)} + onNameChange={name => setDataSourceName(name)} /> - )} -
- {testingMessage && ( -
-
- {testingStatus === 'error' ? ( - - ) : ( - - )} -
-
-
{testingMessage}
-
-
+ {dataSourceMeta.module && ( + )} -
- this.onSubmit(event)} - isReadOnly={this.isReadOnly()} - onDelete={this.onDelete} - /> - +
+ {testingMessage && ( +
+
+ {testingStatus === 'error' ? ( + + ) : ( + + )} +
+
+
{testingMessage}
+
+
+ )} +
+ + this.onSubmit(event)} + isReadOnly={this.isReadOnly()} + onDelete={this.onDelete} + onTest={event => this.onTest(event)} + /> + +
-
} + )} ); diff --git a/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap b/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap index bd190f60b03..d4ec7eeea1e 100644 --- a/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap +++ b/public/app/features/datasources/settings/__snapshots__/ButtonRow.test.tsx.snap @@ -12,6 +12,13 @@ exports[`Render should render component 1`] = ` > Save & Test +
@@ -202,6 +203,7 @@ exports[`Render should render beta info text 1`] = ` isReadOnly={false} onDelete={[Function]} onSubmit={[Function]} + onTest={[Function]} />
@@ -302,6 +304,7 @@ exports[`Render should render component 1`] = ` isReadOnly={false} onDelete={[Function]} onSubmit={[Function]} + onTest={[Function]} />
From da80286f97f1075ff59fd9b82601a4f2c40230be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Tue, 12 Feb 2019 11:10:31 +0100 Subject: [PATCH 111/119] Fixes #15372 with number input and parseFloat --- .../ThresholdsEditor/ThresholdsEditor.test.tsx | 6 +++--- .../ThresholdsEditor/ThresholdsEditor.tsx | 17 ++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx index 845ff5f6bf4..2b6af67df22 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx +++ b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ChangeEvent } from 'react'; import { shallow } from 'enzyme'; import { ThresholdsEditor, Props } from './ThresholdsEditor'; @@ -118,7 +118,7 @@ describe('change threshold value', () => { ]; const instance = setup({ thresholds }); - const mockEvent = { target: { value: 12 } }; + const mockEvent = ({ target: { value: '12' } } as any) as ChangeEvent; instance.onChangeThresholdValue(mockEvent, thresholds[0]); @@ -137,7 +137,7 @@ describe('change threshold value', () => { thresholds, }; - const mockEvent = { target: { value: 78 } }; + const mockEvent = ({ target: { value: '78' } } as any) as ChangeEvent; instance.onChangeThresholdValue(mockEvent, thresholds[1]); diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx index b2a2e07c58d..f4db23d6656 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx +++ b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent, ChangeEvent } from 'react'; import { Threshold } from '../../types'; import { ColorPicker } from '../ColorPicker/ColorPicker'; import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup'; @@ -94,14 +94,15 @@ export class ThresholdsEditor extends PureComponent { ); }; - onChangeThresholdValue = (event: any, threshold: Threshold) => { + onChangeThresholdValue = (event: ChangeEvent, threshold: Threshold) => { if (threshold.index === 0) { return; } const { thresholds } = this.state; - const parsedValue = parseInt(event.target.value, 10); - const value = isNaN(parsedValue) ? null : parsedValue; + const cleanValue = event.target.value.replace(/,/g, '.'); + const parsedValue = parseFloat(cleanValue); + const value = isNaN(parsedValue) ? '' : parsedValue; const newThresholds = thresholds.map(t => { if (t === threshold && t.index !== 0) { @@ -164,16 +165,14 @@ export class ThresholdsEditor extends PureComponent {
{threshold.color && (
- this.onChangeThresholdColor(threshold, color)} - /> + this.onChangeThresholdColor(threshold, color)} />
)}
this.onChangeThresholdValue(event, threshold)} value={value} onBlur={this.onBlur} From 1310d356fc3cfc189fc4f4d26f8b441b12c1ac2a Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 12 Feb 2019 12:33:54 +0100 Subject: [PATCH 112/119] removes unused session code --- pkg/middleware/recovery_test.go | 2 -- pkg/services/session/session.go | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/middleware/recovery_test.go b/pkg/middleware/recovery_test.go index e041d42e56b..6736d699a39 100644 --- a/pkg/middleware/recovery_test.go +++ b/pkg/middleware/recovery_test.go @@ -6,7 +6,6 @@ import ( "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/session" "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" macaron "gopkg.in/macaron.v1" @@ -66,7 +65,6 @@ func recoveryScenario(desc string, url string, fn scenarioFunc) { sc.userAuthTokenService = newFakeUserAuthTokenService() sc.m.Use(GetContextHandler(sc.userAuthTokenService)) // mock out gc goroutine - session.StartSessionGC = func() {} sc.m.Use(OrgRedirect()) sc.m.Use(AddDefaultResponseHeaders()) diff --git a/pkg/services/session/session.go b/pkg/services/session/session.go index 2e60b8a25d7..3481c99ce58 100644 --- a/pkg/services/session/session.go +++ b/pkg/services/session/session.go @@ -19,7 +19,7 @@ const ( var sessionManager *ms.Manager var sessionOptions *ms.Options -var StartSessionGC func() +var StartSessionGC func() = func() {} var GetSessionCount func() int var sessionLogger = log.New("session") var sessionConnMaxLifetime int64 From dd19ec3b22c63e2500a2e2190cdaf6de8ea18954 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Tue, 12 Feb 2019 12:36:46 +0100 Subject: [PATCH 113/119] Move explore selectors to a separate file --- public/app/features/explore/LogsContainer.tsx | 31 ++----------------- .../app/features/explore/state/selectors.ts | 30 ++++++++++++++++++ 2 files changed, 33 insertions(+), 28 deletions(-) create mode 100644 public/app/features/explore/state/selectors.ts diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index 9fd06afae9b..f91b14175cb 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -4,14 +4,14 @@ import { connect } from 'react-redux'; import { RawTimeRange, TimeRange } from '@grafana/ui'; import { ExploreId, ExploreItemState } from 'app/types/explore'; -import { LogsModel, LogsDedupStrategy, LogLevel, filterLogLevels, dedupLogRows } from 'app/core/logs_model'; +import { LogsModel, LogsDedupStrategy, LogLevel } from 'app/core/logs_model'; import { StoreState } from 'app/types'; import { toggleLogs, changeDedupStrategy } from './state/actions'; import Logs from './Logs'; import Panel from './Panel'; import { toggleLogLevelAction } from 'app/features/explore/state/actionTypes'; -import { createLodashMemoizedSelector } from 'app/core/utils/reselect'; +import { deduplicatedLogsSelector, exploreItemUIStateSelector } from 'app/features/explore/state/selectors'; interface LogsContainerProps { exploreId: ExploreId; @@ -98,37 +98,12 @@ export class LogsContainer extends PureComponent { } } -const selectItemUIState = (itemState: ExploreItemState) => { - const { showingGraph, showingLogs, showingTable, showingStartPage, dedupStrategy } = itemState; - return { - showingGraph, - showingLogs, - showingTable, - showingStartPage, - dedupStrategy, - }; -}; - -const logsSelector = (state: ExploreItemState) => state.logsResult; -const hiddenLogLevelsSelector = (state: ExploreItemState) => state.hiddenLogLevels; -const dedupStrategySelector = (state: ExploreItemState) => state.dedupStrategy; -const deduplicatedLogsSelector = createLodashMemoizedSelector( - logsSelector, hiddenLogLevelsSelector, dedupStrategySelector, - (logs, hiddenLogLevels, dedupStrategy) => { - if (!logs) { - return null; - } - const filteredData = filterLogLevels(logs, new Set(hiddenLogLevels)); - return dedupLogRows(filteredData, dedupStrategy); - } -); - function mapStateToProps(state: StoreState, { exploreId }) { const explore = state.explore; const item: ExploreItemState = explore[exploreId]; const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, range } = item; const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done); - const { showingLogs, dedupStrategy } = selectItemUIState(item); + const { showingLogs, dedupStrategy } = exploreItemUIStateSelector(item); const hiddenLogLevels = new Set(item.hiddenLogLevels); const dedupedResult = deduplicatedLogsSelector(item); diff --git a/public/app/features/explore/state/selectors.ts b/public/app/features/explore/state/selectors.ts new file mode 100644 index 00000000000..fff52651646 --- /dev/null +++ b/public/app/features/explore/state/selectors.ts @@ -0,0 +1,30 @@ +import { createLodashMemoizedSelector } from 'app/core/utils/reselect'; +import { ExploreItemState } from 'app/types'; +import { filterLogLevels, dedupLogRows } from 'app/core/logs_model'; + +export const exploreItemUIStateSelector = (itemState: ExploreItemState) => { + const { showingGraph, showingLogs, showingTable, showingStartPage, dedupStrategy } = itemState; + return { + showingGraph, + showingLogs, + showingTable, + showingStartPage, + dedupStrategy, + }; +}; + +const logsSelector = (state: ExploreItemState) => state.logsResult; +const hiddenLogLevelsSelector = (state: ExploreItemState) => state.hiddenLogLevels; +const dedupStrategySelector = (state: ExploreItemState) => state.dedupStrategy; +export const deduplicatedLogsSelector = createLodashMemoizedSelector( + logsSelector, + hiddenLogLevelsSelector, + dedupStrategySelector, + (logs, hiddenLogLevels, dedupStrategy) => { + if (!logs) { + return null; + } + const filteredData = filterLogLevels(logs, new Set(hiddenLogLevels)); + return dedupLogRows(filteredData, dedupStrategy); + } +); From 49e0572611e4b640daf14383f9cf6158964e861a Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 30 Jan 2019 16:42:44 +0100 Subject: [PATCH 114/119] fix: Error tooltip should have white text on red background. Not red text on red background --- packages/grafana-ui/src/components/Tooltip/_Tooltip.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Tooltip/_Tooltip.scss b/packages/grafana-ui/src/components/Tooltip/_Tooltip.scss index 503b977bbf0..a33724b3460 100644 --- a/packages/grafana-ui/src/components/Tooltip/_Tooltip.scss +++ b/packages/grafana-ui/src/components/Tooltip/_Tooltip.scss @@ -31,7 +31,7 @@ $popper-margin-from-ref: 5px; // Themes &.popper__background--error { - @include popper-theme($tooltipBackgroundError, $tooltipBackgroundError); + @include popper-theme($tooltipBackgroundError, $white); } &.popper__background--info { From 2d4e1a80bc968f562aa7b3be9b42266aa028bc51 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 4 Feb 2019 14:50:10 +0100 Subject: [PATCH 115/119] chore: wip: Replace brace with ace-builds to get latest version of ace --- package.json | 2 +- .../components/code_editor/code_editor.ts | 28 +++++++++---------- yarn.lock | 17 ++++------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index f004ee07732..f26cadbe4bf 100644 --- a/package.json +++ b/package.json @@ -151,13 +151,13 @@ "dependencies": { "@babel/polyfill": "^7.0.0", "@torkelo/react-select": "2.1.1", + "ace-builds": "^1.4.2", "angular": "1.6.6", "angular-bindonce": "0.3.1", "angular-native-dragdrop": "1.2.2", "angular-route": "1.6.6", "angular-sanitize": "1.6.6", "baron": "^3.0.3", - "brace": "^0.10.0", "classnames": "^2.2.6", "clipboard": "^1.7.1", "d3": "^4.11.0", diff --git a/public/app/core/components/code_editor/code_editor.ts b/public/app/core/components/code_editor/code_editor.ts index ca9023e8657..004edb780b8 100644 --- a/public/app/core/components/code_editor/code_editor.ts +++ b/public/app/core/components/code_editor/code_editor.ts @@ -30,20 +30,20 @@ import coreModule from 'app/core/core_module'; import config from 'app/core/config'; -import ace from 'brace'; +import * as ace from 'ace-builds/src-noconflict/ace'; import './theme-grafana-dark'; -import 'brace/ext/language_tools'; -import 'brace/theme/textmate'; -import 'brace/mode/text'; -import 'brace/snippets/text'; -import 'brace/mode/sql'; -import 'brace/snippets/sql'; -import 'brace/mode/sqlserver'; -import 'brace/snippets/sqlserver'; -import 'brace/mode/markdown'; -import 'brace/snippets/markdown'; -import 'brace/mode/json'; -import 'brace/snippets/json'; +import 'ace-builds/src-noconflict/ext-language_tools'; +import 'ace-builds/src-noconflict/theme-textmate'; +import 'ace-builds/src-noconflict/mode-text'; +import 'ace-builds/src-noconflict/snippets/text'; +import 'ace-builds/src-noconflict/mode-sql'; +import 'ace-builds/src-noconflict/snippets/sql'; +import 'ace-builds/src-noconflict/mode-sqlserver'; +import 'ace-builds/src-noconflict/snippets/sqlserver'; +import 'ace-builds/src-noconflict/mode-markdown'; +import 'ace-builds/src-noconflict/snippets/markdown'; +import 'ace-builds/src-noconflict/mode-json'; +import 'ace-builds/src-noconflict/snippets/json'; const DEFAULT_THEME_DARK = 'ace/theme/grafana-dark'; const DEFAULT_THEME_LIGHT = 'ace/theme/textmate'; @@ -143,7 +143,7 @@ function link(scope, elem, attrs) { }); function setLangMode(lang) { - ace.acequire('ace/ext/language_tools'); + // ace.acequire('ace/ext/language_tools'); // TODO: Do we need this? codeEditor.setOptions({ enableBasicAutocompletion: true, enableLiveAutocompletion: true, diff --git a/yarn.lock b/yarn.lock index 2fb4a5d3ee2..ac6c8c9ce78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2265,6 +2265,11 @@ accepts@~1.3.4, accepts@~1.3.5: mime-types "~2.1.18" negotiator "0.6.1" +ace-builds@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.2.tgz#6afc2e43a7b5effdc44d8407436112852568e80d" + integrity sha512-M1JtZctO2Zg+1qeGUFZXtYKsyaRptqQtqpVzlj80I0NzGW9MF3um0DBuizIvQlrPYUlTdm+wcOPZpZoerkxQdA== + acorn-dynamic-import@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz#901ceee4c7faaef7e07ad2a47e890675da50a278" @@ -4125,13 +4130,6 @@ brace-expansion@^1.0.0, brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -brace@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/brace/-/brace-0.10.0.tgz#edef4eb9b0928ba1ee5f717ffc157749a6dd5d76" - integrity sha1-7e9OubCSi6HuX3F//BV3SabdXXY= - dependencies: - w3c-blob "0.0.1" - braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" @@ -17283,11 +17281,6 @@ vm-browserify@0.0.4: dependencies: indexof "0.0.1" -w3c-blob@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/w3c-blob/-/w3c-blob-0.0.1.tgz#b0cd352a1a50f515563420ffd5861f950f1d85b8" - integrity sha1-sM01KhpQ9RVWNCD/1YYflQ8dhbg= - w3c-hr-time@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" From 2a655cb38a57022a95a890e3be9d8eaff42756c3 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 12 Feb 2019 12:35:06 +0100 Subject: [PATCH 116/119] Revert "chore: wip: Replace brace with ace-builds to get latest version of ace" This reverts commit c98b86fd6b58ac5f77c197d7551751e62d53bedd. --- package.json | 2 +- .../components/code_editor/code_editor.ts | 28 +++++++++---------- yarn.lock | 17 +++++++---- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index f26cadbe4bf..f004ee07732 100644 --- a/package.json +++ b/package.json @@ -151,13 +151,13 @@ "dependencies": { "@babel/polyfill": "^7.0.0", "@torkelo/react-select": "2.1.1", - "ace-builds": "^1.4.2", "angular": "1.6.6", "angular-bindonce": "0.3.1", "angular-native-dragdrop": "1.2.2", "angular-route": "1.6.6", "angular-sanitize": "1.6.6", "baron": "^3.0.3", + "brace": "^0.10.0", "classnames": "^2.2.6", "clipboard": "^1.7.1", "d3": "^4.11.0", diff --git a/public/app/core/components/code_editor/code_editor.ts b/public/app/core/components/code_editor/code_editor.ts index 004edb780b8..ca9023e8657 100644 --- a/public/app/core/components/code_editor/code_editor.ts +++ b/public/app/core/components/code_editor/code_editor.ts @@ -30,20 +30,20 @@ import coreModule from 'app/core/core_module'; import config from 'app/core/config'; -import * as ace from 'ace-builds/src-noconflict/ace'; +import ace from 'brace'; import './theme-grafana-dark'; -import 'ace-builds/src-noconflict/ext-language_tools'; -import 'ace-builds/src-noconflict/theme-textmate'; -import 'ace-builds/src-noconflict/mode-text'; -import 'ace-builds/src-noconflict/snippets/text'; -import 'ace-builds/src-noconflict/mode-sql'; -import 'ace-builds/src-noconflict/snippets/sql'; -import 'ace-builds/src-noconflict/mode-sqlserver'; -import 'ace-builds/src-noconflict/snippets/sqlserver'; -import 'ace-builds/src-noconflict/mode-markdown'; -import 'ace-builds/src-noconflict/snippets/markdown'; -import 'ace-builds/src-noconflict/mode-json'; -import 'ace-builds/src-noconflict/snippets/json'; +import 'brace/ext/language_tools'; +import 'brace/theme/textmate'; +import 'brace/mode/text'; +import 'brace/snippets/text'; +import 'brace/mode/sql'; +import 'brace/snippets/sql'; +import 'brace/mode/sqlserver'; +import 'brace/snippets/sqlserver'; +import 'brace/mode/markdown'; +import 'brace/snippets/markdown'; +import 'brace/mode/json'; +import 'brace/snippets/json'; const DEFAULT_THEME_DARK = 'ace/theme/grafana-dark'; const DEFAULT_THEME_LIGHT = 'ace/theme/textmate'; @@ -143,7 +143,7 @@ function link(scope, elem, attrs) { }); function setLangMode(lang) { - // ace.acequire('ace/ext/language_tools'); // TODO: Do we need this? + ace.acequire('ace/ext/language_tools'); codeEditor.setOptions({ enableBasicAutocompletion: true, enableLiveAutocompletion: true, diff --git a/yarn.lock b/yarn.lock index ac6c8c9ce78..2fb4a5d3ee2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2265,11 +2265,6 @@ accepts@~1.3.4, accepts@~1.3.5: mime-types "~2.1.18" negotiator "0.6.1" -ace-builds@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.2.tgz#6afc2e43a7b5effdc44d8407436112852568e80d" - integrity sha512-M1JtZctO2Zg+1qeGUFZXtYKsyaRptqQtqpVzlj80I0NzGW9MF3um0DBuizIvQlrPYUlTdm+wcOPZpZoerkxQdA== - acorn-dynamic-import@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz#901ceee4c7faaef7e07ad2a47e890675da50a278" @@ -4130,6 +4125,13 @@ brace-expansion@^1.0.0, brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/brace/-/brace-0.10.0.tgz#edef4eb9b0928ba1ee5f717ffc157749a6dd5d76" + integrity sha1-7e9OubCSi6HuX3F//BV3SabdXXY= + dependencies: + w3c-blob "0.0.1" + braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" @@ -17281,6 +17283,11 @@ vm-browserify@0.0.4: dependencies: indexof "0.0.1" +w3c-blob@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/w3c-blob/-/w3c-blob-0.0.1.tgz#b0cd352a1a50f515563420ffd5861f950f1d85b8" + integrity sha1-sM01KhpQ9RVWNCD/1YYflQ8dhbg= + w3c-hr-time@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" From 335042b2d0343155bb735533c4481f69c2d59c45 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 12 Feb 2019 14:29:27 +0100 Subject: [PATCH 117/119] fix: No need to have edit permissions to be able to "Save as" a dashboard --- .../dashboard/components/DashboardSettings/SettingsCtrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts b/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts index e5cfac97d5f..fc3b98b4848 100755 --- a/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts +++ b/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts @@ -38,7 +38,7 @@ export class SettingsCtrl { }); }); - this.canSaveAs = this.dashboard.meta.canEdit && contextSrv.hasEditPermissionInFolders; + this.canSaveAs = contextSrv.hasEditPermissionInFolders; this.canSave = this.dashboard.meta.canSave; this.canDelete = this.dashboard.meta.canSave; From 2c4cb03cd309de05b3712e955ea54e3eced7c9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 12 Feb 2019 12:49:40 +0100 Subject: [PATCH 118/119] Fixed issues with double page body and husky pre-commit hook --- package.json | 3 +- .../datasources/NewDataSourcePage.tsx | 56 +- .../datasources/settings/ButtonRow.tsx | 8 +- .../settings/DataSourceSettingsPage.tsx | 80 +- .../__snapshots__/ButtonRow.test.tsx.snap | 8 - .../DataSourceSettingsPage.test.tsx.snap | 700 +++++++++--------- .../features/folders/FolderSettingsPage.tsx | 50 +- .../FolderSettingsPage.test.tsx.snap | 192 +++-- public/app/features/teams/TeamPages.tsx | 4 +- yarn.lock | 69 +- 10 files changed, 583 insertions(+), 587 deletions(-) diff --git a/package.json b/package.json index f004ee07732..b8e45a52321 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "html-loader": "^0.5.1", "html-webpack-harddisk-plugin": "^0.2.0", "html-webpack-plugin": "^3.2.0", - "husky": "^0.14.3", + "husky": "^1.3.1", "jest": "^23.6.0", "jest-date-mock": "^1.0.6", "lint-staged": "^8.1.3", @@ -120,7 +120,6 @@ "typecheck": "tsc --noEmit", "jest": "jest --notify --watch", "api-tests": "jest --notify --watch --config=tests/api/jest.js", - "precommit": "grunt precommit", "storybook": "cd packages/grafana-ui && yarn storybook" }, "husky": { diff --git a/public/app/features/datasources/NewDataSourcePage.tsx b/public/app/features/datasources/NewDataSourcePage.tsx index 1d926048b8c..f512bdfe3c9 100644 --- a/public/app/features/datasources/NewDataSourcePage.tsx +++ b/public/app/features/datasources/NewDataSourcePage.tsx @@ -35,34 +35,32 @@ class NewDataSourcePage extends PureComponent { return ( -
-

Choose data source type

-
- -
-
- {dataSourceTypes.map((plugin, index) => { - return ( -
this.onDataSourceTypeClicked(plugin)} - className="add-data-source-grid-item" - key={`${plugin.id}-${index}`} - > - - {plugin.name} -
- ); - })} -
+

Choose data source type

+
+ +
+
+ {dataSourceTypes.map((plugin, index) => { + return ( +
this.onDataSourceTypeClicked(plugin)} + className="add-data-source-grid-item" + key={`${plugin.id}-${index}`} + > + + {plugin.name} +
+ ); + })}
@@ -74,7 +72,7 @@ function mapStateToProps(state: StoreState) { return { navModel: getNavModel(state.navIndex, 'datasources'), dataSourceTypes: getDataSourceTypes(state.dataSources), - isLoading: state.dataSources.isLoadingDataSources + isLoading: state.dataSources.isLoadingDataSources, }; } diff --git a/public/app/features/datasources/settings/ButtonRow.tsx b/public/app/features/datasources/settings/ButtonRow.tsx index 36fd6f0283a..9f633ee6bcf 100644 --- a/public/app/features/datasources/settings/ButtonRow.tsx +++ b/public/app/features/datasources/settings/ButtonRow.tsx @@ -10,9 +10,11 @@ export interface Props { const ButtonRow: FC = ({ isReadOnly, onDelete, onSubmit, onTest }) => { return (
- + {!isReadOnly && ( + + )} {isReadOnly && ( - -
- -
+
+
+
+ + +
+
+ + +
+
diff --git a/public/app/features/folders/__snapshots__/FolderSettingsPage.test.tsx.snap b/public/app/features/folders/__snapshots__/FolderSettingsPage.test.tsx.snap index e51e5c0e180..cd6cdf4a032 100644 --- a/public/app/features/folders/__snapshots__/FolderSettingsPage.test.tsx.snap +++ b/public/app/features/folders/__snapshots__/FolderSettingsPage.test.tsx.snap @@ -7,62 +7,58 @@ exports[`Render should enable save button 1`] = ` -
-

+
+
- Folder Settings -

-
- -
- - + +
+
+
-
+ - -
- -
+ + Delete + +
+
@@ -75,62 +71,58 @@ exports[`Render should render component 1`] = ` -
-

+
+
- Folder Settings -

-
- -
- - + +
+
+
-
+ - -
- -
+ + Delete + +
+
diff --git a/public/app/features/teams/TeamPages.tsx b/public/app/features/teams/TeamPages.tsx index 7a38197ff71..235936aa8f5 100644 --- a/public/app/features/teams/TeamPages.tsx +++ b/public/app/features/teams/TeamPages.tsx @@ -49,9 +49,9 @@ export class TeamPages extends PureComponent { async fetchTeam() { const { loadTeam, teamId } = this.props; - this.setState({isLoading: true}); + this.setState({ isLoading: true }); const team = await loadTeam(teamId); - this.setState({isLoading: false}); + this.setState({ isLoading: false }); return team; } diff --git a/yarn.lock b/yarn.lock index 2fb4a5d3ee2..60a72ec86ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4712,6 +4712,11 @@ ci-info@^1.5.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + cidr-regex@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-1.0.6.tgz#74abfd619df370b9d54ab14475568e97dd64c0c1" @@ -7922,6 +7927,11 @@ get-stdin@^4.0.1: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= +get-stdin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" + integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== + get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -8886,14 +8896,21 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -husky@^0.14.3: - version "0.14.3" - resolved "https://registry.yarnpkg.com/husky/-/husky-0.14.3.tgz#c69ed74e2d2779769a17ba8399b54ce0b63c12c3" - integrity sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA== +husky@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/husky/-/husky-1.3.1.tgz#26823e399300388ca2afff11cfa8a86b0033fae0" + integrity sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg== dependencies: - is-ci "^1.0.10" - normalize-path "^1.0.0" - strip-indent "^2.0.0" + cosmiconfig "^5.0.7" + execa "^1.0.0" + find-up "^3.0.0" + get-stdin "^6.0.0" + is-ci "^2.0.0" + pkg-dir "^3.0.0" + please-upgrade-node "^3.1.1" + read-pkg "^4.0.1" + run-node "^1.0.0" + slash "^2.0.0" iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" @@ -9279,6 +9296,13 @@ is-ci@^1.0.10: dependencies: ci-info "^1.5.0" +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + is-cidr@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-1.0.0.tgz#fb5aacf659255310359da32cae03e40c6a1c2afc" @@ -11925,11 +11949,6 @@ normalize-path@2.0.1: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.0.1.tgz#47886ac1662760d4261b7d979d241709d3ce3f7a" integrity sha1-R4hqwWYnYNQmG32XnSQXCdPOP3o= -normalize-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" - integrity sha1-MtDkcvkf80VwHBWoMRAY07CpA3k= - normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -12948,7 +12967,7 @@ pkg-up@^1.0.0: dependencies: find-up "^1.0.0" -please-upgrade-node@^3.0.2: +please-upgrade-node@^3.0.2, please-upgrade-node@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz#ed320051dfcc5024fae696712c8288993595e8ac" integrity sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ== @@ -14336,6 +14355,15 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" +read-pkg@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237" + integrity sha1-ljYlN48+HE1IyFhytabsfV0JMjc= + dependencies: + normalize-package-data "^2.3.2" + parse-json "^4.0.0" + pify "^3.0.0" + read@1, read@~1.0.1, read@~1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" @@ -14990,6 +15018,11 @@ run-async@^2.0.0, run-async@^2.2.0: dependencies: is-promise "^2.1.0" +run-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e" + integrity sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A== + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -15448,6 +15481,11 @@ slash@^1.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + slate-base64-serializer@^0.2.36: version "0.2.94" resolved "https://registry.yarnpkg.com/slate-base64-serializer/-/slate-base64-serializer-0.2.94.tgz#b908c3af481b9a0ead78f313653414c4b2b4b2d5" @@ -16147,11 +16185,6 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" -strip-indent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" - integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= - strip-json-comments@~1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" From e163aadfe4af86f1ef7c0da6b73ce40294ecccfb Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 11 Feb 2019 21:12:01 +0100 Subject: [PATCH 119/119] use authtoken for session quota restrictions closes #15360 --- pkg/api/api.go | 6 ++-- pkg/api/dashboard.go | 9 +++--- pkg/api/dashboard_test.go | 6 +++- pkg/api/http_server.go | 2 ++ pkg/login/ext_user.go | 24 ++++++++++---- pkg/login/ldap_test.go | 5 ++- pkg/middleware/middleware_test.go | 8 +++++ pkg/middleware/quota.go | 24 ++++++++------ pkg/middleware/quota_test.go | 47 ++++++++++++++++++---------- pkg/models/user_token.go | 1 + pkg/services/auth/auth_token.go | 24 ++++++++++---- pkg/services/auth/auth_token_test.go | 12 +++++++ pkg/services/quota/quota.go | 23 ++++++++++++-- 13 files changed, 139 insertions(+), 52 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 0685ef3814d..6da127fb550 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -16,7 +16,7 @@ func (hs *HTTPServer) registerRoutes() { reqOrgAdmin := middleware.ReqOrgAdmin redirectFromLegacyDashboardURL := middleware.RedirectFromLegacyDashboardURL() redirectFromLegacyDashboardSoloURL := middleware.RedirectFromLegacyDashboardSoloURL() - quota := middleware.Quota + quota := middleware.Quota(hs.QuotaService) bind := binding.Bind r := hs.RouteRegister @@ -286,7 +286,7 @@ func (hs *HTTPServer) registerRoutes() { dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), Wrap(CalculateDashboardDiff)) - dashboardRoute.Post("/db", bind(m.SaveDashboardCommand{}), Wrap(PostDashboard)) + dashboardRoute.Post("/db", bind(m.SaveDashboardCommand{}), Wrap(hs.PostDashboard)) dashboardRoute.Get("/home", Wrap(GetHomeDashboard)) dashboardRoute.Get("/tags", GetDashboardTags) dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), Wrap(ImportDashboard)) @@ -294,7 +294,7 @@ func (hs *HTTPServer) registerRoutes() { dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute routing.RouteRegister) { dashIdRoute.Get("/versions", Wrap(GetDashboardVersions)) dashIdRoute.Get("/versions/:id", Wrap(GetDashboardVersion)) - dashIdRoute.Post("/restore", bind(dtos.RestoreDashboardVersionCommand{}), Wrap(RestoreDashboardVersion)) + dashIdRoute.Post("/restore", bind(dtos.RestoreDashboardVersionCommand{}), Wrap(hs.RestoreDashboardVersion)) dashIdRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) { dashboardPermissionRoute.Get("/", Wrap(GetDashboardPermissionList)) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 2789b0bf51e..20d717ef8fa 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -18,7 +18,6 @@ import ( m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/guardian" - "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -208,14 +207,14 @@ func DeleteDashboardByUID(c *m.ReqContext) Response { }) } -func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response { +func (hs *HTTPServer) PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response { cmd.OrgId = c.OrgId cmd.UserId = c.UserId dash := cmd.GetDashboardModel() if dash.Id == 0 && dash.Uid == "" { - limitReached, err := quota.QuotaReached(c, "dashboard") + limitReached, err := hs.QuotaService.QuotaReached(c, "dashboard") if err != nil { return Error(500, "failed to get quota", err) } @@ -463,7 +462,7 @@ func CalculateDashboardDiff(c *m.ReqContext, apiOptions dtos.CalculateDiffOption } // RestoreDashboardVersion restores a dashboard to the given version. -func RestoreDashboardVersion(c *m.ReqContext, apiCmd dtos.RestoreDashboardVersionCommand) Response { +func (hs *HTTPServer) RestoreDashboardVersion(c *m.ReqContext, apiCmd dtos.RestoreDashboardVersionCommand) Response { dash, rsp := getDashboardHelper(c.OrgId, "", c.ParamsInt64(":dashboardId"), "") if rsp != nil { return rsp @@ -490,7 +489,7 @@ func RestoreDashboardVersion(c *m.ReqContext, apiCmd dtos.RestoreDashboardVersio saveCmd.Dashboard.Set("uid", dash.Uid) saveCmd.Message = fmt.Sprintf("Restored from version %d", version.Version) - return PostDashboard(c, saveCmd) + return hs.PostDashboard(c, saveCmd) } func GetDashboardTags(c *m.ReqContext) { diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 8ee40920cbc..44d5cd32430 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -881,12 +881,16 @@ func postDashboardScenario(desc string, url string, routePattern string, mock *d Convey(desc+" "+url, func() { defer bus.ClearBusHandlers() + hs := HTTPServer{ + Bus: bus.GetBus(), + } + sc := setupScenarioContext(url) sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { sc.context = c sc.context.SignedInUser = &m.SignedInUser{OrgId: cmd.OrgId, UserId: cmd.UserId} - return PostDashboard(c, cmd) + return hs.PostDashboard(c, cmd) }) origNewDashboardService := dashboards.NewService diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index cadf6896bf4..2a430147b55 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -24,6 +24,7 @@ import ( "github.com/grafana/grafana/pkg/services/cache" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/hooks" + "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/session" "github.com/grafana/grafana/pkg/setting" @@ -55,6 +56,7 @@ type HTTPServer struct { CacheService *cache.CacheService `inject:""` DatasourceCache datasources.CacheService `inject:""` AuthTokenService models.UserTokenService `inject:""` + QuotaService *quota.QuotaService `inject:""` } func (hs *HTTPServer) Init() error { diff --git a/pkg/login/ext_user.go b/pkg/login/ext_user.go index 42fb37ff9d0..f217f9fe33c 100644 --- a/pkg/login/ext_user.go +++ b/pkg/login/ext_user.go @@ -4,18 +4,30 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/quota" ) func init() { - bus.AddHandler("auth", UpsertUser) + registry.RegisterService(&LoginService{}) } var ( logger = log.New("login.ext_user") ) -func UpsertUser(cmd *m.UpsertUserCommand) error { +type LoginService struct { + Bus bus.Bus `inject:""` + QuotaService *quota.QuotaService `inject:""` +} + +func (ls *LoginService) Init() error { + ls.Bus.AddHandler(ls.UpsertUser) + + return nil +} + +func (ls *LoginService) UpsertUser(cmd *m.UpsertUserCommand) error { extUser := cmd.ExternalUser userQuery := &m.GetUserByAuthInfoQuery{ @@ -37,7 +49,7 @@ func UpsertUser(cmd *m.UpsertUserCommand) error { return ErrInvalidCredentials } - limitReached, err := quota.QuotaReached(cmd.ReqContext, "user") + limitReached, err := ls.QuotaService.QuotaReached(cmd.ReqContext, "user") if err != nil { log.Warn("Error getting user quota. error: %v", err) return ErrGettingUserQuota @@ -57,7 +69,7 @@ func UpsertUser(cmd *m.UpsertUserCommand) error { AuthModule: extUser.AuthModule, AuthId: extUser.AuthId, } - if err := bus.Dispatch(cmd2); err != nil { + if err := ls.Bus.Dispatch(cmd2); err != nil { return err } } @@ -78,12 +90,12 @@ func UpsertUser(cmd *m.UpsertUserCommand) error { // Sync isGrafanaAdmin permission if extUser.IsGrafanaAdmin != nil && *extUser.IsGrafanaAdmin != cmd.Result.IsAdmin { - if err := bus.Dispatch(&m.UpdateUserPermissionsCommand{UserId: cmd.Result.Id, IsGrafanaAdmin: *extUser.IsGrafanaAdmin}); err != nil { + if err := ls.Bus.Dispatch(&m.UpdateUserPermissionsCommand{UserId: cmd.Result.Id, IsGrafanaAdmin: *extUser.IsGrafanaAdmin}); err != nil { return err } } - err = bus.Dispatch(&m.SyncTeamsCommand{ + err = ls.Bus.Dispatch(&m.SyncTeamsCommand{ User: cmd.Result, ExternalUser: extUser, }) diff --git a/pkg/login/ldap_test.go b/pkg/login/ldap_test.go index c02fa02e030..ef20feb1373 100644 --- a/pkg/login/ldap_test.go +++ b/pkg/login/ldap_test.go @@ -395,8 +395,11 @@ func ldapAutherScenario(desc string, fn scenarioFunc) { defer bus.ClearBusHandlers() sc := &scenarioContext{} + loginService := &LoginService{ + Bus: bus.GetBus(), + } - bus.AddHandler("test", UpsertUser) + bus.AddHandler("test", loginService.UpsertUser) bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.SyncTeamsCommand) error { return nil diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index 8545c3856c9..1fbd303bebd 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -682,6 +682,7 @@ type fakeUserAuthTokenService struct { tryRotateTokenProvider func(token *m.UserToken, clientIP, userAgent string) (bool, error) lookupTokenProvider func(unhashedToken string) (*m.UserToken, error) revokeTokenProvider func(token *m.UserToken) error + activeAuthTokenCount func() (int64, error) } func newFakeUserAuthTokenService() *fakeUserAuthTokenService { @@ -704,6 +705,9 @@ func newFakeUserAuthTokenService() *fakeUserAuthTokenService { revokeTokenProvider: func(token *m.UserToken) error { return nil }, + activeAuthTokenCount: func() (int64, error) { + return 10, nil + }, } } @@ -722,3 +726,7 @@ func (s *fakeUserAuthTokenService) TryRotateToken(token *m.UserToken, clientIP, func (s *fakeUserAuthTokenService) RevokeToken(token *m.UserToken) error { return s.revokeTokenProvider(token) } + +func (s *fakeUserAuthTokenService) ActiveTokenCount() (int64, error) { + return s.activeAuthTokenCount() +} diff --git a/pkg/middleware/quota.go b/pkg/middleware/quota.go index 43efca43485..51f906e2c92 100644 --- a/pkg/middleware/quota.go +++ b/pkg/middleware/quota.go @@ -9,16 +9,20 @@ import ( "github.com/grafana/grafana/pkg/services/quota" ) -func Quota(target string) macaron.Handler { - return func(c *m.ReqContext) { - limitReached, err := quota.QuotaReached(c, target) - if err != nil { - c.JsonApiErr(500, "failed to get quota", err) - return - } - if limitReached { - c.JsonApiErr(403, fmt.Sprintf("%s Quota reached", target), nil) - return +// Quota returns a function that returns a function used to call quotaservice based on target name +func Quota(quotaService *quota.QuotaService) func(target string) macaron.Handler { + //https://open.spotify.com/track/7bZSoBEAEEUsGEuLOf94Jm?si=T1Tdju5qRSmmR0zph_6RBw fuuuuunky + return func(target string) macaron.Handler { + return func(c *m.ReqContext) { + limitReached, err := quotaService.QuotaReached(c, target) + if err != nil { + c.JsonApiErr(500, "failed to get quota", err) + return + } + if limitReached { + c.JsonApiErr(403, fmt.Sprintf("%s Quota reached", target), nil) + return + } } } } diff --git a/pkg/middleware/quota_test.go b/pkg/middleware/quota_test.go index e2a6ef63377..52b696cf037 100644 --- a/pkg/middleware/quota_test.go +++ b/pkg/middleware/quota_test.go @@ -3,9 +3,10 @@ package middleware import ( "testing" + "github.com/grafana/grafana/pkg/services/quota" + "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/session" "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" ) @@ -13,10 +14,6 @@ import ( func TestMiddlewareQuota(t *testing.T) { Convey("Given the grafana quota middleware", t, func() { - session.GetSessionCount = func() int { - return 4 - } - setting.AnonymousEnabled = false setting.Quota = setting.QuotaSettings{ Enabled: true, @@ -39,6 +36,12 @@ func TestMiddlewareQuota(t *testing.T) { }, } + fakeAuthTokenService := newFakeUserAuthTokenService() + qs := "a.QuotaService{ + AuthTokenService: fakeAuthTokenService, + } + QuotaFn := Quota(qs) + middlewareScenario("with user not logged in", func(sc *scenarioContext) { bus.AddHandler("globalQuota", func(query *m.GetGlobalQuotaByTargetQuery) error { query.Result = &m.GlobalQuotaDTO{ @@ -48,26 +51,30 @@ func TestMiddlewareQuota(t *testing.T) { } return nil }) + Convey("global quota not reached", func() { - sc.m.Get("/user", Quota("user"), sc.defaultHandler) + sc.m.Get("/user", QuotaFn("user"), sc.defaultHandler) sc.fakeReq("GET", "/user").exec() So(sc.resp.Code, ShouldEqual, 200) }) + Convey("global quota reached", func() { setting.Quota.Global.User = 4 - sc.m.Get("/user", Quota("user"), sc.defaultHandler) + sc.m.Get("/user", QuotaFn("user"), sc.defaultHandler) sc.fakeReq("GET", "/user").exec() So(sc.resp.Code, ShouldEqual, 403) }) + Convey("global session quota not reached", func() { setting.Quota.Global.Session = 10 - sc.m.Get("/user", Quota("session"), sc.defaultHandler) + sc.m.Get("/user", QuotaFn("session"), sc.defaultHandler) sc.fakeReq("GET", "/user").exec() So(sc.resp.Code, ShouldEqual, 200) }) + Convey("global session quota reached", func() { setting.Quota.Global.Session = 1 - sc.m.Get("/user", Quota("session"), sc.defaultHandler) + sc.m.Get("/user", QuotaFn("session"), sc.defaultHandler) sc.fakeReq("GET", "/user").exec() So(sc.resp.Code, ShouldEqual, 403) }) @@ -95,6 +102,7 @@ func TestMiddlewareQuota(t *testing.T) { } return nil }) + bus.AddHandler("userQuota", func(query *m.GetUserQuotaByTargetQuery) error { query.Result = &m.UserQuotaDTO{ Target: query.Target, @@ -103,6 +111,7 @@ func TestMiddlewareQuota(t *testing.T) { } return nil }) + bus.AddHandler("orgQuota", func(query *m.GetOrgQuotaByTargetQuery) error { query.Result = &m.OrgQuotaDTO{ Target: query.Target, @@ -111,45 +120,49 @@ func TestMiddlewareQuota(t *testing.T) { } return nil }) + Convey("global datasource quota reached", func() { setting.Quota.Global.DataSource = 4 - sc.m.Get("/ds", Quota("data_source"), sc.defaultHandler) + sc.m.Get("/ds", QuotaFn("data_source"), sc.defaultHandler) sc.fakeReq("GET", "/ds").exec() So(sc.resp.Code, ShouldEqual, 403) }) + Convey("user Org quota not reached", func() { setting.Quota.User.Org = 5 - sc.m.Get("/org", Quota("org"), sc.defaultHandler) + sc.m.Get("/org", QuotaFn("org"), sc.defaultHandler) sc.fakeReq("GET", "/org").exec() So(sc.resp.Code, ShouldEqual, 200) }) + Convey("user Org quota reached", func() { setting.Quota.User.Org = 4 - sc.m.Get("/org", Quota("org"), sc.defaultHandler) + sc.m.Get("/org", QuotaFn("org"), sc.defaultHandler) sc.fakeReq("GET", "/org").exec() So(sc.resp.Code, ShouldEqual, 403) }) + Convey("org dashboard quota not reached", func() { setting.Quota.Org.Dashboard = 10 - sc.m.Get("/dashboard", Quota("dashboard"), sc.defaultHandler) + sc.m.Get("/dashboard", QuotaFn("dashboard"), sc.defaultHandler) sc.fakeReq("GET", "/dashboard").exec() So(sc.resp.Code, ShouldEqual, 200) }) + Convey("org dashboard quota reached", func() { setting.Quota.Org.Dashboard = 4 - sc.m.Get("/dashboard", Quota("dashboard"), sc.defaultHandler) + sc.m.Get("/dashboard", QuotaFn("dashboard"), sc.defaultHandler) sc.fakeReq("GET", "/dashboard").exec() So(sc.resp.Code, ShouldEqual, 403) }) + Convey("org dashboard quota reached but quotas disabled", func() { setting.Quota.Org.Dashboard = 4 setting.Quota.Enabled = false - sc.m.Get("/dashboard", Quota("dashboard"), sc.defaultHandler) + sc.m.Get("/dashboard", QuotaFn("dashboard"), sc.defaultHandler) sc.fakeReq("GET", "/dashboard").exec() So(sc.resp.Code, ShouldEqual, 200) }) - }) - }) } diff --git a/pkg/models/user_token.go b/pkg/models/user_token.go index c8084cf1eba..388bc2dd4a2 100644 --- a/pkg/models/user_token.go +++ b/pkg/models/user_token.go @@ -29,4 +29,5 @@ type UserTokenService interface { LookupToken(unhashedToken string) (*UserToken, error) TryRotateToken(token *UserToken, clientIP, userAgent string) (bool, error) RevokeToken(token *UserToken) error + ActiveTokenCount() (int64, error) } diff --git a/pkg/services/auth/auth_token.go b/pkg/services/auth/auth_token.go index ef5dccd779f..648575d54cd 100644 --- a/pkg/services/auth/auth_token.go +++ b/pkg/services/auth/auth_token.go @@ -35,6 +35,13 @@ func (s *UserAuthTokenService) Init() error { return nil } +func (s *UserAuthTokenService) ActiveTokenCount() (int64, error) { + var model userAuthToken + count, err := s.SQLStore.NewSession().Where(`created_at > ? AND rotated_at > ?`, s.createdAfterParam(), s.rotatedAfterParam()).Count(&model) + + return count, err +} + func (s *UserAuthTokenService) CreateToken(userId int64, clientIP, userAgent string) (*models.UserToken, error) { clientIP = util.ParseIPAddress(clientIP) token, err := util.RandomHex(16) @@ -79,13 +86,8 @@ func (s *UserAuthTokenService) LookupToken(unhashedToken string) (*models.UserTo s.log.Debug("looking up token", "unhashed", unhashedToken, "hashed", hashedToken) } - tokenMaxLifetime := time.Duration(s.Cfg.LoginMaxLifetimeDays) * 24 * time.Hour - tokenMaxInactiveLifetime := time.Duration(s.Cfg.LoginMaxInactiveLifetimeDays) * 24 * time.Hour - createdAfter := getTime().Add(-tokenMaxLifetime).Unix() - rotatedAfter := getTime().Add(-tokenMaxInactiveLifetime).Unix() - var model userAuthToken - exists, err := s.SQLStore.NewSession().Where("(auth_token = ? OR prev_auth_token = ?) AND created_at > ? AND rotated_at > ?", hashedToken, hashedToken, createdAfter, rotatedAfter).Get(&model) + exists, err := s.SQLStore.NewSession().Where("(auth_token = ? OR prev_auth_token = ?) AND created_at > ? AND rotated_at > ?", hashedToken, hashedToken, s.createdAfterParam(), s.rotatedAfterParam()).Get(&model) if err != nil { return nil, err } @@ -219,6 +221,16 @@ func (s *UserAuthTokenService) RevokeToken(token *models.UserToken) error { return nil } +func (s *UserAuthTokenService) createdAfterParam() int64 { + tokenMaxLifetime := time.Duration(s.Cfg.LoginMaxLifetimeDays) * 24 * time.Hour + return getTime().Add(-tokenMaxLifetime).Unix() +} + +func (s *UserAuthTokenService) rotatedAfterParam() int64 { + tokenMaxInactiveLifetime := time.Duration(s.Cfg.LoginMaxInactiveLifetimeDays) * 24 * time.Hour + return getTime().Add(-tokenMaxInactiveLifetime).Unix() +} + func hashToken(token string) string { hashBytes := sha256.Sum256([]byte(token + setting.SecretKey)) return hex.EncodeToString(hashBytes[:]) diff --git a/pkg/services/auth/auth_token_test.go b/pkg/services/auth/auth_token_test.go index 26dcbc5c868..49e7acc3a5b 100644 --- a/pkg/services/auth/auth_token_test.go +++ b/pkg/services/auth/auth_token_test.go @@ -31,6 +31,12 @@ func TestUserAuthToken(t *testing.T) { So(userToken, ShouldNotBeNil) So(userToken.AuthTokenSeen, ShouldBeFalse) + Convey("Can count active tokens", func() { + count, err := userAuthTokenService.ActiveTokenCount() + So(err, ShouldBeNil) + So(count, ShouldEqual, 1) + }) + Convey("When lookup unhashed token should return user auth token", func() { userToken, err := userAuthTokenService.LookupToken(userToken.UnhashedToken) So(err, ShouldBeNil) @@ -114,6 +120,12 @@ func TestUserAuthToken(t *testing.T) { notGood, err := userAuthTokenService.LookupToken(userToken.UnhashedToken) So(err, ShouldEqual, models.ErrUserTokenNotFound) So(notGood, ShouldBeNil) + + Convey("should not find active token when expired", func() { + count, err := userAuthTokenService.ActiveTokenCount() + So(err, ShouldBeNil) + So(count, ShouldEqual, 0) + }) }) Convey("when rotated_at is 5 days ago and created_at is 29 days and 23:59:59 ago should not find token", func() { diff --git a/pkg/services/quota/quota.go b/pkg/services/quota/quota.go index 2ec399437e6..ff2528e31e8 100644 --- a/pkg/services/quota/quota.go +++ b/pkg/services/quota/quota.go @@ -3,11 +3,23 @@ package quota import ( "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/session" + "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" ) -func QuotaReached(c *m.ReqContext, target string) (bool, error) { +func init() { + registry.RegisterService(&QuotaService{}) +} + +type QuotaService struct { + AuthTokenService m.UserTokenService `inject:""` +} + +func (qs *QuotaService) Init() error { + return nil +} + +func (qs *QuotaService) QuotaReached(c *m.ReqContext, target string) (bool, error) { if !setting.Quota.Enabled { return false, nil } @@ -30,7 +42,12 @@ func QuotaReached(c *m.ReqContext, target string) (bool, error) { return true, nil } if target == "session" { - usedSessions := session.GetSessionCount() + + usedSessions, err := qs.AuthTokenService.ActiveTokenCount() + if err != nil { + return false, err + } + if int64(usedSessions) > scope.DefaultLimit { c.Logger.Debug("Sessions limit reached", "active", usedSessions, "limit", scope.DefaultLimit) return true, nil