From 72e686457c9f0f7115ce25cc3c2a4041cf4ce797 Mon Sep 17 00:00:00 2001 From: Alfred Krohmer Date: Mon, 11 Dec 2017 21:51:46 +0100 Subject: [PATCH 01/48] Add default message for Pushover notifications If the message field is left empty for Pushover notifications, the API will return an error. This adds a default message if the message would otherwise be empty. This fixes #8006. --- pkg/services/alerting/notifiers/pushover.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/services/alerting/notifiers/pushover.go b/pkg/services/alerting/notifiers/pushover.go index ecb4ed42e3e..0fe93424f63 100644 --- a/pkg/services/alerting/notifiers/pushover.go +++ b/pkg/services/alerting/notifiers/pushover.go @@ -129,6 +129,7 @@ func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error { this.log.Error("Failed get rule link", "error", err) return err } + message := evalContext.Rule.Message for idx, evt := range evalContext.EvalMatches { message += fmt.Sprintf("\n%s: %v", evt.Metric, evt.Value) @@ -142,6 +143,9 @@ func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error { if evalContext.ImagePublicUrl != "" { message += fmt.Sprintf("\nShow graph image", evalContext.ImagePublicUrl) } + if message == "" { + message = "Nothing to see here! (Set a notification message to replace this text.)" + } q := url.Values{} q.Add("user", this.UserKey) From d1e0e3699666dcda2ff453bdb779639b157604ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 15 Dec 2017 14:51:20 +0100 Subject: [PATCH 02/48] fix: dont show settings for viewers --- public/app/features/dashboard/dashboard_model.ts | 2 ++ public/app/features/dashboard/dashnav/dashnav.html | 2 +- public/app/features/dashboard/settings/settings.ts | 9 ++++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashboard_model.ts b/public/app/features/dashboard/dashboard_model.ts index 034ad7d84e1..8ec13bc7a87 100644 --- a/public/app/features/dashboard/dashboard_model.ts +++ b/public/app/features/dashboard/dashboard_model.ts @@ -117,6 +117,8 @@ export class DashboardModel { meta.canSave = meta.canSave !== false; meta.canStar = meta.canStar !== false; meta.canEdit = meta.canEdit !== false; + meta.showSettings = meta.canEdit; + meta.canMakeEditable = meta.canSave && !this.editable; if (!this.editable) { meta.canEdit = false; diff --git a/public/app/features/dashboard/dashnav/dashnav.html b/public/app/features/dashboard/dashnav/dashnav.html index e2a932fd9ab..d0ebb93e2f3 100644 --- a/public/app/features/dashboard/dashnav/dashnav.html +++ b/public/app/features/dashboard/dashnav/dashnav.html @@ -37,7 +37,7 @@ - diff --git a/public/app/features/dashboard/settings/settings.ts b/public/app/features/dashboard/settings/settings.ts index f8b85c81667..d74a303efd8 100644 --- a/public/app/features/dashboard/settings/settings.ts +++ b/public/app/features/dashboard/settings/settings.ts @@ -35,6 +35,7 @@ export class SettingsCtrl { buildSectionList() { this.sections = []; + if (this.dashboard.meta.canEdit) { this.sections.push({ title: 'General', id: 'settings', icon: 'gicon gicon-preferences' }); this.sections.push({ title: 'Annotations', id: 'annotations', icon: 'gicon gicon-annotation' }); @@ -46,9 +47,8 @@ export class SettingsCtrl { this.sections.push({ title: 'Versions', id: 'versions', icon: 'fa fa-fw fa-history' }); } - if (contextSrv.isEditor && !this.dashboard.editable) { + if (this.dashboard.meta.canMakeEditable) { this.sections.push({ title: 'Make Editable', icon: 'fa fa-fw fa-edit', id: 'make_editable' }); - this.viewId = 'make_editable'; } this.sections.push({ title: 'View JSON', id: 'view_json', icon: 'gicon gicon-json' }); @@ -69,11 +69,14 @@ export class SettingsCtrl { this.json = JSON.stringify(this.dashboard.getSaveModelClone(), null, 2); } + if (this.viewId === 'settings' && this.dashboard.meta.canMakeEditable) { + this.viewId = 'make_editable'; + } + const currentSection = _.find(this.sections, { id: this.viewId }); if (!currentSection) { this.sections.unshift({ title: 'Not found', id: '404', icon: 'fa fa-fw fa-warning' }); this.viewId = '404'; - return; } } From 36fe8f5873b638afab0bf95c2f8632d24f25ac26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 15 Dec 2017 15:17:05 +0100 Subject: [PATCH 03/48] menu: fixed create default url --- pkg/api/index.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/index.go b/pkg/api/index.go index 6629c2f5ca5..1b836356189 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -92,7 +92,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { Text: "Create", Id: "create", Icon: "fa fa-fw fa-plus", - Url: setting.AppSubUrl + "dashboard/new", + Url: setting.AppSubUrl + "/dashboard/new", Children: []*dtos.NavLink{ {Text: "Dashboard", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/dashboard/new"}, {Text: "Folder", SubTitle: "Create a new folder to organize your dashboards", Id: "folder", Icon: "gicon gicon-folder-new", Url: setting.AppSubUrl + "/dashboards/folder/new"}, From 4112abd69971fe8b66327a8ff3468aae1a4bedcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 15 Dec 2017 15:43:32 +0100 Subject: [PATCH 04/48] logging: removed logging from panel loader --- public/app/features/dashboard/dashgrid/PanelLoader.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/features/dashboard/dashgrid/PanelLoader.ts b/public/app/features/dashboard/dashgrid/PanelLoader.ts index 567043584a5..a9aa07f5b16 100644 --- a/public/app/features/dashboard/dashgrid/PanelLoader.ts +++ b/public/app/features/dashboard/dashgrid/PanelLoader.ts @@ -23,7 +23,6 @@ export class PanelLoader { return { destroy: () => { - console.log('AttachedPanel:Destroy, id' + panel.id); panelScope.$destroy(); compiledElem.remove(); } From 71658d1e769470707e33f3b4c158645368479aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 15 Dec 2017 16:23:26 +0100 Subject: [PATCH 05/48] grid: disable resize and drag on non editable dashboards, closes #10235 --- package.json | 2 +- .../dashboard/dashgrid/DashboardGrid.tsx | 43 ++++++++++++------- yarn.lock | 6 +-- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 6bcd140cc1b..3f3d67ca691 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "postcss-browser-reporter": "^0.5.0", "postcss-loader": "^2.0.6", "postcss-reporter": "^5.0.0", - "prettier": "1.7.3", + "prettier": "1.9.2", "react-test-renderer": "^16.0.0", "sass-lint": "^1.10.2", "sass-loader": "^6.0.6", diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index be8f1822d62..3f65c33c90d 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -1,16 +1,28 @@ import React from 'react'; import ReactGridLayout from 'react-grid-layout'; -import {GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT} from 'app/core/constants'; -import {DashboardPanel} from './DashboardPanel'; -import {DashboardModel} from '../dashboard_model'; -import {PanelContainer} from './PanelContainer'; -import {PanelModel} from '../panel_model'; +import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants'; +import { DashboardPanel } from './DashboardPanel'; +import { DashboardModel } from '../dashboard_model'; +import { PanelContainer } from './PanelContainer'; +import { PanelModel } from '../panel_model'; import classNames from 'classnames'; import sizeMe from 'react-sizeme'; let lastGridWidth = 1200; -function GridWrapper({size, layout, onLayoutChange, children, onDragStop, onResize, onResizeStop, onWidthChange, className}) { +function GridWrapper({ + size, + layout, + onLayoutChange, + children, + onDragStop, + onResize, + onResizeStop, + onWidthChange, + className, + isResizable, + isDraggable, +}) { if (size.width === 0) { console.log('size is zero!'); } @@ -25,8 +37,8 @@ function GridWrapper({size, layout, onLayoutChange, children, onDragStop, onResi PanelContainer; @@ -54,7 +66,7 @@ export class DashboardGrid extends React.Component { gridToPanelMap: any; panelContainer: PanelContainer; dashboard: DashboardModel; - panelMap: {[id: string]: PanelModel}; + panelMap: { [id: string]: PanelModel }; constructor(props) { super(props); @@ -65,7 +77,7 @@ export class DashboardGrid extends React.Component { this.onDragStop = this.onDragStop.bind(this); this.onWidthChange = this.onWidthChange.bind(this); - this.state = {animated: false}; + this.state = { animated: false }; // subscribe to dashboard events this.dashboard = this.panelContainer.getDashboard(); @@ -153,7 +165,7 @@ export class DashboardGrid extends React.Component { componentDidMount() { setTimeout(() => { this.setState(() => { - return {animated: true}; + return { animated: true }; }); }); } @@ -162,7 +174,7 @@ export class DashboardGrid extends React.Component { const panelElements = []; for (let panel of this.dashboard.panels) { - const panelClasses = classNames({panel: true, 'panel--fullscreen': panel.fullscreen}); + const panelClasses = classNames({ panel: true, 'panel--fullscreen': panel.fullscreen }); panelElements.push(
@@ -176,8 +188,10 @@ export class DashboardGrid extends React.Component { render() { return ( { ); } } - diff --git a/yarn.lock b/yarn.lock index 9ce16177ee7..d5aa06eaffb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7668,9 +7668,9 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -prettier@1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.7.3.tgz#8e6974725273914b1c47439959dd3d3ba53664b6" +prettier@1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827" pretty-bytes@^1.0.0: version "1.0.4" From 7dac64354f9c6abd71a8be909ff49b096c70f8f8 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 15 Dec 2017 16:24:23 +0100 Subject: [PATCH 06/48] teams: missing nginject attribute --- public/app/features/org/team_details_ctrl.ts | 1 + public/app/plugins/datasource/grafana-live/plugin.json | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 public/app/plugins/datasource/grafana-live/plugin.json diff --git a/public/app/features/org/team_details_ctrl.ts b/public/app/features/org/team_details_ctrl.ts index e4dc91386b5..e96c3512180 100644 --- a/public/app/features/org/team_details_ctrl.ts +++ b/public/app/features/org/team_details_ctrl.ts @@ -5,6 +5,7 @@ export default class TeamDetailsCtrl { teamMembers: User[] = []; navModel: any; + /** @ngInject **/ constructor(private $scope, private backendSrv, private $routeParams, navModelSrv) { this.navModel = navModelSrv.getNav('cfg', 'teams', 0); this.get(); diff --git a/public/app/plugins/datasource/grafana-live/plugin.json b/public/app/plugins/datasource/grafana-live/plugin.json new file mode 100644 index 00000000000..1f2ec204949 --- /dev/null +++ b/public/app/plugins/datasource/grafana-live/plugin.json @@ -0,0 +1,7 @@ +{ + "type": "datasource", + "name": "Grafana Live", + "id": "grafana-live", + + "metrics": true +} From 77cc546315767891cf8897da26ff4cedc92b84c9 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 15 Dec 2017 16:36:31 +0100 Subject: [PATCH 07/48] dashfolder: nginject fix --- public/app/features/dashboard/create_folder_ctrl.ts | 1 + public/app/plugins/datasource/grafana-live/plugin.json | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 public/app/plugins/datasource/grafana-live/plugin.json diff --git a/public/app/features/dashboard/create_folder_ctrl.ts b/public/app/features/dashboard/create_folder_ctrl.ts index 8eebfecf1b5..f75fdee926e 100644 --- a/public/app/features/dashboard/create_folder_ctrl.ts +++ b/public/app/features/dashboard/create_folder_ctrl.ts @@ -6,6 +6,7 @@ export class CreateFolderCtrl { nameExists = false; titleTouched = false; + /** @ngInject **/ constructor(private backendSrv, private $location, navModelSrv) { this.navModel = navModelSrv.getNav('dashboards', 'manage-dashboards', 0); } diff --git a/public/app/plugins/datasource/grafana-live/plugin.json b/public/app/plugins/datasource/grafana-live/plugin.json deleted file mode 100644 index 1f2ec204949..00000000000 --- a/public/app/plugins/datasource/grafana-live/plugin.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "datasource", - "name": "Grafana Live", - "id": "grafana-live", - - "metrics": true -} From fcf1ff10d2f1b3b682abb0e04dd135eb58d45cb2 Mon Sep 17 00:00:00 2001 From: Scott Ernst Date: Fri, 15 Dec 2017 12:44:13 -0600 Subject: [PATCH 08/48] Kinesis Metric Capitalization Fixes the capitalization of the AWS/Kinesis ShardId metric, which was incorrectly specified as "ShardID" instead of "ShardId" as specified in the AWS documentation: http://docs.aws.amazon.com/streams/latest/dev/monitoring-with-cloudwatch.html --- pkg/tsdb/cloudwatch/metric_find_query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index b9d4d5b6a80..1e1e855b123 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -127,7 +127,7 @@ func init() { "AWS/Events": {"RuleName"}, "AWS/Firehose": {"DeliveryStreamName"}, "AWS/IoT": {"Protocol"}, - "AWS/Kinesis": {"StreamName", "ShardID"}, + "AWS/Kinesis": {"StreamName", "ShardId"}, "AWS/KinesisAnalytics": {"Flow", "Id", "Application"}, "AWS/Lambda": {"FunctionName", "Resource", "Version", "Alias"}, "AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"}, From 72568845553f1cbb1ffe7051ab8a013c719f6916 Mon Sep 17 00:00:00 2001 From: shankdever <34627333+shankdever@users.noreply.github.com> Date: Mon, 18 Dec 2017 18:03:28 +0530 Subject: [PATCH 09/48] ux: Add missing icon for login with grafana-com, fixes #10238 (#10249) --- public/img/grafana_mask_icon_white.svg | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 public/img/grafana_mask_icon_white.svg diff --git a/public/img/grafana_mask_icon_white.svg b/public/img/grafana_mask_icon_white.svg new file mode 100644 index 00000000000..d810d3735c7 --- /dev/null +++ b/public/img/grafana_mask_icon_white.svg @@ -0,0 +1,12 @@ + + + + grafana_mask_icon + Created with Sketch. + + + + + + + \ No newline at end of file From 51ec64f8721ce1ed0e1f1612244e5a350d154acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 18 Dec 2017 13:38:29 +0100 Subject: [PATCH 10/48] fix: reduced team name column length, fixes #10244 --- pkg/services/sqlstore/migrations/team_mig.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/migrations/team_mig.go b/pkg/services/sqlstore/migrations/team_mig.go index cc479097f9b..374972e5449 100644 --- a/pkg/services/sqlstore/migrations/team_mig.go +++ b/pkg/services/sqlstore/migrations/team_mig.go @@ -7,7 +7,7 @@ func addTeamMigrations(mg *Migrator) { Name: "team", Columns: []*Column{ {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, - {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false}, {Name: "org_id", Type: DB_BigInt}, {Name: "created", Type: DB_DateTime, Nullable: false}, {Name: "updated", Type: DB_DateTime, Nullable: false}, From 40af84a83f7c95ca2105ec6c1b93490f747a5023 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 18 Dec 2017 14:10:29 +0100 Subject: [PATCH 11/48] docs: mysql macros update --- docs/sources/features/datasources/mysql.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/sources/features/datasources/mysql.md b/docs/sources/features/datasources/mysql.md index d9f048e0371..c47a9743e34 100644 --- a/docs/sources/features/datasources/mysql.md +++ b/docs/sources/features/datasources/mysql.md @@ -45,7 +45,14 @@ To simplify syntax and to allow for dynamic parts, like date range filters, the Macro example | Description ------------ | ------------- +*$__time(dateColumn)* | Will be replaced by an expression to convert to a UNIX timestamp and rename the column to `time_sec`. For example, *UNIX_TIMESTAMP(dateColumn) as time_sec* *$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn > FROM_UNIXTIME(1494410783) AND dateColumn < FROM_UNIXTIME(1494497183)* +*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *FROM_UNIXTIME(1494410783)* +*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *FROM_UNIXTIME(1494497183)* +*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed) as time_sec,* +*$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183* +*$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* +*$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183* We plan to add many more macros. If you have suggestions for what macros you would like to see, please [open an issue](https://github.com/grafana/grafana) in our GitHub repo. From 78fb5be2cf3b80c2b7a77e921f71163f2faf681f Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 18 Dec 2017 14:24:04 +0100 Subject: [PATCH 12/48] docs: mysql example with macro --- docs/sources/features/datasources/mysql.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/sources/features/datasources/mysql.md b/docs/sources/features/datasources/mysql.md index c47a9743e34..7fae7441b6d 100644 --- a/docs/sources/features/datasources/mysql.md +++ b/docs/sources/features/datasources/mysql.md @@ -106,6 +106,19 @@ GROUP BY metric1, UNIX_TIMESTAMP(time_date_time) DIV 300 ORDER BY time_sec asc ``` +Example with $__timeGroup macro: + +```sql +SELECT + $__timeGroup(time_date_time,'5m') as time_sec, + min(value_double) as value, + metric_name as metric +FROM test_data +WHERE $__timeFilter(time_date_time) +GROUP BY 1, metric_name +ORDER BY 1 +``` + Currently, there is no support for a dynamic group by time based on time range & panel width. This is something we plan to add. From 6bb7f77346a0590f65160ef5681623942c01a97f Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Mon, 18 Dec 2017 22:29:23 +0900 Subject: [PATCH 13/48] use ace editor in panel edit (#10245) --- public/app/core/components/code_editor/code_editor.ts | 2 ++ public/app/partials/edit_json.html | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/public/app/core/components/code_editor/code_editor.ts b/public/app/core/components/code_editor/code_editor.ts index cc3b1e46ad4..048d7beaa0b 100644 --- a/public/app/core/components/code_editor/code_editor.ts +++ b/public/app/core/components/code_editor/code_editor.ts @@ -38,6 +38,8 @@ import 'brace/mode/sql'; import 'brace/snippets/sql'; import 'brace/mode/markdown'; import 'brace/snippets/markdown'; +import 'brace/mode/json'; +import 'brace/snippets/json'; const DEFAULT_THEME_DARK = "ace/theme/grafana-dark"; const DEFAULT_THEME_LIGHT = "ace/theme/textmate"; diff --git a/public/app/partials/edit_json.html b/public/app/partials/edit_json.html index 48d2237376b..b87bb0ce261 100644 --- a/public/app/partials/edit_json.html +++ b/public/app/partials/edit_json.html @@ -11,7 +11,7 @@
- +
From 3cb841f3db7bb338497bae99f54c9b2b92ee8d44 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Mon, 18 Dec 2017 22:29:48 +0900 Subject: [PATCH 14/48] fix text panel rows limit (#10246) --- public/app/plugins/panel/text/editor.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/text/editor.html b/public/app/plugins/panel/text/editor.html index eab53dc7615..bae36a1ea2a 100644 --- a/public/app/plugins/panel/text/editor.html +++ b/public/app/plugins/panel/text/editor.html @@ -17,7 +17,7 @@
- +
From 22643eb109fc58c3f4ab726c260c4da918155e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 18 Dec 2017 14:36:15 +0100 Subject: [PATCH 15/48] cloudwatch: fixed optimized build issue, fixes #10174 --- public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts index 6bf22b0f2e7..88d5c303f78 100644 --- a/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts +++ b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts @@ -19,6 +19,7 @@ export class CloudWatchQueryParameter { export class CloudWatchQueryParameterCtrl { + /** @ngInject */ constructor($scope, templateSrv, uiSegmentSrv, datasourceSrv, $q) { $scope.init = function() { From e569c8ea103a741fb44a30bb7ae2f44db5bfe81d Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 18 Dec 2017 16:22:58 +0100 Subject: [PATCH 16/48] fix: Navigation on small screens when Grafana is installed in a sub directory (#10252) (#10261) * ux: Add missing icon for login with grafana-com, fixes #10238 * fix: This fix potentially solves installations in sub directories, #10252 --- public/app/core/services/global_event_srv.ts | 22 +++++++++++++++--- .../app/core/specs/global_event_srv.jest.ts | 23 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 public/app/core/specs/global_event_srv.jest.ts diff --git a/public/app/core/services/global_event_srv.ts b/public/app/core/services/global_event_srv.ts index a4d5865eb63..5cc21dc7b7a 100644 --- a/public/app/core/services/global_event_srv.ts +++ b/public/app/core/services/global_event_srv.ts @@ -1,19 +1,35 @@ import coreModule from 'app/core/core_module'; +import config from 'app/core/config'; import appEvents from 'app/core/app_events'; // This service is for registering global events. // Good for communication react > angular and vice verse export class GlobalEventSrv { + private appSubUrl; /** @ngInject */ constructor(private $location, private $timeout) { + this.appSubUrl = config.appSubUrl; + } + + // Angular's $location does not like and absolute urls + stripBaseFromUrl (url = '') { + const appSubUrl = this.appSubUrl; + const stripExtraChars = appSubUrl.endsWith('/') ? 1 : 0; + const urlWithoutBase = url.length > 0 && url.indexOf(appSubUrl) === 0 ? + url.slice(appSubUrl.length - stripExtraChars) + : url; + + return urlWithoutBase; } init() { appEvents.on('location-change', payload => { - this.$timeout(() => { // A hack to use timeout when we're changing things (in this case the url) from outside of Angular. - this.$location.path(payload.href); - }); + const urlWithoutBase = this.stripBaseFromUrl(payload.href); + + this.$timeout(() => { // A hack to use timeout when we're changing things (in this case the url) from outside of Angular. + this.$location.url(urlWithoutBase); + }); }); } } diff --git a/public/app/core/specs/global_event_srv.jest.ts b/public/app/core/specs/global_event_srv.jest.ts new file mode 100644 index 00000000000..0ecd5cbe40b --- /dev/null +++ b/public/app/core/specs/global_event_srv.jest.ts @@ -0,0 +1,23 @@ +import { GlobalEventSrv } from 'app/core/services/global_event_srv'; +import { beforeEach } from 'test/lib/common'; + +jest.mock('app/core/config', () => { + return { + appSubUrl: '/subUrl' + }; +}); + +describe('GlobalEventSrv', () => { + let searchSrv; + + beforeEach(() => { + searchSrv = new GlobalEventSrv(null, null); + }); + + describe('With /subUrl as appSubUrl', () => { + it('/subUrl should be stripped', () => { + const urlWithoutMaster = searchSrv.stripBaseFromUrl('/subUrl/grafana/'); + expect(urlWithoutMaster).toBe('/grafana/'); + }); + }); +}); From 0a28def71fc090071d929f75a3436ea4043bd0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 18 Dec 2017 16:52:31 +0100 Subject: [PATCH 17/48] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0acd4b158c2..bb0d2e026b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # 5.0.0 (unreleased / master branch) +Grafana v5.0 is going to be the biggest and most foundational release Grafana has ever had, coming with a ton of UX improvements, a new dashboard grid engine, dashboard folders, user teams and permissions. Checkout out this [video preview](https://www.youtube.com/watch?v=BC_YRNpqj5k) of Grafana v5. + ### New Features - **Dashboards** Dashboard folders, [#1611](https://github.com/grafana/grafana/issues/1611) - **Teams** User groups (teams) implemented. Can be used in folder & dashboard permission list. From dd2192cccc43e4d8aacc0871e96f206a915eafdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 18 Dec 2017 16:54:09 +0100 Subject: [PATCH 18/48] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5358cd3f3d5..069958d9031 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB. ![](http://docs.grafana.org/assets/img/features/dashboard_ex1.png) +## Grafana v5 Alpha Preview +Grafana master is now v5.0 alpha. This is going to be the biggest and most foundational release Grafana has ever had, coming with a ton of UX improvements, a new dashboard grid engine, dashboard folders, user teams and permissions. Checkout out this [video preview](https://www.youtube.com/watch?v=BC_YRNpqj5k) of Grafana v5. + ## Installation Head to [docs.grafana.org](http://docs.grafana.org/installation/) and [download](https://grafana.com/get) the latest release. From a320a831388e729d541839e279c2bd4c21106216 Mon Sep 17 00:00:00 2001 From: SteelPhase Date: Mon, 18 Dec 2017 13:52:42 -0500 Subject: [PATCH 19/48] Use strings.TrimPrefix to make sure relative url doesn't start with forward slash Closes grafana/grafana#10263 --- pkg/middleware/org_redirect.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/middleware/org_redirect.go b/pkg/middleware/org_redirect.go index a5f90d60e47..9dd764be1bb 100644 --- a/pkg/middleware/org_redirect.go +++ b/pkg/middleware/org_redirect.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "strconv" + "strings" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" @@ -41,7 +42,7 @@ func OrgRedirect() macaron.Handler { return } - newUrl := setting.ToAbsUrl(fmt.Sprintf("%s?%s", c.Req.URL.Path, c.Req.URL.Query().Encode())) - c.Redirect(newUrl, 302) + newURL := setting.ToAbsUrl(fmt.Sprintf("%s?%s", strings.TrimPrefix(c.Req.URL.Path, "/"), c.Req.URL.Query().Encode())) + c.Redirect(newURL, 302) } } From 1816e89730ebb540ee564f61b896643a196614f5 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Tue, 19 Dec 2017 10:22:26 +0100 Subject: [PATCH 20/48] alerting: move test json into files --- pkg/services/alerting/extractor_test.go | 419 +----------------- .../alerting/test-data/graphite-alert.json | 63 +++ .../alerting/test-data/influxdb-alert.json | 282 ++++++++++++ .../alerting/test-data/panels-missing-id.json | 62 +++ .../alerting/test-data/v5-dashboard.json | 0 5 files changed, 415 insertions(+), 411 deletions(-) create mode 100644 pkg/services/alerting/test-data/graphite-alert.json create mode 100644 pkg/services/alerting/test-data/influxdb-alert.json create mode 100644 pkg/services/alerting/test-data/panels-missing-id.json create mode 100644 pkg/services/alerting/test-data/v5-dashboard.json diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index 68054fab5ef..52f6c2862f0 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -1,6 +1,7 @@ package alerting import ( + "io/ioutil" "testing" "github.com/grafana/grafana/pkg/bus" @@ -45,70 +46,8 @@ func TestAlertRuleExtraction(t *testing.T) { return nil }) - json := ` - { - "id": 57, - "title": "Graphite 4", - "originalTitle": "Graphite 4", - "tags": ["graphite"], - "rows": [ - { - "panels": [ - { - "title": "Active desktop users", - "editable": true, - "type": "graph", - "id": 3, - "targets": [ - { - "refId": "A", - "target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)" - } - ], - "datasource": null, - "alert": { - "name": "name1", - "message": "desc1", - "handler": 1, - "frequency": "60s", - "conditions": [ - { - "type": "query", - "query": {"params": ["A", "5m", "now"]}, - "reducer": {"type": "avg", "params": []}, - "evaluator": {"type": ">", "params": [100]} - } - ] - } - }, - { - "title": "Active mobile users", - "id": 4, - "targets": [ - {"refId": "A", "target": ""}, - {"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"} - ], - "datasource": "graphite2", - "alert": { - "name": "name2", - "message": "desc2", - "handler": 0, - "frequency": "60s", - "severity": "warning", - "conditions": [ - { - "type": "query", - "query": {"params": ["B", "5m", "now"]}, - "reducer": {"type": "avg", "params": []}, - "evaluator": {"type": ">", "params": [100]} - } - ] - } - } - ] - } - ] - }` + json, err := ioutil.ReadFile("./test-data/graphite-alert.json") + So(err, ShouldBeNil) Convey("Extractor should not modify the original json", func() { dashJson, err := simplejson.NewJson([]byte(json)) @@ -201,69 +140,8 @@ func TestAlertRuleExtraction(t *testing.T) { }) Convey("Panels missing id should return error", func() { - panelWithoutId := ` - { - "id": 57, - "title": "Graphite 4", - "originalTitle": "Graphite 4", - "tags": ["graphite"], - "rows": [ - { - "panels": [ - { - "title": "Active desktop users", - "editable": true, - "type": "graph", - "targets": [ - { - "refId": "A", - "target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)" - } - ], - "datasource": null, - "alert": { - "name": "name1", - "message": "desc1", - "handler": 1, - "frequency": "60s", - "conditions": [ - { - "type": "query", - "query": {"params": ["A", "5m", "now"]}, - "reducer": {"type": "avg", "params": []}, - "evaluator": {"type": ">", "params": [100]} - } - ] - } - }, - { - "title": "Active mobile users", - "id": 4, - "targets": [ - {"refId": "A", "target": ""}, - {"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"} - ], - "datasource": "graphite2", - "alert": { - "name": "name2", - "message": "desc2", - "handler": 0, - "frequency": "60s", - "severity": "warning", - "conditions": [ - { - "type": "query", - "query": {"params": ["B", "5m", "now"]}, - "reducer": {"type": "avg", "params": []}, - "evaluator": {"type": ">", "params": [100]} - } - ] - } - } - ] - } - ] - }` + panelWithoutId, err := ioutil.ReadFile("./test-data/panels-missing-id.json") + So(err, ShouldBeNil) dashJson, err := simplejson.NewJson([]byte(panelWithoutId)) So(err, ShouldBeNil) @@ -278,291 +156,10 @@ func TestAlertRuleExtraction(t *testing.T) { }) Convey("Parse and validate dashboard containing influxdb alert", func() { + json2, err := ioutil.ReadFile("./test-data/influxdb-alert.json") + So(err, ShouldBeNil) - json2 := `{ - "id": 4, - "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 - }` - - dashJson, err := simplejson.NewJson([]byte(json2)) + dashJson, err := simplejson.NewJson(json2) So(err, ShouldBeNil) dash := m.NewDashboardFromJson(dashJson) extractor := NewDashAlertExtractor(dash, 1) diff --git a/pkg/services/alerting/test-data/graphite-alert.json b/pkg/services/alerting/test-data/graphite-alert.json new file mode 100644 index 00000000000..5f23e224f9a --- /dev/null +++ b/pkg/services/alerting/test-data/graphite-alert.json @@ -0,0 +1,63 @@ +{ + "id": 57, + "title": "Graphite 4", + "originalTitle": "Graphite 4", + "tags": ["graphite"], + "rows": [ + { + "panels": [ + { + "title": "Active desktop users", + "editable": true, + "type": "graph", + "id": 3, + "targets": [ + { + "refId": "A", + "target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)" + } + ], + "datasource": null, + "alert": { + "name": "name1", + "message": "desc1", + "handler": 1, + "frequency": "60s", + "conditions": [ + { + "type": "query", + "query": {"params": ["A", "5m", "now"]}, + "reducer": {"type": "avg", "params": []}, + "evaluator": {"type": ">", "params": [100]} + } + ] + } + }, + { + "title": "Active mobile users", + "id": 4, + "targets": [ + {"refId": "A", "target": ""}, + {"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"} + ], + "datasource": "graphite2", + "alert": { + "name": "name2", + "message": "desc2", + "handler": 0, + "frequency": "60s", + "severity": "warning", + "conditions": [ + { + "type": "query", + "query": {"params": ["B", "5m", "now"]}, + "reducer": {"type": "avg", "params": []}, + "evaluator": {"type": ">", "params": [100]} + } + ] + } + } + ] + } + ] + } \ No newline at end of file diff --git a/pkg/services/alerting/test-data/influxdb-alert.json b/pkg/services/alerting/test-data/influxdb-alert.json new file mode 100644 index 00000000000..79ca355c5a1 --- /dev/null +++ b/pkg/services/alerting/test-data/influxdb-alert.json @@ -0,0 +1,282 @@ +{ + "id": 4, + "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 + } \ No newline at end of file diff --git a/pkg/services/alerting/test-data/panels-missing-id.json b/pkg/services/alerting/test-data/panels-missing-id.json new file mode 100644 index 00000000000..dad96a18dc1 --- /dev/null +++ b/pkg/services/alerting/test-data/panels-missing-id.json @@ -0,0 +1,62 @@ +{ + "id": 57, + "title": "Graphite 4", + "originalTitle": "Graphite 4", + "tags": ["graphite"], + "rows": [ + { + "panels": [ + { + "title": "Active desktop users", + "editable": true, + "type": "graph", + "targets": [ + { + "refId": "A", + "target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)" + } + ], + "datasource": null, + "alert": { + "name": "name1", + "message": "desc1", + "handler": 1, + "frequency": "60s", + "conditions": [ + { + "type": "query", + "query": {"params": ["A", "5m", "now"]}, + "reducer": {"type": "avg", "params": []}, + "evaluator": {"type": ">", "params": [100]} + } + ] + } + }, + { + "title": "Active mobile users", + "id": 4, + "targets": [ + {"refId": "A", "target": ""}, + {"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"} + ], + "datasource": "graphite2", + "alert": { + "name": "name2", + "message": "desc2", + "handler": 0, + "frequency": "60s", + "severity": "warning", + "conditions": [ + { + "type": "query", + "query": {"params": ["B", "5m", "now"]}, + "reducer": {"type": "avg", "params": []}, + "evaluator": {"type": ">", "params": [100]} + } + ] + } + } + ] + } + ] + } \ No newline at end of file diff --git a/pkg/services/alerting/test-data/v5-dashboard.json b/pkg/services/alerting/test-data/v5-dashboard.json new file mode 100644 index 00000000000..e69de29bb2d From 66cf224e31b06d50e76ca12c9b60ca557951d590 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Tue, 19 Dec 2017 11:19:52 +0100 Subject: [PATCH 21/48] alerting: make alert extractor backwards compatible closes #10270 --- pkg/services/alerting/extractor.go | 175 ++++++++++-------- pkg/services/alerting/extractor_test.go | 26 ++- .../alerting/test-data/v5-dashboard.json | 60 ++++++ 3 files changed, 183 insertions(+), 78 deletions(-) diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index b8579ffdc6a..a609824cbc8 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -69,6 +69,90 @@ func copyJson(in *simplejson.Json) (*simplejson.Json, error) { return simplejson.NewJson(rawJson) } +func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) ([]*m.Alert, error) { + alerts := make([]*m.Alert, 0) + + for _, panelObj := range jsonWithPanels.Get("panels").MustArray() { + panel := simplejson.NewFromAny(panelObj) + jsonAlert, hasAlert := panel.CheckGet("alert") + + if !hasAlert { + continue + } + + panelId, err := panel.Get("id").Int64() + if err != nil { + return nil, fmt.Errorf("panel id is required. err %v", err) + } + + // backward compatibility check, can be removed later + enabled, hasEnabled := jsonAlert.CheckGet("enabled") + if hasEnabled && enabled.MustBool() == false { + continue + } + + frequency, err := getTimeDurationStringToSeconds(jsonAlert.Get("frequency").MustString()) + if err != nil { + return nil, ValidationError{Reason: "Could not parse frequency"} + } + + alert := &m.Alert{ + DashboardId: e.Dash.Id, + OrgId: e.OrgId, + PanelId: panelId, + Id: jsonAlert.Get("id").MustInt64(), + Name: jsonAlert.Get("name").MustString(), + Handler: jsonAlert.Get("handler").MustInt64(), + Message: jsonAlert.Get("message").MustString(), + Frequency: frequency, + } + + for _, condition := range jsonAlert.Get("conditions").MustArray() { + jsonCondition := simplejson.NewFromAny(condition) + + jsonQuery := jsonCondition.Get("query") + 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) + return nil, ValidationError{Reason: reason} + } + + dsName := "" + if panelQuery.Get("datasource").MustString() != "" { + dsName = panelQuery.Get("datasource").MustString() + } else if panel.Get("datasource").MustString() != "" { + dsName = panel.Get("datasource").MustString() + } + + if datasource, err := e.lookupDatasourceId(dsName); err != nil { + return nil, err + } else { + jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id) + } + + if interval, err := panel.Get("interval").String(); err == nil { + panelQuery.Set("interval", interval) + } + + jsonQuery.Set("model", panelQuery.Interface()) + } + + alert.Settings = jsonAlert + + // validate + _, err = NewRuleFromDBAlert(alert) + if err == nil && alert.ValidToSave() { + alerts = append(alerts, alert) + } else { + return nil, err + } + } + + return alerts, nil +} + func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) { e.log.Debug("GetAlerts") @@ -78,86 +162,27 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) { } alerts := make([]*m.Alert, 0) - for _, rowObj := range dashboardJson.Get("rows").MustArray() { - row := simplejson.NewFromAny(rowObj) - for _, panelObj := range row.Get("panels").MustArray() { - panel := simplejson.NewFromAny(panelObj) - jsonAlert, hasAlert := panel.CheckGet("alert") - - if !hasAlert { - continue - } - - panelId, err := panel.Get("id").Int64() + // We extract alerts from rows to be backwards compatible + // with the old dashboard json model. + rows := dashboardJson.Get("rows").MustArray() + if len(rows) > 0 { + for _, rowObj := range rows { + row := simplejson.NewFromAny(rowObj) + a, err := e.GetAlertFromPanels(row) if err != nil { - return nil, fmt.Errorf("panel id is required. err %v", err) - } - - // backward compatibility check, can be removed later - enabled, hasEnabled := jsonAlert.CheckGet("enabled") - if hasEnabled && enabled.MustBool() == false { - continue - } - - frequency, err := getTimeDurationStringToSeconds(jsonAlert.Get("frequency").MustString()) - if err != nil { - return nil, ValidationError{Reason: "Could not parse frequency"} - } - - alert := &m.Alert{ - DashboardId: e.Dash.Id, - OrgId: e.OrgId, - PanelId: panelId, - Id: jsonAlert.Get("id").MustInt64(), - Name: jsonAlert.Get("name").MustString(), - Handler: jsonAlert.Get("handler").MustInt64(), - Message: jsonAlert.Get("message").MustString(), - Frequency: frequency, - } - - for _, condition := range jsonAlert.Get("conditions").MustArray() { - jsonCondition := simplejson.NewFromAny(condition) - - jsonQuery := jsonCondition.Get("query") - 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) - return nil, ValidationError{Reason: reason} - } - - dsName := "" - if panelQuery.Get("datasource").MustString() != "" { - dsName = panelQuery.Get("datasource").MustString() - } else if panel.Get("datasource").MustString() != "" { - dsName = panel.Get("datasource").MustString() - } - - if datasource, err := e.lookupDatasourceId(dsName); err != nil { - return nil, err - } else { - jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id) - } - - if interval, err := panel.Get("interval").String(); err == nil { - panelQuery.Set("interval", interval) - } - - jsonQuery.Set("model", panelQuery.Interface()) - } - - alert.Settings = jsonAlert - - // validate - _, err = NewRuleFromDBAlert(alert) - if err == nil && alert.ValidToSave() { - alerts = append(alerts, alert) - } else { return nil, err } + + alerts = append(alerts, a...) } + } else { + a, err := e.GetAlertFromPanels(dashboardJson) + if err != nil { + return nil, err + } + + alerts = append(alerts, a...) } e.log.Debug("Extracted alerts from dashboard", "alertCount", len(alerts)) diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index 52f6c2862f0..cec48d8a36a 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -155,11 +155,31 @@ func TestAlertRuleExtraction(t *testing.T) { }) }) - Convey("Parse and validate dashboard containing influxdb alert", func() { - json2, err := ioutil.ReadFile("./test-data/influxdb-alert.json") + Convey("Parse alerts from dashboard without rows", func() { + json, err := ioutil.ReadFile("./test-data/v5-dashboard.json") So(err, ShouldBeNil) - dashJson, err := simplejson.NewJson(json2) + dashJson, err := simplejson.NewJson(json) + So(err, ShouldBeNil) + dash := m.NewDashboardFromJson(dashJson) + extractor := NewDashAlertExtractor(dash, 1) + + alerts, err := extractor.GetAlerts() + + Convey("Get rules without error", func() { + So(err, ShouldBeNil) + }) + + Convey("Should have 2 alert rule", func() { + So(len(alerts), ShouldEqual, 2) + }) + }) + + Convey("Parse and validate dashboard containing influxdb alert", func() { + json, err := ioutil.ReadFile("./test-data/influxdb-alert.json") + So(err, ShouldBeNil) + + dashJson, err := simplejson.NewJson(json) So(err, ShouldBeNil) dash := m.NewDashboardFromJson(dashJson) extractor := NewDashAlertExtractor(dash, 1) diff --git a/pkg/services/alerting/test-data/v5-dashboard.json b/pkg/services/alerting/test-data/v5-dashboard.json index e69de29bb2d..da7bbd8d048 100644 --- a/pkg/services/alerting/test-data/v5-dashboard.json +++ b/pkg/services/alerting/test-data/v5-dashboard.json @@ -0,0 +1,60 @@ +{ + "id": 57, + "title": "Graphite 4", + "originalTitle": "Graphite 4", + "tags": ["graphite"], + "panels": [ + { + "title": "Active desktop users", + "editable": true, + "type": "graph", + "id": 3, + "targets": [ + { + "refId": "A", + "target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)" + } + ], + "datasource": null, + "alert": { + "name": "name1", + "message": "desc1", + "handler": 1, + "frequency": "60s", + "conditions": [ + { + "type": "query", + "query": {"params": ["A", "5m", "now"]}, + "reducer": {"type": "avg", "params": []}, + "evaluator": {"type": ">", "params": [100]} + } + ] + } + }, + { + "title": "Active mobile users", + "id": 4, + "targets": [ + {"refId": "A", "target": ""}, + {"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"} + ], + "datasource": "graphite2", + "alert": { + "name": "name2", + "message": "desc2", + "handler": 0, + "frequency": "60s", + "severity": "warning", + "conditions": [ + { + "type": "query", + "query": {"params": ["B", "5m", "now"]}, + "reducer": {"type": "avg", "params": []}, + "evaluator": {"type": ">", "params": [100]} + } + ] + } + + } + ] + } \ No newline at end of file From 644c2f7fb0dc9ca2e2190220133b946848d41382 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Tue, 19 Dec 2017 11:23:21 +0100 Subject: [PATCH 22/48] test: remove unused code --- pkg/services/alerting/extractor_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index cec48d8a36a..71f3026025d 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -7,7 +7,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" ) @@ -19,10 +18,6 @@ func TestAlertRuleExtraction(t *testing.T) { return &FakeCondition{}, nil }) - setting.NewConfigContext(&setting.CommandLineArgs{ - HomePath: "../../../", - }) - // mock data defaultDs := &m.DataSource{Id: 12, OrgId: 1, Name: "I am default", IsDefault: true} graphite2Ds := &m.DataSource{Id: 15, OrgId: 1, Name: "graphite2"} From e1a527a87aed5392dbbbef41f8e9a18fbab197a9 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 19 Dec 2017 12:58:55 +0100 Subject: [PATCH 23/48] ux: Fix color picker positioning when scrolled down to the bottom of a page (#10258) (#10271) --- public/app/core/services/popover_srv.ts | 3 ++- public/app/plugins/panel/graph/legend.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/app/core/services/popover_srv.ts b/public/app/core/services/popover_srv.ts index bf0dae2c631..08d5c2bedd0 100644 --- a/public/app/core/services/popover_srv.ts +++ b/public/app/core/services/popover_srv.ts @@ -57,7 +57,7 @@ function popoverSrv($compile, $rootScope, $timeout) { openOn: options.openOn, hoverCloseDelay: 200, tetherOptions: { - constraints: [{to: 'scrollParent', attachment: "none both"}] + constraints: [{to: 'scrollParent', attachment: 'together'}] } }); @@ -79,3 +79,4 @@ function popoverSrv($compile, $rootScope, $timeout) { } coreModule.service('popoverSrv', popoverSrv); + diff --git a/public/app/plugins/panel/graph/legend.ts b/public/app/plugins/panel/graph/legend.ts index 791ebf761ae..bb96f1565c4 100644 --- a/public/app/plugins/panel/graph/legend.ts +++ b/public/app/plugins/panel/graph/legend.ts @@ -53,7 +53,8 @@ module.directive('graphLegend', function(popoverSrv, $timeout) { $timeout(function() { popoverSrv.show({ element: el[0], - position: 'bottom center', + position: 'bottom left', + targetAttachment: 'top left', template: '' + '', openOn: 'hover', From 3e28ce94eb9063b1ee936f76184998e55be5b083 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 19 Dec 2017 13:19:44 +0100 Subject: [PATCH 24/48] ux: Add icon to selected option in PageHeader navigation on small screens, update select boxes for Firefox so the arrow to the right is aligned with the other select boxes (#10190) --- .../core/components/PageHeader/PageHeader.tsx | 15 ++++++--- .../manage_dashboards/manage_dashboards.html | 32 +++++++++++-------- public/sass/components/_gf-form.scss | 24 ++++++++++++-- public/sass/components/_page_header.scss | 3 +- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/public/app/core/components/PageHeader/PageHeader.tsx b/public/app/core/components/PageHeader/PageHeader.tsx index fb09ed29085..095100fcff9 100644 --- a/public/app/core/components/PageHeader/PageHeader.tsx +++ b/public/app/core/components/PageHeader/PageHeader.tsx @@ -41,7 +41,7 @@ function SelectOption(navItem: NavModelItem) { function Navigation({main}: {main: NavModelItem}) { return (); } @@ -57,10 +57,15 @@ function SelectNav({main, customCss}: {main: NavModelItem, customCss: string}) { appEvents.emit('location-change', {href: url}); }; - return (); + return ( +
+
); } function Tabs({main, customCss}: {main: NavModelItem, customCss: string}) { diff --git a/public/app/core/components/manage_dashboards/manage_dashboards.html b/public/app/core/components/manage_dashboards/manage_dashboards.html index f8c96a78b2e..90091f3032c 100644 --- a/public/app/core/components/manage_dashboards/manage_dashboards.html +++ b/public/app/core/components/manage_dashboards/manage_dashboards.html @@ -60,20 +60,24 @@ switch-class="gf-form-switch--transparent gf-form-switch--search-result-filter-row__checkbox" />
- +
+ +
- +