From 32d34aed7af905fd44f4559e1324ecbcf4c53721 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 8 Apr 2017 13:32:16 +0300 Subject: [PATCH 01/37] graph: initial Add Annotation flow #1286 --- .../features/annotations/annotations_srv.ts | 12 ++++ .../dashboard/addAnnotationModalCtrl.ts | 49 +++++++++++++++ public/app/features/dashboard/all.js | 1 + .../partials/addAnnotationModal.html | 62 +++++++++++++++++++ public/app/plugins/panel/graph/graph.ts | 8 +++ public/app/plugins/panel/graph/module.ts | 23 +++++++ 6 files changed, 155 insertions(+) create mode 100644 public/app/features/dashboard/addAnnotationModalCtrl.ts create mode 100644 public/app/features/dashboard/partials/addAnnotationModal.html diff --git a/public/app/features/annotations/annotations_srv.ts b/public/app/features/annotations/annotations_srv.ts index 759e4500c7d..f50fce8e08a 100644 --- a/public/app/features/annotations/annotations_srv.ts +++ b/public/app/features/annotations/annotations_srv.ts @@ -126,6 +126,18 @@ export class AnnotationsSrv { return this.globalAnnotationsPromise; } + postAnnotation(annotation) { + console.log("POST /api/annotations\n", annotation); + + // Not implemented yet + let implemented = false; + if (implemented) { + return this.backendSrv.post('/api/annotations', annotation); + } else { + return Promise.resolve("Not implemented"); + } + } + translateQueryResult(annotation, results) { for (var item of results) { item.source = annotation; diff --git a/public/app/features/dashboard/addAnnotationModalCtrl.ts b/public/app/features/dashboard/addAnnotationModalCtrl.ts new file mode 100644 index 00000000000..0967eda14b1 --- /dev/null +++ b/public/app/features/dashboard/addAnnotationModalCtrl.ts @@ -0,0 +1,49 @@ +/// + +import angular from 'angular'; +import moment from 'moment'; + +export class AddAnnotationModalCtrl { + annotationTime: any; + annotationTimeFormat = 'YYYY-MM-DD HH:mm:ss'; + annotation: any; + graphCtrl: any; + + /** @ngInject */ + constructor(private $scope) { + this.graphCtrl = $scope.ctrl; + $scope.ctrl = this; + + this.annotation = { + time: null, + title: "", + text: "" + }; + + this.annotationTime = moment(this.$scope.annotationTimeUnix).format(this.annotationTimeFormat); + } + + addAnnotation() { + let time = moment(this.annotationTime, this.annotationTimeFormat); + this.annotation.time = time.valueOf(); + + this.graphCtrl.pushAnnotation(this.annotation) + .then(response => { + console.log(response); + this.close(); + }) + .catch(error => { + console.log(error); + this.close(); + }); + } + + close() { + this.graphCtrl.inAddAnnotationMode = false; + this.$scope.dismiss(); + } +} + +angular + .module('grafana.controllers') + .controller('AddAnnotationModalCtrl', AddAnnotationModalCtrl); diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index c362f9cd032..c3a71a11818 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -7,6 +7,7 @@ define([ './saveDashboardAsCtrl', './shareModalCtrl', './shareSnapshotCtrl', + './addAnnotationModalCtrl', './dashboard_srv', './viewStateSrv', './time_srv', diff --git a/public/app/features/dashboard/partials/addAnnotationModal.html b/public/app/features/dashboard/partials/addAnnotationModal.html new file mode 100644 index 00000000000..f16656bba77 --- /dev/null +++ b/public/app/features/dashboard/partials/addAnnotationModal.html @@ -0,0 +1,62 @@ + + diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 5a684a42fd3..3a75c189dbd 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -79,6 +79,14 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { } }, scope); + appEvents.on('graph-click', (event) => { + + // Select time for new annotation + if (ctrl.inAddAnnotationMode) { + ctrl.showAddAnnotationModal(event); + } + }, scope); + function getLegendHeight(panelHeight) { if (!panel.legend.show || panel.legend.rightSide) { return 0; diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index e98d1c25ad7..b8ab763550b 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -24,6 +24,7 @@ class GraphCtrl extends MetricsPanelCtrl { dataList: any = []; annotations: any = []; alertState: any; + inAddAnnotationMode = false; annotationsPromise: any; dataWarning: any; @@ -144,6 +145,7 @@ class GraphCtrl extends MetricsPanelCtrl { actions.push({text: 'Export CSV (series as rows)', click: 'ctrl.exportCsv()'}); actions.push({text: 'Export CSV (series as columns)', click: 'ctrl.exportCsvColumns()'}); actions.push({text: 'Toggle legend', click: 'ctrl.toggleLegend()'}); + actions.push({ text: 'Add Annotation', click: 'ctrl.enableAddAnnotationMode()' }); } issueQueries(datasource) { @@ -300,6 +302,27 @@ class GraphCtrl extends MetricsPanelCtrl { this.refresh(); } + enableAddAnnotationMode() { + // TODO: notify user about time selection mode + this.inAddAnnotationMode = true; + } + + // Get annotation info from dialog and push it to backend + pushAnnotation(annotation) { + return this.annotationsSrv.postAnnotation(annotation); + } + + showAddAnnotationModal(event) { + let addAnnotationScope = this.$scope.$new(); + let annotationTimeUnix = Math.round(event.pos.x); + addAnnotationScope.annotationTimeUnix = annotationTimeUnix; + + this.publishAppEvent('show-modal', { + src: 'public/app/features/dashboard/partials/addAnnotationModal.html', + scope: addAnnotationScope + }); + } + legendValuesOptionChanged() { var legend = this.panel.legend; legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total; From 362860f6873973b64a19e3508fa70bc4a0399619 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 10 Apr 2017 12:38:28 +0300 Subject: [PATCH 02/37] graph(add annotation): able to select region (start and stop time) #1286 --- .../dashboard/addAnnotationModalCtrl.ts | 46 ++++++++++++++----- .../partials/addAnnotationModal.html | 32 +++++++++---- public/app/plugins/panel/graph/graph.ts | 26 ++++++++--- public/app/plugins/panel/graph/module.ts | 9 ++-- 4 files changed, 82 insertions(+), 31 deletions(-) diff --git a/public/app/features/dashboard/addAnnotationModalCtrl.ts b/public/app/features/dashboard/addAnnotationModalCtrl.ts index 0967eda14b1..7c3962074cf 100644 --- a/public/app/features/dashboard/addAnnotationModalCtrl.ts +++ b/public/app/features/dashboard/addAnnotationModalCtrl.ts @@ -4,9 +4,12 @@ import angular from 'angular'; import moment from 'moment'; export class AddAnnotationModalCtrl { - annotationTime: any; annotationTimeFormat = 'YYYY-MM-DD HH:mm:ss'; - annotation: any; + annotationTimeFrom: any; + annotationTimeTo: any = null; + annotationTitle: string; + annotationTextFrom: string; + annotationTextTo: string; graphCtrl: any; /** @ngInject */ @@ -14,20 +17,39 @@ export class AddAnnotationModalCtrl { this.graphCtrl = $scope.ctrl; $scope.ctrl = this; - this.annotation = { - time: null, - title: "", - text: "" - }; - - this.annotationTime = moment(this.$scope.annotationTimeUnix).format(this.annotationTimeFormat); + this.annotationTimeFrom = moment($scope.annotationTimeRange.from).format(this.annotationTimeFormat); + if ($scope.annotationTimeRange.to) { + this.annotationTimeTo = moment($scope.annotationTimeRange.to).format(this.annotationTimeFormat); + } } addAnnotation() { - let time = moment(this.annotationTime, this.annotationTimeFormat); - this.annotation.time = time.valueOf(); + let dashboardId = this.graphCtrl.dashboard.id; + let panelId = this.graphCtrl.panel.id; + let timeFrom = moment(this.annotationTimeFrom, this.annotationTimeFormat).valueOf(); - this.graphCtrl.pushAnnotation(this.annotation) + let annotationFrom = { + dashboardId: dashboardId, + panelId: panelId, + time: timeFrom, + title: this.annotationTitle, + text: this.annotationTextFrom + }; + let annotations = [annotationFrom]; + + if (this.annotationTimeTo) { + let timeTo = moment(this.annotationTimeTo, this.annotationTimeFormat).valueOf(); + let annotationTo = { + dashboardId: dashboardId, + panelId: panelId, + time: timeTo, + title: this.annotationTitle, + text: this.annotationTextTo + }; + annotations.push(annotationTo); + } + + this.graphCtrl.pushAnnotations(annotations) .then(response => { console.log(response); this.close(); diff --git a/public/app/features/dashboard/partials/addAnnotationModal.html b/public/app/features/dashboard/partials/addAnnotationModal.html index f16656bba77..1ea3b7d7a6f 100644 --- a/public/app/features/dashboard/partials/addAnnotationModal.html +++ b/public/app/features/dashboard/partials/addAnnotationModal.html @@ -27,23 +27,39 @@

