From fc9014f9204df635577c4bc0cb57a0bfdb21e25b Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Fri, 16 Mar 2018 12:36:36 +0100 Subject: [PATCH 01/57] added indent to dashboards inside folder in search dropdown, and added indent to dashboard icon in search item --- public/app/core/components/search/search_results.html | 2 +- public/sass/components/_search.scss | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/core/components/search/search_results.html b/public/app/core/components/search/search_results.html index 4e5bc88e0a9..7435f8d0b7e 100644 --- a/public/app/core/components/search/search_results.html +++ b/public/app/core/components/search/search_results.html @@ -20,7 +20,7 @@
- +
Date: Sat, 24 Mar 2018 14:41:41 +0100 Subject: [PATCH 02/57] Add hints for the 'pd' Duplicate Panel command from PR #11264 --- docs/sources/features/shortcuts.md | 1 + public/app/core/components/help/help.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/sources/features/shortcuts.md b/docs/sources/features/shortcuts.md index cbcf3670c83..88c645eafdf 100644 --- a/docs/sources/features/shortcuts.md +++ b/docs/sources/features/shortcuts.md @@ -42,6 +42,7 @@ Hit `?` on your keyboard to open the shortcuts help modal. - `e` Toggle panel edit view - `v` Toggle panel fullscreen view - `p` `s` Open Panel Share Modal +- `p` `d` Duplicate Panel - `p` `r` Remove Panel ### Time Range diff --git a/public/app/core/components/help/help.ts b/public/app/core/components/help/help.ts index a544fc89854..a1d3c34ae5b 100644 --- a/public/app/core/components/help/help.ts +++ b/public/app/core/components/help/help.ts @@ -31,6 +31,7 @@ export class HelpCtrl { { keys: ['e'], description: 'Toggle panel edit view' }, { keys: ['v'], description: 'Toggle panel fullscreen view' }, { keys: ['p', 's'], description: 'Open Panel Share Modal' }, + { keys: ['p', 'd'], description: 'Duplicate Panel' }, { keys: ['p', 'r'], description: 'Remove Panel' }, ], 'Time Range': [ From 03b2561af29a7801977b213832d9ed33e83b1e3b Mon Sep 17 00:00:00 2001 From: Tobias Wolf Date: Sat, 24 Mar 2018 14:49:06 +0100 Subject: [PATCH 03/57] Missed the 'p d' hint in the popup-menu --- public/app/features/panel/panel_ctrl.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 429408ed803..f54877c2c37 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -190,6 +190,7 @@ export class PanelCtrl { text: 'Duplicate', click: 'ctrl.duplicate()', role: 'Editor', + shortcut: 'p d', }); menu.push({ From 391868c5d61c7844a9b71b9e93328d3479faf457 Mon Sep 17 00:00:00 2001 From: Julian Kornberger Date: Wed, 21 Mar 2018 18:28:56 +0100 Subject: [PATCH 04/57] Use net.SplitHostPort to support IPv6 - Add some tests - Make error message more helpful --- pkg/middleware/auth_proxy.go | 32 ++++++++++++++----------------- pkg/middleware/middleware_test.go | 26 +++++++++++++++++++++---- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/pkg/middleware/auth_proxy.go b/pkg/middleware/auth_proxy.go index e1801404453..adf0b7b53d5 100644 --- a/pkg/middleware/auth_proxy.go +++ b/pkg/middleware/auth_proxy.go @@ -1,8 +1,8 @@ package middleware import ( - "errors" "fmt" + "net" "strings" "time" @@ -25,7 +25,7 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool { } // if auth proxy ip(s) defined, check if request comes from one of those - if err := checkAuthenticationProxy(ctx, proxyHeaderValue); err != nil { + if err := checkAuthenticationProxy(ctx.Req.RemoteAddr, proxyHeaderValue); err != nil { ctx.Handle(407, "Proxy authentication required", err) return true } @@ -123,29 +123,25 @@ var syncGrafanaUserWithLdapUser = func(ctx *m.ReqContext, query *m.GetSignedInUs return nil } -func checkAuthenticationProxy(ctx *m.ReqContext, proxyHeaderValue string) error { +func checkAuthenticationProxy(remoteAddr string, proxyHeaderValue string) error { if len(strings.TrimSpace(setting.AuthProxyWhitelist)) == 0 { return nil } + proxies := strings.Split(setting.AuthProxyWhitelist, ",") - remoteAddrSplit := strings.Split(ctx.Req.RemoteAddr, ":") - sourceIP := remoteAddrSplit[0] - - found := false - for _, proxyIP := range proxies { - if sourceIP == strings.TrimSpace(proxyIP) { - found = true - break - } - } - - if !found { - msg := fmt.Sprintf("Request for user (%s) is not from the authentication proxy", proxyHeaderValue) - err := errors.New(msg) + sourceIP, _, err := net.SplitHostPort(remoteAddr) + if err != nil { return err } - return nil + // Compare allowed IP addresses to actual address + for _, proxyIP := range proxies { + if sourceIP == strings.TrimSpace(proxyIP) { + return nil + } + } + + return fmt.Errorf("Request for user (%s) from %s is not from the authentication proxy", proxyHeaderValue, sourceIP) } func getSignedInUserQueryForProxyAuth(headerVal string) *m.GetSignedInUserQuery { diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index c8e9e535cfa..f80a30de02f 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -226,11 +226,11 @@ func TestMiddlewareContext(t *testing.T) { }) }) - middlewareScenario("When auth_proxy is enabled and request RemoteAddr is not trusted", func(sc *scenarioContext) { + middlewareScenario("When auth_proxy is enabled and IPv4 request RemoteAddr is not trusted", func(sc *scenarioContext) { setting.AuthProxyEnabled = true setting.AuthProxyHeaderName = "X-WEBAUTH-USER" setting.AuthProxyHeaderProperty = "username" - setting.AuthProxyWhitelist = "192.168.1.1, 192.168.2.1" + setting.AuthProxyWhitelist = "192.168.1.1, 2001::23" sc.fakeReq("GET", "/") sc.req.Header.Add("X-WEBAUTH-USER", "torkelo") @@ -239,6 +239,24 @@ func TestMiddlewareContext(t *testing.T) { Convey("should return 407 status code", func() { So(sc.resp.Code, ShouldEqual, 407) + So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 192.168.3.1 is not from the authentication proxy") + }) + }) + + middlewareScenario("When auth_proxy is enabled and IPv6 request RemoteAddr is not trusted", func(sc *scenarioContext) { + setting.AuthProxyEnabled = true + setting.AuthProxyHeaderName = "X-WEBAUTH-USER" + setting.AuthProxyHeaderProperty = "username" + setting.AuthProxyWhitelist = "192.168.1.1, 2001::23" + + sc.fakeReq("GET", "/") + sc.req.Header.Add("X-WEBAUTH-USER", "torkelo") + sc.req.RemoteAddr = "[2001:23]:12345" + sc.exec() + + Convey("should return 407 status code", func() { + So(sc.resp.Code, ShouldEqual, 407) + So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 2001:23 is not from the authentication proxy") }) }) @@ -246,7 +264,7 @@ func TestMiddlewareContext(t *testing.T) { setting.AuthProxyEnabled = true setting.AuthProxyHeaderName = "X-WEBAUTH-USER" setting.AuthProxyHeaderProperty = "username" - setting.AuthProxyWhitelist = "192.168.1.1, 192.168.2.1" + setting.AuthProxyWhitelist = "192.168.1.1, 2001::23" bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { query.Result = &m.SignedInUser{OrgId: 4, UserId: 33} @@ -255,7 +273,7 @@ func TestMiddlewareContext(t *testing.T) { sc.fakeReq("GET", "/") sc.req.Header.Add("X-WEBAUTH-USER", "torkelo") - sc.req.RemoteAddr = "192.168.2.1:12345" + sc.req.RemoteAddr = "[2001::23]:12345" sc.exec() Convey("Should init context with user info", func() { From b61bc72e2a072f7aa1ea66ed4fd972165c8d1208 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 26 Mar 2018 13:17:39 +0200 Subject: [PATCH 05/57] provisioning: removes id from dashboard.json if dashboard json files contains `id` we will just remove it form the dashboard model before importing it into the database. closes #11138 --- .../provisioning/dashboards/file_reader.go | 4 +- .../dashboards/file_reader_test.go | 19 +++++- .../containing-id/dashboard1.json | 68 +++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 pkg/services/provisioning/dashboards/test-dashboards/containing-id/dashboard1.json diff --git a/pkg/services/provisioning/dashboards/file_reader.go b/pkg/services/provisioning/dashboards/file_reader.go index d3e9892c8f5..de0a49d34d9 100644 --- a/pkg/services/provisioning/dashboards/file_reader.go +++ b/pkg/services/provisioning/dashboards/file_reader.go @@ -170,8 +170,8 @@ func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.Fil } if dash.Dashboard.Id != 0 { - fr.log.Error("provisioned dashboard json files cannot contain id") - return provisioningMetadata, nil + dash.Dashboard.Data.Set("id", nil) + dash.Dashboard.Id = 0 } if alreadyProvisioned { diff --git a/pkg/services/provisioning/dashboards/file_reader_test.go b/pkg/services/provisioning/dashboards/file_reader_test.go index cd5e3456734..8a301987ea6 100644 --- a/pkg/services/provisioning/dashboards/file_reader_test.go +++ b/pkg/services/provisioning/dashboards/file_reader_test.go @@ -15,9 +15,10 @@ import ( ) var ( - defaultDashboards string = "./test-dashboards/folder-one" - brokenDashboards string = "./test-dashboards/broken-dashboards" - oneDashboard string = "./test-dashboards/one-dashboard" + defaultDashboards = "./test-dashboards/folder-one" + brokenDashboards = "./test-dashboards/broken-dashboards" + oneDashboard = "./test-dashboards/one-dashboard" + containingId = "./test-dashboards/containing-id" fakeService *fakeDashboardProvisioningService ) @@ -85,6 +86,18 @@ func TestDashboardFileReader(t *testing.T) { So(len(fakeService.inserted), ShouldEqual, 1) }) + Convey("Overrides id from dashboard.json files", func() { + cfg.Options["path"] = containingId + + reader, err := NewDashboardFileReader(cfg, logger) + So(err, ShouldBeNil) + + err = reader.startWalkingDisk() + So(err, ShouldBeNil) + + So(len(fakeService.inserted), ShouldEqual, 1) + }) + Convey("Invalid configuration should return error", func() { cfg := &DashboardsAsConfig{ Name: "Default", diff --git a/pkg/services/provisioning/dashboards/test-dashboards/containing-id/dashboard1.json b/pkg/services/provisioning/dashboards/test-dashboards/containing-id/dashboard1.json new file mode 100644 index 00000000000..12a8b81eee6 --- /dev/null +++ b/pkg/services/provisioning/dashboards/test-dashboards/containing-id/dashboard1.json @@ -0,0 +1,68 @@ +{ + "title": "Grafana1", + "tags": [], + "id": 3, + "style": "dark", + "timezone": "browser", + "editable": true, + "rows": [ + { + "title": "New row", + "height": "150px", + "collapse": false, + "editable": true, + "panels": [ + { + "id": 1, + "span": 12, + "editable": true, + "type": "text", + "mode": "html", + "content": "
\n \n
", + "style": {}, + "title": "Welcome to" + } + ] + } + ], + "nav": [ + { + "type": "timepicker", + "collapse": false, + "enable": true, + "status": "Stable", + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ], + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "now": true + } + ], + "time": { + "from": "now-6h", + "to": "now" + }, + "templating": { + "list": [] + }, + "version": 5 + } From ff62036f15a54bd005252854726dfbeffb26d6f8 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Mon, 26 Mar 2018 14:14:57 +0200 Subject: [PATCH 06/57] styled login page for ie11 --- public/sass/pages/_login.scss | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/public/sass/pages/_login.scss b/public/sass/pages/_login.scss index 8622eec4e99..de10808f122 100644 --- a/public/sass/pages/_login.scss +++ b/public/sass/pages/_login.scss @@ -3,6 +3,7 @@ $login-border: #8daac5; .login { background-position: center; min-height: 85vh; + height: 80vh; background-repeat: no-repeat; min-width: 100%; margin-left: 0; @@ -290,9 +291,14 @@ select:-webkit-autofill:focus { } @include media-breakpoint-up(md) { + .login-content { + flex: 1 0 100%; + } + .login-branding { width: 45%; padding: 2rem 4rem; + flex-grow: 1; .logo-icon { width: 130px; @@ -371,7 +377,7 @@ select:-webkit-autofill:focus { left: 0; right: 0; height: 100%; - content: ""; + content: ''; display: block; } From bf4273b5844956e3b3ac6df3264e600ca29e23e1 Mon Sep 17 00:00:00 2001 From: Thomas Rohlik Date: Mon, 26 Mar 2018 16:34:49 +0200 Subject: [PATCH 07/57] Add new currency - Czech koruna Currency used in Czech republic. --- public/app/core/utils/kbn.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/app/core/utils/kbn.ts b/public/app/core/utils/kbn.ts index 4a29f3983e1..dcb04a3e38e 100644 --- a/public/app/core/utils/kbn.ts +++ b/public/app/core/utils/kbn.ts @@ -447,6 +447,7 @@ kbn.valueFormats.currencyDKK = kbn.formatBuilders.currency('kr'); kbn.valueFormats.currencyISK = kbn.formatBuilders.currency('kr'); kbn.valueFormats.currencyNOK = kbn.formatBuilders.currency('kr'); kbn.valueFormats.currencySEK = kbn.formatBuilders.currency('kr'); +kbn.valueFormats.currencyCZK = kbn.formatBuilders.currency('czk'); // Data (Binary) kbn.valueFormats.bits = kbn.formatBuilders.binarySIPrefix('b'); @@ -869,6 +870,7 @@ kbn.getUnitFormats = function() { { text: 'Icelandic Króna (kr)', value: 'currencyISK' }, { text: 'Norwegian Krone (kr)', value: 'currencyNOK' }, { text: 'Swedish Krona (kr)', value: 'currencySEK' }, + { text: 'Czech koruna (czk)', value: 'currencyCZK' }, ], }, { From dbcba4a0094f9c360216182d0d326a0bca127d09 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Tue, 27 Mar 2018 10:41:47 +0200 Subject: [PATCH 08/57] sidemenu fix for internet explorer 11, changed icon width/height to pixels and added height to logo --- public/sass/base/_icons.scss | 6 ++++-- public/sass/components/_sidemenu.scss | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/public/sass/base/_icons.scss b/public/sass/base/_icons.scss index c701cc1249e..31e5ee62d6f 100644 --- a/public/sass/base/_icons.scss +++ b/public/sass/base/_icons.scss @@ -1,8 +1,10 @@ .gicon { line-height: 1; display: inline-block; - width: 1.1057142857em; - height: 1.1057142857em; + //width: 1.1057142857em; + //height: 1.1057142857em; + height: 22px; + width: 22px; text-align: center; background-repeat: no-repeat; background-position: center; diff --git a/public/sass/components/_sidemenu.scss b/public/sass/components/_sidemenu.scss index 8a5c3779714..e48ab0597a2 100644 --- a/public/sass/components/_sidemenu.scss +++ b/public/sass/components/_sidemenu.scss @@ -178,6 +178,7 @@ li.sidemenu-org-switcher { padding: 0.4rem 1rem 0.4rem 0.65rem; min-height: $navbarHeight; position: relative; + height: $navbarHeight - 1px; &:hover { background: $navbarButtonBackgroundHighlight; From e622d5582b29b2aea982bca8893bb07c0771f6af Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 27 Mar 2018 10:56:19 +0200 Subject: [PATCH 09/57] changelog: adds note about closing #11102 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4e962ba301..a940ff044db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ * **Singlestat**: Add color to prefix and postfix in singlestat panel [#11143](https://github.com/grafana/grafana/pull/11143), thx [@ApsOps](https://github.com/ApsOps) * **Dashboards**: Version cleanup fails on old databases with many entries [#11278](https://github.com/grafana/grafana/issues/11278) * **Server**: Adjust permissions of unix socket [#11343](https://github.com/grafana/grafana/pull/11343), thx [@corny](https://github.com/corny) +* **Shortcuts**: Add shortcut for duplicate panel [#11102](https://github.com/grafana/grafana/issues/11102) # 5.0.4 (unreleased) * **Dashboard** Fixed bug where collapsed panels could not be directly linked to/renderer [#11114](https://github.com/grafana/grafana/issues/11114) & [#11086](https://github.com/grafana/grafana/issues/11086) From d4be953d23a4dd15eb2c26a003c11cd0f40dc898 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Tue, 27 Mar 2018 12:36:13 +0200 Subject: [PATCH 10/57] fixed alignment in search + fixed issue ie popup --- public/app/features/dashboard/unsaved_changes_srv.ts | 4 ++-- public/sass/components/_search.scss | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard/unsaved_changes_srv.ts b/public/app/features/dashboard/unsaved_changes_srv.ts index ebf0101cee0..d4c12b8bcd6 100644 --- a/public/app/features/dashboard/unsaved_changes_srv.ts +++ b/public/app/features/dashboard/unsaved_changes_srv.ts @@ -35,12 +35,12 @@ export class Tracker { $window.onbeforeunload = () => { if (this.ignoreChanges()) { - return null; + return undefined; } if (this.hasChanges()) { return 'There are unsaved changes to this dashboard'; } - return null; + return undefined; }; scope.$on('$locationChangeStart', (event, next) => { diff --git a/public/sass/components/_search.scss b/public/sass/components/_search.scss index 47d4a926968..e69068b730a 100644 --- a/public/sass/components/_search.scss +++ b/public/sass/components/_search.scss @@ -31,7 +31,6 @@ //padding: 0.5rem 1.5rem 0.5rem 0; padding: 1rem 1rem 0.75rem 1rem; height: 51px; - line-height: 51px; box-sizing: border-box; outline: none; background: $side-menu-bg; From 52164b0685416bf2f214e1331e6d4c016df68ce1 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 27 Mar 2018 14:13:49 +0200 Subject: [PATCH 11/57] notes about closing #9210 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a940ff044db..f880be9d110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * **Alerting**: Pausing/un alerts now updates new_state_date [#10942](https://github.com/grafana/grafana/pull/10942) * **Alerting**: Support Pagerduty notification channel using Pagerduty V2 API [#10531](https://github.com/grafana/grafana/issues/10531), thx [@jbaublitz](https://github.com/jbaublitz) * **Templating**: Add comma templating format [#10632](https://github.com/grafana/grafana/issues/10632), thx [@mtanda](https://github.com/mtanda) +* **Prometheus**: Show template variable candidate in query editor [#9210](https://github.com/grafana/grafana/issues/9210), thx [@mtanda](https://github.com/mtanda) * **Prometheus**: Support POST for query and query_range [#9859](https://github.com/grafana/grafana/pull/9859), thx [@mtanda](https://github.com/mtanda) * **Alerting**: Add support for retries on alert queries [#5855](https://github.com/grafana/grafana/issues/5855), thx [@Thib17](https://github.com/Thib17) From 67f0382222afa69fc8bc4690a4ef3770ac9449c1 Mon Sep 17 00:00:00 2001 From: aandrew Date: Mon, 5 Mar 2018 18:18:47 +0300 Subject: [PATCH 12/57] add value to text mapping --- .../plugins/panel/table/column_options.html | 55 ++++++++++++++++- .../app/plugins/panel/table/column_options.ts | 28 +++++++++ public/app/plugins/panel/table/renderer.ts | 56 ++++++++++++++++- .../panel/table/specs/renderer.jest.ts | 60 ++++++++++++++++++- 4 files changed, 196 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/panel/table/column_options.html b/public/app/plugins/panel/table/column_options.html index bebc05f0e53..ad0c2a3eb1c 100644 --- a/public/app/plugins/panel/table/column_options.html +++ b/public/app/plugins/panel/table/column_options.html @@ -69,7 +69,60 @@
-
+
+
Mapping
+
+
+
+ + Type + +
+ +
+
+
+
+
+ + + + + + +
+
+ +
+
+
+
+
+ +
Thresholds
-
Mapping
+
Value Mappings
@@ -83,38 +83,37 @@
-
- - - +
+ + + + +
-
- - - - - - +
+ + + + From + + To + + Text +
diff --git a/public/app/plugins/panel/table/column_options.ts b/public/app/plugins/panel/table/column_options.ts index b272309c011..463ab5d77a8 100644 --- a/public/app/plugins/panel/table/column_options.ts +++ b/public/app/plugins/panel/table/column_options.ts @@ -113,29 +113,30 @@ export class ColumnOptionsCtrl { this.render(); }; } - addValueMapping(style) { - if (!style.valueMappings) { - style.valueMappings = []; + + addValueMap(style) { + if (!style.valueMaps) { + style.valueMaps = []; } - style.valueMappings.push({ value: '', text: '' }); + style.valueMaps.push({ value: '', text: '' }); this.panelCtrl.render(); } - removeValueMapping(style, index) { - style.valueMappings.splice(index, 1); + removeValueMap(style, index) { + style.valueMaps.splice(index, 1); this.panelCtrl.render(); } - removeRangeMapping(style, index) { - style.rangeMappings.splice(index, 1); - this.panelCtrl.render(); - } - - addRangeMapping(style) { - if (!style.rangeMappings) { - style.rangeMappings = []; + addRangeMap(style) { + if (!style.rangeMaps) { + style.rangeMaps = []; } - style.rangeMappings.push({ from: '', to: '', text: '' }); + style.rangeMaps.push({ from: '', to: '', text: '' }); + this.panelCtrl.render(); + } + + removeRangeMap(style, index) { + style.rangeMaps.splice(index, 1); this.panelCtrl.render(); } } diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts index 149fd2a72f3..78f224d723f 100644 --- a/public/app/plugins/panel/table/renderer.ts +++ b/public/app/plugins/panel/table/renderer.ts @@ -47,7 +47,6 @@ export class TableRenderer { if (!style.thresholds) { return null; } - value = Number(value); for (var i = style.thresholds.length; i > 0; i--) { if (value >= style.thresholds[i - 1]) { return style.colors[i]; @@ -102,54 +101,54 @@ export class TableRenderer { if (column.style.type === 'string') { return v => { - if (column.style.valueMappings && column.style.mappingType && column.style.mappingType === 1) { - for (let i = 0; i < column.style.valueMappings.length; i++) { - let mapping = column.style.valueMappings[i]; - var value = Number(mapping.value); - if (v === null && mapping.value[0] === 'null') { - return mapping.text; - } - if (v !== null && !_.isArray(v)) { - if (Number(v) === value) { - if (!_.isString(v) && !_.isArray(v)) { - this.colorState[column.style.colorMode] = this.getColorForValue(v, column.style); - } - return this.defaultCellFormatter(mapping.text, column.style); + if (_.isArray(v)) { + v = v.join(', '); + } + + const mappingType = column.style.mappingType || 0; + + if (mappingType === 1 && column.style.valueMaps) { + for (let i = 0; i < column.style.valueMaps.length; i++) { + const map = column.style.valueMaps[i]; + + if (v === null) { + if (map.value === 'null') { + return map.text; } + continue; + } + + // Allow both numeric and string values to be mapped + if ((!_.isString(v) && Number(map.value) === Number(v)) || map.value === v) { + this.setColorState(v, column.style); + return this.defaultCellFormatter(map.text, column.style); } - } - if (v !== null && v !== void 0 && !_.isString(v) && !_.isArray(v)) { - this.colorState[column.style.colorMode] = this.getColorForValue(v, column.style); } } - if (column.style.rangeMappings && column.style.mappingType && column.style.mappingType === 2) { - for (let i = 0; i < column.style.rangeMappings.length; i++) { - let mapping = column.style.rangeMappings[i]; - var from = mapping.from; - var to = mapping.to; - if (v === null && mapping.from[0] === 'null' && mapping.to[0] === 'null') { - return mapping.text; + + if (mappingType === 2 && column.style.rangeMaps) { + for (let i = 0; i < column.style.rangeMaps.length; i++) { + const map = column.style.rangeMaps[i]; + + if (v === null) { + if (map.from === 'null' && map.to === 'null') { + return map.text; + } + continue; } - if ( - v !== null && - !_.isString(v) && - !_.isArray(v) && - from !== '' && - to !== '' && - Number(from[0]) <= v && - Number(to[0]) >= v - ) { - this.colorState[column.style.colorMode] = this.getColorForValue(v, column.style); - return this.defaultCellFormatter(mapping.text, column.style); + + if (Number(map.from) <= Number(v) && Number(map.to) >= Number(v)) { + this.setColorState(v, column.style); + return this.defaultCellFormatter(map.text, column.style); } } - if (v !== null && v !== void 0 && !_.isString(v) && !_.isArray(v)) { - this.colorState[column.style.colorMode] = this.getColorForValue(v, column.style); - } } - if (v === null) { + + if (v === null || v === void 0) { return '-'; } + + this.setColorState(v, column.style); return this.defaultCellFormatter(v, column.style); }; } @@ -166,10 +165,7 @@ export class TableRenderer { return this.defaultCellFormatter(v, column.style); } - if (column.style.colorMode) { - this.colorState[column.style.colorMode] = this.getColorForValue(v, column.style); - } - + this.setColorState(v, column.style); return valueFormatter(v, column.style.decimals, null); }; } @@ -179,6 +175,23 @@ export class TableRenderer { }; } + setColorState(value, style) { + if (!style.colorMode) { + return; + } + + if (value === null || value === void 0 || _.isArray(value)) { + return; + } + + var numericValue = Number(value); + if (numericValue === NaN) { + return; + } + + this.colorState[style.colorMode] = this.getColorForValue(numericValue, style); + } + renderRowVariables(rowIndex) { let scopedVars = {}; let cell_variable; diff --git a/public/app/plugins/panel/table/specs/renderer.jest.ts b/public/app/plugins/panel/table/specs/renderer.jest.ts index bd35699f6e5..22957d1aa66 100644 --- a/public/app/plugins/panel/table/specs/renderer.jest.ts +++ b/public/app/plugins/panel/table/specs/renderer.jest.ts @@ -3,7 +3,7 @@ import TableModel from 'app/core/table_model'; import { TableRenderer } from '../renderer'; describe('when rendering table', () => { - describe('given 2 columns', () => { + describe('given 13 columns', () => { var table = new TableModel(); table.columns = [ { text: 'Time' }, @@ -17,8 +17,12 @@ describe('when rendering table', () => { { text: 'Array' }, { text: 'Mapping' }, { text: 'RangeMapping' }, + { text: 'MappingColored' }, + { text: 'RangeMappingColored' }, + ]; + table.rows = [ + [1388556366666, 1230, 40, undefined, '', '', 'my.host.com', 'host1', ['value1', 'value2'], 1, 2, 1, 2], ]; - table.rows = [[1388556366666, 1230, 40, undefined, '', '', 'my.host.com', 'host1', ['value1', 'value2'], 1, 2]]; var panel = { pageSize: 10, @@ -82,7 +86,7 @@ describe('when rendering table', () => { pattern: 'Mapping', type: 'string', mappingType: 1, - valueMappings: [ + valueMaps: [ { value: '1', text: 'on', @@ -91,13 +95,21 @@ describe('when rendering table', () => { value: '0', text: 'off', }, + { + value: 'HELLO WORLD', + text: 'HELLO GRAFANA', + }, + { + value: 'value1, value2', + text: 'value3, value4', + }, ], }, { pattern: 'RangeMapping', type: 'string', mappingType: 2, - rangeMappings: [ + rangeMaps: [ { from: '1', to: '3', @@ -110,6 +122,44 @@ describe('when rendering table', () => { }, ], }, + { + pattern: 'MappingColored', + type: 'string', + mappingType: 1, + valueMaps: [ + { + value: '1', + text: 'on', + }, + { + value: '0', + text: 'off', + }, + ], + colorMode: 'value', + thresholds: [1, 2], + colors: ['green', 'orange', 'red'], + }, + { + pattern: 'RangeMappingColored', + type: 'string', + mappingType: 2, + rangeMaps: [ + { + from: '1', + to: '3', + text: 'on', + }, + { + from: '3', + to: '6', + text: 'off', + }, + ], + colorMode: 'value', + thresholds: [2, 5], + colors: ['green', 'orange', 'red'], + }, ], }; @@ -231,25 +281,85 @@ describe('when rendering table', () => { expect(html).toBe('value1, value2'); }); - it('value should be mapped to text', () => { + it('numeric value should be mapped to text', () => { var html = renderer.renderCell(9, 0, 1); expect(html).toBe('on'); }); - it('value should be mapped to text', () => { - var html = renderer.renderCell(9, 0, 0); + it('string numeric value should be mapped to text', () => { + var html = renderer.renderCell(9, 0, '0'); expect(html).toBe('off'); }); - it('value should be mapped to text(range)', () => { + it('string value should be mapped to text', () => { + var html = renderer.renderCell(9, 0, 'HELLO WORLD'); + expect(html).toBe('HELLO GRAFANA'); + }); + + it('array column value should be mapped to text', () => { + var html = renderer.renderCell(9, 0, ['value1', 'value2']); + expect(html).toBe('value3, value4'); + }); + + it('value should be mapped to text (range)', () => { var html = renderer.renderCell(10, 0, 2); expect(html).toBe('on'); }); - it('value should be mapped to text(range)', () => { + it('value should be mapped to text (range)', () => { var html = renderer.renderCell(10, 0, 5); expect(html).toBe('off'); }); + + it('array column value should not be mapped to text', () => { + var html = renderer.renderCell(10, 0, ['value1', 'value2']); + expect(html).toBe('value1, value2'); + }); + + it('value should be mapped to text and colored cell should have style', () => { + var html = renderer.renderCell(11, 0, 1); + expect(html).toBe('on'); + }); + + it('value should be mapped to text and colored cell should have style', () => { + var html = renderer.renderCell(11, 0, '1'); + expect(html).toBe('on'); + }); + + it('value should be mapped to text and colored cell should have style', () => { + var html = renderer.renderCell(11, 0, 0); + expect(html).toBe('off'); + }); + + it('value should be mapped to text and colored cell should have style', () => { + var html = renderer.renderCell(11, 0, '0'); + expect(html).toBe('off'); + }); + + it('value should be mapped to text and colored cell should have style', () => { + var html = renderer.renderCell(11, 0, '2.1'); + expect(html).toBe('2.1'); + }); + + it('value should be mapped to text (range) and colored cell should have style', () => { + var html = renderer.renderCell(12, 0, 0); + expect(html).toBe('0'); + }); + + it('value should be mapped to text (range) and colored cell should have style', () => { + var html = renderer.renderCell(12, 0, 1); + expect(html).toBe('on'); + }); + + it('value should be mapped to text (range) and colored cell should have style', () => { + var html = renderer.renderCell(12, 0, 4); + expect(html).toBe('off'); + }); + + it('value should be mapped to text (range) and colored cell should have style', () => { + var html = renderer.renderCell(12, 0, '7.1'); + expect(html).toBe('7.1'); + }); }); }); From 55967075316de3719c36bc8e176fc0e59b4b56e9 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 27 Mar 2018 20:24:11 +0200 Subject: [PATCH 14/57] alert: fixes broken link back to grafana If Grafana was configured to use a subpath it was included twice in the link from the alert notification back to Grafana. closes #11403 --- pkg/models/dashboards.go | 6 +++--- pkg/models/dashboards_test.go | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index 4b771038df6..8cd2b01811c 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -209,14 +209,14 @@ func GetDashboardFolderUrl(isFolder bool, uid string, slug string) string { return GetDashboardUrl(uid, slug) } -// Return the html url for a dashboard +// GetDashboardUrl return the html url for a dashboard func GetDashboardUrl(uid string, slug string) string { return fmt.Sprintf("%s/d/%s/%s", setting.AppSubUrl, uid, slug) } -// Return the full url for a dashboard +// GetFullDashboardUrl return the full url for a dashboard func GetFullDashboardUrl(uid string, slug string) string { - return fmt.Sprintf("%s%s", setting.AppUrl, GetDashboardUrl(uid, slug)) + return fmt.Sprintf("%sd/%s/%s", setting.AppUrl, uid, slug) } // GetFolderUrl return the html url for a folder diff --git a/pkg/models/dashboards_test.go b/pkg/models/dashboards_test.go index ad865b575bb..69bc8ab7bd9 100644 --- a/pkg/models/dashboards_test.go +++ b/pkg/models/dashboards_test.go @@ -4,11 +4,24 @@ import ( "testing" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" ) func TestDashboardModel(t *testing.T) { + Convey("Generate full dashboard url", t, func() { + setting.AppUrl = "http://grafana.local/" + fullUrl := GetFullDashboardUrl("uid", "my-dashboard") + So(fullUrl, ShouldEqual, "http://grafana.local/d/uid/my-dashboard") + }) + + Convey("Generate relative dashboard url", t, func() { + setting.AppUrl = "" + fullUrl := GetDashboardUrl("uid", "my-dashboard") + So(fullUrl, ShouldEqual, "/d/uid/my-dashboard") + }) + Convey("When generating slug", t, func() { dashboard := NewDashboard("Grafana Play Home") dashboard.UpdateSlug() From 2743e8be20cc36d48370f78df140a42ff209cb60 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 27 Mar 2018 21:21:18 +0200 Subject: [PATCH 15/57] docs: not about email notifications and local img store cloes #11054 --- docs/sources/alerting/notifications.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md index fe57fd0fa8f..bb119687750 100644 --- a/docs/sources/alerting/notifications.md +++ b/docs/sources/alerting/notifications.md @@ -41,6 +41,8 @@ Grafana ships with the following set of notification types: To enable email notifications you have to setup [SMTP settings](/installation/configuration/#smtp) in the Grafana config. Email notifications will upload an image of the alert graph to an external image destination if available or fallback to attaching the image to the email. +Be aware that if you use the `local` image storage email servers and clients might not be +able to access the image. ### Slack From f2755982c3bb40acc071d84ecd3581943f5799ff Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 27 Mar 2018 21:53:46 +0200 Subject: [PATCH 16/57] tech: migrates to none deprecated mail lib gomail is missing a maintainer so we are switching to an active fork ref https://github.com/go-gomail/gomail/issues/108 closes #7189 --- Gopkg.lock | 217 +++--------------- Gopkg.toml | 2 +- pkg/services/notifications/mailer.go | 2 +- .../gopkg.in/{gomail.v2 => mail.v2}/LICENSE | 0 .../gopkg.in/{gomail.v2 => mail.v2}/auth.go | 2 +- vendor/gopkg.in/{gomail.v2 => mail.v2}/doc.go | 5 +- vendor/gopkg.in/mail.v2/errors.go | 16 ++ .../{gomail.v2 => mail.v2}/message.go | 22 +- .../gopkg.in/{gomail.v2 => mail.v2}/mime.go | 2 +- .../{gomail.v2 => mail.v2}/mime_go14.go | 2 +- .../gopkg.in/{gomail.v2 => mail.v2}/send.go | 8 +- .../gopkg.in/{gomail.v2 => mail.v2}/smtp.go | 122 ++++++++-- .../{gomail.v2 => mail.v2}/writeto.go | 13 +- 13 files changed, 186 insertions(+), 227 deletions(-) rename vendor/gopkg.in/{gomail.v2 => mail.v2}/LICENSE (100%) rename vendor/gopkg.in/{gomail.v2 => mail.v2}/auth.go (98%) rename vendor/gopkg.in/{gomail.v2 => mail.v2}/doc.go (57%) create mode 100644 vendor/gopkg.in/mail.v2/errors.go rename vendor/gopkg.in/{gomail.v2 => mail.v2}/message.go (91%) rename vendor/gopkg.in/{gomail.v2 => mail.v2}/mime.go (95%) rename vendor/gopkg.in/{gomail.v2 => mail.v2}/mime_go14.go (96%) rename vendor/gopkg.in/{gomail.v2 => mail.v2}/send.go (94%) rename vendor/gopkg.in/{gomail.v2 => mail.v2}/smtp.go (55%) rename vendor/gopkg.in/{gomail.v2 => mail.v2}/writeto.go (96%) diff --git a/Gopkg.lock b/Gopkg.lock index d447223795e..a35f5b23cda 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -27,37 +27,7 @@ [[projects]] name = "github.com/aws/aws-sdk-go" - packages = [ - "aws", - "aws/awserr", - "aws/awsutil", - "aws/client", - "aws/client/metadata", - "aws/corehandlers", - "aws/credentials", - "aws/credentials/ec2rolecreds", - "aws/credentials/endpointcreds", - "aws/credentials/stscreds", - "aws/defaults", - "aws/ec2metadata", - "aws/endpoints", - "aws/request", - "aws/session", - "aws/signer/v4", - "internal/shareddefaults", - "private/protocol", - "private/protocol/ec2query", - "private/protocol/query", - "private/protocol/query/queryutil", - "private/protocol/rest", - "private/protocol/restxml", - "private/protocol/xml/xmlutil", - "service/cloudwatch", - "service/ec2", - "service/ec2/ec2iface", - "service/s3", - "service/sts" - ] + packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/ec2query","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/cloudwatch","service/ec2","service/ec2/ec2iface","service/s3","service/sts"] revision = "decd990ddc5dcdf2f73309cbcab90d06b996ca28" version = "v1.12.67" @@ -105,10 +75,7 @@ [[projects]] name = "github.com/denisenkom/go-mssqldb" - packages = [ - ".", - "internal/cp" - ] + packages = [".","internal/cp"] revision = "270bc3860bb94dd3a3ffd047377d746c5e276726" [[projects]] @@ -150,12 +117,7 @@ [[projects]] branch = "master" name = "github.com/go-macaron/session" - packages = [ - ".", - "memcache", - "postgres", - "redis" - ] + packages = [".","memcache","postgres","redis"] revision = "b8e286a0dba8f4999042d6b258daf51b31d08938" [[projects]] @@ -190,13 +152,7 @@ [[projects]] branch = "master" name = "github.com/golang/protobuf" - packages = [ - "proto", - "ptypes", - "ptypes/any", - "ptypes/duration", - "ptypes/timestamp" - ] + packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"] revision = "c65a0412e71e8b9b3bfd22925720d23c0f054237" [[projects]] @@ -265,10 +221,7 @@ [[projects]] name = "github.com/klauspost/compress" - packages = [ - "flate", - "gzip" - ] + packages = ["flate","gzip"] revision = "6c8db69c4b49dd4df1fff66996cf556176d0b9bf" version = "v1.2.1" @@ -299,10 +252,7 @@ [[projects]] branch = "master" name = "github.com/lib/pq" - packages = [ - ".", - "oid" - ] + packages = [".","oid"] revision = "61fe37aa2ee24fabcdbe5c4ac1d4ac566f88f345" [[projects]] @@ -337,11 +287,7 @@ [[projects]] name = "github.com/opentracing/opentracing-go" - packages = [ - ".", - "ext", - "log" - ] + packages = [".","ext","log"] revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38" version = "v1.0.2" @@ -353,12 +299,7 @@ [[projects]] name = "github.com/prometheus/client_golang" - packages = [ - "api", - "api/prometheus/v1", - "prometheus", - "prometheus/promhttp" - ] + packages = ["api","api/prometheus/v1","prometheus","prometheus/promhttp"] revision = "967789050ba94deca04a5e84cce8ad472ce313c1" version = "v0.9.0-pre1" @@ -371,22 +312,13 @@ [[projects]] branch = "master" name = "github.com/prometheus/common" - packages = [ - "expfmt", - "internal/bitbucket.org/ww/goautoneg", - "model" - ] + packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"] revision = "89604d197083d4781071d3c65855d24ecfb0a563" [[projects]] branch = "master" name = "github.com/prometheus/procfs" - packages = [ - ".", - "internal/util", - "nfsd", - "xfs" - ] + packages = [".","internal/util","nfsd","xfs"] revision = "85fadb6e89903ef7cca6f6a804474cd5ea85b6e1" [[projects]] @@ -403,21 +335,13 @@ [[projects]] name = "github.com/smartystreets/assertions" - packages = [ - ".", - "internal/go-render/render", - "internal/oglematchers" - ] + packages = [".","internal/go-render/render","internal/oglematchers"] revision = "0b37b35ec7434b77e77a4bb29b79677cced992ea" version = "1.8.1" [[projects]] name = "github.com/smartystreets/goconvey" - packages = [ - "convey", - "convey/gotest", - "convey/reporting" - ] + packages = ["convey","convey/gotest","convey/reporting"] revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857" version = "1.6.3" @@ -429,21 +353,7 @@ [[projects]] name = "github.com/uber/jaeger-client-go" - packages = [ - ".", - "config", - "internal/baggage", - "internal/baggage/remote", - "internal/spanlog", - "log", - "rpcmetrics", - "thrift-gen/agent", - "thrift-gen/baggage", - "thrift-gen/jaeger", - "thrift-gen/sampling", - "thrift-gen/zipkincore", - "utils" - ] + packages = [".","config","internal/baggage","internal/baggage/remote","internal/spanlog","log","rpcmetrics","thrift-gen/agent","thrift-gen/baggage","thrift-gen/jaeger","thrift-gen/sampling","thrift-gen/zipkincore","utils"] revision = "3ac96c6e679cb60a74589b0d0aa7c70a906183f7" version = "v2.11.2" @@ -455,10 +365,7 @@ [[projects]] name = "github.com/yudai/gojsondiff" - packages = [ - ".", - "formatter" - ] + packages = [".","formatter"] revision = "7b1b7adf999dab73a6eb02669c3d82dbb27a3dd6" version = "1.0.0" @@ -471,37 +378,19 @@ [[projects]] branch = "master" name = "golang.org/x/crypto" - packages = [ - "md4", - "pbkdf2" - ] + packages = ["md4","pbkdf2"] revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b" [[projects]] branch = "master" name = "golang.org/x/net" - packages = [ - "context", - "context/ctxhttp", - "http2", - "http2/hpack", - "idna", - "internal/timeseries", - "lex/httplex", - "trace" - ] + packages = ["context","context/ctxhttp","http2","http2/hpack","idna","internal/timeseries","lex/httplex","trace"] revision = "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" [[projects]] branch = "master" name = "golang.org/x/oauth2" - packages = [ - ".", - "google", - "internal", - "jws", - "jwt" - ] + packages = [".","google","internal","jws","jwt"] revision = "b28fcf2b08a19742b43084fb40ab78ac6c3d8067" [[projects]] @@ -519,39 +408,12 @@ [[projects]] branch = "master" name = "golang.org/x/text" - packages = [ - "collate", - "collate/build", - "internal/colltab", - "internal/gen", - "internal/tag", - "internal/triegen", - "internal/ucd", - "language", - "secure/bidirule", - "transform", - "unicode/bidi", - "unicode/cldr", - "unicode/norm", - "unicode/rangetable" - ] + packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"] revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3" [[projects]] name = "google.golang.org/appengine" - packages = [ - ".", - "cloudsql", - "internal", - "internal/app_identity", - "internal/base", - "internal/datastore", - "internal/log", - "internal/modules", - "internal/remote_api", - "internal/urlfetch", - "urlfetch" - ] + packages = [".","cloudsql","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"] revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" version = "v1.0.0" @@ -563,32 +425,7 @@ [[projects]] name = "google.golang.org/grpc" - packages = [ - ".", - "balancer", - "balancer/base", - "balancer/roundrobin", - "codes", - "connectivity", - "credentials", - "encoding", - "grpclb/grpc_lb_v1/messages", - "grpclog", - "health", - "health/grpc_health_v1", - "internal", - "keepalive", - "metadata", - "naming", - "peer", - "resolver", - "resolver/dns", - "resolver/passthrough", - "stats", - "status", - "tap", - "transport" - ] + packages = [".","balancer","balancer/base","balancer/roundrobin","codes","connectivity","credentials","encoding","grpclb/grpc_lb_v1/messages","grpclog","health","health/grpc_health_v1","internal","keepalive","metadata","naming","peer","resolver","resolver/dns","resolver/passthrough","stats","status","tap","transport"] revision = "6b51017f791ae1cfbec89c52efdf444b13b550ef" version = "v1.9.2" @@ -610,12 +447,6 @@ revision = "567b2bfa514e796916c4747494d6ff5132a1dfce" version = "v1" -[[projects]] - branch = "v2" - name = "gopkg.in/gomail.v2" - packages = ["."] - revision = "81ebce5c23dfd25c6c67194b37d3dd3f338c98b1" - [[projects]] name = "gopkg.in/ini.v1" packages = ["."] @@ -628,6 +459,12 @@ revision = "75f2e9b42e99652f0d82b28ccb73648f44615faa" version = "v1.2.4" +[[projects]] + branch = "v2" + name = "gopkg.in/mail.v2" + packages = ["."] + revision = "5bc5c8bb07bd8d2803831fbaf8cbd630fcde2c68" + [[projects]] name = "gopkg.in/redis.v2" packages = ["."] @@ -643,6 +480,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "8a9e651fb8ea49dfd3c6ddc99bd3242b39e453ea9edd11321da79bd2c865e9d1" + inputs-digest = "ad3c71fd3244369c313978e9e7464c7116faee764386439a17de0707a08103aa" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 350da50fc4b..a9f79c402df 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -172,7 +172,7 @@ ignored = [ name = "golang.org/x/sync" [[constraint]] - name = "gopkg.in/gomail.v2" + name = "gopkg.in/mail.v2" branch = "v2" [[constraint]] diff --git a/pkg/services/notifications/mailer.go b/pkg/services/notifications/mailer.go index 7fbf39ee41d..05c2e53c748 100644 --- a/pkg/services/notifications/mailer.go +++ b/pkg/services/notifications/mailer.go @@ -17,7 +17,7 @@ import ( "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" - "gopkg.in/gomail.v2" + gomail "gopkg.in/mail.v2" ) var mailQueue chan *Message diff --git a/vendor/gopkg.in/gomail.v2/LICENSE b/vendor/gopkg.in/mail.v2/LICENSE similarity index 100% rename from vendor/gopkg.in/gomail.v2/LICENSE rename to vendor/gopkg.in/mail.v2/LICENSE diff --git a/vendor/gopkg.in/gomail.v2/auth.go b/vendor/gopkg.in/mail.v2/auth.go similarity index 98% rename from vendor/gopkg.in/gomail.v2/auth.go rename to vendor/gopkg.in/mail.v2/auth.go index d28b83ab7d5..b8c0dde7f23 100644 --- a/vendor/gopkg.in/gomail.v2/auth.go +++ b/vendor/gopkg.in/mail.v2/auth.go @@ -1,4 +1,4 @@ -package gomail +package mail import ( "bytes" diff --git a/vendor/gopkg.in/gomail.v2/doc.go b/vendor/gopkg.in/mail.v2/doc.go similarity index 57% rename from vendor/gopkg.in/gomail.v2/doc.go rename to vendor/gopkg.in/mail.v2/doc.go index a8f5091f541..d65bf359160 100644 --- a/vendor/gopkg.in/gomail.v2/doc.go +++ b/vendor/gopkg.in/mail.v2/doc.go @@ -1,5 +1,6 @@ // Package gomail provides a simple interface to compose emails and to mail them // efficiently. // -// More info on Github: https://github.com/go-gomail/gomail -package gomail +// More info on Github: https://github.com/go-mail/mail +// +package mail diff --git a/vendor/gopkg.in/mail.v2/errors.go b/vendor/gopkg.in/mail.v2/errors.go new file mode 100644 index 00000000000..770da8c3854 --- /dev/null +++ b/vendor/gopkg.in/mail.v2/errors.go @@ -0,0 +1,16 @@ +package mail + +import "fmt" + +// A SendError represents the failure to transmit a Message, detailing the cause +// of the failure and index of the Message within a batch. +type SendError struct { + // Index specifies the index of the Message within a batch. + Index uint + Cause error +} + +func (err *SendError) Error() string { + return fmt.Sprintf("gomail: could not send email %d: %v", + err.Index+1, err.Cause) +} diff --git a/vendor/gopkg.in/gomail.v2/message.go b/vendor/gopkg.in/mail.v2/message.go similarity index 91% rename from vendor/gopkg.in/gomail.v2/message.go rename to vendor/gopkg.in/mail.v2/message.go index 4bffb1e7ff6..d1f66159c98 100644 --- a/vendor/gopkg.in/gomail.v2/message.go +++ b/vendor/gopkg.in/mail.v2/message.go @@ -1,4 +1,4 @@ -package gomail +package mail import ( "bytes" @@ -18,6 +18,7 @@ type Message struct { encoding Encoding hEncoder mimeEncoder buf bytes.Buffer + boundary string } type header map[string][]string @@ -97,6 +98,11 @@ const ( Unencoded Encoding = "8bit" ) +// SetBoundary sets a custom multipart boundary. +func (m *Message) SetBoundary(boundary string) { + m.boundary = boundary +} + // SetHeader sets a value to the given header field. func (m *Message) SetHeader(field string, value ...string) { m.encodeHeader(value) @@ -183,9 +189,15 @@ func (m *Message) GetHeader(field string) []string { } // SetBody sets the body of the message. It replaces any content previously set -// by SetBody, AddAlternative or AddAlternativeWriter. +// by SetBody, SetBodyWriter, AddAlternative or AddAlternativeWriter. func (m *Message) SetBody(contentType, body string, settings ...PartSetting) { - m.parts = []*part{m.newPart(contentType, newCopier(body), settings)} + m.SetBodyWriter(contentType, newCopier(body), settings...) +} + +// SetBodyWriter sets the body of the message. It can be useful with the +// text/template or html/template packages. +func (m *Message) SetBodyWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) { + m.parts = []*part{m.newPart(contentType, f, settings)} } // AddAlternative adds an alternative part to the message. @@ -226,8 +238,8 @@ func (m *Message) newPart(contentType string, f func(io.Writer) error, settings } // A PartSetting can be used as an argument in Message.SetBody, -// Message.AddAlternative or Message.AddAlternativeWriter to configure the part -// added to a message. +// Message.SetBodyWriter, Message.AddAlternative or Message.AddAlternativeWriter +// to configure the part added to a message. type PartSetting func(*part) // SetPartEncoding sets the encoding of the part added to the message. By diff --git a/vendor/gopkg.in/gomail.v2/mime.go b/vendor/gopkg.in/mail.v2/mime.go similarity index 95% rename from vendor/gopkg.in/gomail.v2/mime.go rename to vendor/gopkg.in/mail.v2/mime.go index 194d4a769a8..d95ea2eb240 100644 --- a/vendor/gopkg.in/gomail.v2/mime.go +++ b/vendor/gopkg.in/mail.v2/mime.go @@ -1,6 +1,6 @@ // +build go1.5 -package gomail +package mail import ( "mime" diff --git a/vendor/gopkg.in/gomail.v2/mime_go14.go b/vendor/gopkg.in/mail.v2/mime_go14.go similarity index 96% rename from vendor/gopkg.in/gomail.v2/mime_go14.go rename to vendor/gopkg.in/mail.v2/mime_go14.go index 3dc26aa2ae0..bdb605dcca3 100644 --- a/vendor/gopkg.in/gomail.v2/mime_go14.go +++ b/vendor/gopkg.in/mail.v2/mime_go14.go @@ -1,6 +1,6 @@ // +build !go1.5 -package gomail +package mail import "gopkg.in/alexcesaro/quotedprintable.v3" diff --git a/vendor/gopkg.in/gomail.v2/send.go b/vendor/gopkg.in/mail.v2/send.go similarity index 94% rename from vendor/gopkg.in/gomail.v2/send.go rename to vendor/gopkg.in/mail.v2/send.go index 9115ebe7267..62e67f0b81a 100644 --- a/vendor/gopkg.in/gomail.v2/send.go +++ b/vendor/gopkg.in/mail.v2/send.go @@ -1,10 +1,10 @@ -package gomail +package mail import ( "errors" "fmt" "io" - "net/mail" + stdmail "net/mail" ) // Sender is the interface that wraps the Send method. @@ -36,7 +36,7 @@ func (f SendFunc) Send(from string, to []string, msg io.WriterTo) error { func Send(s Sender, msg ...*Message) error { for i, m := range msg { if err := send(s, m); err != nil { - return fmt.Errorf("gomail: could not send email %d: %v", i+1, err) + return &SendError{Cause: err, Index: uint(i)} } } @@ -108,7 +108,7 @@ func addAddress(list []string, addr string) []string { } func parseAddress(field string) (string, error) { - addr, err := mail.ParseAddress(field) + addr, err := stdmail.ParseAddress(field) if err != nil { return "", fmt.Errorf("gomail: invalid address %q: %v", field, err) } diff --git a/vendor/gopkg.in/gomail.v2/smtp.go b/vendor/gopkg.in/mail.v2/smtp.go similarity index 55% rename from vendor/gopkg.in/gomail.v2/smtp.go rename to vendor/gopkg.in/mail.v2/smtp.go index 2aa49c8b612..547e04d16b0 100644 --- a/vendor/gopkg.in/gomail.v2/smtp.go +++ b/vendor/gopkg.in/mail.v2/smtp.go @@ -1,4 +1,4 @@ -package gomail +package mail import ( "crypto/tls" @@ -27,23 +27,39 @@ type Dialer struct { // most cases since the authentication mechanism should use the STARTTLS // extension instead. SSL bool - // TSLConfig represents the TLS configuration used for the TLS (when the + // TLSConfig represents the TLS configuration used for the TLS (when the // STARTTLS extension is used) or SSL connection. TLSConfig *tls.Config + // StartTLSPolicy represents the TLS security level required to + // communicate with the SMTP server. + // + // This defaults to OpportunisticStartTLS for backwards compatibility, + // but we recommend MandatoryStartTLS for all modern SMTP servers. + // + // This option has no effect if SSL is set to true. + StartTLSPolicy StartTLSPolicy // LocalName is the hostname sent to the SMTP server with the HELO command. // By default, "localhost" is sent. LocalName string + // Timeout to use for read/write operations. Defaults to 10 seconds, can + // be set to 0 to disable timeouts. + Timeout time.Duration + // Whether we should retry mailing if the connection returned an error, + // defaults to true. + RetryFailure bool } // NewDialer returns a new SMTP Dialer. The given parameters are used to connect // to the SMTP server. func NewDialer(host string, port int, username, password string) *Dialer { return &Dialer{ - Host: host, - Port: port, - Username: username, - Password: password, - SSL: port == 465, + Host: host, + Port: port, + Username: username, + Password: password, + SSL: port == 465, + Timeout: 10 * time.Second, + RetryFailure: true, } } @@ -55,10 +71,15 @@ func NewPlainDialer(host string, port int, username, password string) *Dialer { return NewDialer(host, port, username, password) } +// NetDialTimeout specifies the DialTimeout function to establish a connection +// to the SMTP server. This can be used to override dialing in the case that a +// proxy or other special behavior is needed. +var NetDialTimeout = net.DialTimeout + // Dial dials and authenticates to an SMTP server. The returned SendCloser // should be closed when done using it. func (d *Dialer) Dial() (SendCloser, error) { - conn, err := netDialTimeout("tcp", addr(d.Host, d.Port), 10*time.Second) + conn, err := NetDialTimeout("tcp", addr(d.Host, d.Port), d.Timeout) if err != nil { return nil, err } @@ -72,14 +93,25 @@ func (d *Dialer) Dial() (SendCloser, error) { return nil, err } + if d.Timeout > 0 { + conn.SetDeadline(time.Now().Add(d.Timeout)) + } + if d.LocalName != "" { if err := c.Hello(d.LocalName); err != nil { return nil, err } } - if !d.SSL { - if ok, _ := c.Extension("STARTTLS"); ok { + if !d.SSL && d.StartTLSPolicy != NoStartTLS { + ok, _ := c.Extension("STARTTLS") + if !ok && d.StartTLSPolicy == MandatoryStartTLS { + err := StartTLSUnsupportedError{ + Policy: d.StartTLSPolicy} + return nil, err + } + + if ok { if err := c.StartTLS(d.tlsConfig()); err != nil { c.Close() return nil, err @@ -111,7 +143,7 @@ func (d *Dialer) Dial() (SendCloser, error) { } } - return &smtpSender{c, d}, nil + return &smtpSender{c, conn, d}, nil } func (d *Dialer) tlsConfig() *tls.Config { @@ -121,6 +153,47 @@ func (d *Dialer) tlsConfig() *tls.Config { return d.TLSConfig } +// StartTLSPolicy constants are valid values for Dialer.StartTLSPolicy. +type StartTLSPolicy int + +const ( + // OpportunisticStartTLS means that SMTP transactions are encrypted if + // STARTTLS is supported by the SMTP server. Otherwise, messages are + // sent in the clear. This is the default setting. + OpportunisticStartTLS StartTLSPolicy = iota + // MandatoryStartTLS means that SMTP transactions must be encrypted. + // SMTP transactions are aborted unless STARTTLS is supported by the + // SMTP server. + MandatoryStartTLS + // NoStartTLS means encryption is disabled and messages are sent in the + // clear. + NoStartTLS = -1 +) + +func (policy *StartTLSPolicy) String() string { + switch *policy { + case OpportunisticStartTLS: + return "OpportunisticStartTLS" + case MandatoryStartTLS: + return "MandatoryStartTLS" + case NoStartTLS: + return "NoStartTLS" + default: + return fmt.Sprintf("StartTLSPolicy:%v", *policy) + } +} + +// StartTLSUnsupportedError is returned by Dial when connecting to an SMTP +// server that does not support STARTTLS. +type StartTLSUnsupportedError struct { + Policy StartTLSPolicy +} + +func (e StartTLSUnsupportedError) Error() string { + return "gomail: " + e.Policy.String() + " required, but " + + "SMTP server does not support STARTTLS" +} + func addr(host string, port int) string { return fmt.Sprintf("%s:%d", host, port) } @@ -139,12 +212,29 @@ func (d *Dialer) DialAndSend(m ...*Message) error { type smtpSender struct { smtpClient - d *Dialer + conn net.Conn + d *Dialer +} + +func (c *smtpSender) retryError(err error) bool { + if !c.d.RetryFailure { + return false + } + + if nerr, ok := err.(net.Error); ok && nerr.Timeout() { + return true + } + + return err == io.EOF } func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error { + if c.d.Timeout > 0 { + c.conn.SetDeadline(time.Now().Add(c.d.Timeout)) + } + if err := c.Mail(from); err != nil { - if err == io.EOF { + if c.retryError(err) { // This is probably due to a timeout, so reconnect and try again. sc, derr := c.d.Dial() if derr == nil { @@ -154,6 +244,7 @@ func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error { } } } + return err } @@ -182,9 +273,8 @@ func (c *smtpSender) Close() error { // Stubbed out for tests. var ( - netDialTimeout = net.DialTimeout - tlsClient = tls.Client - smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) { + tlsClient = tls.Client + smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) { return smtp.NewClient(conn, host) } ) diff --git a/vendor/gopkg.in/gomail.v2/writeto.go b/vendor/gopkg.in/mail.v2/writeto.go similarity index 96% rename from vendor/gopkg.in/gomail.v2/writeto.go rename to vendor/gopkg.in/mail.v2/writeto.go index 9fb6b86e80b..9086e13c37d 100644 --- a/vendor/gopkg.in/gomail.v2/writeto.go +++ b/vendor/gopkg.in/mail.v2/writeto.go @@ -1,4 +1,4 @@ -package gomail +package mail import ( "encoding/base64" @@ -28,15 +28,15 @@ func (w *messageWriter) writeMessage(m *Message) { w.writeHeaders(m.header) if m.hasMixedPart() { - w.openMultipart("mixed") + w.openMultipart("mixed", m.boundary) } if m.hasRelatedPart() { - w.openMultipart("related") + w.openMultipart("related", m.boundary) } if m.hasAlternativePart() { - w.openMultipart("alternative") + w.openMultipart("alternative", m.boundary) } for _, part := range m.parts { w.writePart(part, m.charset) @@ -77,8 +77,11 @@ type messageWriter struct { err error } -func (w *messageWriter) openMultipart(mimeType string) { +func (w *messageWriter) openMultipart(mimeType, boundary string) { mw := multipart.NewWriter(w) + if boundary != "" { + mw.SetBoundary(boundary) + } contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary() w.writers[w.depth] = mw From 9e2e6fc586df0e25279000ae8202a2f51645b0b4 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 27 Mar 2018 22:52:29 +0200 Subject: [PATCH 17/57] add fallback for gravatar in org/admin view closes #11095 --- pkg/api/dtos/models.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index a702b06fad5..2348e217a41 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -50,6 +50,10 @@ type UserStars struct { } func GetGravatarUrl(text string) string { + if setting.DisableGravatar { + return "/public/img/user_profile.png" + } + if text == "" { return "" } From 68833fa97807989ed34932ab040d6578fbcdfb66 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 28 Mar 2018 11:31:33 +0200 Subject: [PATCH 18/57] dashboard: allow alerts to be saved for new/provisioned dashboards This changes the logic for the alert validation in the extractor. Validation is executed twice, once before attempting to save the alerts and once after saving the dashboard while saving the alerts to the db. The first validation will not require that the alert has a dashboard id which allows new dashboards with alerts to be saved. Fixes #11247 --- pkg/services/alerting/commands.go | 19 +- pkg/services/alerting/extractor.go | 97 +++--- pkg/services/alerting/extractor_test.go | 21 ++ .../alerting/test-data/dash-without-id.json | 281 ++++++++++++++++++ .../alerting/test-data/influxdb-alert.json | 2 +- 5 files changed, 366 insertions(+), 54 deletions(-) create mode 100644 pkg/services/alerting/test-data/dash-without-id.json diff --git a/pkg/services/alerting/commands.go b/pkg/services/alerting/commands.go index 2c145614751..02186d697ee 100644 --- a/pkg/services/alerting/commands.go +++ b/pkg/services/alerting/commands.go @@ -13,11 +13,7 @@ func init() { func validateDashboardAlerts(cmd *m.ValidateDashboardAlertsCommand) error { extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId) - if _, err := extractor.GetAlerts(); err != nil { - return err - } - - return nil + return extractor.ValidateAlerts() } func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error { @@ -29,15 +25,12 @@ func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error { extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId) - if alerts, err := extractor.GetAlerts(); err != nil { - return err - } else { - saveAlerts.Alerts = alerts - } - - if err := bus.Dispatch(&saveAlerts); err != nil { + alerts, err := extractor.GetAlerts() + if err != nil { return err } - return nil + saveAlerts.Alerts = alerts + + return bus.Dispatch(&saveAlerts) } diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index 2ae26c1a382..edd872b8fce 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -11,76 +11,78 @@ import ( m "github.com/grafana/grafana/pkg/models" ) +// DashAlertExtractor extracts alerts from the dashboard json type DashAlertExtractor struct { Dash *m.Dashboard - OrgId int64 + OrgID int64 log log.Logger } -func NewDashAlertExtractor(dash *m.Dashboard, orgId int64) *DashAlertExtractor { +// NewDashAlertExtractor returns a new DashAlertExtractor +func NewDashAlertExtractor(dash *m.Dashboard, orgID int64) *DashAlertExtractor { return &DashAlertExtractor{ Dash: dash, - OrgId: orgId, + OrgID: orgID, log: log.New("alerting.extractor"), } } -func (e *DashAlertExtractor) lookupDatasourceId(dsName string) (*m.DataSource, error) { +func (e *DashAlertExtractor) lookupDatasourceID(dsName string) (*m.DataSource, error) { if dsName == "" { - query := &m.GetDataSourcesQuery{OrgId: e.OrgId} + query := &m.GetDataSourcesQuery{OrgId: e.OrgID} if err := bus.Dispatch(query); err != nil { return nil, err - } else { - for _, ds := range query.Result { - if ds.IsDefault { - return ds, nil - } + } + + for _, ds := range query.Result { + if ds.IsDefault { + return ds, nil } } } else { - query := &m.GetDataSourceByNameQuery{Name: dsName, OrgId: e.OrgId} + query := &m.GetDataSourceByNameQuery{Name: dsName, OrgId: e.OrgID} if err := bus.Dispatch(query); err != nil { return nil, err - } else { - return query.Result, nil } + + return query.Result, nil } return nil, errors.New("Could not find datasource id for " + dsName) } -func findPanelQueryByRefId(panel *simplejson.Json, refId string) *simplejson.Json { +func findPanelQueryByRefID(panel *simplejson.Json, refID string) *simplejson.Json { for _, targetsObj := range panel.Get("targets").MustArray() { target := simplejson.NewFromAny(targetsObj) - if target.Get("refId").MustString() == refId { + if target.Get("refId").MustString() == refID { return target } } return nil } -func copyJson(in *simplejson.Json) (*simplejson.Json, error) { - rawJson, err := in.MarshalJSON() +func copyJSON(in *simplejson.Json) (*simplejson.Json, error) { + rawJSON, err := in.MarshalJSON() if err != nil { return nil, err } - return simplejson.NewJson(rawJson) + return simplejson.NewJson(rawJSON) } -func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) ([]*m.Alert, error) { +func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, validateAlertFunc func(*m.Alert) bool) ([]*m.Alert, error) { alerts := make([]*m.Alert, 0) for _, panelObj := range jsonWithPanels.Get("panels").MustArray() { panel := simplejson.NewFromAny(panelObj) - collapsedJson, collapsed := panel.CheckGet("collapsed") + collapsedJSON, collapsed := panel.CheckGet("collapsed") // check if the panel is collapsed - if collapsed && collapsedJson.MustBool() { + if collapsed && collapsedJSON.MustBool() { // extract alerts from sub panels for collapsed panels - als, err := e.GetAlertFromPanels(panel) + als, err := e.getAlertFromPanels(panel, validateAlertFunc) if err != nil { return nil, err } @@ -95,7 +97,7 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) continue } - panelId, err := panel.Get("id").Int64() + panelID, err := panel.Get("id").Int64() if err != nil { return nil, fmt.Errorf("panel id is required. err %v", err) } @@ -113,8 +115,8 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) alert := &m.Alert{ DashboardId: e.Dash.Id, - OrgId: e.OrgId, - PanelId: panelId, + OrgId: e.OrgID, + PanelId: panelID, Id: jsonAlert.Get("id").MustInt64(), Name: jsonAlert.Get("name").MustString(), Handler: jsonAlert.Get("handler").MustInt64(), @@ -126,11 +128,11 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) jsonCondition := simplejson.NewFromAny(condition) jsonQuery := jsonCondition.Get("query") - queryRefId := jsonQuery.Get("params").MustArray()[0].(string) - panelQuery := findPanelQueryByRefId(panel, queryRefId) + queryRefID := jsonQuery.Get("params").MustArray()[0].(string) + panelQuery := findPanelQueryByRefID(panel, queryRefID) if panelQuery == nil { - reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefId) + reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefID) return nil, ValidationError{Reason: reason} } @@ -141,12 +143,13 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) dsName = panel.Get("datasource").MustString() } - if datasource, err := e.lookupDatasourceId(dsName); err != nil { + datasource, err := e.lookupDatasourceID(dsName) + if err != nil { return nil, err - } else { - jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id) } + jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id) + if interval, err := panel.Get("interval").String(); err == nil { panelQuery.Set("interval", interval) } @@ -162,21 +165,28 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) return nil, err } - if alert.ValidToSave() { - alerts = append(alerts, alert) - } else { + if !validateAlertFunc(alert) { e.log.Debug("Invalid Alert Data. Dashboard, Org or Panel ID is not correct", "alertName", alert.Name, "panelId", alert.PanelId) return nil, m.ErrDashboardContainsInvalidAlertData } + + alerts = append(alerts, alert) } return alerts, nil } -func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) { - e.log.Debug("GetAlerts") +func validateAlertRule(alert *m.Alert) bool { + return alert.ValidToSave() +} - dashboardJson, err := copyJson(e.Dash.Data) +// GetAlerts extracts alerts from the dashboard json and does full validation on the alert json data +func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) { + return e.extractAlerts(validateAlertRule) +} + +func (e *DashAlertExtractor) extractAlerts(validateFunc func(alert *m.Alert) bool) ([]*m.Alert, error) { + dashboardJSON, err := copyJSON(e.Dash.Data) if err != nil { return nil, err } @@ -185,11 +195,11 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) { // We extract alerts from rows to be backwards compatible // with the old dashboard json model. - rows := dashboardJson.Get("rows").MustArray() + rows := dashboardJSON.Get("rows").MustArray() if len(rows) > 0 { for _, rowObj := range rows { row := simplejson.NewFromAny(rowObj) - a, err := e.GetAlertFromPanels(row) + a, err := e.getAlertFromPanels(row, validateFunc) if err != nil { return nil, err } @@ -197,7 +207,7 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) { alerts = append(alerts, a...) } } else { - a, err := e.GetAlertFromPanels(dashboardJson) + a, err := e.getAlertFromPanels(dashboardJSON, validateFunc) if err != nil { return nil, err } @@ -208,3 +218,10 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) { e.log.Debug("Extracted alerts from dashboard", "alertCount", len(alerts)) return alerts, nil } + +// ValidateAlerts validates alerts in the dashboard json but does not require a valid dashboard id +// in the first validation pass +func (e *DashAlertExtractor) ValidateAlerts() error { + _, err := e.extractAlerts(func(alert *m.Alert) bool { return alert.OrgId != 0 && alert.PanelId != 0 }) + return err +} diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index 3bda6c771fb..861e9b9cbfc 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -240,5 +240,26 @@ func TestAlertRuleExtraction(t *testing.T) { So(len(alerts), ShouldEqual, 4) }) }) + + Convey("Parse and validate dashboard without id and containing an alert", func() { + json, err := ioutil.ReadFile("./test-data/dash-without-id.json") + So(err, ShouldBeNil) + + dashJSON, err := simplejson.NewJson(json) + So(err, ShouldBeNil) + dash := m.NewDashboardFromJson(dashJSON) + extractor := NewDashAlertExtractor(dash, 1) + + err = extractor.ValidateAlerts() + + Convey("Should validate without error", func() { + So(err, ShouldBeNil) + }) + + Convey("Should fail on save", func() { + _, err := extractor.GetAlerts() + So(err, ShouldEqual, m.ErrDashboardContainsInvalidAlertData) + }) + }) }) } diff --git a/pkg/services/alerting/test-data/dash-without-id.json b/pkg/services/alerting/test-data/dash-without-id.json new file mode 100644 index 00000000000..e0a212695d8 --- /dev/null +++ b/pkg/services/alerting/test-data/dash-without-id.json @@ -0,0 +1,281 @@ +{ + "title": "Influxdb", + "tags": [ + "apa" + ], + "style": "dark", + "timezone": "browser", + "editable": true, + "hideControls": false, + "sharedCrosshair": false, + "rows": [ + { + "collapse": false, + "editable": true, + "height": "450px", + "panels": [ + { + "alert": { + "conditions": [ + { + "evaluator": { + "params": [ + 10 + ], + "type": "gt" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "frequency": "3s", + "handler": 1, + "name": "Influxdb", + "noDataState": "no_data", + "notifications": [ + { + "id": 6 + } + ] + }, + "alerting": {}, + "aliasColors": { + "logins.count.count": "#890F02" + }, + "bars": false, + "datasource": "InfluxDB", + "editable": true, + "error": false, + "fill": 1, + "grid": {}, + "id": 1, + "interval": ">10s", + "isNew": true, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "span": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "datacenter" + ], + "type": "tag" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "logins.count", + "policy": "default", + "query": "SELECT 8 * count(\"value\") FROM \"logins.count\" WHERE $timeFilter GROUP BY time($interval), \"datacenter\" fill(none)", + "rawQuery": true, + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "count" + } + ] + ], + "tags": [] + }, + { + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": true, + "measurement": "cpu", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ], + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "sum" + } + ] + ], + "tags": [] + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 10 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Panel Title", + "tooltip": { + "msResolution": false, + "ordering": "alphabetical", + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "editable": true, + "error": false, + "id": 2, + "isNew": true, + "limit": 10, + "links": [], + "show": "current", + "span": 2, + "stateFilter": [ + "alerting" + ], + "title": "Alert status", + "type": "alertlist" + } + ], + "title": "Row" + } + ], + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "now": true, + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "templating": { + "list": [] + }, + "annotations": { + "list": [] + }, + "schemaVersion": 13, + "version": 120, + "links": [], + "gnetId": null + } diff --git a/pkg/services/alerting/test-data/influxdb-alert.json b/pkg/services/alerting/test-data/influxdb-alert.json index 79ca355c5a1..fd6feb31a47 100644 --- a/pkg/services/alerting/test-data/influxdb-alert.json +++ b/pkg/services/alerting/test-data/influxdb-alert.json @@ -279,4 +279,4 @@ "version": 120, "links": [], "gnetId": null - } \ No newline at end of file + } From 8985e5ac5380a84280ead594c2f43795efcf8373 Mon Sep 17 00:00:00 2001 From: Tariq Ettaji Date: Wed, 28 Mar 2018 12:51:12 +0200 Subject: [PATCH 19/57] Clarified formatting multiple values doc --- docs/sources/reference/templating.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/reference/templating.md b/docs/sources/reference/templating.md index f9e16e26610..016d64d9ee9 100644 --- a/docs/sources/reference/templating.md +++ b/docs/sources/reference/templating.md @@ -174,6 +174,8 @@ Interpolating a variable with multiple values selected is tricky as it is not st is valid in the given context where the variable is used. Grafana tries to solve this by allowing each data source plugin to inform the templating interpolation engine what format to use for multiple values. +Note that the *Custom all value* option on the variable will have to be left blank for Grafana to format all values into a single string. + **Graphite**, for example, uses glob expressions. A variable with multiple values would, in this case, be interpolated as `{host1,host2,host3}` if the current variable value was *host1*, *host2* and *host3*. From d29563ff8e126325be103ee49df680d1926e6847 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 27 Mar 2018 09:19:14 +0200 Subject: [PATCH 20/57] alerting: bad default state for notifiers when we made image upload optional we didnt show the default value properly in the UI. Which caused confusing. This commit apply the default values to existing notifiers in the edit pages and reverts back to using uploadimage=true as the default value. --- pkg/services/alerting/notifiers/base.go | 6 ++++- pkg/services/alerting/notifiers/base_test.go | 24 +++++++++++++++++++ .../alerting/notification_edit_ctrl.ts | 3 ++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/pkg/services/alerting/notifiers/base.go b/pkg/services/alerting/notifiers/base.go index 7a3cc71c4db..51676efdfd5 100644 --- a/pkg/services/alerting/notifiers/base.go +++ b/pkg/services/alerting/notifiers/base.go @@ -15,7 +15,11 @@ type NotifierBase struct { } func NewNotifierBase(id int64, isDefault bool, name, notifierType string, model *simplejson.Json) NotifierBase { - uploadImage := model.Get("uploadImage").MustBool(false) + uploadImage := true + value, exist := model.CheckGet("uploadImage") + if exist { + uploadImage = value.MustBool() + } return NotifierBase{ Id: id, diff --git a/pkg/services/alerting/notifiers/base_test.go b/pkg/services/alerting/notifiers/base_test.go index 4225e203a3d..b7142d144cc 100644 --- a/pkg/services/alerting/notifiers/base_test.go +++ b/pkg/services/alerting/notifiers/base_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/grafana/grafana/pkg/components/simplejson" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" . "github.com/smartystreets/goconvey/convey" @@ -11,6 +12,29 @@ import ( func TestBaseNotifier(t *testing.T) { Convey("Base notifier tests", t, func() { + Convey("default constructor for notifiers", func() { + bJson := simplejson.New() + + Convey("can parse false value", func() { + bJson.Set("uploadImage", false) + + base := NewNotifierBase(1, false, "name", "email", bJson) + So(base.UploadImage, ShouldBeFalse) + }) + + Convey("can parse true value", func() { + bJson.Set("uploadImage", true) + + base := NewNotifierBase(1, false, "name", "email", bJson) + So(base.UploadImage, ShouldBeTrue) + }) + + Convey("default value should be true for backwards compatibility", func() { + base := NewNotifierBase(1, false, "name", "email", bJson) + So(base.UploadImage, ShouldBeTrue) + }) + }) + Convey("should notify", func() { Convey("pending -> ok", func() { context := alerting.NewEvalContext(context.TODO(), &alerting.Rule{ diff --git a/public/app/features/alerting/notification_edit_ctrl.ts b/public/app/features/alerting/notification_edit_ctrl.ts index bca6f6e8137..18b1c4d1d55 100644 --- a/public/app/features/alerting/notification_edit_ctrl.ts +++ b/public/app/features/alerting/notification_edit_ctrl.ts @@ -43,6 +43,7 @@ export class AlertNotificationEditCtrl { return this.backendSrv.get(`/api/alert-notifications/${this.$routeParams.id}`).then(result => { this.navModel.breadcrumbs.push({ text: result.name }); this.navModel.node = { text: result.name }; + result.settings = _.defaults(result.settings, this.defaults.settings); return result; }); }) @@ -89,7 +90,7 @@ export class AlertNotificationEditCtrl { } typeChanged() { - this.model.settings = {}; + this.model.settings = _.defaults({}, this.defaults.settings); this.notifierTemplateId = this.getNotifierTemplateId(this.model.type); } From a3f15ced6805d3d949d02c5a8a1d5267a6dac3a7 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 28 Mar 2018 14:19:17 +0200 Subject: [PATCH 21/57] fixed graphpanel editmode and custom width for right side legend for IE11 --- public/app/plugins/panel/graph/legend.ts | 5 ++++- public/sass/pages/_dashboard.scss | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/graph/legend.ts b/public/app/plugins/panel/graph/legend.ts index d1186ae0b1e..4dfeb75ff55 100644 --- a/public/app/plugins/panel/graph/legend.ts +++ b/public/app/plugins/panel/graph/legend.ts @@ -131,8 +131,11 @@ module.directive('graphLegend', function(popoverSrv, $timeout) { elem.empty(); // Set min-width if side style and there is a value, otherwise remove the CSS propery - var width = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth + 'px' : ''; + // Set width so it works with IE11 + var width: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth + 'px' : ''; + var ieWidth: any = panel.legend.rightSide && panel.legend.sideWidth ? panel.legend.sideWidth - 1 + 'px' : ''; elem.css('min-width', width); + elem.css('width', ieWidth); elem.toggleClass('graph-legend-table', panel.legend.alignAsTable === true); diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 871db4dfc2d..c957b6af790 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -33,7 +33,7 @@ div.flot-text { border: $panel-border; position: relative; border-radius: 3px; - height: 100%; + //height: 100%; &.panel-transparent { background-color: transparent; From df89be4de9e378581a3164cf33341746af43e091 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 28 Mar 2018 14:18:30 +0200 Subject: [PATCH 22/57] changelog: adds note about closing issues --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f880be9d110..c4756f4944d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ * **Dashboards**: Version cleanup fails on old databases with many entries [#11278](https://github.com/grafana/grafana/issues/11278) * **Server**: Adjust permissions of unix socket [#11343](https://github.com/grafana/grafana/pull/11343), thx [@corny](https://github.com/corny) * **Shortcuts**: Add shortcut for duplicate panel [#11102](https://github.com/grafana/grafana/issues/11102) +* **AuthProxy**: Support IPv6 in Auth proxy white list [#11330](https://github.com/grafana/grafana/pull/11330), thx [@corny](https://github.com/corny) +* **SMTP**: Don't connect to STMP server using TLS unless configured. [#7189](https://github.com/grafana/grafana/issues/7189) # 5.0.4 (unreleased) * **Dashboard** Fixed bug where collapsed panels could not be directly linked to/renderer [#11114](https://github.com/grafana/grafana/issues/11114) & [#11086](https://github.com/grafana/grafana/issues/11086) From b1d3535cbb14d91f52e5afe9ff295c67057d237b Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 28 Mar 2018 14:50:27 +0200 Subject: [PATCH 23/57] changelog: update for v5.0.4 --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4756f4944d..0055c028520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,8 +26,12 @@ * **AuthProxy**: Support IPv6 in Auth proxy white list [#11330](https://github.com/grafana/grafana/pull/11330), thx [@corny](https://github.com/corny) * **SMTP**: Don't connect to STMP server using TLS unless configured. [#7189](https://github.com/grafana/grafana/issues/7189) -# 5.0.4 (unreleased) -* **Dashboard** Fixed bug where collapsed panels could not be directly linked to/renderer [#11114](https://github.com/grafana/grafana/issues/11114) & [#11086](https://github.com/grafana/grafana/issues/11086) +# 5.0.4 (2018-03-28) +* **Dashboard** Fixed bug where collapsed panels could not be directly linked to/renderer [#11114](https://github.com/grafana/grafana/issues/11114) & [#11086](https://github.com/grafana/grafana/issues/11086) & [#11296](https://github.com/grafana/grafana/issues/11296) +* **Dashboard** Provisioning dashboard with alert rules should create alerts [#11247](https://github.com/grafana/grafana/issues/11247) +* **Snapshots** For snapshots, the Graph panel renders the legend incorrectly on right hand side [#11318](https://github.com/grafana/grafana/issues/11318) +* **Alerting** Link back to Grafana returns wrong URL if root_path contains sub-path components [#11403](https://github.com/grafana/grafana/issues/11403) +* **Alerting** Incorrect default value for upload images setting for alert notifiers [#11413](https://github.com/grafana/grafana/pull/11413) # 5.0.3 (2018-03-16) * **Mysql**: Mysql panic occurring occasionally upon Grafana dashboard access (a bigger patch than the one in 5.0.2) [#11155](https://github.com/grafana/grafana/issues/11155) From 33bd979494a5ac67db2211245e328125bc82bdba Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 28 Mar 2018 14:57:11 +0200 Subject: [PATCH 24/57] changelog: another update for v5.0.4 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0055c028520..03ad228f3c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ * **SMTP**: Don't connect to STMP server using TLS unless configured. [#7189](https://github.com/grafana/grafana/issues/7189) # 5.0.4 (2018-03-28) + +* **Docker** Can't start Grafana on Kubernetes 1.7.14, 1.8.9, or 1.9.4 [#140 in grafana-docker repo](https://github.com/grafana/grafana-docker/issues/140) thx [@suquant](https://github.com/suquant) * **Dashboard** Fixed bug where collapsed panels could not be directly linked to/renderer [#11114](https://github.com/grafana/grafana/issues/11114) & [#11086](https://github.com/grafana/grafana/issues/11086) & [#11296](https://github.com/grafana/grafana/issues/11296) * **Dashboard** Provisioning dashboard with alert rules should create alerts [#11247](https://github.com/grafana/grafana/issues/11247) * **Snapshots** For snapshots, the Graph panel renders the legend incorrectly on right hand side [#11318](https://github.com/grafana/grafana/issues/11318) From d40b7433ead654eea5e942eb095b94ac1ea73c76 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 28 Mar 2018 15:06:20 +0200 Subject: [PATCH 25/57] removed padding for icons and added margin --- public/sass/pages/_alerting.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/sass/pages/_alerting.scss b/public/sass/pages/_alerting.scss index f44e26d5c20..5a481b55a8a 100644 --- a/public/sass/pages/_alerting.scss +++ b/public/sass/pages/_alerting.scss @@ -108,7 +108,7 @@ justify-content: center; align-items: center; width: 40px; - padding: 0 28px 0 16px; + margin-right: 8px; .icon-gf, .fa { font-size: 200%; From 485d0f1c954f0eec80c1d98bbe3f47b58fcd7d27 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 28 Mar 2018 15:19:46 +0200 Subject: [PATCH 26/57] docs: install pages for v5.0.4 --- docs/sources/installation/debian.md | 6 +++--- docs/sources/installation/rpm.md | 10 +++++----- docs/sources/installation/windows.md | 3 +-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index d4d3b05343a..27c0e41ac15 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -15,7 +15,7 @@ weight = 1 Description | Download ------------ | ------------- -Stable for Debian-based Linux | [grafana_5.0.3_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.3_amd64.deb) +Stable for Debian-based Linux | [grafana_5.0.4_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.4_amd64.deb) Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing installation. @@ -24,9 +24,9 @@ installation. ```bash -wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.3_amd64.deb +wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.4_amd64.deb sudo apt-get install -y adduser libfontconfig -sudo dpkg -i grafana_5.0.3_amd64.deb +sudo dpkg -i grafana_5.0.4_amd64.deb ``` ## APT Repository diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index b0405eb6533..86930af21a5 100644 --- a/docs/sources/installation/rpm.md +++ b/docs/sources/installation/rpm.md @@ -15,7 +15,7 @@ weight = 2 Description | Download ------------ | ------------- -Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [5.0.3 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.3-1.x86_64.rpm) +Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [5.0.4 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4-1.x86_64.rpm) Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing @@ -26,7 +26,7 @@ installation. You can install Grafana using Yum directly. ```bash -$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.3-1.x86_64.rpm +$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4-1.x86_64.rpm ``` Or install manually using `rpm`. @@ -34,15 +34,15 @@ Or install manually using `rpm`. #### On CentOS / Fedora / Redhat: ```bash -$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.3-1.x86_64.rpm +$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4-1.x86_64.rpm $ sudo yum install initscripts fontconfig -$ sudo rpm -Uvh grafana-5.0.3-1.x86_64.rpm +$ sudo rpm -Uvh grafana-5.0.4-1.x86_64.rpm ``` #### On OpenSuse: ```bash -$ sudo rpm -i --nodeps grafana-5.0.3-1.x86_64.rpm +$ sudo rpm -i --nodeps grafana-5.0.4-1.x86_64.rpm ``` ## Install via YUM Repository diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index 2dac13a6322..4f8d7696b57 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -8,12 +8,11 @@ parent = "installation" weight = 3 +++ - # Installing on Windows Description | Download ------------ | ------------- -Latest stable package for Windows | [grafana-5.0.3.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.3.windows-x64.zip) +Latest stable package for Windows | [grafana-5.0.4.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4.windows-x64.zip) Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing installation. From 86776154cc8332c4c19b8854ec4f0a3ad7910fb4 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 28 Mar 2018 15:35:51 +0200 Subject: [PATCH 27/57] docs: rpm install page - update to centos 7 --- docs/sources/installation/rpm.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index 86930af21a5..05192512e5a 100644 --- a/docs/sources/installation/rpm.md +++ b/docs/sources/installation/rpm.md @@ -52,7 +52,7 @@ Add the following to a new file at `/etc/yum.repos.d/grafana.repo` ```bash [grafana] name=grafana -baseurl=https://packagecloud.io/grafana/stable/el/6/$basearch +baseurl=https://packagecloud.io/grafana/stable/el/7/$basearch repo_gpgcheck=1 enabled=1 gpgcheck=1 @@ -64,7 +64,7 @@ sslcacert=/etc/pki/tls/certs/ca-bundle.crt There is also a testing repository if you want beta or release candidates. ```bash -baseurl=https://packagecloud.io/grafana/testing/el/6/$basearch +baseurl=https://packagecloud.io/grafana/testing/el/7/$basearch ``` Then install Grafana via the `yum` command. From e645c51f7c68ef554037d89a9fb9e96d89ba1f3c Mon Sep 17 00:00:00 2001 From: Angus Burroughs Date: Wed, 28 Mar 2018 16:26:15 +0200 Subject: [PATCH 28/57] Fixed typo in upgrading.md Line 26: 'you' to 'your' in "make a backup of you 'grafana.db'" --- docs/sources/installation/upgrading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/upgrading.md b/docs/sources/installation/upgrading.md index 5b00fd92924..49cdd4ca1d3 100644 --- a/docs/sources/installation/upgrading.md +++ b/docs/sources/installation/upgrading.md @@ -23,7 +23,7 @@ Before upgrading it can be a good idea to backup your Grafana database. This wil #### sqlite -If you use sqlite you only need to make a backup of you `grafana.db` file. This is usually located at `/var/lib/grafana/grafana.db` on unix system. +If you use sqlite you only need to make a backup of your `grafana.db` file. This is usually located at `/var/lib/grafana/grafana.db` on unix system. If you are unsure what database you use and where it is stored check you grafana configuration file. If you installed grafana to custom location using a binary tar/zip it is usally in `/data`. From 2ccbf12d1c57cdbbf6c5ad59208faf6604e19075 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 28 Mar 2018 18:03:33 +0200 Subject: [PATCH 29/57] settings: return error instead of ignoring it closes #11281 --- pkg/setting/setting.go | 25 +++++++++++++++++++------ pkg/setting/setting_test.go | 7 +++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 5b79e866964..30a40602b1c 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -223,7 +223,7 @@ func shouldRedactURLKey(s string) bool { return strings.Contains(uppercased, "DATABASE_URL") } -func applyEnvVariableOverrides() { +func applyEnvVariableOverrides() error { appliedEnvOverrides = make([]string, 0) for _, section := range Cfg.Sections() { for _, key := range section.Keys() { @@ -238,7 +238,10 @@ func applyEnvVariableOverrides() { envValue = "*********" } if shouldRedactURLKey(envKey) { - u, _ := url.Parse(envValue) + u, err := url.Parse(envValue) + if err != nil { + return fmt.Errorf("could not parse environment variable. key: %s, value: %s. error: %v", envKey, envValue, err) + } ui := u.User if ui != nil { _, exists := ui.Password() @@ -252,6 +255,8 @@ func applyEnvVariableOverrides() { } } } + + return nil } func applyCommandLineDefaultProperties(props map[string]string) { @@ -377,7 +382,7 @@ func loadSpecifedConfigFile(configFile string) error { return nil } -func loadConfiguration(args *CommandLineArgs) { +func loadConfiguration(args *CommandLineArgs) error { var err error // load config defaults @@ -395,7 +400,7 @@ func loadConfiguration(args *CommandLineArgs) { if err != nil { fmt.Println(fmt.Sprintf("Failed to parse defaults.ini, %v", err)) os.Exit(1) - return + return err } Cfg.BlockMode = false @@ -413,7 +418,10 @@ func loadConfiguration(args *CommandLineArgs) { } // apply environment overrides - applyEnvVariableOverrides() + err = applyEnvVariableOverrides() + if err != nil { + return err + } // apply command line overrides applyCommandLineProperties(commandLineProps) @@ -424,6 +432,8 @@ func loadConfiguration(args *CommandLineArgs) { // update data path and logging config DataPath = makeAbsolute(Cfg.Section("paths").Key("data").String(), HomePath) initLogging() + + return err } func pathExists(path string) bool { @@ -471,7 +481,10 @@ func validateStaticRootPath() error { func NewConfigContext(args *CommandLineArgs) error { setHomePath(args) - loadConfiguration(args) + err := loadConfiguration(args) + if err != nil { + return err + } Env = Cfg.Section("").Key("app_mode").MustString("development") InstanceName = Cfg.Section("").Key("instance_name").MustString("unknown_instance_name") diff --git a/pkg/setting/setting_test.go b/pkg/setting/setting_test.go index 640a1648340..87f9916075e 100644 --- a/pkg/setting/setting_test.go +++ b/pkg/setting/setting_test.go @@ -37,6 +37,13 @@ func TestLoadingSettings(t *testing.T) { So(appliedEnvOverrides, ShouldContain, "GF_SECURITY_ADMIN_PASSWORD=*********") }) + Convey("Should replace password when defined in environment2", func() { + os.Setenv("GF_DATABASE_URL", "postgres://grafana:sec{ret@postgres:5432/grafana") + err := NewConfigContext(&CommandLineArgs{HomePath: "../../"}) + + So(err, ShouldNotBeNil) + }) + Convey("Should replace password in URL when url environment is defined", func() { os.Setenv("GF_DATABASE_URL", "mysql://user:secret@localhost:3306/database") NewConfigContext(&CommandLineArgs{HomePath: "../../"}) From 45d9bfca97a3a3ab98889ad41d516667d575c735 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 28 Mar 2018 22:51:21 +0200 Subject: [PATCH 30/57] print to stderr since logger might not exist --- pkg/cmd/grafana-server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index 5bbf43087ec..b8387403161 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -111,7 +111,7 @@ func (g *GrafanaServerImpl) initLogging() { }) if err != nil { - g.log.Error(err.Error()) + fmt.Fprintf(os.Stderr, "Failed to start grafana. error: %s\n", err.Error()) os.Exit(1) } From 8195c085fa2a095fe753f725663ff7217cc14365 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Thu, 29 Mar 2018 09:15:15 +0200 Subject: [PATCH 31/57] bounnd the esc key to exit timepicker --- public/app/core/services/keybindingSrv.ts | 7 +++++++ public/app/features/dashboard/timepicker/timepicker.ts | 3 +++ 2 files changed, 10 insertions(+) diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 0d468b6980f..829a3415cc1 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -10,6 +10,7 @@ import 'mousetrap-global-bind'; export class KeybindingSrv { helpModal: boolean; modalOpen = false; + timepickerOpen = false; /** @ngInject */ constructor(private $rootScope, private $location) { @@ -22,6 +23,7 @@ export class KeybindingSrv { this.setupGlobal(); appEvents.on('show-modal', () => (this.modalOpen = true)); + $rootScope.onAppEvent('openTimepicker', () => (this.timepickerOpen = true)); } setupGlobal() { @@ -72,6 +74,11 @@ export class KeybindingSrv { appEvents.emit('hide-modal'); + if (this.timepickerOpen === true) { + this.$rootScope.appEvent('closeTimepicker'); + this.timepickerOpen = false; + } + if (!this.modalOpen) { this.$rootScope.appEvent('panel-change-view', { fullscreen: false, edit: false }); } else { diff --git a/public/app/features/dashboard/timepicker/timepicker.ts b/public/app/features/dashboard/timepicker/timepicker.ts index 2434e691515..19c3db7f6d3 100644 --- a/public/app/features/dashboard/timepicker/timepicker.ts +++ b/public/app/features/dashboard/timepicker/timepicker.ts @@ -32,6 +32,7 @@ export class TimePickerCtrl { $rootScope.onAppEvent('shift-time-forward', () => this.move(1), $scope); $rootScope.onAppEvent('shift-time-backward', () => this.move(-1), $scope); $rootScope.onAppEvent('refresh', this.onRefresh.bind(this), $scope); + $rootScope.onAppEvent('closeTimepicker', this.openDropdown.bind(this), $scope); // init options this.panel = this.dashboard.timepicker; @@ -100,6 +101,8 @@ export class TimePickerCtrl { return; } + this.$rootScope.appEvent('openTimepicker'); + this.onRefresh(); this.editTimeRaw = this.timeRaw; this.timeOptions = rangeUtil.getRelativeTimesList(this.panel, this.rangeString); From 1f1719c49822d2a43f225143bb6d389408ad7b43 Mon Sep 17 00:00:00 2001 From: Julien Pivotto Date: Tue, 27 Mar 2018 16:16:00 +0200 Subject: [PATCH 32/57] Fix #10555 #6888 Better escape for Prometheus variables Prior this commit, C:\bar was escaped C:\\\bar. Should be C:\\\\bar. Signed-off-by: Julien Pivotto --- .../datasource/prometheus/datasource.ts | 10 +++-- .../prometheus/specs/datasource.jest.ts | 39 ++++++++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 4c736f2c664..6cf6c713a90 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -6,8 +6,12 @@ import * as dateMath from 'app/core/utils/datemath'; import PrometheusMetricFindQuery from './metric_find_query'; import { ResultTransformer } from './result_transformer'; -function prometheusSpecialRegexEscape(value) { - return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&'); +export function prometheusRegularEscape(value) { + return value.replace(/'/g, "\\\\'"); +} + +export function prometheusSpecialRegexEscape(value) { + return prometheusRegularEscape(value.replace(/\\/g, '\\\\\\\\').replace(/[$^*{}\[\]+?.()]/g, '\\\\$&')); } export class PrometheusDatasource { @@ -80,7 +84,7 @@ export class PrometheusDatasource { interpolateQueryExpr(value, variable, defaultFormatFn) { // if no multi or include all do not regexEscape if (!variable.multi && !variable.includeAll) { - return value; + return prometheusRegularEscape(value); } if (typeof value === 'string') { diff --git a/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts b/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts index cca74e023e7..d2620b93bbc 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource.jest.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import moment from 'moment'; import q from 'q'; -import { PrometheusDatasource } from '../datasource'; +import { PrometheusDatasource, prometheusSpecialRegexEscape, prometheusRegularEscape } from '../datasource'; describe('PrometheusDatasource', () => { let ctx: any = {}; @@ -101,4 +101,41 @@ describe('PrometheusDatasource', () => { }); }); }); + + describe('Prometheus regular escaping', function() { + it('should not escape simple string', function() { + expect(prometheusRegularEscape('cryptodepression')).toEqual('cryptodepression'); + }); + it("should escape '", function() { + expect(prometheusRegularEscape("looking'glass")).toEqual("looking\\\\'glass"); + }); + it('should escape multiple characters', function() { + expect(prometheusRegularEscape("'looking'glass'")).toEqual("\\\\'looking\\\\'glass\\\\'"); + }); + }); + + describe('Prometheus regexes escaping', function() { + it('should not escape simple string', function() { + expect(prometheusSpecialRegexEscape('cryptodepression')).toEqual('cryptodepression'); + }); + it('should escape $^*+?.()\\', function() { + expect(prometheusSpecialRegexEscape("looking'glass")).toEqual("looking\\\\'glass"); + expect(prometheusSpecialRegexEscape('looking{glass')).toEqual('looking\\\\{glass'); + expect(prometheusSpecialRegexEscape('looking}glass')).toEqual('looking\\\\}glass'); + expect(prometheusSpecialRegexEscape('looking[glass')).toEqual('looking\\\\[glass'); + expect(prometheusSpecialRegexEscape('looking]glass')).toEqual('looking\\\\]glass'); + expect(prometheusSpecialRegexEscape('looking$glass')).toEqual('looking\\\\$glass'); + expect(prometheusSpecialRegexEscape('looking^glass')).toEqual('looking\\\\^glass'); + expect(prometheusSpecialRegexEscape('looking*glass')).toEqual('looking\\\\*glass'); + expect(prometheusSpecialRegexEscape('looking+glass')).toEqual('looking\\\\+glass'); + expect(prometheusSpecialRegexEscape('looking?glass')).toEqual('looking\\\\?glass'); + expect(prometheusSpecialRegexEscape('looking.glass')).toEqual('looking\\\\.glass'); + expect(prometheusSpecialRegexEscape('looking(glass')).toEqual('looking\\\\(glass'); + expect(prometheusSpecialRegexEscape('looking)glass')).toEqual('looking\\\\)glass'); + expect(prometheusSpecialRegexEscape('looking\\glass')).toEqual('looking\\\\\\\\glass'); + }); + it('should escape multiple special characters', function() { + expect(prometheusSpecialRegexEscape('+looking$glass?')).toEqual('\\\\+looking\\\\$glass\\\\?'); + }); + }); }); From 65f7c5f08f891bab0cbb87e6cf18fbd8aa23d93b Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Thu, 29 Mar 2018 10:40:45 +0200 Subject: [PATCH 33/57] started migration to ts --- .../app/plugins/panel/graph/graph_tooltip.js | 292 ------------------ .../app/plugins/panel/graph/graph_tooltip.ts | 290 +++++++++++++++++ 2 files changed, 290 insertions(+), 292 deletions(-) delete mode 100644 public/app/plugins/panel/graph/graph_tooltip.js create mode 100644 public/app/plugins/panel/graph/graph_tooltip.ts diff --git a/public/app/plugins/panel/graph/graph_tooltip.js b/public/app/plugins/panel/graph/graph_tooltip.js deleted file mode 100644 index 89197717e42..00000000000 --- a/public/app/plugins/panel/graph/graph_tooltip.js +++ /dev/null @@ -1,292 +0,0 @@ -define([ - 'jquery', - 'app/core/core', -], -function ($, core) { - 'use strict'; - - var appEvents = core.appEvents; - - function GraphTooltip(elem, dashboard, scope, getSeriesFn) { - var self = this; - var ctrl = scope.ctrl; - var panel = ctrl.panel; - - var $tooltip = $('
'); - - this.destroy = function() { - $tooltip.remove(); - }; - - this.findHoverIndexFromDataPoints = function(posX, series, last) { - var ps = series.datapoints.pointsize; - var initial = last*ps; - var len = series.datapoints.points.length; - for (var j = initial; j < len; j += ps) { - // Special case of a non stepped line, highlight the very last point just before a null point - if ((!series.lines.steps && series.datapoints.points[initial] != null && series.datapoints.points[j] == null) - //normal case - || series.datapoints.points[j] > posX) { - return Math.max(j - ps, 0)/ps; - } - } - return j/ps - 1; - }; - - this.findHoverIndexFromData = function(posX, series) { - var lower = 0; - var upper = series.data.length - 1; - var middle; - while (true) { - if (lower > upper) { - return Math.max(upper, 0); - } - middle = Math.floor((lower + upper) / 2); - if (series.data[middle][0] === posX) { - return middle; - } else if (series.data[middle][0] < posX) { - lower = middle + 1; - } else { - upper = middle - 1; - } - } - }; - - this.renderAndShow = function(absoluteTime, innerHtml, pos, xMode) { - if (xMode === 'time') { - innerHtml = '
'+ absoluteTime + '
' + innerHtml; - } - $tooltip.html(innerHtml).place_tt(pos.pageX + 20, pos.pageY); - }; - - this.getMultiSeriesPlotHoverInfo = function(seriesList, pos) { - var value, i, series, hoverIndex, hoverDistance, pointTime, yaxis; - // 3 sub-arrays, 1st for hidden series, 2nd for left yaxis, 3rd for right yaxis. - var results = [[],[],[]]; - - //now we know the current X (j) position for X and Y values - var last_value = 0; //needed for stacked values - - var minDistance, minTime; - - for (i = 0; i < seriesList.length; i++) { - series = seriesList[i]; - - if (!series.data.length || (panel.legend.hideEmpty && series.allIsNull)) { - // Init value so that it does not brake series sorting - results[0].push({ hidden: true, value: 0 }); - continue; - } - - if (!series.data.length || (panel.legend.hideZero && series.allIsZero)) { - // Init value so that it does not brake series sorting - results[0].push({ hidden: true, value: 0 }); - continue; - } - - hoverIndex = this.findHoverIndexFromData(pos.x, series); - hoverDistance = pos.x - series.data[hoverIndex][0]; - pointTime = series.data[hoverIndex][0]; - - // Take the closest point before the cursor, or if it does not exist, the closest after - if (! minDistance - || (hoverDistance >=0 && (hoverDistance < minDistance || minDistance < 0)) - || (hoverDistance < 0 && hoverDistance > minDistance)) { - minDistance = hoverDistance; - minTime = pointTime; - } - - if (series.stack) { - if (panel.tooltip.value_type === 'individual') { - value = series.data[hoverIndex][1]; - } else if (!series.stack) { - value = series.data[hoverIndex][1]; - } else { - last_value += series.data[hoverIndex][1]; - value = last_value; - } - } else { - value = series.data[hoverIndex][1]; - } - - // Highlighting multiple Points depending on the plot type - if (series.lines.steps || series.stack) { - // stacked and steppedLine plots can have series with different length. - // Stacked series can increase its length on each new stacked serie if null points found, - // to speed the index search we begin always on the last found hoverIndex. - hoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex); - } - - // Be sure we have a yaxis so that it does not brake series sorting - yaxis = 0; - if (series.yaxis) { - yaxis = series.yaxis.n; - } - - results[yaxis].push({ - value: value, - hoverIndex: hoverIndex, - color: series.color, - label: series.aliasEscaped, - time: pointTime, - distance: hoverDistance, - index: i - }); - } - - // Contat the 3 sub-arrays - results = results[0].concat(results[1],results[2]); - - // Time of the point closer to pointer - results.time = minTime; - - return results; - }; - - elem.mouseleave(function () { - if (panel.tooltip.shared) { - var plot = elem.data().plot; - if (plot) { - $tooltip.detach(); - plot.unhighlight(); - } - } - appEvents.emit('graph-hover-clear'); - }); - - elem.bind("plothover", function (event, pos, item) { - self.show(pos, item); - - // broadcast to other graph panels that we are hovering! - pos.panelRelY = (pos.pageY - elem.offset().top) / elem.height(); - appEvents.emit('graph-hover', {pos: pos, panel: panel}); - }); - - elem.bind("plotclick", function (event, pos, item) { - appEvents.emit('graph-click', {pos: pos, panel: panel, item: item}); - }); - - this.clear = function(plot) { - $tooltip.detach(); - plot.clearCrosshair(); - plot.unhighlight(); - }; - - this.show = function(pos, item) { - var plot = elem.data().plot; - var plotData = plot.getData(); - var xAxes = plot.getXAxes(); - var xMode = xAxes[0].options.mode; - var seriesList = getSeriesFn(); - var allSeriesMode = panel.tooltip.shared; - var group, value, absoluteTime, hoverInfo, i, series, seriesHtml, tooltipFormat; - - // if panelRelY is defined another panel wants us to show a tooltip - // get pageX from position on x axis and pageY from relative position in original panel - if (pos.panelRelY) { - var pointOffset = plot.pointOffset({x: pos.x}); - if (Number.isNaN(pointOffset.left) || pointOffset.left < 0 || pointOffset.left > elem.width()) { - self.clear(plot); - return; - } - pos.pageX = elem.offset().left + pointOffset.left; - pos.pageY = elem.offset().top + elem.height() * pos.panelRelY; - var isVisible = pos.pageY >= $(window).scrollTop() && pos.pageY <= $(window).innerHeight() + $(window).scrollTop(); - if (!isVisible) { - self.clear(plot); - return; - } - plot.setCrosshair(pos); - allSeriesMode = true; - - if (dashboard.sharedCrosshairModeOnly()) { - // if only crosshair mode we are done - return; - } - } - - if (seriesList.length === 0) { - return; - } - - if (seriesList[0].hasMsResolution) { - tooltipFormat = 'YYYY-MM-DD HH:mm:ss.SSS'; - } else { - tooltipFormat = 'YYYY-MM-DD HH:mm:ss'; - } - - if (allSeriesMode) { - plot.unhighlight(); - - var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(plotData, pos); - - seriesHtml = ''; - - absoluteTime = dashboard.formatDate(seriesHoverInfo.time, tooltipFormat); - - // Dynamically reorder the hovercard for the current time point if the - // option is enabled. - if (panel.tooltip.sort === 2) { - seriesHoverInfo.sort(function(a, b) { - return b.value - a.value; - }); - } else if (panel.tooltip.sort === 1) { - seriesHoverInfo.sort(function(a, b) { - return a.value - b.value; - }); - } - - for (i = 0; i < seriesHoverInfo.length; i++) { - hoverInfo = seriesHoverInfo[i]; - - if (hoverInfo.hidden) { - continue; - } - - var highlightClass = ''; - if (item && hoverInfo.index === item.seriesIndex) { - highlightClass = 'graph-tooltip-list-item--highlight'; - } - - series = seriesList[hoverInfo.index]; - - value = series.formatValue(hoverInfo.value); - - seriesHtml += '
'; - seriesHtml += ' ' + hoverInfo.label + ':
'; - seriesHtml += '
' + value + '
'; - plot.highlight(hoverInfo.index, hoverInfo.hoverIndex); - } - - self.renderAndShow(absoluteTime, seriesHtml, pos, xMode); - } - // single series tooltip - else if (item) { - series = seriesList[item.seriesIndex]; - group = '
'; - group += ' ' + series.aliasEscaped + ':
'; - - if (panel.stack && panel.tooltip.value_type === 'individual') { - value = item.datapoint[1] - item.datapoint[2]; - } - else { - value = item.datapoint[1]; - } - - value = series.formatValue(value); - - absoluteTime = dashboard.formatDate(item.datapoint[0], tooltipFormat); - - group += '
' + value + '
'; - - self.renderAndShow(absoluteTime, group, pos, xMode); - } - // no hit - else { - $tooltip.detach(); - } - }; - } - - return GraphTooltip; -}); diff --git a/public/app/plugins/panel/graph/graph_tooltip.ts b/public/app/plugins/panel/graph/graph_tooltip.ts new file mode 100644 index 00000000000..846a165bbaf --- /dev/null +++ b/public/app/plugins/panel/graph/graph_tooltip.ts @@ -0,0 +1,290 @@ +import $ from 'jquery'; +import { appEvents } from 'app/core/core'; + +//var appEvents = appEvents; + +export default function GraphTooltip(elem, dashboard, scope, getSeriesFn) { + let self = this; + let ctrl = scope.ctrl; + let panel = ctrl.panel; + + let $tooltip = $('
'); + + this.destroy = function() { + $tooltip.remove(); + }; + + this.findHoverIndexFromDataPoints = function(posX, series, last) { + var ps = series.datapoints.pointsize; + var initial = last * ps; + var len = series.datapoints.points.length; + for (var j = initial; j < len; j += ps) { + // Special case of a non stepped line, highlight the very last point just before a null point + if ( + (!series.lines.steps && series.datapoints.points[initial] != null && series.datapoints.points[j] == null) || + //normal case + series.datapoints.points[j] > posX + ) { + return Math.max(j - ps, 0) / ps; + } + } + return j / ps - 1; + }; + + this.findHoverIndexFromData = function(posX, series) { + var lower = 0; + var upper = series.data.length - 1; + var middle; + while (true) { + if (lower > upper) { + return Math.max(upper, 0); + } + middle = Math.floor((lower + upper) / 2); + if (series.data[middle][0] === posX) { + return middle; + } else if (series.data[middle][0] < posX) { + lower = middle + 1; + } else { + upper = middle - 1; + } + } + }; + + this.renderAndShow = function(absoluteTime, innerHtml, pos, xMode) { + if (xMode === 'time') { + innerHtml = '
' + absoluteTime + '
' + innerHtml; + } + $tooltip.html(innerHtml).place_tt(pos.pageX + 20, pos.pageY); + }; + + this.getMultiSeriesPlotHoverInfo = function(seriesList, pos) { + var value, i, series, hoverIndex, hoverDistance, pointTime, yaxis; + // 3 sub-arrays, 1st for hidden series, 2nd for left yaxis, 3rd for right yaxis. + var results: any = [[], [], []]; + + //now we know the current X (j) position for X and Y values + var last_value = 0; //needed for stacked values + + var minDistance, minTime; + + for (i = 0; i < seriesList.length; i++) { + series = seriesList[i]; + + if (!series.data.length || (panel.legend.hideEmpty && series.allIsNull)) { + // Init value so that it does not brake series sorting + results[0].push({ hidden: true, value: 0 }); + continue; + } + + if (!series.data.length || (panel.legend.hideZero && series.allIsZero)) { + // Init value so that it does not brake series sorting + results[0].push({ hidden: true, value: 0 }); + continue; + } + + hoverIndex = this.findHoverIndexFromData(pos.x, series); + hoverDistance = pos.x - series.data[hoverIndex][0]; + pointTime = series.data[hoverIndex][0]; + + // Take the closest point before the cursor, or if it does not exist, the closest after + if ( + !minDistance || + (hoverDistance >= 0 && (hoverDistance < minDistance || minDistance < 0)) || + (hoverDistance < 0 && hoverDistance > minDistance) + ) { + minDistance = hoverDistance; + minTime = pointTime; + } + + if (series.stack) { + if (panel.tooltip.value_type === 'individual') { + value = series.data[hoverIndex][1]; + } else if (!series.stack) { + value = series.data[hoverIndex][1]; + } else { + last_value += series.data[hoverIndex][1]; + value = last_value; + } + } else { + value = series.data[hoverIndex][1]; + } + + // Highlighting multiple Points depending on the plot type + if (series.lines.steps || series.stack) { + // stacked and steppedLine plots can have series with different length. + // Stacked series can increase its length on each new stacked serie if null points found, + // to speed the index search we begin always on the last found hoverIndex. + hoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex); + } + + // Be sure we have a yaxis so that it does not brake series sorting + yaxis = 0; + if (series.yaxis) { + yaxis = series.yaxis.n; + } + + results[yaxis].push({ + value: value, + hoverIndex: hoverIndex, + color: series.color, + label: series.aliasEscaped, + time: pointTime, + distance: hoverDistance, + index: i, + }); + } + + // Contat the 3 sub-arrays + results = results[0].concat(results[1], results[2]); + + // Time of the point closer to pointer + results.time = minTime; + + return results; + }; + + elem.mouseleave(function() { + if (panel.tooltip.shared) { + var plot = elem.data().plot; + if (plot) { + $tooltip.detach(); + plot.unhighlight(); + } + } + appEvents.emit('graph-hover-clear'); + }); + + elem.bind('plothover', function(event, pos, item) { + self.show(pos, item); + + // broadcast to other graph panels that we are hovering! + pos.panelRelY = (pos.pageY - elem.offset().top) / elem.height(); + appEvents.emit('graph-hover', { pos: pos, panel: panel }); + }); + + elem.bind('plotclick', function(event, pos, item) { + appEvents.emit('graph-click', { pos: pos, panel: panel, item: item }); + }); + + this.clear = function(plot) { + $tooltip.detach(); + plot.clearCrosshair(); + plot.unhighlight(); + }; + + this.show = function(pos, item) { + var plot = elem.data().plot; + var plotData = plot.getData(); + var xAxes = plot.getXAxes(); + var xMode = xAxes[0].options.mode; + var seriesList = getSeriesFn(); + var allSeriesMode = panel.tooltip.shared; + var group, value, absoluteTime, hoverInfo, i, series, seriesHtml, tooltipFormat; + + // if panelRelY is defined another panel wants us to show a tooltip + // get pageX from position on x axis and pageY from relative position in original panel + if (pos.panelRelY) { + var pointOffset = plot.pointOffset({ x: pos.x }); + if (Number.isNaN(pointOffset.left) || pointOffset.left < 0 || pointOffset.left > elem.width()) { + self.clear(plot); + return; + } + pos.pageX = elem.offset().left + pointOffset.left; + pos.pageY = elem.offset().top + elem.height() * pos.panelRelY; + var isVisible = + pos.pageY >= $(window).scrollTop() && pos.pageY <= $(window).innerHeight() + $(window).scrollTop(); + if (!isVisible) { + self.clear(plot); + return; + } + plot.setCrosshair(pos); + allSeriesMode = true; + + if (dashboard.sharedCrosshairModeOnly()) { + // if only crosshair mode we are done + return; + } + } + + if (seriesList.length === 0) { + return; + } + + if (seriesList[0].hasMsResolution) { + tooltipFormat = 'YYYY-MM-DD HH:mm:ss.SSS'; + } else { + tooltipFormat = 'YYYY-MM-DD HH:mm:ss'; + } + + if (allSeriesMode) { + plot.unhighlight(); + + var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(plotData, pos); + + seriesHtml = ''; + + absoluteTime = dashboard.formatDate(seriesHoverInfo.time, tooltipFormat); + + // Dynamically reorder the hovercard for the current time point if the + // option is enabled. + if (panel.tooltip.sort === 2) { + seriesHoverInfo.sort(function(a, b) { + return b.value - a.value; + }); + } else if (panel.tooltip.sort === 1) { + seriesHoverInfo.sort(function(a, b) { + return a.value - b.value; + }); + } + + for (i = 0; i < seriesHoverInfo.length; i++) { + hoverInfo = seriesHoverInfo[i]; + + if (hoverInfo.hidden) { + continue; + } + + var highlightClass = ''; + if (item && hoverInfo.index === item.seriesIndex) { + highlightClass = 'graph-tooltip-list-item--highlight'; + } + + series = seriesList[hoverInfo.index]; + + value = series.formatValue(hoverInfo.value); + + seriesHtml += + '
'; + seriesHtml += + ' ' + hoverInfo.label + ':
'; + seriesHtml += '
' + value + '
'; + plot.highlight(hoverInfo.index, hoverInfo.hoverIndex); + } + + self.renderAndShow(absoluteTime, seriesHtml, pos, xMode); + } else if (item) { + // single series tooltip + series = seriesList[item.seriesIndex]; + group = '
'; + group += + ' ' + series.aliasEscaped + ':
'; + + if (panel.stack && panel.tooltip.value_type === 'individual') { + value = item.datapoint[1] - item.datapoint[2]; + } else { + value = item.datapoint[1]; + } + + value = series.formatValue(value); + + absoluteTime = dashboard.formatDate(item.datapoint[0], tooltipFormat); + + group += '
' + value + '
'; + + self.renderAndShow(absoluteTime, group, pos, xMode); + } else { + // no hit + $tooltip.detach(); + } + }; +} From 7b9b34c6e188344665948cca705c0e4d24389608 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Thu, 29 Mar 2018 11:51:34 +0200 Subject: [PATCH 34/57] migrated graph_tooltip to ts --- .../app/plugins/panel/graph/graph_tooltip.ts | 49 +++++++++---------- .../panel/graph/specs/tooltip_specs.ts | 5 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/public/app/plugins/panel/graph/graph_tooltip.ts b/public/app/plugins/panel/graph/graph_tooltip.ts index 846a165bbaf..509d15b8a25 100644 --- a/public/app/plugins/panel/graph/graph_tooltip.ts +++ b/public/app/plugins/panel/graph/graph_tooltip.ts @@ -1,8 +1,6 @@ import $ from 'jquery'; import { appEvents } from 'app/core/core'; -//var appEvents = appEvents; - export default function GraphTooltip(elem, dashboard, scope, getSeriesFn) { let self = this; let ctrl = scope.ctrl; @@ -15,10 +13,11 @@ export default function GraphTooltip(elem, dashboard, scope, getSeriesFn) { }; this.findHoverIndexFromDataPoints = function(posX, series, last) { - var ps = series.datapoints.pointsize; - var initial = last * ps; - var len = series.datapoints.points.length; - for (var j = initial; j < len; j += ps) { + let ps = series.datapoints.pointsize; + let initial = last * ps; + let len = series.datapoints.points.length; + let j; + for (j = initial; j < len; j += ps) { // Special case of a non stepped line, highlight the very last point just before a null point if ( (!series.lines.steps && series.datapoints.points[initial] != null && series.datapoints.points[j] == null) || @@ -32,9 +31,9 @@ export default function GraphTooltip(elem, dashboard, scope, getSeriesFn) { }; this.findHoverIndexFromData = function(posX, series) { - var lower = 0; - var upper = series.data.length - 1; - var middle; + let lower = 0; + let upper = series.data.length - 1; + let middle; while (true) { if (lower > upper) { return Math.max(upper, 0); @@ -58,14 +57,14 @@ export default function GraphTooltip(elem, dashboard, scope, getSeriesFn) { }; this.getMultiSeriesPlotHoverInfo = function(seriesList, pos) { - var value, i, series, hoverIndex, hoverDistance, pointTime, yaxis; + let value, i, series, hoverIndex, hoverDistance, pointTime, yaxis; // 3 sub-arrays, 1st for hidden series, 2nd for left yaxis, 3rd for right yaxis. - var results: any = [[], [], []]; + let results: any = [[], [], []]; //now we know the current X (j) position for X and Y values - var last_value = 0; //needed for stacked values + let last_value = 0; //needed for stacked values - var minDistance, minTime; + let minDistance, minTime; for (i = 0; i < seriesList.length; i++) { series = seriesList[i]; @@ -145,7 +144,7 @@ export default function GraphTooltip(elem, dashboard, scope, getSeriesFn) { elem.mouseleave(function() { if (panel.tooltip.shared) { - var plot = elem.data().plot; + let plot = elem.data().plot; if (plot) { $tooltip.detach(); plot.unhighlight(); @@ -173,25 +172,25 @@ export default function GraphTooltip(elem, dashboard, scope, getSeriesFn) { }; this.show = function(pos, item) { - var plot = elem.data().plot; - var plotData = plot.getData(); - var xAxes = plot.getXAxes(); - var xMode = xAxes[0].options.mode; - var seriesList = getSeriesFn(); - var allSeriesMode = panel.tooltip.shared; - var group, value, absoluteTime, hoverInfo, i, series, seriesHtml, tooltipFormat; + let plot = elem.data().plot; + let plotData = plot.getData(); + let xAxes = plot.getXAxes(); + let xMode = xAxes[0].options.mode; + let seriesList = getSeriesFn(); + let allSeriesMode = panel.tooltip.shared; + let group, value, absoluteTime, hoverInfo, i, series, seriesHtml, tooltipFormat; // if panelRelY is defined another panel wants us to show a tooltip // get pageX from position on x axis and pageY from relative position in original panel if (pos.panelRelY) { - var pointOffset = plot.pointOffset({ x: pos.x }); + let pointOffset = plot.pointOffset({ x: pos.x }); if (Number.isNaN(pointOffset.left) || pointOffset.left < 0 || pointOffset.left > elem.width()) { self.clear(plot); return; } pos.pageX = elem.offset().left + pointOffset.left; pos.pageY = elem.offset().top + elem.height() * pos.panelRelY; - var isVisible = + let isVisible = pos.pageY >= $(window).scrollTop() && pos.pageY <= $(window).innerHeight() + $(window).scrollTop(); if (!isVisible) { self.clear(plot); @@ -219,7 +218,7 @@ export default function GraphTooltip(elem, dashboard, scope, getSeriesFn) { if (allSeriesMode) { plot.unhighlight(); - var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(plotData, pos); + let seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(plotData, pos); seriesHtml = ''; @@ -244,7 +243,7 @@ export default function GraphTooltip(elem, dashboard, scope, getSeriesFn) { continue; } - var highlightClass = ''; + let highlightClass = ''; if (item && hoverInfo.index === item.seriesIndex) { highlightClass = 'graph-tooltip-list-item--highlight'; } diff --git a/public/app/plugins/panel/graph/specs/tooltip_specs.ts b/public/app/plugins/panel/graph/specs/tooltip_specs.ts index c12697eadac..7dd5ed9b8a9 100644 --- a/public/app/plugins/panel/graph/specs/tooltip_specs.ts +++ b/public/app/plugins/panel/graph/specs/tooltip_specs.ts @@ -11,6 +11,7 @@ var scope = { var elem = $('
'); var dashboard = {}; +var getSeriesFn; function describeSharedTooltip(desc, fn) { var ctx: any = {}; @@ -30,7 +31,7 @@ function describeSharedTooltip(desc, fn) { describe(desc, function() { beforeEach(function() { ctx.setupFn(); - var tooltip = new GraphTooltip(elem, dashboard, scope); + var tooltip = new GraphTooltip(elem, dashboard, scope, getSeriesFn); ctx.results = tooltip.getMultiSeriesPlotHoverInfo(ctx.data, ctx.pos); }); @@ -39,7 +40,7 @@ function describeSharedTooltip(desc, fn) { } describe('findHoverIndexFromData', function() { - var tooltip = new GraphTooltip(elem, dashboard, scope); + var tooltip = new GraphTooltip(elem, dashboard, scope, getSeriesFn); var series = { data: [[100, 0], [101, 0], [102, 0], [103, 0], [104, 0], [105, 0], [106, 0], [107, 0]], }; From 9ef5f2700dd12fc7bfa1c6ac1c4f7cedcf2a52f3 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Thu, 29 Mar 2018 15:02:00 +0200 Subject: [PATCH 35/57] timepicker now closes without exiting edit/view mode, close order: modal, timepicker, view --- public/app/core/services/keybindingSrv.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 829a3415cc1..e51c0477ffa 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -74,13 +74,13 @@ export class KeybindingSrv { appEvents.emit('hide-modal'); - if (this.timepickerOpen === true) { - this.$rootScope.appEvent('closeTimepicker'); - this.timepickerOpen = false; - } - if (!this.modalOpen) { - this.$rootScope.appEvent('panel-change-view', { fullscreen: false, edit: false }); + if (this.timepickerOpen) { + this.$rootScope.appEvent('closeTimepicker'); + this.timepickerOpen = false; + } else { + this.$rootScope.appEvent('panel-change-view', { fullscreen: false, edit: false }); + } } else { this.modalOpen = false; } From cb156ee30b415a3b8fea5ad9bfc8a81f5b85cedf Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Fri, 30 Mar 2018 16:42:48 +0200 Subject: [PATCH 36/57] fix some typos --- docs/sources/reference/dashboard.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index dbc3ed8635c..59a5e4155ba 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -71,13 +71,13 @@ Each field in the dashboard JSON is explained below with its usage: | **timepicker** | timepicker metadata, see [timepicker section](#timepicker) for details | | **templating** | templating metadata, see [templating section](#templating) for details | | **annotations** | annotations metadata, see [annotations section](#annotations) for details | -| **schemaVersion** | version of the JSON schema (integer), incremented each time a Grafana update brings changes to the said schema | +| **schemaVersion** | version of the JSON schema (integer), incremented each time a Grafana update brings changes to said schema | | **version** | version of the dashboard (integer), incremented each time the dashboard is updated | | **panels** | panels array, see below for detail. | ## Panels -Panels are the building blocks a dashboard. It consists of datasource queries, type of graphs, aliases, etc. Panel JSON consists of an array of JSON objects, each representing a different panel. Most of the fields are common for all panels but some fields depends on the panel type. Following is an example of panel JSON of a text panel. +Panels are the building blocks of a dashboard. It consists of datasource queries, type of graphs, aliases, etc. Panel JSON consists of an array of JSON objects, each representing a different panel. Most of the fields are common for all panels but some fields depend on the panel type. Following is an example of panel JSON of a text panel. ```json "panels": [ @@ -105,7 +105,7 @@ The gridPos property describes the panel size and position in grid coordinates. - `x` The x position, in same unit as `w`. - `y` The y position, in same unit as `h`. -The grid has a negative gravity that moves panels up if there i empty space above a panel. +The grid has a negative gravity that moves panels up if there is empty space above a panel. ### timepicker @@ -161,7 +161,7 @@ Usage of the fields is explained below: ### templating -`templating` fields contains array of template variables with their saved values along with some other metadata, for example: +`templating` field contains an array of template variables with their saved values along with some other metadata, for example: ```json "templating": { @@ -236,7 +236,7 @@ Usage of the above mentioned fields in the templating section is explained below | Name | Usage | | ---- | ----- | | **enable** | whether templating is enabled or not | -| **list** | an array of objects representing, each representing one template variable | +| **list** | an array of objects each representing one template variable | | **allFormat** | format to use while fetching all values from datasource, eg: `wildcard`, `glob`, `regex`, `pipe`, etc. | | **current** | shows current selected variable text/value on the dashboard | | **datasource** | shows datasource for the variables | From 2bdcebb5b1ce52f090dfd39e3367d05596c65521 Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Fri, 30 Mar 2018 16:47:51 +0200 Subject: [PATCH 37/57] add article --- docs/sources/reference/dashboard.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index 59a5e4155ba..30581968743 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -161,7 +161,7 @@ Usage of the fields is explained below: ### templating -`templating` field contains an array of template variables with their saved values along with some other metadata, for example: +The `templating` field contains an array of template variables with their saved values along with some other metadata, for example: ```json "templating": { From 4538ffc0e827777f7731a4519ca102d17d4857e3 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 30 Mar 2018 20:30:57 +0200 Subject: [PATCH 38/57] changelog: adds note about closing #11555 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03ad228f3c3..48b5ab29aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ * **Shortcuts**: Add shortcut for duplicate panel [#11102](https://github.com/grafana/grafana/issues/11102) * **AuthProxy**: Support IPv6 in Auth proxy white list [#11330](https://github.com/grafana/grafana/pull/11330), thx [@corny](https://github.com/corny) * **SMTP**: Don't connect to STMP server using TLS unless configured. [#7189](https://github.com/grafana/grafana/issues/7189) +* **Prometheus**: Escape backslash in labels correctly. [#10555](https://github.com/grafana/grafana/issues/10555), thx [@roidelapluie](https://github.com/roidelapluie) # 5.0.4 (2018-03-28) From 13deb891f35c963a64d2dfe6db1282d6dbde67c2 Mon Sep 17 00:00:00 2001 From: Alexey Velikiy Date: Mon, 2 Apr 2018 09:09:10 +0300 Subject: [PATCH 39/57] No need for node_modules/bin in npm run-script (#11449) how run-script works: https://docs.npmjs.com/cli/run-script --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6dcfc16b82b..030219fe587 100644 --- a/package.json +++ b/package.json @@ -104,10 +104,10 @@ "test": "grunt test", "test:coverage": "grunt test --coverage=true", "lint": "tslint -c tslint.json --project tsconfig.json --type-check", - "karma": "node ./node_modules/grunt-cli/bin/grunt karma:dev", - "jest": "node ./node_modules/jest-cli/bin/jest.js --notify --watch", - "api-tests": "node ./node_modules/jest-cli/bin/jest.js --notify --watch --config=tests/api/jest.js", - "precommit": "lint-staged && node ./node_modules/grunt-cli/bin/grunt precommit" + "karma": "grunt karma:dev", + "jest": "jest --notify --watch", + "api-tests": "jest --notify --watch --config=tests/api/jest.js", + "precommit": "lint-staged && grunt precommit" }, "lint-staged": { "*.{ts,tsx}": [ From 00f67ea7c7511ff5cb18fd05cfaba9c6f299e98f Mon Sep 17 00:00:00 2001 From: Alexey Velikiy Date: Mon, 2 Apr 2018 09:13:22 +0300 Subject: [PATCH 40/57] rm panel.type constrain from threshold_mapper.ts (#11448) --- public/app/features/alerting/threshold_mapper.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/public/app/features/alerting/threshold_mapper.ts b/public/app/features/alerting/threshold_mapper.ts index 3025e13aacd..9142c74b6e3 100644 --- a/public/app/features/alerting/threshold_mapper.ts +++ b/public/app/features/alerting/threshold_mapper.ts @@ -1,9 +1,5 @@ export class ThresholdMapper { static alertToGraphThresholds(panel) { - if (panel.type !== 'graph') { - return false; - } - for (var i = 0; i < panel.alert.conditions.length; i++) { let condition = panel.alert.conditions[i]; if (condition.type !== 'query') { From 6320bdf39399b0f73b65025f3c55f4d264fc1dc0 Mon Sep 17 00:00:00 2001 From: Alexey Velikiy Date: Tue, 3 Apr 2018 07:21:36 +0300 Subject: [PATCH 41/57] Webpack Grafana plugin template project to links (#11457) --- PLUGIN_DEV.md | 1 + 1 file changed, 1 insertion(+) diff --git a/PLUGIN_DEV.md b/PLUGIN_DEV.md index 9d831a95697..4e2e080ebe6 100644 --- a/PLUGIN_DEV.md +++ b/PLUGIN_DEV.md @@ -9,6 +9,7 @@ upgrading Grafana please check here before creating an issue. - [Datasource plugin written in typescript](https://github.com/grafana/typescript-template-datasource) - [Simple json dataource plugin](https://github.com/grafana/simple-json-datasource) - [Plugin development guide](http://docs.grafana.org/plugins/developing/development/) +- [Webpack Grafana plugin template project](https://github.com/CorpGlory/grafana-plugin-template-webpack) ## Changes in v4.6 From 98e1404fed0a1cab9cdc6f7404d805459f374f48 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Tue, 3 Apr 2018 09:39:46 +0200 Subject: [PATCH 42/57] added if to onAppevent, renamed appevent, add appevent to applyCustom and setRelativeFilter --- public/app/core/services/keybindingSrv.ts | 8 +++++++- public/app/features/dashboard/timepicker/timepicker.ts | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index e51c0477ffa..35cd7808d12 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -23,7 +23,13 @@ export class KeybindingSrv { this.setupGlobal(); appEvents.on('show-modal', () => (this.modalOpen = true)); - $rootScope.onAppEvent('openTimepicker', () => (this.timepickerOpen = true)); + $rootScope.onAppEvent('escTimepicker', () => { + if (!this.timepickerOpen) { + this.timepickerOpen = true; + } else { + this.timepickerOpen = false; + } + }); } setupGlobal() { diff --git a/public/app/features/dashboard/timepicker/timepicker.ts b/public/app/features/dashboard/timepicker/timepicker.ts index 19c3db7f6d3..32ce07e4468 100644 --- a/public/app/features/dashboard/timepicker/timepicker.ts +++ b/public/app/features/dashboard/timepicker/timepicker.ts @@ -96,13 +96,12 @@ export class TimePickerCtrl { } openDropdown() { + this.$rootScope.appEvent('escTimepicker'); if (this.isOpen) { this.isOpen = false; return; } - this.$rootScope.appEvent('openTimepicker'); - this.onRefresh(); this.editTimeRaw = this.timeRaw; this.timeOptions = rangeUtil.getRelativeTimesList(this.panel, this.rangeString); @@ -118,6 +117,7 @@ export class TimePickerCtrl { } applyCustom() { + this.$rootScope.appEvent('escTimepicker'); if (this.refresh.value !== this.dashboard.refresh) { this.timeSrv.setAutoRefresh(this.refresh.value); } @@ -139,6 +139,7 @@ export class TimePickerCtrl { } setRelativeFilter(timespan) { + this.$rootScope.appEvent('escTimepicker'); var range = { from: timespan.from, to: timespan.to }; if (this.panel.nowDelay && range.to === 'now') { From 8b2441e0985f68b11e16f153c9a485cc2c873656 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 3 Apr 2018 09:51:29 +0200 Subject: [PATCH 43/57] mssql: typos in help sections Fixes #11455 --- .../plugins/datasource/mssql/partials/annotations.editor.html | 2 +- public/app/plugins/datasource/mssql/partials/query.editor.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/mssql/partials/annotations.editor.html b/public/app/plugins/datasource/mssql/partials/annotations.editor.html index 75eaa3ed1d9..8a94c470379 100644 --- a/public/app/plugins/datasource/mssql/partials/annotations.editor.html +++ b/public/app/plugins/datasource/mssql/partials/annotations.editor.html @@ -28,7 +28,7 @@ An annotation is an event that is overlayed on top of graphs. The query can have Macros: - $__time(column) -> column AS time - $__timeEpoch(column) -> DATEDIFF(second, '1970-01-01', column) AS time -- $__timeFilter(column) -> column >= DATEADD(s, 18446744066914186738, '1970-01-01') AND column &t;= DATEADD(s, 18446744066914187038, '1970-01-01') +- $__timeFilter(column) -> column >= DATEADD(s, 18446744066914186738, '1970-01-01') AND column <= DATEADD(s, 18446744066914187038, '1970-01-01') - $__unixEpochFilter(column) -> column >= 1492750877 AND column <= 1492750877 Or build your own conditionals using these macros which just return the values: diff --git a/public/app/plugins/datasource/mssql/partials/query.editor.html b/public/app/plugins/datasource/mssql/partials/query.editor.html index c7dc030be6e..f29dfa18db2 100644 --- a/public/app/plugins/datasource/mssql/partials/query.editor.html +++ b/public/app/plugins/datasource/mssql/partials/query.editor.html @@ -49,7 +49,7 @@ Table: Macros: - $__time(column) -> column AS time - $__timeEpoch(column) -> DATEDIFF(second, '1970-01-01', column) AS time -- $__timeFilter(column) -> column >= DATEADD(s, 18446744066914186738, '1970-01-01') AND column &t;= DATEADD(s, 18446744066914187038, '1970-01-01') +- $__timeFilter(column) -> column >= DATEADD(s, 18446744066914186738, '1970-01-01') AND column <= DATEADD(s, 18446744066914187038, '1970-01-01') - $__unixEpochFilter(column) -> column >= 1492750877 AND column <= 1492750877 - $__timeGroup(column, '5m'[, fillvalue]) -> CAST(ROUND(DATEDIFF(second, '1970-01-01', column)/300.0, 0) as bigint)*300. Providing a fillValue of NULL or floating value will automatically fill empty series in timerange with that value. From feb222f63327c8dbac43a2d44d4207f80bcead2c Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Tue, 3 Apr 2018 09:53:14 +0200 Subject: [PATCH 44/57] changed variable for tabbed close btn hover, and changed text-strong variable for lighttheme, removed commented out variable --- public/sass/_variables.light.scss | 3 +-- public/sass/components/_tabbed_view.scss | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index a59350d2195..bb8f93dbe69 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -59,9 +59,8 @@ $critical: #ec2128; $body-bg: $gray-7; $page-bg: $gray-7; $body-color: $gray-1; -//$text-color: $dark-4; $text-color: $gray-1; -$text-color-strong: $white; +$text-color-strong: $dark-2; $text-color-weak: $gray-2; $text-color-faint: $gray-4; $text-color-emphasis: $dark-5; diff --git a/public/sass/components/_tabbed_view.scss b/public/sass/components/_tabbed_view.scss index dfd760753fe..bf95d453504 100644 --- a/public/sass/components/_tabbed_view.scss +++ b/public/sass/components/_tabbed_view.scss @@ -43,7 +43,7 @@ font-size: 120%; } &:hover { - color: $white; + color: $text-color-strong; } } From 658c6a8ff4b959b6cd394550ea7d9ecc7d2e301c Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Tue, 3 Apr 2018 15:43:25 +0200 Subject: [PATCH 45/57] changed from margin to padding --- public/sass/pages/_alerting.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/sass/pages/_alerting.scss b/public/sass/pages/_alerting.scss index 5a481b55a8a..fb6b6e78d1b 100644 --- a/public/sass/pages/_alerting.scss +++ b/public/sass/pages/_alerting.scss @@ -108,7 +108,8 @@ justify-content: center; align-items: center; width: 40px; - margin-right: 8px; + //margin-right: 8px; + padding: 0 4px 0 2px; .icon-gf, .fa { font-size: 200%; From 533f3a9e8c7df8dfd0f769c707c49aff55241bf8 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 3 Apr 2018 19:09:49 +0200 Subject: [PATCH 46/57] settings: fixes test For some reason, the url parse does not fail anymore for curly braces. Add a colon in the first segment to make sure the url parse fails. --- pkg/setting/setting_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/setting/setting_test.go b/pkg/setting/setting_test.go index 87f9916075e..2da728b7298 100644 --- a/pkg/setting/setting_test.go +++ b/pkg/setting/setting_test.go @@ -37,8 +37,8 @@ func TestLoadingSettings(t *testing.T) { So(appliedEnvOverrides, ShouldContain, "GF_SECURITY_ADMIN_PASSWORD=*********") }) - Convey("Should replace password when defined in environment2", func() { - os.Setenv("GF_DATABASE_URL", "postgres://grafana:sec{ret@postgres:5432/grafana") + Convey("Should return an error when url is invalid", func() { + os.Setenv("GF_DATABASE_URL", "postgres.%31://grafana:secret@postgres:5432/grafana") err := NewConfigContext(&CommandLineArgs{HomePath: "../../"}) So(err, ShouldNotBeNil) From a6c76355285aef2f477e288a9104293870ed8ac6 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 3 Apr 2018 19:39:50 +0200 Subject: [PATCH 47/57] graphite: fixes #11434 --- public/app/plugins/datasource/graphite/add_graphite_func.ts | 1 + public/app/plugins/datasource/graphite/func_editor.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/public/app/plugins/datasource/graphite/add_graphite_func.ts b/public/app/plugins/datasource/graphite/add_graphite_func.ts index f2a596c7071..6e64b5d12d0 100644 --- a/public/app/plugins/datasource/graphite/add_graphite_func.ts +++ b/public/app/plugins/datasource/graphite/add_graphite_func.ts @@ -4,6 +4,7 @@ import $ from 'jquery'; import rst2html from 'rst2html'; import Drop from 'tether-drop'; +/** @ngInject */ export function graphiteAddFunc($compile) { const inputTemplate = ''; diff --git a/public/app/plugins/datasource/graphite/func_editor.ts b/public/app/plugins/datasource/graphite/func_editor.ts index 86135aef343..82a838e7660 100644 --- a/public/app/plugins/datasource/graphite/func_editor.ts +++ b/public/app/plugins/datasource/graphite/func_editor.ts @@ -3,6 +3,7 @@ import _ from 'lodash'; import $ from 'jquery'; import rst2html from 'rst2html'; +/** @ngInject */ export function graphiteFuncEditor($compile, templateSrv, popoverSrv) { const funcSpanTemplate = '{{func.def.name}}('; const paramTemplate = From 4a93766143a73fe8af896c25cf102e1ca93a90f7 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Fri, 16 Mar 2018 12:56:39 -0400 Subject: [PATCH 48/57] Add case-insensitive sort for variables. --- public/app/features/templating/editor_ctrl.ts | 2 ++ public/app/features/templating/query_variable.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/public/app/features/templating/editor_ctrl.ts b/public/app/features/templating/editor_ctrl.ts index 428770a21e5..f20e93be42c 100644 --- a/public/app/features/templating/editor_ctrl.ts +++ b/public/app/features/templating/editor_ctrl.ts @@ -23,6 +23,8 @@ export class VariableEditorCtrl { { value: 2, text: 'Alphabetical (desc)' }, { value: 3, text: 'Numerical (asc)' }, { value: 4, text: 'Numerical (desc)' }, + { value: 5, text: 'Alphabetical (case-insensitive, asc)' }, + { value: 6, text: 'Alphabetical (case-insensitive, desc)' }, ]; $scope.hideOptions = [{ value: 0, text: '' }, { value: 1, text: 'Label' }, { value: 2, text: 'Variable' }]; diff --git a/public/app/features/templating/query_variable.ts b/public/app/features/templating/query_variable.ts index 58c7b692581..b87167ad646 100644 --- a/public/app/features/templating/query_variable.ts +++ b/public/app/features/templating/query_variable.ts @@ -197,6 +197,8 @@ export class QueryVariable implements Variable { return parseInt(matches[1], 10); } }); + } else if (sortType === 3) { + options = _.sortBy(options, opt => { return _.toLower(opt.text); }); } if (reverseSort) { From 70eb281840f9d7b786eb315356ed4f195b489506 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 3 Apr 2018 22:34:16 +0200 Subject: [PATCH 49/57] variables: adds test for variable sorting ref #11280 --- .../templating/specs/query_variable.jest.ts | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/public/app/features/templating/specs/query_variable.jest.ts b/public/app/features/templating/specs/query_variable.jest.ts index 7840d9e4242..ce753a4b205 100644 --- a/public/app/features/templating/specs/query_variable.jest.ts +++ b/public/app/features/templating/specs/query_variable.jest.ts @@ -40,11 +40,11 @@ describe('QueryVariable', () => { }); describe('can convert and sort metric names', () => { - var variable = new QueryVariable({}, null, null, null, null); - variable.sort = 3; // Numerical (asc) + const variable = new QueryVariable({}, null, null, null, null); + let input; - describe('can sort a mixed array of metric variables', () => { - var input = [ + beforeEach(() => { + input = [ { text: '0', value: '0' }, { text: '1', value: '1' }, { text: null, value: 3 }, @@ -58,11 +58,18 @@ describe('QueryVariable', () => { { text: '', value: undefined }, { text: undefined, value: '' }, ]; + }); + + describe('can sort a mixed array of metric variables in numeric order', () => { + let result; + + beforeEach(() => { + variable.sort = 3; // Numerical (asc) + result = variable.metricNamesToVariableValues(input); + }); - var result = variable.metricNamesToVariableValues(input); it('should return in same order', () => { var i = 0; - expect(result.length).toBe(11); expect(result[i++].text).toBe(''); expect(result[i++].text).toBe('0'); @@ -73,5 +80,27 @@ describe('QueryVariable', () => { expect(result[i++].text).toBe('6'); }); }); + + describe('can sort a mixed array of metric variables in alphabetical order', () => { + let result; + + beforeEach(() => { + variable.sort = 5; // Alphabetical CI (asc) + result = variable.metricNamesToVariableValues(input); + }); + + it('should return in same order', () => { + var i = 0; + console.log(result); + expect(result.length).toBe(11); + expect(result[i++].text).toBe(''); + expect(result[i++].text).toBe('0'); + expect(result[i++].text).toBe('1'); + expect(result[i++].text).toBe('10'); + expect(result[i++].text).toBe('3'); + expect(result[i++].text).toBe('4'); + expect(result[i++].text).toBe('5'); + }); + }); }); }); From 4b1c1acab42f06e317ddbf2e91ff59336f2a9306 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 3 Apr 2018 22:40:48 +0200 Subject: [PATCH 50/57] changelog: adds note for #11128 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48b5ab29aa6..3f5b76486c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ * **AuthProxy**: Support IPv6 in Auth proxy white list [#11330](https://github.com/grafana/grafana/pull/11330), thx [@corny](https://github.com/corny) * **SMTP**: Don't connect to STMP server using TLS unless configured. [#7189](https://github.com/grafana/grafana/issues/7189) * **Prometheus**: Escape backslash in labels correctly. [#10555](https://github.com/grafana/grafana/issues/10555), thx [@roidelapluie](https://github.com/roidelapluie) +* **Variables** Case-insensitive sorting for template values [#11128](https://github.com/grafana/grafana/issues/11128) thx [@cross](https://github.com/cross) # 5.0.4 (2018-03-28) From 9841c81952de6eb13e433ffb6d820dc43ea55b96 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 4 Apr 2018 13:40:08 +0200 Subject: [PATCH 51/57] Notes for closing #7119 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f5b76486c4..4d221480de2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * **Prometheus**: Show template variable candidate in query editor [#9210](https://github.com/grafana/grafana/issues/9210), thx [@mtanda](https://github.com/mtanda) * **Prometheus**: Support POST for query and query_range [#9859](https://github.com/grafana/grafana/pull/9859), thx [@mtanda](https://github.com/mtanda) * **Alerting**: Add support for retries on alert queries [#5855](https://github.com/grafana/grafana/issues/5855), thx [@Thib17](https://github.com/Thib17) +* **Table**: Table plugin value mappings [#7119](https://github.com/grafana/grafana/issues/7119), thx [infernix](https://github.com/infernix) ### Minor * **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes) From 13f6d3be87a6c49a6b1f691324e5aca23aa94158 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 4 Apr 2018 14:16:39 +0200 Subject: [PATCH 52/57] migrated last all.js to ts --- public/app/core/services/all.js | 13 ------------- public/app/core/services/all.ts | 10 ++++++++++ public/app/features/all.js | 15 --------------- public/app/features/all.ts | 13 +++++++++++++ public/app/features/panel/all.js | 9 --------- public/app/features/panel/all.ts | 7 +++++++ public/app/features/playlist/all.js | 7 ------- public/app/features/playlist/all.ts | 5 +++++ 8 files changed, 35 insertions(+), 44 deletions(-) delete mode 100644 public/app/core/services/all.js create mode 100644 public/app/core/services/all.ts delete mode 100644 public/app/features/all.js create mode 100644 public/app/features/all.ts delete mode 100644 public/app/features/panel/all.js create mode 100644 public/app/features/panel/all.ts delete mode 100644 public/app/features/playlist/all.js create mode 100644 public/app/features/playlist/all.ts diff --git a/public/app/core/services/all.js b/public/app/core/services/all.js deleted file mode 100644 index 0a973440c5e..00000000000 --- a/public/app/core/services/all.js +++ /dev/null @@ -1,13 +0,0 @@ -define([ - './alert_srv', - './util_srv', - './context_srv', - './timer', - './analytics', - './popover_srv', - './segment_srv', - './backend_srv', - './dynamic_directive_srv', - './bridge_srv' -], -function () {}); diff --git a/public/app/core/services/all.ts b/public/app/core/services/all.ts new file mode 100644 index 00000000000..989015d2872 --- /dev/null +++ b/public/app/core/services/all.ts @@ -0,0 +1,10 @@ +import './alert_srv'; +import './util_srv'; +import './context_srv'; +import './timer'; +import './analytics'; +import './popover_srv'; +import './segment_srv'; +import './backend_srv'; +import './dynamic_directive_srv'; +import './bridge_srv'; diff --git a/public/app/features/all.js b/public/app/features/all.js deleted file mode 100644 index 759be6c11d2..00000000000 --- a/public/app/features/all.js +++ /dev/null @@ -1,15 +0,0 @@ -define([ - './panellinks/module', - './dashlinks/module', - './annotations/all', - './templating/all', - './plugins/all', - './dashboard/all', - './playlist/all', - './snapshot/all', - './panel/all', - './org/all', - './admin/admin', - './alerting/all', - './styleguide/styleguide', -], function () {}); diff --git a/public/app/features/all.ts b/public/app/features/all.ts new file mode 100644 index 00000000000..df987a8b59b --- /dev/null +++ b/public/app/features/all.ts @@ -0,0 +1,13 @@ +import './panellinks/module'; +import './dashlinks/module'; +import './annotations/all'; +import './templating/all'; +import './plugins/all'; +import './dashboard/all'; +import './playlist/all'; +import './snapshot/all'; +import './panel/all'; +import './org/all'; +import './admin/admin'; +import './alerting/all'; +import './styleguide/styleguide'; diff --git a/public/app/features/panel/all.js b/public/app/features/panel/all.js deleted file mode 100644 index aaa6d0d4ed0..00000000000 --- a/public/app/features/panel/all.js +++ /dev/null @@ -1,9 +0,0 @@ -define([ - './panel_header', - './panel_directive', - './solo_panel_ctrl', - './query_ctrl', - './panel_editor_tab', - './query_editor_row', - './query_troubleshooter', -], function () {}); diff --git a/public/app/features/panel/all.ts b/public/app/features/panel/all.ts new file mode 100644 index 00000000000..bdf1a097352 --- /dev/null +++ b/public/app/features/panel/all.ts @@ -0,0 +1,7 @@ +import './panel_header'; +import './panel_directive'; +import './solo_panel_ctrl'; +import './query_ctrl'; +import './panel_editor_tab'; +import './query_editor_row'; +import './query_troubleshooter'; diff --git a/public/app/features/playlist/all.js b/public/app/features/playlist/all.js deleted file mode 100644 index 3b07b0d74c5..00000000000 --- a/public/app/features/playlist/all.js +++ /dev/null @@ -1,7 +0,0 @@ -define([ - './playlists_ctrl', - './playlist_search', - './playlist_srv', - './playlist_edit_ctrl', - './playlist_routes' -], function () {}); diff --git a/public/app/features/playlist/all.ts b/public/app/features/playlist/all.ts new file mode 100644 index 00000000000..eb427b883ca --- /dev/null +++ b/public/app/features/playlist/all.ts @@ -0,0 +1,5 @@ +import './playlists_ctrl'; +import './playlist_search'; +import './playlist_srv'; +import './playlist_edit_ctrl'; +import './playlist_routes'; From 84dce3282aaac2a5d2b83de9af027619a880936c Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 4 Apr 2018 15:26:23 +0200 Subject: [PATCH 53/57] migrated playlist-routes to ts --- .../app/features/playlist/playlist_routes.js | 39 ------------------- .../app/features/playlist/playlist_routes.ts | 33 ++++++++++++++++ 2 files changed, 33 insertions(+), 39 deletions(-) delete mode 100644 public/app/features/playlist/playlist_routes.js create mode 100644 public/app/features/playlist/playlist_routes.ts diff --git a/public/app/features/playlist/playlist_routes.js b/public/app/features/playlist/playlist_routes.js deleted file mode 100644 index 193b0b52b20..00000000000 --- a/public/app/features/playlist/playlist_routes.js +++ /dev/null @@ -1,39 +0,0 @@ -define([ - 'angular', - 'lodash' -], -function (angular) { - 'use strict'; - - var module = angular.module('grafana.routes'); - - module.config(function($routeProvider) { - $routeProvider - .when('/playlists', { - templateUrl: 'public/app/features/playlist/partials/playlists.html', - controllerAs: 'ctrl', - controller : 'PlaylistsCtrl' - }) - .when('/playlists/create', { - templateUrl: 'public/app/features/playlist/partials/playlist.html', - controllerAs: 'ctrl', - controller : 'PlaylistEditCtrl' - }) - .when('/playlists/edit/:id', { - templateUrl: 'public/app/features/playlist/partials/playlist.html', - controllerAs: 'ctrl', - controller : 'PlaylistEditCtrl' - }) - .when('/playlists/play/:id', { - templateUrl: 'public/app/features/playlist/partials/playlists.html', - controllerAs: 'ctrl', - controller : 'PlaylistsCtrl', - resolve: { - init: function(playlistSrv, $route) { - var playlistId = $route.current.params.id; - playlistSrv.start(playlistId); - } - } - }); - }); -}); diff --git a/public/app/features/playlist/playlist_routes.ts b/public/app/features/playlist/playlist_routes.ts new file mode 100644 index 00000000000..c94446c2c1b --- /dev/null +++ b/public/app/features/playlist/playlist_routes.ts @@ -0,0 +1,33 @@ +import angular from 'angular'; + +function grafanaRoutes($routeProvider) { + $routeProvider + .when('/playlists', { + templateUrl: 'public/app/features/playlist/partials/playlists.html', + controllerAs: 'ctrl', + controller: 'PlaylistsCtrl', + }) + .when('/playlists/create', { + templateUrl: 'public/app/features/playlist/partials/playlist.html', + controllerAs: 'ctrl', + controller: 'PlaylistEditCtrl', + }) + .when('/playlists/edit/:id', { + templateUrl: 'public/app/features/playlist/partials/playlist.html', + controllerAs: 'ctrl', + controller: 'PlaylistEditCtrl', + }) + .when('/playlists/play/:id', { + templateUrl: 'public/app/features/playlist/partials/playlists.html', + controllerAs: 'ctrl', + controller: 'PlaylistsCtrl', + resolve: { + init: function(playlistSrv, $route) { + let playlistId = $route.current.params.id; + playlistSrv.start(playlistId); + }, + }, + }); +} + +angular.module('grafana.routes').config(grafanaRoutes); From 0273365df3dd25656330cd638b56617cd221d060 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 4 Apr 2018 16:20:01 +0200 Subject: [PATCH 54/57] created closeDropdown function, renamed appevent, added second appevent for open timepicker --- public/app/core/services/keybindingSrv.ts | 9 ++------- .../features/dashboard/timepicker/timepicker.ts | 16 +++++++++------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 35cd7808d12..55d968fd981 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -23,13 +23,8 @@ export class KeybindingSrv { this.setupGlobal(); appEvents.on('show-modal', () => (this.modalOpen = true)); - $rootScope.onAppEvent('escTimepicker', () => { - if (!this.timepickerOpen) { - this.timepickerOpen = true; - } else { - this.timepickerOpen = false; - } - }); + $rootScope.onAppEvent('timepickerOpen', () => (this.timepickerOpen = true)); + $rootScope.onAppEvent('timepickerClosed', () => (this.timepickerOpen = false)); } setupGlobal() { diff --git a/public/app/features/dashboard/timepicker/timepicker.ts b/public/app/features/dashboard/timepicker/timepicker.ts index 32ce07e4468..33cfff92e7f 100644 --- a/public/app/features/dashboard/timepicker/timepicker.ts +++ b/public/app/features/dashboard/timepicker/timepicker.ts @@ -22,7 +22,6 @@ export class TimePickerCtrl { refresh: any; isUtc: boolean; firstDayOfWeek: number; - closeDropdown: any; isOpen: boolean; /** @ngInject */ @@ -96,9 +95,8 @@ export class TimePickerCtrl { } openDropdown() { - this.$rootScope.appEvent('escTimepicker'); if (this.isOpen) { - this.isOpen = false; + this.closeDropdown(); return; } @@ -114,16 +112,21 @@ export class TimePickerCtrl { this.refresh.options.unshift({ text: 'off' }); this.isOpen = true; + this.$rootScope.appEvent('timepickerOpen'); + } + + closeDropdown() { + this.isOpen = false; + this.$rootScope.appEvent('timepickerClosed'); } applyCustom() { - this.$rootScope.appEvent('escTimepicker'); if (this.refresh.value !== this.dashboard.refresh) { this.timeSrv.setAutoRefresh(this.refresh.value); } this.timeSrv.setTime(this.editTimeRaw); - this.isOpen = false; + this.closeDropdown(); } absoluteFromChanged() { @@ -139,7 +142,6 @@ export class TimePickerCtrl { } setRelativeFilter(timespan) { - this.$rootScope.appEvent('escTimepicker'); var range = { from: timespan.from, to: timespan.to }; if (this.panel.nowDelay && range.to === 'now') { @@ -147,7 +149,7 @@ export class TimePickerCtrl { } this.timeSrv.setTime(range); - this.isOpen = false; + this.closeDropdown(); } } From 90aab445586b4a5ca00fba4d2f6de72d223d1235 Mon Sep 17 00:00:00 2001 From: Jarno Tuovinen Date: Wed, 4 Apr 2018 21:07:22 +0300 Subject: [PATCH 55/57] Use curly brackets around hyperlink help text #11478 (#11479) --- public/app/plugins/panel/table/column_options.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/panel/table/column_options.html b/public/app/plugins/panel/table/column_options.html index 9e8e4b404ae..4a4a6d0db9c 100644 --- a/public/app/plugins/panel/table/column_options.html +++ b/public/app/plugins/panel/table/column_options.html @@ -163,10 +163,10 @@ Use special variables to specify cell values:
- $__cell refers to current cell value + ${__cell} refers to current cell value
- $__cell_n refers to Nth column value in current row. Column indexes are started from 0. For instance, - $__cell_1 refers to second column's value. + ${__cell_n} refers to Nth column value in current row. Column indexes are started from 0. For instance, + ${__cell_1} refers to second column's value.
From b321a21cb569d1ac3c0dc88c53393aea2e48d2d5 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Thu, 5 Apr 2018 11:00:15 +0200 Subject: [PATCH 56/57] removed indent for manage dashboards --- public/sass/components/_search.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/sass/components/_search.scss b/public/sass/components/_search.scss index e6b3795d752..ab63f96be50 100644 --- a/public/sass/components/_search.scss +++ b/public/sass/components/_search.scss @@ -206,7 +206,7 @@ padding: 5px; flex: 0 0 auto; font-size: 19px; - padding: 5px 2px 5px 16px; + padding: 5px 2px 5px 10px; } .search-item__tags { From fcfc33ee5795c5cd745248450212086394c7999e Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 5 Apr 2018 15:02:54 +0200 Subject: [PATCH 57/57] changelog: adds note for #11165 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d221480de2..2f763a15c82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * **Prometheus**: Support POST for query and query_range [#9859](https://github.com/grafana/grafana/pull/9859), thx [@mtanda](https://github.com/mtanda) * **Alerting**: Add support for retries on alert queries [#5855](https://github.com/grafana/grafana/issues/5855), thx [@Thib17](https://github.com/Thib17) * **Table**: Table plugin value mappings [#7119](https://github.com/grafana/grafana/issues/7119), thx [infernix](https://github.com/infernix) +* **IE11**: IE 11 compatibility [#11165](https://github.com/grafana/grafana/issues/11165) ### Minor * **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes)