From 30645a6d3d757cc5862067eb6595f1827cc664e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Oct 2016 13:06:29 +0200 Subject: [PATCH] refactor(alerting): initial step for seperating alerting and dash model, #6273 --- pkg/api/alerting.go | 2 +- public/app/features/dashboard/alerting_srv.ts | 22 + public/app/features/dashboard/all.js | 1 + .../app/features/dashboard/dashboard_ctrl.ts | 2 + .../app/features/dashboard/dashboard_srv.ts | 670 +++--------------- .../app/features/dashboard/dashnav/dashnav.ts | 93 +-- public/app/features/dashboard/model.ts | 576 +++++++++++++++ 7 files changed, 701 insertions(+), 665 deletions(-) create mode 100644 public/app/features/dashboard/alerting_srv.ts create mode 100644 public/app/features/dashboard/model.ts diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 40b117835ec..e745f820aec 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -252,7 +252,7 @@ func NotificationTest(c *middleware.Context, dto dtos.NotificationTestCommand) R return ApiSuccess("Test notification sent") } -//POST /api/:alertId/pause +//POST /api/alerts/:alertId/pause func PauseAlert(c *middleware.Context, dto dtos.PauseAlertCommand) Response { cmd := models.PauseAlertCommand{ OrgId: c.OrgId, diff --git a/public/app/features/dashboard/alerting_srv.ts b/public/app/features/dashboard/alerting_srv.ts new file mode 100644 index 00000000000..0e1c160b726 --- /dev/null +++ b/public/app/features/dashboard/alerting_srv.ts @@ -0,0 +1,22 @@ +/// + +import config from 'app/core/config'; +import angular from 'angular'; +import moment from 'moment'; +import _ from 'lodash'; + +import coreModule from 'app/core/core_module'; + +export class AlertingSrv { + dashboard: any; + alerts: any[]; + + init(dashboard, alerts) { + this.dashboard = dashboard; + this.alerts = alerts || []; + } +} + + +coreModule.service('alertingSrv', AlertingSrv); + diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index b49b910f95f..3aa298001a5 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -1,5 +1,6 @@ define([ './dashboard_ctrl', + './alerting_srv', './dashboardLoaderSrv', './dashnav/dashnav', './submenu/submenu', diff --git a/public/app/features/dashboard/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts index 0e80598e758..649a04474cc 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -16,6 +16,7 @@ export class DashboardCtrl { dashboardKeybindings, timeSrv, variableSrv, + alertingSrv, dashboardSrv, unsavedChangesSrv, dynamicDashboardSrv, @@ -43,6 +44,7 @@ export class DashboardCtrl { // init services timeSrv.init(dashboard); + alertingSrv.init(dashboard, data.alerts); // template values service needs to initialize completely before // the rest of the dashboard can load diff --git a/public/app/features/dashboard/dashboard_srv.ts b/public/app/features/dashboard/dashboard_srv.ts index 94026233134..96a3bb1af7b 100644 --- a/public/app/features/dashboard/dashboard_srv.ts +++ b/public/app/features/dashboard/dashboard_srv.ts @@ -1,595 +1,113 @@ /// -import config from 'app/core/config'; -import angular from 'angular'; -import moment from 'moment'; import _ from 'lodash'; -import $ from 'jquery'; - -import {Emitter} from 'app/core/core'; -import {contextSrv} from 'app/core/services/context_srv'; import coreModule from 'app/core/core_module'; - -export class DashboardModel { - id: any; - title: any; - autoUpdate: any; - description: any; - tags: any; - style: any; - timezone: any; - editable: any; - hideControls: any; - sharedCrosshair: any; - rows: any; - time: any; - timepicker: any; - templating: any; - annotations: any; - refresh: any; - snapshot: any; - schemaVersion: number; - version: number; - revision: number; - links: any; - gnetId: any; - meta: any; - events: any; - - constructor(data, meta) { - if (!data) { - data = {}; - } - - this.events = new Emitter(); - this.id = data.id || null; - this.revision = data.revision; - this.title = data.title || 'No Title'; - this.autoUpdate = data.autoUpdate; - this.description = data.description; - this.tags = data.tags || []; - this.style = data.style || "dark"; - this.timezone = data.timezone || ''; - this.editable = data.editable !== false; - this.hideControls = data.hideControls || false; - this.sharedCrosshair = data.sharedCrosshair || false; - this.rows = data.rows || []; - this.time = data.time || { from: 'now-6h', to: 'now' }; - this.timepicker = data.timepicker || {}; - this.templating = this.ensureListExist(data.templating); - this.annotations = this.ensureListExist(data.annotations); - this.refresh = data.refresh; - this.snapshot = data.snapshot; - this.schemaVersion = data.schemaVersion || 0; - this.version = data.version || 0; - this.links = data.links || []; - this.gnetId = data.gnetId || null; - - this.updateSchema(data); - this.initMeta(meta); - } - - private initMeta(meta) { - meta = meta || {}; - - meta.canShare = meta.canShare !== false; - meta.canSave = meta.canSave !== false; - meta.canStar = meta.canStar !== false; - meta.canEdit = meta.canEdit !== false; - - if (!this.editable) { - meta.canEdit = false; - meta.canDelete = false; - meta.canSave = false; - this.hideControls = true; - } - - this.meta = meta; - } - - // cleans meta data and other non peristent state - getSaveModelClone() { - // temp remove stuff - var events = this.events; - var meta = this.meta; - delete this.events; - delete this.meta; - - events.emit('prepare-save-model'); - var copy = $.extend(true, {}, this); - - // restore properties - this.events = events; - this.meta = meta; - return copy; - } - - private ensureListExist(data) { - if (!data) { data = {}; } - if (!data.list) { data.list = []; } - return data; - } - - getNextPanelId() { - var i, j, row, panel, max = 0; - for (i = 0; i < this.rows.length; i++) { - row = this.rows[i]; - for (j = 0; j < row.panels.length; j++) { - panel = row.panels[j]; - if (panel.id > max) { max = panel.id; } - } - } - return max + 1; - } - - forEachPanel(callback) { - var i, j, row; - for (i = 0; i < this.rows.length; i++) { - row = this.rows[i]; - for (j = 0; j < row.panels.length; j++) { - callback(row.panels[j], j, row, i); - } - } - } - - getPanelById(id) { - for (var i = 0; i < this.rows.length; i++) { - var row = this.rows[i]; - for (var j = 0; j < row.panels.length; j++) { - var panel = row.panels[j]; - if (panel.id === id) { - return panel; - } - } - } - return null; - } - - rowSpan(row) { - return _.reduce(row.panels, function(p,v) { - return p + v.span; - },0); - }; - - addPanel(panel, row) { - var rowSpan = this.rowSpan(row); - var panelCount = row.panels.length; - var space = (12 - rowSpan) - panel.span; - panel.id = this.getNextPanelId(); - - // try to make room of there is no space left - if (space <= 0) { - if (panelCount === 1) { - row.panels[0].span = 6; - panel.span = 6; - } else if (panelCount === 2) { - row.panels[0].span = 4; - row.panels[1].span = 4; - panel.span = 4; - } - } - - row.panels.push(panel); - } - - isSubmenuFeaturesEnabled() { - var visableTemplates = _.filter(this.templating.list, function(template) { - return template.hideVariable === undefined || template.hideVariable === false; - }); - - return visableTemplates.length > 0 || this.annotations.list.length > 0 || this.links.length > 0; - } - - getPanelInfoById(panelId) { - var result: any = {}; - _.each(this.rows, function(row) { - _.each(row.panels, function(panel, index) { - if (panel.id === panelId) { - result.panel = panel; - result.row = row; - result.index = index; - } - }); - }); - - if (!result.panel) { - return null; - } - - return result; - } - - duplicatePanel(panel, row) { - var rowIndex = _.indexOf(this.rows, row); - var newPanel = angular.copy(panel); - newPanel.id = this.getNextPanelId(); - - delete newPanel.repeat; - delete newPanel.repeatIteration; - delete newPanel.repeatPanelId; - delete newPanel.scopedVars; - - var currentRow = this.rows[rowIndex]; - currentRow.panels.push(newPanel); - return newPanel; - } - - formatDate(date, format) { - date = moment.isMoment(date) ? date : moment(date); - format = format || 'YYYY-MM-DD HH:mm:ss'; - this.timezone = this.getTimezone(); - - return this.timezone === 'browser' ? - moment(date).format(format) : - moment.utc(date).format(format); - } - - getRelativeTime(date) { - date = moment.isMoment(date) ? date : moment(date); - - return this.timezone === 'browser' ? - moment(date).fromNow() : - moment.utc(date).fromNow(); - } - - getNextQueryLetter(panel) { - var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - - return _.find(letters, function(refId) { - return _.every(panel.targets, function(other) { - return other.refId !== refId; - }); - }); - } - - isTimezoneUtc() { - return this.getTimezone() === 'utc'; - } - - getTimezone() { - return this.timezone ? this.timezone : contextSrv.user.timezone; - } - - private updateSchema(old) { - var i, j, k; - var oldVersion = this.schemaVersion; - var panelUpgrades = []; - this.schemaVersion = 13; - - if (oldVersion === this.schemaVersion) { - return; - } - - // version 2 schema changes - if (oldVersion < 2) { - - if (old.services) { - if (old.services.filter) { - this.time = old.services.filter.time; - this.templating.list = old.services.filter.list || []; - } - } - - panelUpgrades.push(function(panel) { - // rename panel type - if (panel.type === 'graphite') { - panel.type = 'graph'; - } - - if (panel.type !== 'graph') { - return; - } - - if (_.isBoolean(panel.legend)) { panel.legend = { show: panel.legend }; } - - if (panel.grid) { - if (panel.grid.min) { - panel.grid.leftMin = panel.grid.min; - delete panel.grid.min; - } - - if (panel.grid.max) { - panel.grid.leftMax = panel.grid.max; - delete panel.grid.max; - } - } - - if (panel.y_format) { - panel.y_formats[0] = panel.y_format; - delete panel.y_format; - } - - if (panel.y2_format) { - panel.y_formats[1] = panel.y2_format; - delete panel.y2_format; - } - }); - } - - // schema version 3 changes - if (oldVersion < 3) { - // ensure panel ids - var maxId = this.getNextPanelId(); - panelUpgrades.push(function(panel) { - if (!panel.id) { - panel.id = maxId; - maxId += 1; - } - }); - } - - // schema version 4 changes - if (oldVersion < 4) { - // move aliasYAxis changes - panelUpgrades.push(function(panel) { - if (panel.type !== 'graph') { return; } - _.each(panel.aliasYAxis, function(value, key) { - panel.seriesOverrides = [{ alias: key, yaxis: value }]; - }); - delete panel.aliasYAxis; - }); - } - - if (oldVersion < 6) { - // move pulldowns to new schema - var annotations = _.find(old.pulldowns, { type: 'annotations' }); - - if (annotations) { - this.annotations = { - list: annotations.annotations || [], - }; - } - - // update template variables - for (i = 0 ; i < this.templating.list.length; i++) { - var variable = this.templating.list[i]; - if (variable.datasource === void 0) { variable.datasource = null; } - if (variable.type === 'filter') { variable.type = 'query'; } - if (variable.type === void 0) { variable.type = 'query'; } - if (variable.allFormat === void 0) { variable.allFormat = 'glob'; } - } - } - - if (oldVersion < 7) { - if (old.nav && old.nav.length) { - this.timepicker = old.nav[0]; - } - - // ensure query refIds - panelUpgrades.push(function(panel) { - _.each(panel.targets, function(target) { - if (!target.refId) { - target.refId = this.getNextQueryLetter(panel); - } - }.bind(this)); - }); - } - - if (oldVersion < 8) { - panelUpgrades.push(function(panel) { - _.each(panel.targets, function(target) { - // update old influxdb query schema - if (target.fields && target.tags && target.groupBy) { - if (target.rawQuery) { - delete target.fields; - delete target.fill; - } else { - target.select = _.map(target.fields, function(field) { - var parts = []; - parts.push({type: 'field', params: [field.name]}); - parts.push({type: field.func, params: []}); - if (field.mathExpr) { - parts.push({type: 'math', params: [field.mathExpr]}); - } - if (field.asExpr) { - parts.push({type: 'alias', params: [field.asExpr]}); - } - return parts; - }); - delete target.fields; - _.each(target.groupBy, function(part) { - if (part.type === 'time' && part.interval) { - part.params = [part.interval]; - delete part.interval; - } - if (part.type === 'tag' && part.key) { - part.params = [part.key]; - delete part.key; - } - }); - - if (target.fill) { - target.groupBy.push({type: 'fill', params: [target.fill]}); - delete target.fill; - } - } - } - }); - }); - } - - // schema version 9 changes - if (oldVersion < 9) { - // move aliasYAxis changes - panelUpgrades.push(function(panel) { - if (panel.type !== 'singlestat' && panel.thresholds !== "") { return; } - - if (panel.thresholds) { - var k = panel.thresholds.split(","); - - if (k.length >= 3) { - k.shift(); - panel.thresholds = k.join(","); - } - } - }); - } - - // schema version 10 changes - if (oldVersion < 10) { - // move aliasYAxis changes - panelUpgrades.push(function(panel) { - if (panel.type !== 'table') { return; } - - _.each(panel.styles, function(style) { - if (style.thresholds && style.thresholds.length >= 3) { - var k = style.thresholds; - k.shift(); - style.thresholds = k; - } - }); - }); - } - - if (oldVersion < 12) { - // update template variables - _.each(this.templating.list, function(templateVariable) { - if (templateVariable.refresh) { templateVariable.refresh = 1; } - if (!templateVariable.refresh) { templateVariable.refresh = 0; } - if (templateVariable.hideVariable) { - templateVariable.hide = 2; - } else if (templateVariable.hideLabel) { - templateVariable.hide = 1; - } else { - templateVariable.hide = 0; - } - }); - } - - if (oldVersion < 12) { - // update graph yaxes changes - panelUpgrades.push(function(panel) { - if (panel.type !== 'graph') { return; } - if (!panel.grid) { return; } - - if (!panel.yaxes) { - panel.yaxes = [ - { - show: panel['y-axis'], - min: panel.grid.leftMin, - max: panel.grid.leftMax, - logBase: panel.grid.leftLogBase, - format: panel.y_formats[0], - label: panel.leftYAxisLabel, - }, - { - show: panel['y-axis'], - min: panel.grid.rightMin, - max: panel.grid.rightMax, - logBase: panel.grid.rightLogBase, - format: panel.y_formats[1], - label: panel.rightYAxisLabel, - } - ]; - - panel.xaxis = { - show: panel['x-axis'], - }; - - delete panel.grid.leftMin; - delete panel.grid.leftMax; - delete panel.grid.leftLogBase; - delete panel.grid.rightMin; - delete panel.grid.rightMax; - delete panel.grid.rightLogBase; - delete panel.y_formats; - delete panel.leftYAxisLabel; - delete panel.rightYAxisLabel; - delete panel['y-axis']; - delete panel['x-axis']; - } - }); - } - - if (oldVersion < 13) { - // update graph yaxes changes - panelUpgrades.push(function(panel) { - if (panel.type !== 'graph') { return; } - - panel.thresholds = []; - var t1: any = {}, t2: any = {}; - - if (panel.grid.threshold1 !== null) { - t1.value = panel.grid.threshold1; - if (panel.grid.thresholdLine) { - t1.line = true; - t1.lineColor = panel.grid.threshold1Color; - t1.colorMode = 'custom'; - } else { - t1.fill = true; - t1.fillColor = panel.grid.threshold1Color; - t1.colorMode = 'custom'; - } - } - - if (panel.grid.threshold2 !== null) { - t2.value = panel.grid.threshold2; - if (panel.grid.thresholdLine) { - t2.line = true; - t2.lineColor = panel.grid.threshold2Color; - t2.colorMode = 'custom'; - } else { - t2.fill = true; - t2.fillColor = panel.grid.threshold2Color; - t2.colorMode = 'custom'; - } - } - - if (_.isNumber(t1.value)) { - if (_.isNumber(t2.value)) { - if (t1.value > t2.value) { - t1.op = t2.op = 'lt'; - panel.thresholds.push(t1); - panel.thresholds.push(t2); - } else { - t1.op = t2.op = 'gt'; - panel.thresholds.push(t1); - panel.thresholds.push(t2); - } - } else { - t1.op = 'gt'; - panel.thresholds.push(t1); - } - } - - delete panel.grid.threshold1; - delete panel.grid.threshold1Color; - delete panel.grid.threshold2; - delete panel.grid.threshold2Color; - delete panel.grid.thresholdLine; - }); - } - - if (panelUpgrades.length === 0) { - return; - } - - for (i = 0; i < this.rows.length; i++) { - var row = this.rows[i]; - for (j = 0; j < row.panels.length; j++) { - for (k = 0; k < panelUpgrades.length; k++) { - panelUpgrades[k].call(this, row.panels[j]); - } - } - } - } -} - +import {DashboardModel} from './model'; export class DashboardSrv { - currentDashboard: any; + dash: any; + + /** @ngInject */ + constructor(private backendSrv, private $rootScope, private $location) { + } create(dashboard, meta) { return new DashboardModel(dashboard, meta); } setCurrent(dashboard) { - this.currentDashboard = dashboard; + this.dash = dashboard; } getCurrent() { - return this.currentDashboard; + return this.dash; } + + saveDashboard(options) { + if (!this.dash.meta.canSave && options.makeEditable !== true) { + return Promise.resolve(); + } + + var clone = this.dash.getSaveModelClone(); + + return this.backendSrv.saveDashboard(clone, options).then(data => { + this.dash.version = data.version; + + this.$rootScope.appEvent('dashboard-saved', this.dash); + + var dashboardUrl = '/dashboard/db/' + data.slug; + if (dashboardUrl !== this.$location.path()) { + this.$location.url(dashboardUrl); + } + + this.$rootScope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + clone.title]); + }).catch(this.handleSaveDashboardError.bind(this)); + } + + handleSaveDashboardError(err) { + if (err.data && err.data.status === "version-mismatch") { + err.isHandled = true; + + this.$rootScope.appEvent('confirm-modal', { + title: 'Conflict', + text: 'Someone else has updated this dashboard.', + text2: 'Would you still like to save this dashboard?', + yesText: "Save & Overwrite", + icon: "fa-warning", + onConfirm: () => { + this.saveDashboard({overwrite: true}); + } + }); + } + + if (err.data && err.data.status === "name-exists") { + err.isHandled = true; + + this.$rootScope.appEvent('confirm-modal', { + title: 'Conflict', + text: 'Dashboard with the same name exists.', + text2: 'Would you still like to save this dashboard?', + yesText: "Save & Overwrite", + icon: "fa-warning", + onConfirm: () => { + this.saveDashboard({overwrite: true}); + } + }); + } + + if (err.data && err.data.status === "plugin-dashboard") { + err.isHandled = true; + + this.$rootScope.appEvent('confirm-modal', { + title: 'Plugin Dashboard', + text: err.data.message, + text2: 'Your changes will be lost when you update the plugin. Use Save As to create custom version.', + yesText: "Overwrite", + icon: "fa-warning", + altActionText: "Save As", + onAltAction: () => { + this.saveDashboardAs(); + }, + onConfirm: function() { + this.saveDashboard({overwrite: true}); + } + }); + } + } + + saveDashboardAs() { + var newScope = this.$rootScope.$new(); + newScope.clone = this.dash.getSaveModelClone(); + newScope.clone.editable = true; + newScope.clone.hideControls = false; + + this.$rootScope.appEvent('show-modal', { + src: 'public/app/features/dashboard/partials/saveDashboardAs.html', + scope: newScope, + modalClass: 'modal--narrow' + }); + } + } coreModule.service('dashboardSrv', DashboardSrv); diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts index 6ef324ccf44..929cc66a5b0 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/dashnav/dashnav.ts @@ -9,7 +9,7 @@ import {DashboardExporter} from '../export/exporter'; export class DashNavCtrl { /** @ngInject */ - constructor($scope, $rootScope, alertSrv, $location, playlistSrv, backendSrv, $timeout, datasourceSrv) { + constructor($scope, $rootScope, dashboardSrv, $location, playlistSrv, backendSrv, $timeout, datasourceSrv) { $scope.init = function() { $scope.onAppEvent('save-dashboard', $scope.saveDashboard); @@ -71,88 +71,14 @@ export class DashNavCtrl { $scope.makeEditable = function() { $scope.dashboard.editable = true; - var clone = $scope.dashboard.getSaveModelClone(); - - backendSrv.saveDashboard(clone, {overwrite: false}).then(function(data) { - $scope.dashboard.version = data.version; - $scope.appEvent('dashboard-saved', $scope.dashboard); - $scope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + clone.title]); - + return dashboardSrv.saveDashboard({makeEditable: true, overwrite: false}).then(function() { // force refresh whole page window.location.href = window.location.href; - }, $scope.handleSaveDashError); + }); }; $scope.saveDashboard = function(options) { - if ($scope.dashboardMeta.canSave === false) { - return; - } - - var clone = $scope.dashboard.getSaveModelClone(); - - backendSrv.saveDashboard(clone, options).then(function(data) { - $scope.dashboard.version = data.version; - $scope.appEvent('dashboard-saved', $scope.dashboard); - - var dashboardUrl = '/dashboard/db/' + data.slug; - - if (dashboardUrl !== $location.path()) { - $location.url(dashboardUrl); - } - - $scope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + clone.title]); - }, $scope.handleSaveDashError); - }; - - $scope.handleSaveDashError = function(err) { - if (err.data && err.data.status === "version-mismatch") { - err.isHandled = true; - - $scope.appEvent('confirm-modal', { - title: 'Conflict', - text: 'Someone else has updated this dashboard.', - text2: 'Would you still like to save this dashboard?', - yesText: "Save & Overwrite", - icon: "fa-warning", - onConfirm: function() { - $scope.saveDashboard({overwrite: true}); - } - }); - } - - if (err.data && err.data.status === "name-exists") { - err.isHandled = true; - - $scope.appEvent('confirm-modal', { - title: 'Conflict', - text: 'Dashboard with the same name exists.', - text2: 'Would you still like to save this dashboard?', - yesText: "Save & Overwrite", - icon: "fa-warning", - onConfirm: function() { - $scope.saveDashboard({overwrite: true}); - } - }); - } - - if (err.data && err.data.status === "plugin-dashboard") { - err.isHandled = true; - - $scope.appEvent('confirm-modal', { - title: 'Plugin Dashboard', - text: err.data.message, - text2: 'Your changes will be lost when you update the plugin. Use Save As to create custom version.', - yesText: "Overwrite", - icon: "fa-warning", - altActionText: "Save As", - onAltAction: function() { - $scope.saveDashboardAs(); - }, - onConfirm: function() { - $scope.saveDashboard({overwrite: true}); - } - }); - } + return dashboardSrv.saveDashboard(options); }; $scope.deleteDashboard = function() { @@ -189,16 +115,7 @@ export class DashNavCtrl { }; $scope.saveDashboardAs = function() { - var newScope = $rootScope.$new(); - newScope.clone = $scope.dashboard.getSaveModelClone(); - newScope.clone.editable = true; - newScope.clone.hideControls = false; - - $scope.appEvent('show-modal', { - src: 'public/app/features/dashboard/partials/saveDashboardAs.html', - scope: newScope, - modalClass: 'modal--narrow' - }); + return dashboardSrv.saveDashboardAs(); }; $scope.viewJson = function() { diff --git a/public/app/features/dashboard/model.ts b/public/app/features/dashboard/model.ts new file mode 100644 index 00000000000..3149d76b4df --- /dev/null +++ b/public/app/features/dashboard/model.ts @@ -0,0 +1,576 @@ +/// + +import config from 'app/core/config'; +import angular from 'angular'; +import moment from 'moment'; +import _ from 'lodash'; +import $ from 'jquery'; + +import {Emitter} from 'app/core/core'; +import {contextSrv} from 'app/core/services/context_srv'; + +export class DashboardModel { + id: any; + title: any; + autoUpdate: any; + description: any; + tags: any; + style: any; + timezone: any; + editable: any; + hideControls: any; + sharedCrosshair: any; + rows: any; + time: any; + timepicker: any; + templating: any; + annotations: any; + refresh: any; + snapshot: any; + schemaVersion: number; + version: number; + revision: number; + links: any; + gnetId: any; + meta: any; + events: any; + + constructor(data, meta) { + if (!data) { + data = {}; + } + + this.events = new Emitter(); + this.id = data.id || null; + this.revision = data.revision; + this.title = data.title || 'No Title'; + this.autoUpdate = data.autoUpdate; + this.description = data.description; + this.tags = data.tags || []; + this.style = data.style || "dark"; + this.timezone = data.timezone || ''; + this.editable = data.editable !== false; + this.hideControls = data.hideControls || false; + this.sharedCrosshair = data.sharedCrosshair || false; + this.rows = data.rows || []; + this.time = data.time || { from: 'now-6h', to: 'now' }; + this.timepicker = data.timepicker || {}; + this.templating = this.ensureListExist(data.templating); + this.annotations = this.ensureListExist(data.annotations); + this.refresh = data.refresh; + this.snapshot = data.snapshot; + this.schemaVersion = data.schemaVersion || 0; + this.version = data.version || 0; + this.links = data.links || []; + this.gnetId = data.gnetId || null; + + this.updateSchema(data); + this.initMeta(meta); + } + + private initMeta(meta) { + meta = meta || {}; + + meta.canShare = meta.canShare !== false; + meta.canSave = meta.canSave !== false; + meta.canStar = meta.canStar !== false; + meta.canEdit = meta.canEdit !== false; + + if (!this.editable) { + meta.canEdit = false; + meta.canDelete = false; + meta.canSave = false; + this.hideControls = true; + } + + this.meta = meta; + } + + // cleans meta data and other non peristent state + getSaveModelClone() { + // temp remove stuff + var events = this.events; + var meta = this.meta; + delete this.events; + delete this.meta; + + events.emit('prepare-save-model'); + var copy = $.extend(true, {}, this); + + // restore properties + this.events = events; + this.meta = meta; + return copy; + } + + private ensureListExist(data) { + if (!data) { data = {}; } + if (!data.list) { data.list = []; } + return data; + } + + getNextPanelId() { + var i, j, row, panel, max = 0; + for (i = 0; i < this.rows.length; i++) { + row = this.rows[i]; + for (j = 0; j < row.panels.length; j++) { + panel = row.panels[j]; + if (panel.id > max) { max = panel.id; } + } + } + return max + 1; + } + + forEachPanel(callback) { + var i, j, row; + for (i = 0; i < this.rows.length; i++) { + row = this.rows[i]; + for (j = 0; j < row.panels.length; j++) { + callback(row.panels[j], j, row, i); + } + } + } + + getPanelById(id) { + for (var i = 0; i < this.rows.length; i++) { + var row = this.rows[i]; + for (var j = 0; j < row.panels.length; j++) { + var panel = row.panels[j]; + if (panel.id === id) { + return panel; + } + } + } + return null; + } + + rowSpan(row) { + return _.reduce(row.panels, function(p,v) { + return p + v.span; + },0); + }; + + addPanel(panel, row) { + var rowSpan = this.rowSpan(row); + var panelCount = row.panels.length; + var space = (12 - rowSpan) - panel.span; + panel.id = this.getNextPanelId(); + + // try to make room of there is no space left + if (space <= 0) { + if (panelCount === 1) { + row.panels[0].span = 6; + panel.span = 6; + } else if (panelCount === 2) { + row.panels[0].span = 4; + row.panels[1].span = 4; + panel.span = 4; + } + } + + row.panels.push(panel); + } + + isSubmenuFeaturesEnabled() { + var visableTemplates = _.filter(this.templating.list, function(template) { + return template.hideVariable === undefined || template.hideVariable === false; + }); + + return visableTemplates.length > 0 || this.annotations.list.length > 0 || this.links.length > 0; + } + + getPanelInfoById(panelId) { + var result: any = {}; + _.each(this.rows, function(row) { + _.each(row.panels, function(panel, index) { + if (panel.id === panelId) { + result.panel = panel; + result.row = row; + result.index = index; + } + }); + }); + + if (!result.panel) { + return null; + } + + return result; + } + + duplicatePanel(panel, row) { + var rowIndex = _.indexOf(this.rows, row); + var newPanel = angular.copy(panel); + newPanel.id = this.getNextPanelId(); + + delete newPanel.repeat; + delete newPanel.repeatIteration; + delete newPanel.repeatPanelId; + delete newPanel.scopedVars; + + var currentRow = this.rows[rowIndex]; + currentRow.panels.push(newPanel); + return newPanel; + } + + formatDate(date, format) { + date = moment.isMoment(date) ? date : moment(date); + format = format || 'YYYY-MM-DD HH:mm:ss'; + this.timezone = this.getTimezone(); + + return this.timezone === 'browser' ? + moment(date).format(format) : + moment.utc(date).format(format); + } + + getRelativeTime(date) { + date = moment.isMoment(date) ? date : moment(date); + + return this.timezone === 'browser' ? + moment(date).fromNow() : + moment.utc(date).fromNow(); + } + + getNextQueryLetter(panel) { + var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + return _.find(letters, function(refId) { + return _.every(panel.targets, function(other) { + return other.refId !== refId; + }); + }); + } + + isTimezoneUtc() { + return this.getTimezone() === 'utc'; + } + + getTimezone() { + return this.timezone ? this.timezone : contextSrv.user.timezone; + } + + private updateSchema(old) { + var i, j, k; + var oldVersion = this.schemaVersion; + var panelUpgrades = []; + this.schemaVersion = 13; + + if (oldVersion === this.schemaVersion) { + return; + } + + // version 2 schema changes + if (oldVersion < 2) { + + if (old.services) { + if (old.services.filter) { + this.time = old.services.filter.time; + this.templating.list = old.services.filter.list || []; + } + } + + panelUpgrades.push(function(panel) { + // rename panel type + if (panel.type === 'graphite') { + panel.type = 'graph'; + } + + if (panel.type !== 'graph') { + return; + } + + if (_.isBoolean(panel.legend)) { panel.legend = { show: panel.legend }; } + + if (panel.grid) { + if (panel.grid.min) { + panel.grid.leftMin = panel.grid.min; + delete panel.grid.min; + } + + if (panel.grid.max) { + panel.grid.leftMax = panel.grid.max; + delete panel.grid.max; + } + } + + if (panel.y_format) { + panel.y_formats[0] = panel.y_format; + delete panel.y_format; + } + + if (panel.y2_format) { + panel.y_formats[1] = panel.y2_format; + delete panel.y2_format; + } + }); + } + + // schema version 3 changes + if (oldVersion < 3) { + // ensure panel ids + var maxId = this.getNextPanelId(); + panelUpgrades.push(function(panel) { + if (!panel.id) { + panel.id = maxId; + maxId += 1; + } + }); + } + + // schema version 4 changes + if (oldVersion < 4) { + // move aliasYAxis changes + panelUpgrades.push(function(panel) { + if (panel.type !== 'graph') { return; } + _.each(panel.aliasYAxis, function(value, key) { + panel.seriesOverrides = [{ alias: key, yaxis: value }]; + }); + delete panel.aliasYAxis; + }); + } + + if (oldVersion < 6) { + // move pulldowns to new schema + var annotations = _.find(old.pulldowns, { type: 'annotations' }); + + if (annotations) { + this.annotations = { + list: annotations.annotations || [], + }; + } + + // update template variables + for (i = 0 ; i < this.templating.list.length; i++) { + var variable = this.templating.list[i]; + if (variable.datasource === void 0) { variable.datasource = null; } + if (variable.type === 'filter') { variable.type = 'query'; } + if (variable.type === void 0) { variable.type = 'query'; } + if (variable.allFormat === void 0) { variable.allFormat = 'glob'; } + } + } + + if (oldVersion < 7) { + if (old.nav && old.nav.length) { + this.timepicker = old.nav[0]; + } + + // ensure query refIds + panelUpgrades.push(function(panel) { + _.each(panel.targets, function(target) { + if (!target.refId) { + target.refId = this.getNextQueryLetter(panel); + } + }.bind(this)); + }); + } + + if (oldVersion < 8) { + panelUpgrades.push(function(panel) { + _.each(panel.targets, function(target) { + // update old influxdb query schema + if (target.fields && target.tags && target.groupBy) { + if (target.rawQuery) { + delete target.fields; + delete target.fill; + } else { + target.select = _.map(target.fields, function(field) { + var parts = []; + parts.push({type: 'field', params: [field.name]}); + parts.push({type: field.func, params: []}); + if (field.mathExpr) { + parts.push({type: 'math', params: [field.mathExpr]}); + } + if (field.asExpr) { + parts.push({type: 'alias', params: [field.asExpr]}); + } + return parts; + }); + delete target.fields; + _.each(target.groupBy, function(part) { + if (part.type === 'time' && part.interval) { + part.params = [part.interval]; + delete part.interval; + } + if (part.type === 'tag' && part.key) { + part.params = [part.key]; + delete part.key; + } + }); + + if (target.fill) { + target.groupBy.push({type: 'fill', params: [target.fill]}); + delete target.fill; + } + } + } + }); + }); + } + + // schema version 9 changes + if (oldVersion < 9) { + // move aliasYAxis changes + panelUpgrades.push(function(panel) { + if (panel.type !== 'singlestat' && panel.thresholds !== "") { return; } + + if (panel.thresholds) { + var k = panel.thresholds.split(","); + + if (k.length >= 3) { + k.shift(); + panel.thresholds = k.join(","); + } + } + }); + } + + // schema version 10 changes + if (oldVersion < 10) { + // move aliasYAxis changes + panelUpgrades.push(function(panel) { + if (panel.type !== 'table') { return; } + + _.each(panel.styles, function(style) { + if (style.thresholds && style.thresholds.length >= 3) { + var k = style.thresholds; + k.shift(); + style.thresholds = k; + } + }); + }); + } + + if (oldVersion < 12) { + // update template variables + _.each(this.templating.list, function(templateVariable) { + if (templateVariable.refresh) { templateVariable.refresh = 1; } + if (!templateVariable.refresh) { templateVariable.refresh = 0; } + if (templateVariable.hideVariable) { + templateVariable.hide = 2; + } else if (templateVariable.hideLabel) { + templateVariable.hide = 1; + } else { + templateVariable.hide = 0; + } + }); + } + + if (oldVersion < 12) { + // update graph yaxes changes + panelUpgrades.push(function(panel) { + if (panel.type !== 'graph') { return; } + if (!panel.grid) { return; } + + if (!panel.yaxes) { + panel.yaxes = [ + { + show: panel['y-axis'], + min: panel.grid.leftMin, + max: panel.grid.leftMax, + logBase: panel.grid.leftLogBase, + format: panel.y_formats[0], + label: panel.leftYAxisLabel, + }, + { + show: panel['y-axis'], + min: panel.grid.rightMin, + max: panel.grid.rightMax, + logBase: panel.grid.rightLogBase, + format: panel.y_formats[1], + label: panel.rightYAxisLabel, + } + ]; + + panel.xaxis = { + show: panel['x-axis'], + }; + + delete panel.grid.leftMin; + delete panel.grid.leftMax; + delete panel.grid.leftLogBase; + delete panel.grid.rightMin; + delete panel.grid.rightMax; + delete panel.grid.rightLogBase; + delete panel.y_formats; + delete panel.leftYAxisLabel; + delete panel.rightYAxisLabel; + delete panel['y-axis']; + delete panel['x-axis']; + } + }); + } + + if (oldVersion < 13) { + // update graph yaxes changes + panelUpgrades.push(function(panel) { + if (panel.type !== 'graph') { return; } + + panel.thresholds = []; + var t1: any = {}, t2: any = {}; + + if (panel.grid.threshold1 !== null) { + t1.value = panel.grid.threshold1; + if (panel.grid.thresholdLine) { + t1.line = true; + t1.lineColor = panel.grid.threshold1Color; + t1.colorMode = 'custom'; + } else { + t1.fill = true; + t1.fillColor = panel.grid.threshold1Color; + t1.colorMode = 'custom'; + } + } + + if (panel.grid.threshold2 !== null) { + t2.value = panel.grid.threshold2; + if (panel.grid.thresholdLine) { + t2.line = true; + t2.lineColor = panel.grid.threshold2Color; + t2.colorMode = 'custom'; + } else { + t2.fill = true; + t2.fillColor = panel.grid.threshold2Color; + t2.colorMode = 'custom'; + } + } + + if (_.isNumber(t1.value)) { + if (_.isNumber(t2.value)) { + if (t1.value > t2.value) { + t1.op = t2.op = 'lt'; + panel.thresholds.push(t1); + panel.thresholds.push(t2); + } else { + t1.op = t2.op = 'gt'; + panel.thresholds.push(t1); + panel.thresholds.push(t2); + } + } else { + t1.op = 'gt'; + panel.thresholds.push(t1); + } + } + + delete panel.grid.threshold1; + delete panel.grid.threshold1Color; + delete panel.grid.threshold2; + delete panel.grid.threshold2Color; + delete panel.grid.thresholdLine; + }); + } + + if (panelUpgrades.length === 0) { + return; + } + + for (i = 0; i < this.rows.length; i++) { + var row = this.rows[i]; + for (j = 0; j < row.panels.length; j++) { + for (k = 0; k < panelUpgrades.length; k++) { + panelUpgrades[k].call(this, row.panels[j]); + } + } + } + } +} +