- Title - + Title +
- Time - + Time + Time Start + +
+
+ Time Stop +
-
- Description -
+
Description
+
Description Start
+ + +
+
+
Description Stop
+
+
diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 3a75c189dbd..5aaf5324156 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -83,7 +83,13 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { // Select time for new annotation if (ctrl.inAddAnnotationMode) { - ctrl.showAddAnnotationModal(event); + let timeRange = { + from: event.pos.x, + to: null + }; + + ctrl.showAddAnnotationModal(timeRange); + ctrl.inAddAnnotationMode = false; } }, scope); @@ -647,12 +653,20 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { } elem.bind("plotselected", function (event, ranges) { - scope.$apply(function() { - timeSrv.setTime({ - from : moment.utc(ranges.xaxis.from), - to : moment.utc(ranges.xaxis.to), + if (ctrl.inAddAnnotationMode) { + // Select time range for new annotation + let timeRange = ranges.xaxis; + ctrl.showAddAnnotationModal(timeRange); + plot.clearSelection(); + ctrl.inAddAnnotationMode = false; + } else { + scope.$apply(function() { + timeSrv.setTime({ + from : moment.utc(ranges.xaxis.from), + to : moment.utc(ranges.xaxis.to), + }); }); - }); + } }); scope.$on('$destroy', function() { diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index b8ab763550b..5a24f39cf80 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -308,14 +308,13 @@ class GraphCtrl extends MetricsPanelCtrl { } // Get annotation info from dialog and push it to backend - pushAnnotation(annotation) { - return this.annotationsSrv.postAnnotation(annotation); + pushAnnotations(annotations) { + return this.annotationsSrv.postAnnotation(annotations); } - showAddAnnotationModal(event) { + showAddAnnotationModal(timeRange) { let addAnnotationScope = this.$scope.$new(); - let annotationTimeUnix = Math.round(event.pos.x); - addAnnotationScope.annotationTimeUnix = annotationTimeUnix; + addAnnotationScope.annotationTimeRange = timeRange; this.publishAppEvent('show-modal', { src: 'public/app/features/dashboard/partials/addAnnotationModal.html', From d553498a33299aeb62931cf553e36e1f1190e959 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 10 Apr 2017 20:22:58 +0300 Subject: [PATCH 03/37] graph(add annotation): initial backend implementation #1286 --- pkg/api/annotations.go | 21 +++++++++++++++++++ pkg/api/api.go | 1 + pkg/api/dtos/annotations.go | 8 +++++++ pkg/services/annotations/annotations.go | 8 +++++++ .../features/annotations/annotations_srv.ts | 13 ++++++++---- 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 48bf6c327ad..af72b9d3876 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -45,6 +45,27 @@ func GetAnnotations(c *middleware.Context) Response { return Json(200, result) } +func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response { + repo := annotations.GetRepository() + + item := annotations.Item{ + OrgId: c.OrgId, + DashboardId: cmd.DashboardId, + PanelId: cmd.PanelId, + Epoch: cmd.Time / 1000, + Title: cmd.Title, + Text: cmd.Text, + } + + err := repo.Save(&item) + + if err != nil { + return ApiError(500, "Failed to save annotation", err) + } + + return ApiSuccess("Annotation added") +} + func DeleteAnnotations(c *middleware.Context, cmd dtos.DeleteAnnotationsCmd) Response { repo := annotations.GetRepository() diff --git a/pkg/api/api.go b/pkg/api/api.go index 843b68eb915..0b9a8acf851 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -277,6 +277,7 @@ func (hs *HttpServer) registerRoutes() { }, reqEditorRole) r.Get("/annotations", wrap(GetAnnotations)) + r.Post("/annotations", bind(dtos.PostAnnotationsCmd{}), wrap(PostAnnotation)) r.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), wrap(DeleteAnnotations)) // error test diff --git a/pkg/api/dtos/annotations.go b/pkg/api/dtos/annotations.go index 45415978ee1..cd8c0c1d523 100644 --- a/pkg/api/dtos/annotations.go +++ b/pkg/api/dtos/annotations.go @@ -16,6 +16,14 @@ type Annotation struct { Data *simplejson.Json `json:"data"` } +type PostAnnotationsCmd struct { + DashboardId int64 `json:"dashboardId"` + PanelId int64 `json:"panelId"` + Time int64 `json:"time"` + Title string `json:"title"` + Text string `json:"text"` +} + type DeleteAnnotationsCmd struct { AlertId int64 `json:"alertId"` DashboardId int64 `json:"dashboardId"` diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index d9d15bca34b..a308f546c8a 100644 --- a/pkg/services/annotations/annotations.go +++ b/pkg/services/annotations/annotations.go @@ -21,6 +21,14 @@ type ItemQuery struct { Limit int64 `json:"limit"` } +type PostParams struct { + DashboardId int64 `json:"dashboardId"` + PanelId int64 `json:"panelId"` + Epoch int64 `json:"epoch"` + Title string `json:"title"` + Text string `json:"text"` +} + type DeleteParams struct { AlertId int64 `json:"alertId"` DashboardId int64 `json:"dashboardId"` diff --git a/public/app/features/annotations/annotations_srv.ts b/public/app/features/annotations/annotations_srv.ts index f50fce8e08a..d3b83982f51 100644 --- a/public/app/features/annotations/annotations_srv.ts +++ b/public/app/features/annotations/annotations_srv.ts @@ -126,13 +126,18 @@ export class AnnotationsSrv { return this.globalAnnotationsPromise; } - postAnnotation(annotation) { - console.log("POST /api/annotations\n", annotation); + postAnnotation(annotations) { + console.log("POST /api/annotations\n", annotations); // Not implemented yet - let implemented = false; + let implemented = true; if (implemented) { - return this.backendSrv.post('/api/annotations', annotation); + return Promise.all(_.map(annotations, annotation => { + return this.backendSrv.post('/api/annotations', annotation); + })) + .catch(error => { + console.log(error); + }); } else { return Promise.resolve("Not implemented"); } From 70bca219e3b39fa04df7efe7d0a524795b2faa45 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 11 Apr 2017 10:24:21 +0300 Subject: [PATCH 04/37] graph(add annotation): Add keybinding for CTRL key --- public/app/plugins/panel/graph/graph.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 5aaf5324156..c9cd251777f 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -80,9 +80,11 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { }, scope); appEvents.on('graph-click', (event) => { + // Add event only for selected panel + let thisPanelEvent = event.panel.id === ctrl.panel.id; // Select time for new annotation - if (ctrl.inAddAnnotationMode) { + if (ctrl.inAddAnnotationMode && thisPanelEvent) { let timeRange = { from: event.pos.x, to: null @@ -93,6 +95,22 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { } }, scope); + // Add keybinding for Add Annotation mode + $(document).keydown(onCtrlKeyDown); + $(document).keyup(onCtrlKeyUp); + + function onCtrlKeyDown(event) { + if (event.key === 'Control') { + ctrl.inAddAnnotationMode = true; + } + } + + function onCtrlKeyUp(event) { + if (event.key === 'Control') { + ctrl.inAddAnnotationMode = false; + } + } + function getLegendHeight(panelHeight) { if (!panel.legend.show || panel.legend.rightSide) { return 0; From 232513bb4e5eaa850906e1cf6b529e4cb1803b69 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 12 Apr 2017 15:26:34 +0300 Subject: [PATCH 05/37] graph(add annotation): refactor pass ctrlKey and metaKey through flot events --- public/app/plugins/panel/graph/graph.ts | 23 ++++----------------- public/vendor/flot/jquery.flot.js | 4 ++++ public/vendor/flot/jquery.flot.selection.js | 8 +++++-- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index c9cd251777f..7ae1b19ca21 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -84,7 +84,8 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { let thisPanelEvent = event.panel.id === ctrl.panel.id; // Select time for new annotation - if (ctrl.inAddAnnotationMode && thisPanelEvent) { + let createAnnotation = ctrl.inAddAnnotationMode || event.pos.ctrlKey || event.pos.metaKey; + if (createAnnotation && thisPanelEvent) { let timeRange = { from: event.pos.x, to: null @@ -95,22 +96,6 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { } }, scope); - // Add keybinding for Add Annotation mode - $(document).keydown(onCtrlKeyDown); - $(document).keyup(onCtrlKeyUp); - - function onCtrlKeyDown(event) { - if (event.key === 'Control') { - ctrl.inAddAnnotationMode = true; - } - } - - function onCtrlKeyUp(event) { - if (event.key === 'Control') { - ctrl.inAddAnnotationMode = false; - } - } - function getLegendHeight(panelHeight) { if (!panel.legend.show || panel.legend.rightSide) { return 0; @@ -671,8 +656,8 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { } elem.bind("plotselected", function (event, ranges) { - if (ctrl.inAddAnnotationMode) { - // Select time range for new annotation + if (ctrl.inAddAnnotationMode || ranges.ctrlKey || ranges.metaKey) { + // Create new annotation from time range let timeRange = ranges.xaxis; ctrl.showAddAnnotationModal(timeRange); plot.clearSelection(); diff --git a/public/vendor/flot/jquery.flot.js b/public/vendor/flot/jquery.flot.js index e9feacafb84..2f1b60b0830 100644 --- a/public/vendor/flot/jquery.flot.js +++ b/public/vendor/flot/jquery.flot.js @@ -2972,6 +2972,10 @@ Licensed under the MIT license. pos.pageX = event.pageX; pos.pageY = event.pageY; + // Add ctrlKey and metaKey to event + pos.ctrlKey = event.ctrlKey; + pos.metaKey = event.metaKey; + var item = findNearbyItem(canvasX, canvasY, seriesFilter); if (item) { diff --git a/public/vendor/flot/jquery.flot.selection.js b/public/vendor/flot/jquery.flot.selection.js index b7993d92ee3..12e453c4c40 100644 --- a/public/vendor/flot/jquery.flot.selection.js +++ b/public/vendor/flot/jquery.flot.selection.js @@ -145,7 +145,7 @@ The plugin allso adds the following methods to the plot object: updateSelection(e); if (selectionIsSane()) - triggerSelectedEvent(); + triggerSelectedEvent(e); else { // this counts as a clear plot.getPlaceholder().trigger("plotunselected", [ ]); @@ -180,9 +180,13 @@ The plugin allso adds the following methods to the plot object: return r; } - function triggerSelectedEvent() { + function triggerSelectedEvent(event) { var r = getSelection(); + // Add ctrlKey and metaKey to event + r.ctrlKey = event.ctrlKey; + r.metaKey = event.metaKey; + plot.getPlaceholder().trigger("plotselected", [ r ]); // backwards-compat stuff, to be removed in future From 752b42798ae38ab974b224ebc3cd5d3d0e2c25b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Apr 2017 15:01:17 +0200 Subject: [PATCH 06/37] annotations: added new options hide toggle, and show option --- public/app/core/services/keybindingSrv.ts | 4 - .../app/features/annotations/editor_ctrl.ts | 12 ++- .../features/annotations/partials/editor.html | 70 +++++++++------- public/app/features/dashboard/model.ts | 28 +++---- .../dashboard/specs/dashboard_model_specs.ts | 81 +++++++++++++++++++ .../features/dashboard/submenu/submenu.html | 2 +- .../partials/annotations.editor.html | 10 ++- .../grafana/partials/annotations.editor.html | 1 - .../graphite/partials/annotations.editor.html | 11 ++- .../influxdb/partials/annotations.editor.html | 3 +- 10 files changed, 158 insertions(+), 64 deletions(-) diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 7abc3993e9d..acf0123962b 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -83,10 +83,6 @@ export class KeybindingSrv { } setupDashboardBindings(scope, dashboard) { - // this.bind('b', () => { - // dashboard.toggleEditMode(); - // }); - this.bind('mod+o', () => { dashboard.graphTooltip = (dashboard.graphTooltip + 1) % 3; appEvents.emit('graph-hover-clear'); diff --git a/public/app/features/annotations/editor_ctrl.ts b/public/app/features/annotations/editor_ctrl.ts index cb76045dbec..a0a8fda01d1 100644 --- a/public/app/features/annotations/editor_ctrl.ts +++ b/public/app/features/annotations/editor_ctrl.ts @@ -17,9 +17,16 @@ export class AnnotationsEditorCtrl { name: '', datasource: null, iconColor: 'rgba(255, 96, 96, 1)', - enable: true + enable: true, + show: 0, + hide: false, }; + showOptions: any = [ + {text: 'All Panels', value: 0}, + {text: 'Specifc Panels', value: 1}, + ]; + /** @ngInject */ constructor(private $scope, private datasourceSrv) { $scope.ctrl = this; @@ -44,6 +51,7 @@ export class AnnotationsEditorCtrl { edit(annotation) { this.currentAnnotation = annotation; + this.currentAnnotation.show = this.currentAnnotation.show || 0; this.currentIsNew = false; this.datasourceChanged(); this.mode = 'edit'; @@ -74,7 +82,7 @@ export class AnnotationsEditorCtrl { removeAnnotation(annotation) { var index = _.indexOf(this.annotations, annotation); this.annotations.splice(index, 1); - this.$scope.updateSubmenuVisibility(); + this.$scope.dashboard.updateSubmenuVisibility(); this.$scope.broadcastRefresh(); } } diff --git a/public/app/features/annotations/partials/editor.html b/public/app/features/annotations/partials/editor.html index 0bfc8bb2028..b5c631ec6a8 100644 --- a/public/app/features/annotations/partials/editor.html +++ b/public/app/features/annotations/partials/editor.html @@ -7,16 +7,16 @@ @@ -62,37 +62,53 @@
-
-
- Name - -
-
- Datasource -
- +
Options
+
+
+ Name + +
+
+ Data source +
+ +
-
- - +
+
+
+ Show in +
+ +
+
+ + +
+
+ + +
-
- - - - +
Query
+ + + + -
-
- - +
+
+ + +
-
diff --git a/public/app/features/dashboard/model.ts b/public/app/features/dashboard/model.ts index e31a6c1afd0..e62c98b8598 100644 --- a/public/app/features/dashboard/model.ts +++ b/public/app/features/dashboard/model.ts @@ -193,32 +193,22 @@ export class DashboardModel { }); } - toggleEditMode() { - if (!this.meta.canEdit) { - console.log('Not allowed to edit dashboard'); - return; - } - - this.editMode = !this.editMode; - this.updateSubmenuVisibility(); - this.events.emit('edit-mode-changed', this.editMode); - } - setPanelFocus(id) { this.meta.focusPanelId = id; } updateSubmenuVisibility() { - if (this.editMode) { - this.meta.submenuEnabled = true; - return; - } + this.meta.submenuEnabled = (() => { + if (this.links.length > 0) { return true; } - var visibleVars = _.filter(this.templating.list, function(template) { - return template.hide !== 2; - }); + var visibleVars = _.filter(this.templating.list, variable => variable.hide !== 2); + if (visibleVars.length > 0) { return true; } - this.meta.submenuEnabled = visibleVars.length > 0 || this.annotations.list.length > 0 || this.links.length > 0; + var visibleAnnotations = _.filter(this.annotations.list, annotation => annotation.hide !== true); + if (visibleAnnotations.length > 0) { return true; } + + return false; + })(); } getPanelInfoById(panelId) { diff --git a/public/app/features/dashboard/specs/dashboard_model_specs.ts b/public/app/features/dashboard/specs/dashboard_model_specs.ts index c7d85b8a190..1c7415d342e 100644 --- a/public/app/features/dashboard/specs/dashboard_model_specs.ts +++ b/public/app/features/dashboard/specs/dashboard_model_specs.ts @@ -364,4 +364,85 @@ describe('DashboardModel', function() { }); }); + describe('updateSubmenuVisibility with empty lists', function() { + var model; + + beforeEach(function() { + model = new DashboardModel({}); + model.updateSubmenuVisibility(); + }); + + it('should not enable submmenu', function() { + expect(model.meta.submenuEnabled).to.be(false); + }); + }); + + describe('updateSubmenuVisibility with annotation', function() { + var model; + + beforeEach(function() { + model = new DashboardModel({ + annotations: { + list: [{}] + } + }); + model.updateSubmenuVisibility(); + }); + + it('should enable submmenu', function() { + expect(model.meta.submenuEnabled).to.be(true); + }); + }); + + describe('updateSubmenuVisibility with template var', function() { + var model; + + beforeEach(function() { + model = new DashboardModel({ + templating: { + list: [{}] + } + }); + model.updateSubmenuVisibility(); + }); + + it('should enable submmenu', function() { + expect(model.meta.submenuEnabled).to.be(true); + }); + }); + + describe('updateSubmenuVisibility with hidden template var', function() { + var model; + + beforeEach(function() { + model = new DashboardModel({ + templating: { + list: [{hide: 2}] + } + }); + model.updateSubmenuVisibility(); + }); + + it('should not enable submmenu', function() { + expect(model.meta.submenuEnabled).to.be(false); + }); + }); + + describe('updateSubmenuVisibility with hidden annotation toggle', function() { + var model; + + beforeEach(function() { + model = new DashboardModel({ + annotations: { + list: [{hide: true}] + } + }); + model.updateSubmenuVisibility(); + }); + + it('should not enable submmenu', function() { + expect(model.meta.submenuEnabled).to.be(false); + }); + }); + }); diff --git a/public/app/features/dashboard/submenu/submenu.html b/public/app/features/dashboard/submenu/submenu.html index 3e09fe4425e..367106f8a16 100644 --- a/public/app/features/dashboard/submenu/submenu.html +++ b/public/app/features/dashboard/submenu/submenu.html @@ -11,7 +11,7 @@
- diff --git a/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html b/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html index 1db00904c04..ad68312b727 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html +++ b/public/app/plugins/datasource/elasticsearch/partials/annotations.editor.html @@ -3,9 +3,11 @@ Index name
-
- Search query (lucene) Use [[filterName]] in query to replace part of the query with a filter value - +
+
+ +
@@ -33,4 +35,4 @@
- \ No newline at end of file + diff --git a/public/app/plugins/datasource/grafana/partials/annotations.editor.html b/public/app/plugins/datasource/grafana/partials/annotations.editor.html index 54e21ac902e..a1528a6d708 100644 --- a/public/app/plugins/datasource/grafana/partials/annotations.editor.html +++ b/public/app/plugins/datasource/grafana/partials/annotations.editor.html @@ -1,6 +1,5 @@
-
Filters
Type diff --git a/public/app/plugins/datasource/graphite/partials/annotations.editor.html b/public/app/plugins/datasource/graphite/partials/annotations.editor.html index 421f7c3f3e5..9d228b8e4f9 100644 --- a/public/app/plugins/datasource/graphite/partials/annotations.editor.html +++ b/public/app/plugins/datasource/graphite/partials/annotations.editor.html @@ -1,10 +1,13 @@
- Graphite metrics query - + Graphite query +
+ +
Or
+
- Or Graphite events query - + Graphite events tags +
diff --git a/public/app/plugins/datasource/influxdb/partials/annotations.editor.html b/public/app/plugins/datasource/influxdb/partials/annotations.editor.html index a32044ca4ee..da8f4edf881 100644 --- a/public/app/plugins/datasource/influxdb/partials/annotations.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/annotations.editor.html @@ -1,12 +1,11 @@ -
Query
-
Column mappings If your influxdb query returns more than one column you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.
+
Field mappings If your influxdb query returns more than one field you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.
From d2f3d7d138bde58708c61bbd2932b61847f94085 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 12 Apr 2017 16:06:48 +0300 Subject: [PATCH 07/37] graph(add annotation): get alerts for all panels --- public/app/features/annotations/annotations_srv.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/annotations/annotations_srv.ts b/public/app/features/annotations/annotations_srv.ts index d3b83982f51..380cc93ead9 100644 --- a/public/app/features/annotations/annotations_srv.ts +++ b/public/app/features/annotations/annotations_srv.ts @@ -53,7 +53,7 @@ export class AnnotationsSrv { var panel = options.panel; var dashboard = options.dashboard; - if (panel && panel.alert) { + if (panel) { return this.backendSrv.get('/api/annotations', { from: options.range.from.valueOf(), to: options.range.to.valueOf(), From 0156a94a491886ee64440092f6ea648ea0cb3244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Apr 2017 15:46:41 +0200 Subject: [PATCH 08/37] annotations: you can now read annoations via manually created annoation query --- pkg/api/annotations.go | 3 +++ pkg/api/api.go | 6 ++++-- pkg/api/dtos/annotations.go | 1 + pkg/services/annotations/annotations.go | 1 + public/app/features/annotations/annotations_srv.ts | 7 +++++++ public/app/features/dashboard/addAnnotationModalCtrl.ts | 1 - .../datasource/grafana/partials/annotations.editor.html | 2 +- public/app/plugins/panel/graph/graph.ts | 6 ++---- public/app/plugins/panel/graph/module.ts | 5 ++--- 9 files changed, 21 insertions(+), 11 deletions(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index af72b9d3876..88e8c955497 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -39,6 +39,7 @@ func GetAnnotations(c *middleware.Context) Response { Text: item.Text, Metric: item.Metric, Title: item.Title, + PanelId: item.PanelId, }) } @@ -55,6 +56,8 @@ func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response Epoch: cmd.Time / 1000, Title: cmd.Title, Text: cmd.Text, + CategoryId: cmd.CategoryId, + Type: annotations.EventType, } err := repo.Save(&item) diff --git a/pkg/api/api.go b/pkg/api/api.go index 0b9a8acf851..6dcc900c16f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -277,8 +277,10 @@ func (hs *HttpServer) registerRoutes() { }, reqEditorRole) r.Get("/annotations", wrap(GetAnnotations)) - r.Post("/annotations", bind(dtos.PostAnnotationsCmd{}), wrap(PostAnnotation)) - r.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), wrap(DeleteAnnotations)) + + r.Group("/annotations", func() { + r.Post("/", bind(dtos.PostAnnotationsCmd{}), wrap(PostAnnotation)) + }, reqEditorRole) // error test r.Get("/metrics/error", wrap(GenerateError)) diff --git a/pkg/api/dtos/annotations.go b/pkg/api/dtos/annotations.go index cd8c0c1d523..28e048db3ec 100644 --- a/pkg/api/dtos/annotations.go +++ b/pkg/api/dtos/annotations.go @@ -19,6 +19,7 @@ type Annotation struct { type PostAnnotationsCmd struct { DashboardId int64 `json:"dashboardId"` PanelId int64 `json:"panelId"` + CategoryId int64 `json:"categoryId"` Time int64 `json:"time"` Title string `json:"title"` Text string `json:"text"` diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index a308f546c8a..a3b4eacc0c3 100644 --- a/pkg/services/annotations/annotations.go +++ b/pkg/services/annotations/annotations.go @@ -49,6 +49,7 @@ type ItemType string const ( AlertType ItemType = "alert" + EventType ItemType = "event" ) type Item struct { diff --git a/public/app/features/annotations/annotations_srv.ts b/public/app/features/annotations/annotations_srv.ts index d3b83982f51..691153b197b 100644 --- a/public/app/features/annotations/annotations_srv.ts +++ b/public/app/features/annotations/annotations_srv.ts @@ -35,6 +35,13 @@ export class AnnotationsSrv { // combine the annotations and flatten results var annotations = _.flattenDeep([results[0], results[1]]); + // filter out annotations that do not belong to requesting panel + annotations = _.filter(annotations, item => { + if (item.panelId && options.panel.id !== item.panelId) { + return false; + } + return true; + }); // look for alert state for this panel var alertState = _.find(results[2], {panelId: options.panel.id}); diff --git a/public/app/features/dashboard/addAnnotationModalCtrl.ts b/public/app/features/dashboard/addAnnotationModalCtrl.ts index 7c3962074cf..789d24bdbee 100644 --- a/public/app/features/dashboard/addAnnotationModalCtrl.ts +++ b/public/app/features/dashboard/addAnnotationModalCtrl.ts @@ -61,7 +61,6 @@ export class AddAnnotationModalCtrl { } close() { - this.graphCtrl.inAddAnnotationMode = false; this.$scope.dismiss(); } } diff --git a/public/app/plugins/datasource/grafana/partials/annotations.editor.html b/public/app/plugins/datasource/grafana/partials/annotations.editor.html index a1528a6d708..24a06a2abd6 100644 --- a/public/app/plugins/datasource/grafana/partials/annotations.editor.html +++ b/public/app/plugins/datasource/grafana/partials/annotations.editor.html @@ -4,7 +4,7 @@
Type
-
diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 7ae1b19ca21..4d68e10bbc0 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -84,7 +84,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { let thisPanelEvent = event.panel.id === ctrl.panel.id; // Select time for new annotation - let createAnnotation = ctrl.inAddAnnotationMode || event.pos.ctrlKey || event.pos.metaKey; + let createAnnotation = event.pos.ctrlKey || event.pos.metaKey; if (createAnnotation && thisPanelEvent) { let timeRange = { from: event.pos.x, @@ -92,7 +92,6 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { }; ctrl.showAddAnnotationModal(timeRange); - ctrl.inAddAnnotationMode = false; } }, scope); @@ -656,12 +655,11 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { } elem.bind("plotselected", function (event, ranges) { - if (ctrl.inAddAnnotationMode || ranges.ctrlKey || ranges.metaKey) { + if (ranges.ctrlKey || ranges.metaKey) { // Create new annotation from time range let timeRange = ranges.xaxis; ctrl.showAddAnnotationModal(timeRange); plot.clearSelection(); - ctrl.inAddAnnotationMode = false; } else { scope.$apply(function() { timeSrv.setTime({ diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 5a24f39cf80..5686ef2bfe8 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -24,7 +24,6 @@ class GraphCtrl extends MetricsPanelCtrl { dataList: any = []; annotations: any = []; alertState: any; - inAddAnnotationMode = false; annotationsPromise: any; dataWarning: any; @@ -303,8 +302,8 @@ class GraphCtrl extends MetricsPanelCtrl { } enableAddAnnotationMode() { - // TODO: notify user about time selection mode - this.inAddAnnotationMode = true; + // placehoder for some other way to teach users + alert('selection region while holding down CTRL or CMD'); } // Get annotation info from dialog and push it to backend From b867921b3b533b847725463891d141278e89c49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Apr 2017 15:49:59 +0200 Subject: [PATCH 09/37] annotation: cleanup --- .../app/features/annotations/annotations_srv.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/public/app/features/annotations/annotations_srv.ts b/public/app/features/annotations/annotations_srv.ts index 691153b197b..86c984b78dd 100644 --- a/public/app/features/annotations/annotations_srv.ts +++ b/public/app/features/annotations/annotations_srv.ts @@ -134,20 +134,9 @@ export class AnnotationsSrv { } postAnnotation(annotations) { - console.log("POST /api/annotations\n", annotations); - - // Not implemented yet - let implemented = true; - if (implemented) { - return Promise.all(_.map(annotations, annotation => { - return this.backendSrv.post('/api/annotations', annotation); - })) - .catch(error => { - console.log(error); - }); - } else { - return Promise.resolve("Not implemented"); - } + return Promise.all(_.map(annotations, annotation => { + return this.backendSrv.post('/api/annotations', annotation); + })); } translateQueryResult(annotation, results) { From 593b2ef866e96bfaa8c2db568de234a2e38d70cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Apr 2017 16:26:34 +0200 Subject: [PATCH 10/37] annotation: added region support to annoations --- pkg/api/annotations.go | 22 ++++++++++++++++--- pkg/api/dtos/annotations.go | 5 +++++ pkg/services/annotations/annotations.go | 2 ++ pkg/services/sqlstore/annotation.go | 11 ++++++++++ .../sqlstore/migrations/annotation_mig.go | 4 ++++ 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 88e8c955497..a7783e4be88 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -40,6 +40,7 @@ func GetAnnotations(c *middleware.Context) Response { Metric: item.Metric, Title: item.Title, PanelId: item.PanelId, + RegionId: item.RegionId, }) } @@ -57,15 +58,30 @@ func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response Title: cmd.Title, Text: cmd.Text, CategoryId: cmd.CategoryId, + NewState: cmd.FillColor, Type: annotations.EventType, } - err := repo.Save(&item) - - if err != nil { + if err := repo.Save(&item); err != nil { return ApiError(500, "Failed to save annotation", err) } + // handle regions + if cmd.IsRegion { + item.RegionId = item.Id + + if err := repo.Update(&item); err != nil { + return ApiError(500, "Failed set regionId on annotation", err) + } + + item.Id = 0 + item.Epoch = cmd.EndTime + + if err := repo.Save(&item); err != nil { + return ApiError(500, "Failed save annotation for region end time", err) + } + } + return ApiSuccess("Annotation added") } diff --git a/pkg/api/dtos/annotations.go b/pkg/api/dtos/annotations.go index 28e048db3ec..bd9dd06c457 100644 --- a/pkg/api/dtos/annotations.go +++ b/pkg/api/dtos/annotations.go @@ -12,6 +12,7 @@ type Annotation struct { Title string `json:"title"` Text string `json:"text"` Metric string `json:"metric"` + RegionId int64 `json:"regionId"` Data *simplejson.Json `json:"data"` } @@ -23,6 +24,10 @@ type PostAnnotationsCmd struct { Time int64 `json:"time"` Title string `json:"title"` Text string `json:"text"` + + FillColor string `json:"fillColor"` + IsRegion bool `json:"isRegion"` + EndTime int64 `json:"endTime"` } type DeleteAnnotationsCmd struct { diff --git a/pkg/services/annotations/annotations.go b/pkg/services/annotations/annotations.go index a3b4eacc0c3..be9d3f2d4d0 100644 --- a/pkg/services/annotations/annotations.go +++ b/pkg/services/annotations/annotations.go @@ -4,6 +4,7 @@ import "github.com/grafana/grafana/pkg/components/simplejson" type Repository interface { Save(item *Item) error + Update(item *Item) error Find(query *ItemQuery) ([]*Item, error) Delete(params *DeleteParams) error } @@ -58,6 +59,7 @@ type Item struct { DashboardId int64 `json:"dashboardId"` PanelId int64 `json:"panelId"` CategoryId int64 `json:"categoryId"` + RegionId int64 `json:"regionId"` Type ItemType `json:"type"` Title string `json:"title"` Text string `json:"text"` diff --git a/pkg/services/sqlstore/annotation.go b/pkg/services/sqlstore/annotation.go index e219f48d2fe..62a10ee2106 100644 --- a/pkg/services/sqlstore/annotation.go +++ b/pkg/services/sqlstore/annotation.go @@ -23,6 +23,17 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error { }) } +func (r *SqlAnnotationRepo) Update(item *annotations.Item) error { + return inTransaction(func(sess *xorm.Session) error { + + if _, err := sess.Table("annotation").Id(item.Id).Update(item); err != nil { + return err + } + + return nil + }) +} + func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.Item, error) { var sql bytes.Buffer params := make([]interface{}, 0) diff --git a/pkg/services/sqlstore/migrations/annotation_mig.go b/pkg/services/sqlstore/migrations/annotation_mig.go index 4a7206f9d64..aeb2afed4fb 100644 --- a/pkg/services/sqlstore/migrations/annotation_mig.go +++ b/pkg/services/sqlstore/migrations/annotation_mig.go @@ -54,4 +54,8 @@ func addAnnotationMig(mg *Migrator) { {Name: "new_state", Type: DB_NVarchar, Length: 25, Nullable: false}, {Name: "data", Type: DB_Text, Nullable: false}, })) + + mg.AddMigration("Add column region_id to annotation table", NewAddColumnMigration(table, &Column{ + Name: "region_id", Type: DB_BigInt, Nullable: true, Default: "0", + })) } From de21be30d2526861c80c57ba34ce81d9bd9a3e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Apr 2017 17:28:32 +0200 Subject: [PATCH 11/37] ux: working on how to show form in popover --- public/app/core/components/switch.ts | 2 +- public/app/features/dashboard/all.js | 1 + public/app/features/dashboard/event_editor.ts | 22 +++++++++++++++ .../dashboard/partials/event_editor.html | 27 +++++++++++++++++++ public/app/plugins/panel/graph/graph.ts | 19 ++++++++++--- 5 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 public/app/features/dashboard/event_editor.ts create mode 100644 public/app/features/dashboard/partials/event_editor.html diff --git a/public/app/core/components/switch.ts b/public/app/core/components/switch.ts index 2a64ec487f7..889398d5138 100644 --- a/public/app/core/components/switch.ts +++ b/public/app/core/components/switch.ts @@ -9,7 +9,7 @@ import Drop from 'tether-drop'; var template = ` diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index c3a71a11818..5a3d6ea5203 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -22,4 +22,5 @@ define([ './ad_hoc_filters', './row/row_ctrl', './repeat_option/repeat_option', + './event_editor', ], function () {}); diff --git a/public/app/features/dashboard/event_editor.ts b/public/app/features/dashboard/event_editor.ts new file mode 100644 index 00000000000..35606a5ac73 --- /dev/null +++ b/public/app/features/dashboard/event_editor.ts @@ -0,0 +1,22 @@ +/// + +import _ from 'lodash'; +import coreModule from 'app/core/core_module'; + +export class EventEditorCtrl { + /** @ngInject */ + constructor() { + } +} + +export function eventEditor() { + return { + restrict: 'E', + controller: EventEditorCtrl, + bindToController: true, + controllerAs: 'ctrl', + templateUrl: 'public/app/features/dashboard/partials/event_editor.html', + }; +} + +coreModule.directive('eventEditor', eventEditor); diff --git a/public/app/features/dashboard/partials/event_editor.html b/public/app/features/dashboard/partials/event_editor.html new file mode 100644 index 00000000000..3aaa9d6855f --- /dev/null +++ b/public/app/features/dashboard/partials/event_editor.html @@ -0,0 +1,27 @@ +
+
Add annotation event
+ +
+ Title + +
+
+ Time + +
+
+ To + +
+
+ Description + +
+
+ +
+ +
diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 4d68e10bbc0..a0e18dae597 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -19,7 +19,7 @@ import GraphTooltip from './graph_tooltip'; import {ThresholdManager} from './threshold_manager'; import {convertValuesToHistogram, getSeriesValues} from './histogram'; -coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { +coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { return { restrict: 'A', template: '', @@ -91,10 +91,23 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { to: null }; - ctrl.showAddAnnotationModal(timeRange); + showAddAnnotationView(timeRange); } }, scope); + function showAddAnnotationView(timeRange) { + popoverSrv.show({ + element: elem[0], + position: 'bottom center', + openOn: 'click', + template: '', + model: { + timeRange: timeRange, + panelCtrl: ctrl, + }, + }); + } + function getLegendHeight(panelHeight) { if (!panel.legend.show || panel.legend.rightSide) { return 0; @@ -658,7 +671,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { if (ranges.ctrlKey || ranges.metaKey) { // Create new annotation from time range let timeRange = ranges.xaxis; - ctrl.showAddAnnotationModal(timeRange); + showAddAnnotationView(timeRange); plot.clearSelection(); } else { scope.$apply(function() { From 8a1c35e1c233562ea595d8c8a226afb3ee758e33 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 12 Apr 2017 20:27:32 +0300 Subject: [PATCH 12/37] graph(create annotation): refactor, fix two modal after range selection bind create annotation handler directly to plotclick event, not to global graph-click --- public/app/plugins/panel/graph/graph.ts | 30 ++++++++++++------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 4d68e10bbc0..88a0f545cc9 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -79,22 +79,6 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { } }, scope); - appEvents.on('graph-click', (event) => { - // Add event only for selected panel - let thisPanelEvent = event.panel.id === ctrl.panel.id; - - // Select time for new annotation - let createAnnotation = event.pos.ctrlKey || event.pos.metaKey; - if (createAnnotation && thisPanelEvent) { - let timeRange = { - from: event.pos.x, - to: null - }; - - ctrl.showAddAnnotationModal(timeRange); - } - }, scope); - function getLegendHeight(panelHeight) { if (!panel.legend.show || panel.legend.rightSide) { return 0; @@ -670,6 +654,20 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv) { } }); + elem.bind("plotclick", function (event, pos, item) { + // Skip if range selected (added in "plotselected" event handler) + let isRangeSelection = pos.x !== pos.x1; + let createAnnotation = !isRangeSelection && (pos.ctrlKey || pos.metaKey); + if (createAnnotation) { + let timeRange = { + from: pos.x, + to: null + }; + + ctrl.showAddAnnotationModal(timeRange); + } + }); + scope.$on('$destroy', function() { tooltip.destroy(); elem.off(); From ef99ff0ad737a8b1c9d493f6307039cf5e3e7455 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 12 Apr 2017 20:37:33 +0300 Subject: [PATCH 13/37] graph(create annotation): use single description for range --- .../dashboard/partials/addAnnotationModal.html | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/public/app/features/dashboard/partials/addAnnotationModal.html b/public/app/features/dashboard/partials/addAnnotationModal.html index 1ea3b7d7a6f..49832075a17 100644 --- a/public/app/features/dashboard/partials/addAnnotationModal.html +++ b/public/app/features/dashboard/partials/addAnnotationModal.html @@ -42,8 +42,7 @@
-
Description
-
Description Start
+
Description
- -
-
-
Description Stop
-
- -
-
diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 5686ef2bfe8..fac9971ece3 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -307,8 +307,8 @@ class GraphCtrl extends MetricsPanelCtrl { } // Get annotation info from dialog and push it to backend - pushAnnotations(annotations) { - return this.annotationsSrv.postAnnotation(annotations); + pushAnnotation(annotation) { + return this.annotationsSrv.postAnnotation(annotation); } showAddAnnotationModal(timeRange) { From 2142323da9fbd623543aa461c357538b269ce86e Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 12 Apr 2017 21:07:39 +0300 Subject: [PATCH 15/37] graph(create annotation): refactor, AddAnnotationModalCtrl --- .../features/dashboard/addAnnotationModalCtrl.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/public/app/features/dashboard/addAnnotationModalCtrl.ts b/public/app/features/dashboard/addAnnotationModalCtrl.ts index 00fa9d8887b..f691da1c0d0 100644 --- a/public/app/features/dashboard/addAnnotationModalCtrl.ts +++ b/public/app/features/dashboard/addAnnotationModalCtrl.ts @@ -4,12 +4,7 @@ import angular from 'angular'; import moment from 'moment'; export class AddAnnotationModalCtrl { - annotationTimeFormat = 'YYYY-MM-DD HH:mm:ss'; - annotationTimeFrom: any; - annotationTimeTo: any = null; - annotationTitle: string; - annotationTextFrom: string; - annotationTextTo: string; + timeFormat = 'YYYY-MM-DD HH:mm:ss'; annotation: any; graphCtrl: any; @@ -29,21 +24,20 @@ export class AddAnnotationModalCtrl { text: "" }; - this.annotation.time = moment($scope.annotationTimeRange.from).format(this.annotationTimeFormat); + this.annotation.time = moment($scope.annotationTimeRange.from).format(this.timeFormat); if ($scope.annotationTimeRange.to) { - this.annotation.timeTo = moment($scope.annotationTimeRange.to).format(this.annotationTimeFormat); + this.annotation.timeTo = moment($scope.annotationTimeRange.to).format(this.timeFormat); } } addAnnotation() { - this.annotation.time = moment(this.annotation.time, this.annotationTimeFormat).valueOf(); + this.annotation.time = moment(this.annotation.time, this.timeFormat).valueOf(); if (this.annotation.timeTo) { - this.annotation.timeTo = moment(this.annotation.timeTo, this.annotationTimeFormat).valueOf(); + this.annotation.timeTo = moment(this.annotation.timeTo, this.timeFormat).valueOf(); } this.graphCtrl.pushAnnotation(this.annotation) .then(response => { - console.log(response); this.close(); }) .catch(error => { From ab99a7c1c7083ea7ad376a2fdd01538c43c3fe89 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 13 Apr 2017 16:57:22 +0300 Subject: [PATCH 16/37] graph(create annotation): add initial annotation_category table --- .../migrations/annotation_category_mig.go | 26 +++++++++++++++++++ .../sqlstore/migrations/migrations.go | 1 + 2 files changed, 27 insertions(+) create mode 100644 pkg/services/sqlstore/migrations/annotation_category_mig.go diff --git a/pkg/services/sqlstore/migrations/annotation_category_mig.go b/pkg/services/sqlstore/migrations/annotation_category_mig.go new file mode 100644 index 00000000000..331aeea2500 --- /dev/null +++ b/pkg/services/sqlstore/migrations/annotation_category_mig.go @@ -0,0 +1,26 @@ +package migrations + +import ( + . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" +) + +func addAnnotationCategoryMig(mg *Migrator) { + category := Table{ + Name: "annotation_category", + Columns: []*Column{ + {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, + {Name: "org_id", Type: DB_BigInt, Nullable: false}, + {Name: "user_id", Type: DB_BigInt, Nullable: true}, + {Name: "name", Type: DB_Text, Nullable: false}, + }, + Indices: []*Index{ + {Cols: []string{"org_id", "name"}, Type: IndexType}, + }, + } + + // create table + mg.AddMigration("create annotation_category table", NewAddTableMigration(category)) + + // create indices + mg.AddMigration("add index org_id & name", NewAddIndexMigration(category, category.Indices[0])) +} diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 163c6d762a8..e9e20fb190c 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -26,6 +26,7 @@ func AddMigrations(mg *Migrator) { addAnnotationMig(mg) addStatsMigrations(mg) addTestDataMigrations(mg) + // addAnnotationCategoryMig(mg) } func addMigrationLogMigrations(mg *Migrator) { From 2f61fc6afe7c42f3058023d09534db225484b8d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Apr 2017 18:39:49 +0200 Subject: [PATCH 17/37] ux: made progress on popover forms --- public/app/core/services/popover_srv.ts | 9 +++- .../dashboard/partials/event_editor.html | 48 +++++++++---------- public/app/plugins/panel/graph/graph.ts | 3 +- public/sass/_variables.dark.scss | 2 +- public/sass/components/_drop.scss | 22 +++++---- public/sass/mixins/_drop_element.scss | 34 ++++++------- 6 files changed, 65 insertions(+), 53 deletions(-) diff --git a/public/app/core/services/popover_srv.ts b/public/app/core/services/popover_srv.ts index 73249a67b5b..a6fb7c10655 100644 --- a/public/app/core/services/popover_srv.ts +++ b/public/app/core/services/popover_srv.ts @@ -10,9 +10,14 @@ import Drop from 'tether-drop'; function popoverSrv($compile, $rootScope) { this.show = function(options) { + var classNames = 'drop-popover'; var popoverScope = _.extend($rootScope.$new(true), options.model); var drop; + if (options.classNames) { + classNames = options.classNames; + } + function destroyDrop() { setTimeout(function() { if (drop.tether) { @@ -35,11 +40,11 @@ function popoverSrv($compile, $rootScope) { target: options.element, content: contentElement, position: options.position, - classes: 'drop-popover', + classes: classNames, openOn: options.openOn || 'hover', hoverCloseDelay: 200, tetherOptions: { - constraints: [{to: 'window', pin: true, attachment: "both"}] + constraints: [{to: 'scrollParent', attachment: "none both"}] } }); diff --git a/public/app/features/dashboard/partials/event_editor.html b/public/app/features/dashboard/partials/event_editor.html index 3aaa9d6855f..9d8d84bbee1 100644 --- a/public/app/features/dashboard/partials/event_editor.html +++ b/public/app/features/dashboard/partials/event_editor.html @@ -1,27 +1,27 @@ -
-
Add annotation event
-
- Title - -
-
- Time - -
-
- To - -
-
- Description - +
Create event
+ +
+
+
+ Title + +
+
+ Time + +
+
+ To + +
+
+ Description + +
+ +
+ +
- -
- -
diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index a0e18dae597..8eaf9d00b83 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -98,6 +98,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { function showAddAnnotationView(timeRange) { popoverSrv.show({ element: elem[0], + classNames: 'drop-popover drop-popover--form', position: 'bottom center', openOn: 'click', template: '', @@ -672,7 +673,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { // Create new annotation from time range let timeRange = ranges.xaxis; showAddAnnotationView(timeRange); - plot.clearSelection(); + //plot.clearSelection(); } else { scope.$apply(function() { timeSrv.setTime({ diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 8ecad9e3287..9539da9ad8f 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -251,7 +251,7 @@ $infoText: $blue-dark; $infoBackground: $blue-dark; // popover -$popover-bg: $dark-4; +$popover-bg: $panel-bg; $popover-color: $text-color; $popover-help-bg: $btn-secondary-bg; diff --git a/public/sass/components/_drop.scss b/public/sass/components/_drop.scss index 6f3aff23cef..cd42d1b6fa2 100644 --- a/public/sass/components/_drop.scss +++ b/public/sass/components/_drop.scss @@ -1,11 +1,18 @@ $popover-arrow-size: 0.7rem; $color: inherit; -$backgroundColor: $btn-secondary-bg; $color: $text-color; $useDropShadow: false; $attachmentOffset: 0%; $easing: cubic-bezier(0, 0, 0.265, 1.00); +@include drop-theme("help", $popover-help-bg, $popover-help-color); +@include drop-theme("error", $errorBackground, $popover-color); +@include drop-theme("popover", $popover-bg, $popover-color, $brand-primary); + +@include drop-animation-scale("drop", "help", $attachmentOffset: $attachmentOffset, $easing: $easing); +@include drop-animation-scale("drop", "error", $attachmentOffset: $attachmentOffset, $easing: $easing); +@include drop-animation-scale("drop", "popover", $attachmentOffset: $attachmentOffset, $easing: $easing); + .drop-element { z-index: 10000; position: absolute; @@ -44,11 +51,8 @@ $easing: cubic-bezier(0, 0, 0.265, 1.00); } } -@include drop-theme("help", $popover-help-bg, $popover-help-color); -@include drop-theme("error", $errorBackground, $popover-color); -@include drop-theme("popover", $popover-bg, $popover-color); - -@include drop-animation-scale("drop", "help", $attachmentOffset: $attachmentOffset, $easing: $easing); -@include drop-animation-scale("drop", "error", $attachmentOffset: $attachmentOffset, $easing: $easing); -@include drop-animation-scale("drop", "popover", $attachmentOffset: $attachmentOffset, $easing: $easing); - +.drop-element.drop-popover--form { + .drop-content { + max-width: none; + } +} diff --git a/public/sass/mixins/_drop_element.scss b/public/sass/mixins/_drop_element.scss index 0f7eda19efe..d73837e0b59 100644 --- a/public/sass/mixins/_drop_element.scss +++ b/public/sass/mixins/_drop_element.scss @@ -1,5 +1,5 @@ -@mixin drop-theme($themeName, $theme-bg, $theme-color) { +@mixin drop-theme($themeName, $theme-bg, $theme-color, $border-color: $theme-color) { .drop-element.drop-#{$themeName} { max-width: 100%; max-height: 100%; @@ -14,6 +14,8 @@ font-size: $font-size-sm; word-wrap: break-word; max-width: 20rem; + border: 1px solid $border-color; + box-shadow: 0 0 10px #f86e06; &:before { content: ""; @@ -43,7 +45,7 @@ top: 100%; left: 50%; margin-left: - $popover-arrow-size; - border-top-color: $theme-bg; + border-top-color: $border-color; } } @@ -54,7 +56,7 @@ bottom: 100%; left: 50%; margin-left: - $popover-arrow-size; - border-bottom-color: $theme-bg; + border-bottom-color: $border-color; } } @@ -65,7 +67,7 @@ left: 100%; top: 50%; margin-top: - $popover-arrow-size; - border-left-color: $theme-bg; + border-left-color: $border-color; } } @@ -76,7 +78,7 @@ right: 100%; top: 50%; margin-top: - $popover-arrow-size; - border-right-color: $theme-bg; + border-right-color: $border-color; } } @@ -95,7 +97,7 @@ &:before { bottom: 100%; left: $popover-arrow-size; - border-bottom-color: $theme-bg; + border-bottom-color: $border-color; } } @@ -105,7 +107,7 @@ &:before { bottom: 100%; right: $popover-arrow-size; - border-bottom-color: $theme-bg; + border-bottom-color: $border-color; } } @@ -115,7 +117,7 @@ &:before { top: 100%; left: $popover-arrow-size; - border-top-color: $theme-bg; + border-top-color: $border-color; } } @@ -125,7 +127,7 @@ &:before { top: 100%; right: $popover-arrow-size; - border-top-color: $theme-bg; + border-top-color: $border-color; } } @@ -136,7 +138,7 @@ &:before { bottom: 100%; left: $popover-arrow-size; - border-bottom-color: $theme-bg; + border-bottom-color: $border-color; } } @@ -146,7 +148,7 @@ &:before { bottom: 100%; right: $popover-arrow-size; - border-bottom-color: $theme-bg; + border-bottom-color: $border-color; } } @@ -166,7 +168,7 @@ &:before { top: 100%; right: $popover-arrow-size; - border-top-color: $theme-bg; + border-top-color: $border-color; } } @@ -177,7 +179,7 @@ &:before { top: $popover-arrow-size; left: 100%; - border-left-color: $theme-bg; + border-left-color: $border-color; } } @@ -187,7 +189,7 @@ &:before { top: $popover-arrow-size; right: 100%; - border-right-color: $theme-bg; + border-right-color: $border-color; } } @@ -197,7 +199,7 @@ &:before { bottom: $popover-arrow-size; left: 100%; - border-left-color: $theme-bg; + border-left-color: $border-color; } } @@ -207,7 +209,7 @@ &:before { bottom: $popover-arrow-size; right: 100%; - border-right-color: $theme-bg; + border-right-color: $border-color; } } } From c68fffcd6d984ee97b5448ee0d7100077b744479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Apr 2017 20:28:13 +0200 Subject: [PATCH 18/37] ux: popover forms --- public/app/plugins/datasource/cloudwatch/partials/config.html | 2 +- public/sass/_variables.dark.scss | 4 ++-- public/sass/components/_drop.scss | 4 ++-- public/sass/mixins/_drop_element.scss | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/partials/config.html b/public/app/plugins/datasource/cloudwatch/partials/config.html index c4b05f6acf9..2b06d2de035 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/config.html +++ b/public/app/plugins/datasource/cloudwatch/partials/config.html @@ -46,7 +46,7 @@
- + Namespaces of Custom Metrics diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 9539da9ad8f..c4cc463d3ce 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -251,8 +251,8 @@ $infoText: $blue-dark; $infoBackground: $blue-dark; // popover -$popover-bg: $panel-bg; -$popover-color: $text-color; +$popover-bg: $panel-bg; +$popover-color: $text-color; $popover-help-bg: $btn-secondary-bg; $popover-help-color: $text-color; diff --git a/public/sass/components/_drop.scss b/public/sass/components/_drop.scss index cd42d1b6fa2..7349a8c7c5d 100644 --- a/public/sass/components/_drop.scss +++ b/public/sass/components/_drop.scss @@ -5,9 +5,9 @@ $useDropShadow: false; $attachmentOffset: 0%; $easing: cubic-bezier(0, 0, 0.265, 1.00); -@include drop-theme("help", $popover-help-bg, $popover-help-color); @include drop-theme("error", $errorBackground, $popover-color); -@include drop-theme("popover", $popover-bg, $popover-color, $brand-primary); +@include drop-theme("popover", $popover-bg, $popover-color, #b3460d); +@include drop-theme("help", $popover-bg, $popover-color, #b3460d); @include drop-animation-scale("drop", "help", $attachmentOffset: $attachmentOffset, $easing: $easing); @include drop-animation-scale("drop", "error", $attachmentOffset: $attachmentOffset, $easing: $easing); diff --git a/public/sass/mixins/_drop_element.scss b/public/sass/mixins/_drop_element.scss index d73837e0b59..f0adf6aae96 100644 --- a/public/sass/mixins/_drop_element.scss +++ b/public/sass/mixins/_drop_element.scss @@ -15,7 +15,7 @@ word-wrap: break-word; max-width: 20rem; border: 1px solid $border-color; - box-shadow: 0 0 10px #f86e06; + box-shadow: 0 0 15px #5d3000; &:before { content: ""; @@ -158,7 +158,7 @@ &:before { top: 100%; left: $popover-arrow-size; - border-top-color: $theme-bg; + border-top-color: $border-color; } } From dbe5480edcd2b6d966d82af167d26ac6b1fb4b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Apr 2017 11:41:02 +0200 Subject: [PATCH 19/37] create annotations work --- pkg/api/annotations.go | 2 +- pkg/api/dtos/annotations.go | 2 +- public/app/core/services/popover_srv.ts | 51 +++++++++-------- .../features/annotations/annotations_srv.ts | 5 +- .../app/features/annotations/event_editor.ts | 29 +++++++--- .../annotations/partials/event_editor.html | 51 +++++++++-------- .../dashboard/addAnnotationModalCtrl.ts | 56 ------------------- public/app/plugins/panel/graph/graph.ts | 4 +- public/app/plugins/panel/graph/legend.js | 1 + public/sass/components/_modals.scss | 1 - 10 files changed, 82 insertions(+), 120 deletions(-) delete mode 100644 public/app/features/dashboard/addAnnotationModalCtrl.ts diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index a7783e4be88..a5211cfbec2 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -75,7 +75,7 @@ func PostAnnotation(c *middleware.Context, cmd dtos.PostAnnotationsCmd) Response } item.Id = 0 - item.Epoch = cmd.EndTime + item.Epoch = cmd.TimeEnd if err := repo.Save(&item); err != nil { return ApiError(500, "Failed save annotation for region end time", err) diff --git a/pkg/api/dtos/annotations.go b/pkg/api/dtos/annotations.go index bd9dd06c457..7bf33618261 100644 --- a/pkg/api/dtos/annotations.go +++ b/pkg/api/dtos/annotations.go @@ -27,7 +27,7 @@ type PostAnnotationsCmd struct { FillColor string `json:"fillColor"` IsRegion bool `json:"isRegion"` - EndTime int64 `json:"endTime"` + TimeEnd int64 `json:"timeEnd"` } type DeleteAnnotationsCmd struct { diff --git a/public/app/core/services/popover_srv.ts b/public/app/core/services/popover_srv.ts index a6fb7c10655..43afde31849 100644 --- a/public/app/core/services/popover_srv.ts +++ b/public/app/core/services/popover_srv.ts @@ -7,41 +7,49 @@ import coreModule from 'app/core/core_module'; import Drop from 'tether-drop'; /** @ngInject **/ -function popoverSrv($compile, $rootScope) { +function popoverSrv($compile, $rootScope, $timeout) { + let openDrop = null; + + this.close = function() { + if (openDrop) { + openDrop.close(); + } + }; this.show = function(options) { - var classNames = 'drop-popover'; - var popoverScope = _.extend($rootScope.$new(true), options.model); + if (openDrop) { + openDrop.close(); + } + + var scope = _.extend($rootScope.$new(true), options.model); var drop; - if (options.classNames) { - classNames = options.classNames; - } + var cleanUp = () => { + setTimeout(() => { + scope.$destroy(); + drop.destroy(); - function destroyDrop() { - setTimeout(function() { - if (drop.tether) { - drop.destroy(); + if (options.onClose) { + options.onClose(); } }); - } + }; - popoverScope.dismiss = function() { - popoverScope.$destroy(); - destroyDrop(); + scope.dismiss = () => { + drop.close(); }; var contentElement = document.createElement('div'); contentElement.innerHTML = options.template; - $compile(contentElement)(popoverScope); + $compile(contentElement)(scope); drop = new Drop({ target: options.element, content: contentElement, position: options.position, - classes: classNames, - openOn: options.openOn || 'hover', + classes: options.classNames || 'drop-popover', + openOn: options.openOn, hoverCloseDelay: 200, tetherOptions: { constraints: [{to: 'scrollParent', attachment: "none both"}] @@ -49,14 +57,11 @@ function popoverSrv($compile, $rootScope) { }); drop.on('close', () => { - popoverScope.dismiss({fromDropClose: true}); - destroyDrop(); - if (options.onClose) { - options.onClose(); - } + cleanUp(); }); - setTimeout(() => { drop.open(); }, 10); + openDrop = drop; + $timeout(() => { drop.open(); }, 10); }; } diff --git a/public/app/features/annotations/annotations_srv.ts b/public/app/features/annotations/annotations_srv.ts index 6ba70ea92f8..e9cdc0c20ec 100644 --- a/public/app/features/annotations/annotations_srv.ts +++ b/public/app/features/annotations/annotations_srv.ts @@ -35,6 +35,7 @@ export class AnnotationsSrv { // combine the annotations and flatten results var annotations = _.flattenDeep([results[0], results[1]]); + // filter out annotations that do not belong to requesting panel annotations = _.filter(annotations, item => { if (item.panelId && options.panel.id !== item.panelId) { @@ -60,7 +61,7 @@ export class AnnotationsSrv { var panel = options.panel; var dashboard = options.dashboard; - if (panel) { + if (panel && panel.alert) { return this.backendSrv.get('/api/annotations', { from: options.range.from.valueOf(), to: options.range.to.valueOf(), @@ -133,7 +134,7 @@ export class AnnotationsSrv { return this.globalAnnotationsPromise; } - postAnnotation(annotation) { + saveAnnotationEvent(annotation) { return this.backendSrv.post('/api/annotations', annotation); } diff --git a/public/app/features/annotations/event_editor.ts b/public/app/features/annotations/event_editor.ts index 83aaf595923..46e29a660a9 100644 --- a/public/app/features/annotations/event_editor.ts +++ b/public/app/features/annotations/event_editor.ts @@ -5,11 +5,11 @@ import moment from 'moment'; import coreModule from 'app/core/core_module'; import {MetricsPanelCtrl} from 'app/plugins/sdk'; -export class AnnotationItem { +export class AnnotationEvent { dashboardId: number; panelId: number; - time: Date; - timeEnd: Date; + time: any; + timeEnd: any; isRegion: boolean; title: string; text: string; @@ -17,14 +17,13 @@ export class AnnotationItem { export class EventEditorCtrl { panelCtrl: MetricsPanelCtrl; - timeFormat = 'YYYY-MM-DD HH:mm:ss'; - annotation: AnnotationItem; + annotation: AnnotationEvent; timeRange: {from: number, to: number}; form: any; /** @ngInject **/ - constructor() { - this.annotation = new AnnotationItem(); + constructor(private annotationsSrv) { + this.annotation = new AnnotationEvent(); this.annotation.panelId = this.panelCtrl.panel.id; this.annotation.dashboardId = this.panelCtrl.dashboard.id; this.annotation.text = "hello"; @@ -40,6 +39,19 @@ export class EventEditorCtrl { if (!this.form.$valid) { return; } + + let saveModel = _.cloneDeep(this.annotation); + saveModel.time = saveModel.time.valueOf(); + if (saveModel.isRegion) { + saveModel.timeEnd = saveModel.timeEnd.valueOf(); + } + + if (saveModel.timeEnd < saveModel.time) { + console.log('invalid time'); + return; + } + + this.annotationsSrv.saveAnnotationEvent(saveModel); } } @@ -52,7 +64,8 @@ export function eventEditor() { templateUrl: 'public/app/features/annotations/partials/event_editor.html', scope: { "panelCtrl": "=", - "timeRange": "=" + "timeRange": "=", + "cancel": "&", } }; } diff --git a/public/app/features/annotations/partials/event_editor.html b/public/app/features/annotations/partials/event_editor.html index a99e1374e82..c2a75e92603 100644 --- a/public/app/features/annotations/partials/event_editor.html +++ b/public/app/features/annotations/partials/event_editor.html @@ -9,31 +9,30 @@
-
-
- Time - -
-
-
- -
-
- Start - -
-
- End - -
-
-
- Description - -
+
+ Time + +
+
+ +
+
+ Start + +
+
+ End + +
+
+
+ Description + +
-
- -
-
+
+ + Cancel +
+
diff --git a/public/app/features/dashboard/addAnnotationModalCtrl.ts b/public/app/features/dashboard/addAnnotationModalCtrl.ts deleted file mode 100644 index 02b6462c0ed..00000000000 --- a/public/app/features/dashboard/addAnnotationModalCtrl.ts +++ /dev/null @@ -1,56 +0,0 @@ -/// - -import angular from 'angular'; -import moment from 'moment'; - -export class AddAnnotationModalCtrl { - timeFormat = 'YYYY-MM-DD HH:mm:ss'; - annotation: any; - graphCtrl: any; - - /** @ngInject */ - constructor(private $scope) { - this.graphCtrl = $scope.ctrl; - $scope.ctrl = this; - - let dashboardId = this.graphCtrl.dashboard.id; - let panelId = this.graphCtrl.panel.id; - this.annotation = { - dashboardId: dashboardId, - panelId: panelId, - time: null, - timeTo: null, - title: "", - text: "" - }; - - this.annotation.time = moment($scope.annotationTimeRange.from).format(this.timeFormat);0 - if ($scope.annotationTimeRange.to) { - this.annotation.timeTo = moment($scope.annotationTimeRange.to).format(this.timeFormat); - } - } - - addAnnotation() { - this.annotation.time = moment(this.annotation.time, this.timeFormat).valueOf(); - if (this.annotation.timeTo) { - this.annotation.timeTo = moment(this.annotation.timeTo, this.timeFormat).valueOf(); - } - - this.graphCtrl.pushAnnotation(this.annotation) - .then(response => { - this.close(); - }) - .catch(error => { - console.log(error); - this.close(); - }); - } - - close() { - this.$scope.dismiss(); - } -} - -angular - .module('grafana.controllers') - .controller('AddAnnotationModalCtrl', AddAnnotationModalCtrl); diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 15de6cf3abc..668f1973b42 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -84,8 +84,8 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { element: elem[0], classNames: 'drop-popover drop-popover--form', position: 'bottom center', - openOn: 'click', - template: '', + openOn: null, + template: '', model: { timeRange: timeRange, panelCtrl: ctrl, diff --git a/public/app/plugins/panel/graph/legend.js b/public/app/plugins/panel/graph/legend.js index 2cdb1821793..c29e8949f8e 100644 --- a/public/app/plugins/panel/graph/legend.js +++ b/public/app/plugins/panel/graph/legend.js @@ -48,6 +48,7 @@ function (angular, _, $) { element: el[0], position: 'bottom center', template: '', + openOn: 'hover', model: { series: series, toggleAxis: function() { diff --git a/public/sass/components/_modals.scss b/public/sass/components/_modals.scss index 76539c0035a..78293ef801a 100644 --- a/public/sass/components/_modals.scss +++ b/public/sass/components/_modals.scss @@ -67,7 +67,6 @@ .modal-content { padding: $spacer*2; - min-height: $spacer*15; } // Remove bottom margin if need be From ff426ae9a37d31d16292f3c77619be4a0fc3593b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Apr 2017 12:23:32 +0200 Subject: [PATCH 20/37] more work on annotations --- public/app/core/services/popover_srv.ts | 42 +++++++++++-------- .../features/annotations/annotations_srv.ts | 1 + .../app/features/annotations/event_editor.ts | 11 +++-- .../annotations/partials/event_editor.html | 2 +- public/app/features/dashboard/all.js | 1 - public/app/plugins/panel/graph/graph.ts | 4 +- 6 files changed, 36 insertions(+), 25 deletions(-) diff --git a/public/app/core/services/popover_srv.ts b/public/app/core/services/popover_srv.ts index 43afde31849..9cd13dfc280 100644 --- a/public/app/core/services/popover_srv.ts +++ b/public/app/core/services/popover_srv.ts @@ -19,6 +19,7 @@ function popoverSrv($compile, $rootScope, $timeout) { this.show = function(options) { if (openDrop) { openDrop.close(); + openDrop = null; } var scope = _.extend($rootScope.$new(true), options.model); @@ -27,12 +28,17 @@ function popoverSrv($compile, $rootScope, $timeout) { var cleanUp = () => { setTimeout(() => { scope.$destroy(); - drop.destroy(); + + if (drop.tether) { + drop.destroy(); + } if (options.onClose) { options.onClose(); } }); + + openDrop = null; }; scope.dismiss = () => { @@ -44,24 +50,26 @@ function popoverSrv($compile, $rootScope, $timeout) { $compile(contentElement)(scope); - drop = new Drop({ - target: options.element, - content: contentElement, - position: options.position, - classes: options.classNames || 'drop-popover', - openOn: options.openOn, - hoverCloseDelay: 200, - tetherOptions: { - constraints: [{to: 'scrollParent', attachment: "none both"}] - } - }); + $timeout(() => { + drop = new Drop({ + target: options.element, + content: contentElement, + position: options.position, + classes: options.classNames || 'drop-popover', + openOn: options.openOn, + hoverCloseDelay: 200, + tetherOptions: { + constraints: [{to: 'scrollParent', attachment: "none both"}] + } + }); - drop.on('close', () => { - cleanUp(); - }); + drop.on('close', () => { + cleanUp(); + }); - openDrop = drop; - $timeout(() => { drop.open(); }, 10); + openDrop = drop; + openDrop.open(); + }, 10); }; } diff --git a/public/app/features/annotations/annotations_srv.ts b/public/app/features/annotations/annotations_srv.ts index e9cdc0c20ec..310f565804c 100644 --- a/public/app/features/annotations/annotations_srv.ts +++ b/public/app/features/annotations/annotations_srv.ts @@ -135,6 +135,7 @@ export class AnnotationsSrv { } saveAnnotationEvent(annotation) { + this.globalAnnotationsPromise = null; return this.backendSrv.post('/api/annotations', annotation); } diff --git a/public/app/features/annotations/event_editor.ts b/public/app/features/annotations/event_editor.ts index 46e29a660a9..dc811661111 100644 --- a/public/app/features/annotations/event_editor.ts +++ b/public/app/features/annotations/event_editor.ts @@ -2,7 +2,7 @@ import _ from 'lodash'; import moment from 'moment'; -import coreModule from 'app/core/core_module'; +import {coreModule} from 'app/core/core'; import {MetricsPanelCtrl} from 'app/plugins/sdk'; export class AnnotationEvent { @@ -20,13 +20,13 @@ export class EventEditorCtrl { annotation: AnnotationEvent; timeRange: {from: number, to: number}; form: any; + close: any; /** @ngInject **/ constructor(private annotationsSrv) { this.annotation = new AnnotationEvent(); this.annotation.panelId = this.panelCtrl.panel.id; this.annotation.dashboardId = this.panelCtrl.dashboard.id; - this.annotation.text = "hello"; this.annotation.time = moment(this.timeRange.from); if (this.timeRange.to) { @@ -51,7 +51,10 @@ export class EventEditorCtrl { return; } - this.annotationsSrv.saveAnnotationEvent(saveModel); + this.annotationsSrv.saveAnnotationEvent(saveModel).then(() => { + this.panelCtrl.refresh(); + this.close(); + }); } } @@ -65,7 +68,7 @@ export function eventEditor() { scope: { "panelCtrl": "=", "timeRange": "=", - "cancel": "&", + "close": "&", } }; } diff --git a/public/app/features/annotations/partials/event_editor.html b/public/app/features/annotations/partials/event_editor.html index c2a75e92603..9454024c801 100644 --- a/public/app/features/annotations/partials/event_editor.html +++ b/public/app/features/annotations/partials/event_editor.html @@ -32,7 +32,7 @@
- Cancel + Cancel
diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index c3a71a11818..c362f9cd032 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -7,7 +7,6 @@ define([ './saveDashboardAsCtrl', './shareModalCtrl', './shareSnapshotCtrl', - './addAnnotationModalCtrl', './dashboard_srv', './viewStateSrv', './time_srv', diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 668f1973b42..f3a7f78b887 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -84,8 +84,8 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { element: elem[0], classNames: 'drop-popover drop-popover--form', position: 'bottom center', - openOn: null, - template: '', + openOn: 'click', + template: '', model: { timeRange: timeRange, panelCtrl: ctrl, From ea92ddccb3e20943972c5482a02696f5a9239015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Apr 2017 12:46:02 +0200 Subject: [PATCH 21/37] create annotations progress --- public/app/core/services/popover_srv.ts | 2 +- public/app/features/annotations/annotations_srv.ts | 7 ++++++- public/app/features/annotations/editor_ctrl.ts | 6 +++--- public/app/features/annotations/partials/editor.html | 2 +- public/app/features/annotations/partials/event_editor.html | 2 +- public/app/partials/dashboard.html | 2 +- 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/public/app/core/services/popover_srv.ts b/public/app/core/services/popover_srv.ts index 9cd13dfc280..6f61ee742a3 100644 --- a/public/app/core/services/popover_srv.ts +++ b/public/app/core/services/popover_srv.ts @@ -69,7 +69,7 @@ function popoverSrv($compile, $rootScope, $timeout) { openDrop = drop; openDrop.open(); - }, 10); + }, 100); }; } diff --git a/public/app/features/annotations/annotations_srv.ts b/public/app/features/annotations/annotations_srv.ts index 310f565804c..45536d73b98 100644 --- a/public/app/features/annotations/annotations_srv.ts +++ b/public/app/features/annotations/annotations_srv.ts @@ -38,7 +38,12 @@ export class AnnotationsSrv { // filter out annotations that do not belong to requesting panel annotations = _.filter(annotations, item => { - if (item.panelId && options.panel.id !== item.panelId) { + console.log(item); + // shownIn === 1 requires annotation matching panel id + if (item.source.showIn === 1) { + if (item.panelId && options.panel.id === item.panelId) { + return true; + } return false; } return true; diff --git a/public/app/features/annotations/editor_ctrl.ts b/public/app/features/annotations/editor_ctrl.ts index a0a8fda01d1..deb90691d91 100644 --- a/public/app/features/annotations/editor_ctrl.ts +++ b/public/app/features/annotations/editor_ctrl.ts @@ -18,13 +18,13 @@ export class AnnotationsEditorCtrl { datasource: null, iconColor: 'rgba(255, 96, 96, 1)', enable: true, - show: 0, + showIn: 0, hide: false, }; showOptions: any = [ {text: 'All Panels', value: 0}, - {text: 'Specifc Panels', value: 1}, + {text: 'Specific Panels', value: 1}, ]; /** @ngInject */ @@ -51,7 +51,7 @@ export class AnnotationsEditorCtrl { edit(annotation) { this.currentAnnotation = annotation; - this.currentAnnotation.show = this.currentAnnotation.show || 0; + this.currentAnnotation.showIn = this.currentAnnotation.showIn || 0; this.currentIsNew = false; this.datasourceChanged(); this.mode = 'edit'; diff --git a/public/app/features/annotations/partials/editor.html b/public/app/features/annotations/partials/editor.html index b5c631ec6a8..9e8d5cac2d6 100644 --- a/public/app/features/annotations/partials/editor.html +++ b/public/app/features/annotations/partials/editor.html @@ -80,7 +80,7 @@
Show in
- +
Add annotation
-
+
Title diff --git a/public/app/partials/dashboard.html b/public/app/partials/dashboard.html index 7dd925c3f02..7c8ac3e6aed 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -13,7 +13,7 @@ -
+
ADD ROW From 0335c1f368eef454c1d4c081cf7c17a25cb9fa65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Apr 2017 13:08:03 +0200 Subject: [PATCH 22/37] ux: updated styles a bit --- public/sass/_variables.dark.scss | 1 + public/sass/_variables.light.scss | 1 + public/sass/components/_drop.scss | 4 ++-- public/sass/mixins/_drop_element.scss | 7 +++++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index c4cc463d3ce..7e7865b6b88 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -253,6 +253,7 @@ $infoBackground: $blue-dark; // popover $popover-bg: $panel-bg; $popover-color: $text-color; +$popover-border-color: $gray-1; $popover-help-bg: $btn-secondary-bg; $popover-help-color: $text-color; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index c089be378b4..a10f94afc1b 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -278,6 +278,7 @@ $infoBorder: transparent; // popover $popover-bg: $gray-5; $popover-color: $text-color; +$popover-border-color: $gray-3; $popover-help-bg: $blue-dark; $popover-help-color: $gray-6; diff --git a/public/sass/components/_drop.scss b/public/sass/components/_drop.scss index 7349a8c7c5d..6ed94c560a0 100644 --- a/public/sass/components/_drop.scss +++ b/public/sass/components/_drop.scss @@ -6,8 +6,8 @@ $attachmentOffset: 0%; $easing: cubic-bezier(0, 0, 0.265, 1.00); @include drop-theme("error", $errorBackground, $popover-color); -@include drop-theme("popover", $popover-bg, $popover-color, #b3460d); -@include drop-theme("help", $popover-bg, $popover-color, #b3460d); +@include drop-theme("popover", $popover-bg, $popover-color, $popover-border-color); +@include drop-theme("help", $popover-help-bg, $popover-help-color); @include drop-animation-scale("drop", "help", $attachmentOffset: $attachmentOffset, $easing: $easing); @include drop-animation-scale("drop", "error", $attachmentOffset: $attachmentOffset, $easing: $easing); diff --git a/public/sass/mixins/_drop_element.scss b/public/sass/mixins/_drop_element.scss index f0adf6aae96..7aa51fff256 100644 --- a/public/sass/mixins/_drop_element.scss +++ b/public/sass/mixins/_drop_element.scss @@ -1,5 +1,5 @@ -@mixin drop-theme($themeName, $theme-bg, $theme-color, $border-color: $theme-color) { +@mixin drop-theme($themeName, $theme-bg, $theme-color, $border-color: $theme-bg) { .drop-element.drop-#{$themeName} { max-width: 100%; max-height: 100%; @@ -15,7 +15,10 @@ word-wrap: break-word; max-width: 20rem; border: 1px solid $border-color; - box-shadow: 0 0 15px #5d3000; + + @if $theme-bg != $border-color { + box-shadow: 0 0 15px $border-color; + } &:before { content: ""; From a151de1d3782164955039d3512982f0e4b244ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Apr 2017 14:43:06 +0200 Subject: [PATCH 23/37] progess on adding annotations --- public/app/features/annotations/all.ts | 5 +- .../app/features/annotations/event_editor.ts | 89 ++++++++++++++---- .../annotations/partials/event_editor.html | 14 +-- public/app/plugins/panel/graph/graph.ts | 92 +++++++++++-------- 4 files changed, 135 insertions(+), 65 deletions(-) diff --git a/public/app/features/annotations/all.ts b/public/app/features/annotations/all.ts index 0be292a7fa1..cde82c24a48 100644 --- a/public/app/features/annotations/all.ts +++ b/public/app/features/annotations/all.ts @@ -1,8 +1,9 @@ import {AnnotationsSrv} from './annotations_srv'; -import {eventEditor} from './event_editor'; +import {eventEditor, EventManager} from './event_editor'; export { AnnotationsSrv, - eventEditor + eventEditor, + EventManager }; diff --git a/public/app/features/annotations/event_editor.ts b/public/app/features/annotations/event_editor.ts index dc811661111..737e48d3776 100644 --- a/public/app/features/annotations/event_editor.ts +++ b/public/app/features/annotations/event_editor.ts @@ -17,22 +17,15 @@ export class AnnotationEvent { export class EventEditorCtrl { panelCtrl: MetricsPanelCtrl; - annotation: AnnotationEvent; + event: AnnotationEvent; timeRange: {from: number, to: number}; form: any; close: any; /** @ngInject **/ constructor(private annotationsSrv) { - this.annotation = new AnnotationEvent(); - this.annotation.panelId = this.panelCtrl.panel.id; - this.annotation.dashboardId = this.panelCtrl.dashboard.id; - - this.annotation.time = moment(this.timeRange.from); - if (this.timeRange.to) { - this.annotation.timeEnd = moment(this.timeRange.to); - this.annotation.isRegion = true; - } + this.event.panelId = this.panelCtrl.panel.id; + this.event.dashboardId = this.panelCtrl.dashboard.id; } save() { @@ -40,15 +33,17 @@ export class EventEditorCtrl { return; } - let saveModel = _.cloneDeep(this.annotation); + let saveModel = _.cloneDeep(this.event); saveModel.time = saveModel.time.valueOf(); + saveModel.timeEnd = 0; + if (saveModel.isRegion) { saveModel.timeEnd = saveModel.timeEnd.valueOf(); - } - if (saveModel.timeEnd < saveModel.time) { - console.log('invalid time'); - return; + if (saveModel.timeEnd < saveModel.time) { + console.log('invalid time'); + return; + } } this.annotationsSrv.saveAnnotationEvent(saveModel).then(() => { @@ -56,6 +51,10 @@ export class EventEditorCtrl { this.close(); }); } + + timeChanged() { + this.panelCtrl.render(); + } } export function eventEditor() { @@ -67,10 +66,68 @@ export function eventEditor() { templateUrl: 'public/app/features/annotations/partials/event_editor.html', scope: { "panelCtrl": "=", - "timeRange": "=", + "event": "=", "close": "&", } }; } coreModule.directive('eventEditor', eventEditor); + +export class EventManager { + event: AnnotationEvent; + + constructor(private panelCtrl: MetricsPanelCtrl, + private elem, + private popoverSrv) { + } + + editorClosed() { + console.log('editorClosed'); + this.event = null; + this.panelCtrl.render(); + } + + updateTime(range) { + let newEvent = true; + + if (this.event) { + newEvent = false; + } else { + // init new event + this.event = new AnnotationEvent(); + this.event.dashboardId = this.panelCtrl.dashboard.id; + this.event.panelId = this.panelCtrl.panel.id; + } + + // update time + this.event.time = moment(range.from); + this.event.isRegion = false; + if (range.to) { + this.event.timeEnd = moment(range.to); + this.event.isRegion = true; + } + + // newEvent means the editor is not visible + if (!newEvent) { + this.panelCtrl.render(); + return; + } + + this.popoverSrv.show({ + element: this.elem[0], + classNames: 'drop-popover drop-popover--form', + position: 'bottom center', + openOn: null, + template: '', + onClose: this.editorClosed.bind(this), + model: { + event: this.event, + panelCtrl: this.panelCtrl, + }, + }); + + this.panelCtrl.render(); + } +} + diff --git a/public/app/features/annotations/partials/event_editor.html b/public/app/features/annotations/partials/event_editor.html index 639c0ad6911..6e44b6f768d 100644 --- a/public/app/features/annotations/partials/event_editor.html +++ b/public/app/features/annotations/partials/event_editor.html @@ -5,29 +5,29 @@
Title - +
-
+
Time - +
-
+
Start - +
End - +
Description - +
diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index f3a7f78b887..af03bafc4ac 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -17,6 +17,7 @@ import {tickStep} from 'app/core/utils/ticks'; import {appEvents, coreModule} from 'app/core/core'; import GraphTooltip from './graph_tooltip'; import {ThresholdManager} from './threshold_manager'; +import {EventManager} from 'app/features/annotations/all'; import {convertValuesToHistogram, getSeriesValues} from './histogram'; coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { @@ -27,13 +28,14 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { var ctrl = scope.ctrl; var dashboard = ctrl.dashboard; var panel = ctrl.panel; + var annotations = []; var data; - var annotations; var plot; var sortedSeries; var legendSideLastValue = null; var rootScope = scope.$root; var panelWidth = 0; + var eventManager = new EventManager(ctrl, elem, popoverSrv); var thresholdManager = new ThresholdManager(ctrl); var tooltip = new GraphTooltip(elem, dashboard, scope, function() { return sortedSeries; @@ -54,7 +56,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { if (!data) { return; } - annotations = ctrl.annotations; + annotations = ctrl.annotations || []; render_panel(); }); @@ -79,20 +81,6 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { } }, scope); - function showAddAnnotationView(timeRange) { - popoverSrv.show({ - element: elem[0], - classNames: 'drop-popover drop-popover--form', - position: 'bottom center', - openOn: 'click', - template: '', - model: { - timeRange: timeRange, - panelCtrl: ctrl, - }, - }); - } - function getLegendHeight(panelHeight) { if (!panel.legend.show || panel.legend.rightSide) { return 0; @@ -343,7 +331,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { } thresholdManager.addPlotOptions(options, panel); - addAnnotations(options); + addAnnotationEvents(options); configureAxisOptions(data, options); sortedSeries = _.sortBy(data, function(series) { return series.zindex; }); @@ -475,8 +463,12 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { }; } - function addAnnotations(options) { - if (!annotations || annotations.length === 0) { + function hasAnnotationEvents() { + return eventManager.event || annotations.length > 0 ; + } + + function addAnnotationEvents(options) { + if (!hasAnnotationEvents()) { return; } @@ -501,26 +493,41 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { types['$__execution_error'] = ['$__no_data']; - for (var i = 0; i < annotations.length; i++) { - var item = annotations[i]; - if (item.newState) { - console.log(item.newState); - item.eventType = '$__' + item.newState; - continue; - } + var annotationsToShow; + // adding/edditing event, only show that one + if (eventManager.event) { + const event = eventManager.event; + annotationsToShow = [ + { + min: event.time.valueOf(), + title: event.title, + description: event.text, + eventType: '$__alerting', + } + ]; + } else { + // annotations from query + for (var i = 0; i < annotations.length; i++) { + var item = annotations[i]; + if (item.newState) { + item.eventType = '$__' + item.newState; + continue; + } - if (!types[item.source.name]) { - types[item.source.name] = { - color: item.source.iconColor, - position: 'BOTTOM', - markerSize: 5, - }; + if (!types[item.source.name]) { + types[item.source.name] = { + color: item.source.iconColor, + position: 'BOTTOM', + markerSize: 5, + }; + } } + annotationsToShow = annotations; } options.events = { levels: _.keys(types).length + 1, - data: annotations, + data: annotationsToShow, types: types, }; } @@ -653,8 +660,10 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { } elem.bind("plotselected", function (event, ranges) { - if (ranges.ctrlKey || ranges.metaKey) { - showAddAnnotationView(ranges.xaxis); + if (ranges.ctrlKey || ranges.metaKey) { + scope.$apply(() => { + eventManager.updateTime(ranges.xaxis); + }); } else { scope.$apply(function() { timeSrv.setTime({ @@ -666,11 +675,14 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { }); elem.bind("plotclick", function (event, pos, item) { - // Skip if range selected (added in "plotselected" event handler) - let isRangeSelection = pos.x !== pos.x1; - let createAnnotation = !isRangeSelection && (pos.ctrlKey || pos.metaKey); - if (createAnnotation) { - showAddAnnotationView({from: pos.x, to: null}); + if (pos.ctrlKey || pos.metaKey || eventManager.event) { + // Skip if range selected (added in "plotselected" event handler) + let isRangeSelection = pos.x !== pos.x1; + if (!isRangeSelection) { + scope.$apply(() => { + eventManager.updateTime({from: pos.x, to: null}); + }); + } } }); From 03ef1fd7587c67922978e5b756b020244324092d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Apr 2017 22:56:01 +0200 Subject: [PATCH 24/37] refactoring: event / annotation handling in graph panel broken out --- public/app/features/annotations/all.ts | 7 +- .../features/annotations/annotations_srv.ts | 1 - public/app/features/annotations/event.ts | 10 ++ .../app/features/annotations/event_editor.ts | 69 +---------- .../app/features/annotations/event_manager.ts | 117 ++++++++++++++++++ public/app/plugins/panel/graph/graph.ts | 71 +---------- 6 files changed, 134 insertions(+), 141 deletions(-) create mode 100644 public/app/features/annotations/event.ts create mode 100644 public/app/features/annotations/event_manager.ts diff --git a/public/app/features/annotations/all.ts b/public/app/features/annotations/all.ts index cde82c24a48..5f195928c7a 100644 --- a/public/app/features/annotations/all.ts +++ b/public/app/features/annotations/all.ts @@ -1,9 +1,12 @@ import {AnnotationsSrv} from './annotations_srv'; -import {eventEditor, EventManager} from './event_editor'; +import {eventEditor} from './event_editor'; +import {EventManager} from './event_manager'; +import {AnnotationEvent} from './event'; export { AnnotationsSrv, eventEditor, - EventManager + EventManager, + AnnotationEvent, }; diff --git a/public/app/features/annotations/annotations_srv.ts b/public/app/features/annotations/annotations_srv.ts index 45536d73b98..c2422a63c27 100644 --- a/public/app/features/annotations/annotations_srv.ts +++ b/public/app/features/annotations/annotations_srv.ts @@ -38,7 +38,6 @@ export class AnnotationsSrv { // filter out annotations that do not belong to requesting panel annotations = _.filter(annotations, item => { - console.log(item); // shownIn === 1 requires annotation matching panel id if (item.source.showIn === 1) { if (item.panelId && options.panel.id === item.panelId) { diff --git a/public/app/features/annotations/event.ts b/public/app/features/annotations/event.ts new file mode 100644 index 00000000000..53afbea5b07 --- /dev/null +++ b/public/app/features/annotations/event.ts @@ -0,0 +1,10 @@ + +export class AnnotationEvent { + dashboardId: number; + panelId: number; + time: any; + timeEnd: any; + isRegion: boolean; + title: string; + text: string; +} diff --git a/public/app/features/annotations/event_editor.ts b/public/app/features/annotations/event_editor.ts index 737e48d3776..939920e21ad 100644 --- a/public/app/features/annotations/event_editor.ts +++ b/public/app/features/annotations/event_editor.ts @@ -4,16 +4,7 @@ import _ from 'lodash'; import moment from 'moment'; import {coreModule} from 'app/core/core'; import {MetricsPanelCtrl} from 'app/plugins/sdk'; - -export class AnnotationEvent { - dashboardId: number; - panelId: number; - time: any; - timeEnd: any; - isRegion: boolean; - title: string; - text: string; -} +import {AnnotationEvent} from './event'; export class EventEditorCtrl { panelCtrl: MetricsPanelCtrl; @@ -73,61 +64,3 @@ export function eventEditor() { } coreModule.directive('eventEditor', eventEditor); - -export class EventManager { - event: AnnotationEvent; - - constructor(private panelCtrl: MetricsPanelCtrl, - private elem, - private popoverSrv) { - } - - editorClosed() { - console.log('editorClosed'); - this.event = null; - this.panelCtrl.render(); - } - - updateTime(range) { - let newEvent = true; - - if (this.event) { - newEvent = false; - } else { - // init new event - this.event = new AnnotationEvent(); - this.event.dashboardId = this.panelCtrl.dashboard.id; - this.event.panelId = this.panelCtrl.panel.id; - } - - // update time - this.event.time = moment(range.from); - this.event.isRegion = false; - if (range.to) { - this.event.timeEnd = moment(range.to); - this.event.isRegion = true; - } - - // newEvent means the editor is not visible - if (!newEvent) { - this.panelCtrl.render(); - return; - } - - this.popoverSrv.show({ - element: this.elem[0], - classNames: 'drop-popover drop-popover--form', - position: 'bottom center', - openOn: null, - template: '', - onClose: this.editorClosed.bind(this), - model: { - event: this.event, - panelCtrl: this.panelCtrl, - }, - }); - - this.panelCtrl.render(); - } -} - diff --git a/public/app/features/annotations/event_manager.ts b/public/app/features/annotations/event_manager.ts new file mode 100644 index 00000000000..7e1ca41ca6c --- /dev/null +++ b/public/app/features/annotations/event_manager.ts @@ -0,0 +1,117 @@ + +import moment from 'moment'; +import {MetricsPanelCtrl} from 'app/plugins/sdk'; +import {AnnotationEvent} from './event'; + +export class EventManager { + event: AnnotationEvent; + + constructor(private panelCtrl: MetricsPanelCtrl, private elem, private popoverSrv) { + } + + editorClosed() { + console.log('editorClosed'); + this.event = null; + this.panelCtrl.render(); + } + + updateTime(range) { + let newEvent = true; + + if (this.event) { + newEvent = false; + } else { + // init new event + this.event = new AnnotationEvent(); + this.event.dashboardId = this.panelCtrl.dashboard.id; + this.event.panelId = this.panelCtrl.panel.id; + } + + // update time + this.event.time = moment(range.from); + this.event.isRegion = false; + if (range.to) { + this.event.timeEnd = moment(range.to); + this.event.isRegion = true; + } + + // newEvent means the editor is not visible + if (!newEvent) { + this.panelCtrl.render(); + return; + } + + this.popoverSrv.show({ + element: this.elem[0], + classNames: 'drop-popover drop-popover--form', + position: 'bottom center', + openOn: null, + template: '', + onClose: this.editorClosed.bind(this), + model: { + event: this.event, + panelCtrl: this.panelCtrl, + }, + }); + + this.panelCtrl.render(); + } + + addPlotEvents(annotations) { + if (this.event || annotations.length === 0) { + return; + } + + var types = { + '$__alerting': { + color: 'rgba(237, 46, 24, 1)', + position: 'BOTTOM', + markerSize: 5, + }, + '$__ok': { + color: 'rgba(11, 237, 50, 1)', + position: 'BOTTOM', + markerSize: 5, + }, + '$__no_data': { + color: 'rgba(150, 150, 150, 1)', + position: 'BOTTOM', + markerSize: 5, + }, + }; + + if (this.event) { + annotations = [ + { + min: this.event.time.valueOf(), + title: this.event.title, + text: this.event.text, + eventType: '$__alerting', + } + ]; + } else { + // annotations from query + for (var i = 0; i < annotations.length; i++) { + var item = annotations[i]; + if (item.newState) { + item.eventType = '$__' + item.newState; + continue; + } + + if (!types[item.source.name]) { + types[item.source.name] = { + color: item.source.iconColor, + position: 'BOTTOM', + markerSize: 5, + }; + } + } + } + + options.events = { + levels: _.keys(types).length + 1, + data: annotations, + types: types, + }; + } +} diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index af03bafc4ac..9eebae31874 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -331,7 +331,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { } thresholdManager.addPlotOptions(options, panel); - addAnnotationEvents(options); + eventManager.addPlotEvents(annotations, options); configureAxisOptions(data, options); sortedSeries = _.sortBy(data, function(series) { return series.zindex; }); @@ -463,75 +463,6 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { }; } - function hasAnnotationEvents() { - return eventManager.event || annotations.length > 0 ; - } - - function addAnnotationEvents(options) { - if (!hasAnnotationEvents()) { - return; - } - - var types = {}; - types['$__alerting'] = { - color: 'rgba(237, 46, 24, 1)', - position: 'BOTTOM', - markerSize: 5, - }; - - types['$__ok'] = { - color: 'rgba(11, 237, 50, 1)', - position: 'BOTTOM', - markerSize: 5, - }; - - types['$__no_data'] = { - color: 'rgba(150, 150, 150, 1)', - position: 'BOTTOM', - markerSize: 5, - }; - - types['$__execution_error'] = ['$__no_data']; - - var annotationsToShow; - // adding/edditing event, only show that one - if (eventManager.event) { - const event = eventManager.event; - annotationsToShow = [ - { - min: event.time.valueOf(), - title: event.title, - description: event.text, - eventType: '$__alerting', - } - ]; - } else { - // annotations from query - for (var i = 0; i < annotations.length; i++) { - var item = annotations[i]; - if (item.newState) { - item.eventType = '$__' + item.newState; - continue; - } - - if (!types[item.source.name]) { - types[item.source.name] = { - color: item.source.iconColor, - position: 'BOTTOM', - markerSize: 5, - }; - } - } - annotationsToShow = annotations; - } - - options.events = { - levels: _.keys(types).length + 1, - data: annotationsToShow, - types: types, - }; - } - function configureAxisOptions(data, options) { var defaults = { position: 'left', From fa2a7db65771d2e6ceec1e1bdd61c2be02cb9d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Apr 2017 23:10:56 +0200 Subject: [PATCH 25/37] ux: create annotations --- public/app/features/annotations/event_manager.ts | 4 ++-- public/app/plugins/panel/graph/graph.ts | 4 ++-- .../app/plugins/panel/graph/specs/threshold_manager_specs.ts | 2 +- public/app/plugins/panel/graph/threshold_manager.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/app/features/annotations/event_manager.ts b/public/app/features/annotations/event_manager.ts index 7e1ca41ca6c..6613c17a67a 100644 --- a/public/app/features/annotations/event_manager.ts +++ b/public/app/features/annotations/event_manager.ts @@ -57,7 +57,7 @@ export class EventManager { this.panelCtrl.render(); } - addPlotEvents(annotations) { + addFlotEvents(annotations, flotOptions) { if (this.event || annotations.length === 0) { return; } @@ -108,7 +108,7 @@ export class EventManager { } } - options.events = { + flotOptions.events = { levels: _.keys(types).length + 1, data: annotations, types: types, diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 9eebae31874..d6e054152ec 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -330,8 +330,8 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { } } - thresholdManager.addPlotOptions(options, panel); - eventManager.addPlotEvents(annotations, options); + thresholdManager.addFlotOptions(options, panel); + eventManager.addFlotEvents(annotations, options); configureAxisOptions(data, options); sortedSeries = _.sortBy(data, function(series) { return series.zindex; }); diff --git a/public/app/plugins/panel/graph/specs/threshold_manager_specs.ts b/public/app/plugins/panel/graph/specs/threshold_manager_specs.ts index de598ea73ed..742ce69ec7c 100644 --- a/public/app/plugins/panel/graph/specs/threshold_manager_specs.ts +++ b/public/app/plugins/panel/graph/specs/threshold_manager_specs.ts @@ -21,7 +21,7 @@ describe('ThresholdManager', function() { ctx.setup = function(thresholds) { ctx.panel.thresholds = thresholds; var manager = new ThresholdManager(ctx.panelCtrl); - manager.addPlotOptions(ctx.options, ctx.panel); + manager.addFlotOptions(ctx.options, ctx.panel); }; func(ctx); diff --git a/public/app/plugins/panel/graph/threshold_manager.ts b/public/app/plugins/panel/graph/threshold_manager.ts index 645046690fd..03e3ae3c737 100644 --- a/public/app/plugins/panel/graph/threshold_manager.ts +++ b/public/app/plugins/panel/graph/threshold_manager.ts @@ -158,7 +158,7 @@ export class ThresholdManager { this.needsCleanup = true; } - addPlotOptions(options, panel) { + addFlotOptions(options, panel) { if (!panel.thresholds || panel.thresholds.length === 0) { return; } From 5df82be29099be5e5fa04dca26706b04027fe86b Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 17 Apr 2017 11:37:23 +0300 Subject: [PATCH 26/37] create-annotations: fix missing lodash import --- public/app/features/annotations/event_manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/annotations/event_manager.ts b/public/app/features/annotations/event_manager.ts index 6613c17a67a..0c88eccdddf 100644 --- a/public/app/features/annotations/event_manager.ts +++ b/public/app/features/annotations/event_manager.ts @@ -1,4 +1,4 @@ - +import _ from 'lodash'; import moment from 'moment'; import {MetricsPanelCtrl} from 'app/plugins/sdk'; import {AnnotationEvent} from './event'; From 715453204e33744a2c784edf33e7720a0f9b7dc4 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Fri, 14 Apr 2017 17:41:22 -0400 Subject: [PATCH 27/37] make sure graphite queries containing references are properly updated --- .../graphite/partials/query.editor.html | 2 +- .../plugins/datasource/graphite/query_ctrl.ts | 58 ++++++++++++++----- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/public/app/plugins/datasource/graphite/partials/query.editor.html b/public/app/plugins/datasource/graphite/partials/query.editor.html index 60372fb8ab0..5646f005be9 100755 --- a/public/app/plugins/datasource/graphite/partials/query.editor.html +++ b/public/app/plugins/datasource/graphite/partials/query.editor.html @@ -1,7 +1,7 @@
- +
diff --git a/public/app/plugins/datasource/graphite/query_ctrl.ts b/public/app/plugins/datasource/graphite/query_ctrl.ts index f9f34284971..ce2baea0b76 100644 --- a/public/app/plugins/datasource/graphite/query_ctrl.ts +++ b/public/app/plugins/datasource/graphite/query_ctrl.ts @@ -55,7 +55,7 @@ export class GraphiteQueryCtrl extends QueryCtrl { } try { - this.parseTargeRecursive(astNode, null, 0); + this.parseTargetRecursive(astNode, null, 0); } catch (err) { console.log('error parsing target:', err.message); this.error = err.message; @@ -72,7 +72,7 @@ export class GraphiteQueryCtrl extends QueryCtrl { func.params[index] = value; } - parseTargeRecursive(astNode, func, index) { + parseTargetRecursive(astNode, func, index) { if (astNode === null) { return null; } @@ -81,7 +81,7 @@ export class GraphiteQueryCtrl extends QueryCtrl { case 'function': var innerFunc = gfunc.createFuncInstance(astNode.name, { withDefaultParams: false }); _.each(astNode.params, (param, index) => { - this.parseTargeRecursive(param, innerFunc, index); + this.parseTargetRecursive(param, innerFunc, index); }); innerFunc.updateText(); @@ -209,30 +209,56 @@ export class GraphiteQueryCtrl extends QueryCtrl { } targetTextChanged() { - this.parseTarget(); - this.panelCtrl.refresh(); + this.updateModelTarget(); + this.refresh(); } updateModelTarget() { // render query - var metricPath = this.getSegmentPathUpTo(this.segments.length); - this.target.target = _.reduce(this.functions, this.wrapFunction, metricPath); + if (!this.target.textEditor) { + var metricPath = this.getSegmentPathUpTo(this.segments.length); + this.target.target = _.reduce(this.functions, this.wrapFunction, metricPath); + } + // loop through queries and update targetFull as needed + for (const target of this.panelCtrl.panel.targets) { + this.resolveTarget(target); + } + } + + resolveTarget(target) { // render nested query var targetsByRefId = _.keyBy(this.panelCtrl.panel.targets, 'refId'); + + // no references to self + delete targetsByRefId[target.refId]; + var nestedSeriesRefRegex = /\#([A-Z])/g; - var targetWithNestedQueries = this.target.target.replace(nestedSeriesRefRegex, (match, g1) => { - var target = targetsByRefId[g1]; - if (!target) { - return match; + var targetWithNestedQueries = target.target; + + while (targetWithNestedQueries.match(nestedSeriesRefRegex)) { + var updated = targetWithNestedQueries.replace(nestedSeriesRefRegex, (match, g1) => { + var t = targetsByRefId[g1]; + if (!t) { + return match; + } + + // no circular references + delete targetsByRefId[g1]; + + return t.target; + }); + + if (updated === targetWithNestedQueries) { + break; } - return target.targetFull || target.target; - }); + targetWithNestedQueries = updated; + } - delete this.target.targetFull; - if (this.target.target !== targetWithNestedQueries) { - this.target.targetFull = targetWithNestedQueries; + delete target.targetFull; + if (target.target !== targetWithNestedQueries) { + target.targetFull = targetWithNestedQueries; } } From a64e000f1aa5bd3d7a3f71e937439a49b66e90a2 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Mon, 17 Apr 2017 11:10:55 -0400 Subject: [PATCH 28/37] process this.target separately to fix issues with tests --- public/app/plugins/datasource/graphite/query_ctrl.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/graphite/query_ctrl.ts b/public/app/plugins/datasource/graphite/query_ctrl.ts index ce2baea0b76..7c89ac1a0a9 100644 --- a/public/app/plugins/datasource/graphite/query_ctrl.ts +++ b/public/app/plugins/datasource/graphite/query_ctrl.ts @@ -220,9 +220,13 @@ export class GraphiteQueryCtrl extends QueryCtrl { this.target.target = _.reduce(this.functions, this.wrapFunction, metricPath); } - // loop through queries and update targetFull as needed - for (const target of this.panelCtrl.panel.targets) { - this.resolveTarget(target); + this.resolveTarget(this.target); + + // loop through other queries and update targetFull as needed + for (const target of this.panelCtrl.panel.targets || []) { + if (target.refId !== this.target.refId) { + this.resolveTarget(target); + } } } From f0816b37bd695ebfb6d04d0e005955e2f0fc245f Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 17 Apr 2017 18:53:10 +0300 Subject: [PATCH 29/37] rename annotation_category to category --- .../{annotation_category_mig.go => category_mig.go} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename pkg/services/sqlstore/migrations/{annotation_category_mig.go => category_mig.go} (81%) diff --git a/pkg/services/sqlstore/migrations/annotation_category_mig.go b/pkg/services/sqlstore/migrations/category_mig.go similarity index 81% rename from pkg/services/sqlstore/migrations/annotation_category_mig.go rename to pkg/services/sqlstore/migrations/category_mig.go index 331aeea2500..2a1b27abeb2 100644 --- a/pkg/services/sqlstore/migrations/annotation_category_mig.go +++ b/pkg/services/sqlstore/migrations/category_mig.go @@ -6,12 +6,13 @@ import ( func addAnnotationCategoryMig(mg *Migrator) { category := Table{ - Name: "annotation_category", + Name: "category", Columns: []*Column{ {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "org_id", Type: DB_BigInt, Nullable: false}, {Name: "user_id", Type: DB_BigInt, Nullable: true}, {Name: "name", Type: DB_Text, Nullable: false}, + {Name: "description", Type: DB_Text, Nullable: false}, }, Indices: []*Index{ {Cols: []string{"org_id", "name"}, Type: IndexType}, @@ -19,7 +20,7 @@ func addAnnotationCategoryMig(mg *Migrator) { } // create table - mg.AddMigration("create annotation_category table", NewAddTableMigration(category)) + mg.AddMigration("create category table", NewAddTableMigration(category)) // create indices mg.AddMigration("add index org_id & name", NewAddIndexMigration(category, category.Indices[0])) From 7aa992bde4db78d4f66800457d6dd9f6db8412c2 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 17 Apr 2017 18:56:39 +0300 Subject: [PATCH 30/37] initial category types --- pkg/services/category/category.go | 39 +++++++++++++++++++++++++++++++ pkg/services/sqlstore/category.go | 24 +++++++++++++++++++ pkg/services/sqlstore/sqlstore.go | 3 +++ 3 files changed, 66 insertions(+) create mode 100644 pkg/services/category/category.go create mode 100644 pkg/services/sqlstore/category.go diff --git a/pkg/services/category/category.go b/pkg/services/category/category.go new file mode 100644 index 00000000000..2f0df5523a8 --- /dev/null +++ b/pkg/services/category/category.go @@ -0,0 +1,39 @@ +package category + +type Repository interface { + Save(item *Item) error + Update(item *Item) error + Delete(params *DeleteParams) error + Find(query *FindParams) ([]*Item, error) +} + +var repositoryInstance Repository + +func GetRepository() Repository { + return repositoryInstance +} + +func SetRepository(rep Repository) { + repositoryInstance = rep +} + +type FindParams struct { + OrgId int64 `json:"orgId"` + UserId int64 `json:"userId"` + Limit int64 `json:"limit"` +} + +type DeleteParams struct { + Id int64 `json:"id"` + Name string `json:"title"` +} + +type ItemType string + +type Item struct { + Id int64 `json:"id"` + OrgId int64 `json:"orgId"` + UserId int64 `json:"userId"` + Name string `json:"title"` + Description string `json:"text"` +} diff --git a/pkg/services/sqlstore/category.go b/pkg/services/sqlstore/category.go new file mode 100644 index 00000000000..b20dfb85f4f --- /dev/null +++ b/pkg/services/sqlstore/category.go @@ -0,0 +1,24 @@ +package sqlstore + +import ( + "github.com/grafana/grafana/pkg/services/category" +) + +type SqlCategoryRepo struct { +} + +func (r *SqlCategoryRepo) Save(item *category.Item) error { + return nil +} + +func (r *SqlCategoryRepo) Update(item *category.Item) error { + return nil +} + +func (r *SqlCategoryRepo) Delete(params *category.DeleteParams) error { + return nil +} + +func (r *SqlCategoryRepo) Find(params *category.FindParams) ([]*category.Item, error) { + return nil, nil +} diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 27fba0068d1..ce2090fc701 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/annotations" + "github.com/grafana/grafana/pkg/services/category" "github.com/grafana/grafana/pkg/services/sqlstore/migrations" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/setting" @@ -98,7 +99,9 @@ func SetEngine(engine *xorm.Engine) (err error) { return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err) } + // Init repo instances annotations.SetRepository(&SqlAnnotationRepo{}) + category.SetRepository(&SqlCategoryRepo{}) return nil } From 5dad324ab7c668cb37003e4c612f7f6e8e128ffa Mon Sep 17 00:00:00 2001 From: Kevin Conaway Date: Tue, 18 Apr 2017 07:49:04 -0400 Subject: [PATCH 31/37] #8144 Only require root to start/stop grafana (#8145) --- packaging/deb/init.d/grafana-server | 16 +++++++++------- packaging/rpm/init.d/grafana-server | 14 +++++++++----- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/packaging/deb/init.d/grafana-server b/packaging/deb/init.d/grafana-server index 61e82d4c612..d01778560f7 100755 --- a/packaging/deb/init.d/grafana-server +++ b/packaging/deb/init.d/grafana-server @@ -37,14 +37,8 @@ MAX_OPEN_FILES=10000 PID_FILE=/var/run/$NAME.pid DAEMON=/usr/sbin/$NAME - umask 0027 -if [ `id -u` -ne 0 ]; then - echo "You need root privileges to run this script" - exit 4 -fi - if [ ! -x $DAEMON ]; then echo "Program not installed or not executable" exit 5 @@ -63,9 +57,16 @@ fi DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR} cfg:default.paths.plugins=${PLUGINS_DIR}" +function checkUser() { + if [ `id -u` -ne 0 ]; then + echo "You need root privileges to run this script" + exit 4 + fi +} + case "$1" in start) - + checkUser log_daemon_msg "Starting $DESC" pid=`pidofproc -p $PID_FILE grafana` @@ -112,6 +113,7 @@ case "$1" in log_end_msg $return ;; stop) + checkUser log_daemon_msg "Stopping $DESC" if [ -f "$PID_FILE" ]; then diff --git a/packaging/rpm/init.d/grafana-server b/packaging/rpm/init.d/grafana-server index cb9bb73de7d..a9e2988bdb7 100755 --- a/packaging/rpm/init.d/grafana-server +++ b/packaging/rpm/init.d/grafana-server @@ -36,11 +36,6 @@ MAX_OPEN_FILES=10000 PID_FILE=/var/run/$NAME.pid DAEMON=/usr/sbin/$NAME -if [ `id -u` -ne 0 ]; then - echo "You need root privileges to run this script" - exit 4 -fi - if [ ! -x $DAEMON ]; then echo "Program not installed or not executable" exit 5 @@ -70,8 +65,16 @@ function isRunning() { status -p $PID_FILE $NAME > /dev/null 2>&1 } +function checkUser() { + if [ `id -u` -ne 0 ]; then + echo "You need root privileges to run this script" + exit 4 + fi +} + case "$1" in start) + checkUser isRunning if [ $? -eq 0 ]; then echo "Already running." @@ -115,6 +118,7 @@ case "$1" in exit $return ;; stop) + checkUser echo -n "Stopping $DESC: ..." if [ -f "$PID_FILE" ]; then From 473006e8cfd3da590c549a3ce1fec52c12060306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 18 Apr 2017 15:36:38 +0200 Subject: [PATCH 32/37] build: updated grunt watch to explain best usage --- package.json | 2 +- tasks/options/exec.js | 2 +- tasks/options/watch.js | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d38de899bf5..326c813dc93 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "systemjs-builder": "^0.15.34", "tether": "^1.4.0", "tether-drop": "https://github.com/torkelo/drop", - "tslint": "^4.0.2", + "tslint": "^4.5.1", "typescript": "^2.1.4", "virtual-scroll": "^1.1.1" } diff --git a/tasks/options/exec.js b/tasks/options/exec.js index 65de74fef9f..98927436518 100644 --- a/tasks/options/exec.js +++ b/tasks/options/exec.js @@ -1,7 +1,7 @@ module.exports = function(config) { 'use strict' return { - tslint : "node ./node_modules/tslint/lib/tslint-cli.js -c tslint.json --project ./tsconfig.json", + tslint : "node ./node_modules/tslint/lib/tslint-cli.js -c tslint.json --project ./tsconfig.json <%= tslint.source.files.src %>", tscompile: "node ./node_modules/typescript/lib/tsc.js -p tsconfig.json --diagnostics", tswatch: "node ./node_modules/typescript/lib/tsc.js -p tsconfig.json --diagnostics --watch", }; diff --git a/tasks/options/watch.js b/tasks/options/watch.js index 545a149054a..612b4d2b86c 100644 --- a/tasks/options/watch.js +++ b/tasks/options/watch.js @@ -8,6 +8,10 @@ module.exports = function(config, grunt) { var lastTime; grunt.registerTask('watch', function() { + if (!grunt.option('skip-ts-compile')) { + grunt.log.writeln('We recommoned starting with: grunt watch --force --skip-ts-compile') + grunt.log.writeln('Then do incremental typescript builds with: grunt exec:tswatch') + } done = this.async(); lastTime = new Date().getTime(); @@ -58,6 +62,14 @@ module.exports = function(config, grunt) { newPath = filepath.replace(/^public/, 'public_gen'); grunt.log.writeln('Copying to ' + newPath); grunt.file.copy(filepath, newPath); + + if (grunt.option('skip-ts-compile')) { + grunt.log.writeln('Skipping ts compile, run grunt exec:tswatch to start typescript watcher') + } else { + grunt.task.run('exec:tscompile'); + } + + grunt.config('tslint.source.files.src', filepath); grunt.task.run('exec:tslint'); } From 85baa50194226e1c3433531c0eca73331d66e0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 18 Apr 2017 16:30:20 +0200 Subject: [PATCH 33/37] recfactor: added unit test for the new scenario, #8143 --- .../plugins/datasource/graphite/query_ctrl.ts | 10 +++++----- .../graphite/specs/query_ctrl_specs.ts | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/datasource/graphite/query_ctrl.ts b/public/app/plugins/datasource/graphite/query_ctrl.ts index 7c89ac1a0a9..1cf4406c7b8 100644 --- a/public/app/plugins/datasource/graphite/query_ctrl.ts +++ b/public/app/plugins/datasource/graphite/query_ctrl.ts @@ -28,7 +28,6 @@ export class GraphiteQueryCtrl extends QueryCtrl { } toggleEditorMode() { - this.target.textEditor = !this.target.textEditor; this.parseTarget(); } @@ -220,17 +219,17 @@ export class GraphiteQueryCtrl extends QueryCtrl { this.target.target = _.reduce(this.functions, this.wrapFunction, metricPath); } - this.resolveTarget(this.target); + this.updateRenderedTarget(this.target); // loop through other queries and update targetFull as needed for (const target of this.panelCtrl.panel.targets || []) { if (target.refId !== this.target.refId) { - this.resolveTarget(target); + this.updateRenderedTarget(target); } } } - resolveTarget(target) { + updateRenderedTarget(target) { // render nested query var targetsByRefId = _.keyBy(this.panelCtrl.panel.targets, 'refId'); @@ -240,6 +239,8 @@ export class GraphiteQueryCtrl extends QueryCtrl { var nestedSeriesRefRegex = /\#([A-Z])/g; var targetWithNestedQueries = target.target; + // Keep interpolating until there are no query references + // The reason for the loop is that the referenced query might contain another reference to another query while (targetWithNestedQueries.match(nestedSeriesRefRegex)) { var updated = targetWithNestedQueries.replace(nestedSeriesRefRegex, (match, g1) => { var t = targetsByRefId[g1]; @@ -249,7 +250,6 @@ export class GraphiteQueryCtrl extends QueryCtrl { // no circular references delete targetsByRefId[g1]; - return t.target; }); diff --git a/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts b/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts index 95691ae0b7b..e88fbc044c1 100644 --- a/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts +++ b/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts @@ -186,4 +186,24 @@ describe('GraphiteQueryCtrl', function() { expect(ctx.ctrl.target.targetFull).to.be('scaleToSeconds(nested.query.count)'); }); }); + + describe('when updating target used in other query', function() { + beforeEach(function() { + ctx.ctrl.target.target = 'metrics.a.count'; + ctx.ctrl.target.refId = 'A'; + ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}])); + ctx.ctrl.parseTarget(); + + ctx.ctrl.panelCtrl.panel.targets = [ + ctx.ctrl.target, {target: 'sumSeries(#A)', refId: 'B'} + ]; + + ctx.ctrl.updateModelTarget(); + }); + + it('targetFull of other query should update', function() { + expect(ctx.ctrl.panel.targets[1].targetFull).to.be('sumSeries(metrics.a.count)'); + }); + }); + }); From db36639ffc1964730a907959a3dcee36df796679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 18 Apr 2017 16:58:34 +0200 Subject: [PATCH 34/37] fix: #8111 --- public/app/features/dashboard/submenu/submenu.html | 2 +- public/sass/components/_submenu.scss | 6 ++++++ tasks/options/exec.js | 5 +++-- tasks/options/tslint.js | 11 +++++++++++ tasks/options/watch.js | 2 +- tasks/tslint.js | 0 6 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 tasks/options/tslint.js create mode 100644 tasks/tslint.js diff --git a/public/app/features/dashboard/submenu/submenu.html b/public/app/features/dashboard/submenu/submenu.html index 3e09fe4425e..b4e9e26fa0a 100644 --- a/public/app/features/dashboard/submenu/submenu.html +++ b/public/app/features/dashboard/submenu/submenu.html @@ -1,4 +1,4 @@ -