From 437b880b3e54f3f4bf6eb9b3681f5d0e5985c2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 25 Mar 2016 22:14:29 +0100 Subject: [PATCH 001/350] feat(plugins): work on setup wizard started --- public/app/core/components/grafana_app.ts | 3 + public/app/core/services/util_srv.js | 31 --------- public/app/core/services/util_srv.ts | 41 ++++++++++++ .../plugins/import_list/import_list.ts | 4 -- .../app/features/plugins/partials/wizard.html | 30 +++++++++ .../app/features/plugins/plugin_edit_ctrl.ts | 64 +++++++++++++++---- public/app/features/plugins/wizard.ts | 47 ++++++++++++++ 7 files changed, 173 insertions(+), 47 deletions(-) delete mode 100644 public/app/core/services/util_srv.js create mode 100644 public/app/core/services/util_srv.ts create mode 100644 public/app/features/plugins/partials/wizard.html create mode 100644 public/app/features/plugins/wizard.ts diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index 0a2e49e5d72..5a72567c202 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -5,7 +5,9 @@ import store from 'app/core/store'; import _ from 'lodash'; import angular from 'angular'; import $ from 'jquery'; + import coreModule from 'app/core/core_module'; +import appEvents from 'app/core/app_events'; export class GrafanaCtrl { @@ -47,6 +49,7 @@ export class GrafanaCtrl { $rootScope.appEvent = function(name, payload) { $rootScope.$emit(name, payload); + appEvents.emit(name, payload); }; $rootScope.colors = [ diff --git a/public/app/core/services/util_srv.js b/public/app/core/services/util_srv.js deleted file mode 100644 index e6bf3ae08bf..00000000000 --- a/public/app/core/services/util_srv.js +++ /dev/null @@ -1,31 +0,0 @@ -define([ - 'angular', - '../core_module', -], -function (angular, coreModule) { - 'use strict'; - - coreModule.default.service('utilSrv', function($rootScope, $modal, $q) { - - this.init = function() { - $rootScope.onAppEvent('show-modal', this.showModal, $rootScope); - }; - - this.showModal = function(e, options) { - var modal = $modal({ - modalClass: options.modalClass, - template: options.src, - persist: false, - show: false, - scope: options.scope, - keyboard: false - }); - - $q.when(modal).then(function(modalEl) { - modalEl.modal('show'); - }); - }; - - }); - -}); diff --git a/public/app/core/services/util_srv.ts b/public/app/core/services/util_srv.ts new file mode 100644 index 00000000000..0f15a65a0dc --- /dev/null +++ b/public/app/core/services/util_srv.ts @@ -0,0 +1,41 @@ +/// + +import config from 'app/core/config'; +import _ from 'lodash'; +import $ from 'jquery'; + +import coreModule from 'app/core/core_module'; +import appEvents from 'app/core/app_events'; + +export class UtilSrv { + + /** @ngInject */ + constructor(private $rootScope, private $modal) { + } + + init() { + appEvents.on('show-modal', this.showModal.bind(this), this.$rootScope); + } + + showModal(options) { + if (options.model) { + options.scope = this.$rootScope.$new(); + options.scope.model = options.model; + } + + var modal = this.$modal({ + modalClass: options.modalClass, + template: options.src, + persist: false, + show: false, + scope: options.scope, + keyboard: false + }); + + Promise.resolve(modal).then(function(modalEl) { + modalEl.modal('show'); + }); + } +} + +coreModule.service('utilSrv', UtilSrv); diff --git a/public/app/features/plugins/import_list/import_list.ts b/public/app/features/plugins/import_list/import_list.ts index 8eb726b81c0..607cf8cc5ec 100644 --- a/public/app/features/plugins/import_list/import_list.ts +++ b/public/app/features/plugins/import_list/import_list.ts @@ -88,7 +88,3 @@ export function dashboardImportList() { } coreModule.directive('dashboardImportList', dashboardImportList); - - - - diff --git a/public/app/features/plugins/partials/wizard.html b/public/app/features/plugins/partials/wizard.html new file mode 100644 index 00000000000..4a532e81a0f --- /dev/null +++ b/public/app/features/plugins/partials/wizard.html @@ -0,0 +1,30 @@ + + diff --git a/public/app/features/plugins/plugin_edit_ctrl.ts b/public/app/features/plugins/plugin_edit_ctrl.ts index 7f3f6bff08f..59af8f78c30 100644 --- a/public/app/features/plugins/plugin_edit_ctrl.ts +++ b/public/app/features/plugins/plugin_edit_ctrl.ts @@ -4,6 +4,8 @@ import angular from 'angular'; import _ from 'lodash'; import appEvents from 'app/core/app_events'; +import {WizardFlow} from './wizard'; + export class PluginEditCtrl { model: any; pluginIcon: string; @@ -79,20 +81,58 @@ export class PluginEditCtrl { } update() { - this.preUpdateHook().then(() => { - var updateCmd = _.extend({ - enabled: this.model.enabled, - pinned: this.model.pinned, - jsonData: this.model.jsonData, - secureJsonData: this.model.secureJsonData, - }, {}); + var wizard = new WizardFlow("Application Setup"); - return this.backendSrv.post(`/api/plugins/${this.pluginId}/settings`, updateCmd); - }) - .then(this.postUpdateHook) - .then((res) => { - window.location.href = window.location.href; + wizard.addStep("Validating form", () => { + return new Promise((resolve) => { + setTimeout(resolve, 2000); + }); }); + + wizard.addStep("Saving application config", () => { + return new Promise((resolve) => { + setTimeout(resolve, 2000); + }); + }); + + wizard.addStep("Validing key", () => { + return new Promise((resolve) => { + setTimeout(resolve, 2000); + }); + }); + + wizard.addStep("Adding Raintank metric data source", () => { + return new Promise((resolve) => { + setTimeout(resolve, 2000); + }); + }); + + wizard.addStep("Adding Raintank event data source", () => { + return new Promise((resolve) => { + setTimeout(resolve, 2000); + }); + }); + + wizard.addStep("Importing worldPing dashboards", () => { + return new Promise((resolve) => { + setTimeout(resolve, 2000); + }); + }); + + wizard.start(); + // this.preUpdateHook().then(() => { + // var updateCmd = _.extend({ + // enabled: this.model.enabled, + // pinned: this.model.pinned, + // jsonData: this.model.jsonData, + // secureJsonData: this.model.secureJsonData, + // }, {}); + // return this.backendSrv.post(`/api/plugins/${this.pluginId}/settings`, updateCmd); + // }) + // .then(this.postUpdateHook) + // .then((res) => { + // window.location.href = window.location.href; + // }); } importDashboards() { diff --git a/public/app/features/plugins/wizard.ts b/public/app/features/plugins/wizard.ts new file mode 100644 index 00000000000..741b0f01b3e --- /dev/null +++ b/public/app/features/plugins/wizard.ts @@ -0,0 +1,47 @@ +/// + +import config from 'app/core/config'; +import _ from 'lodash'; +import $ from 'jquery'; + +import coreModule from 'app/core/core_module'; +import appEvents from 'app/core/app_events'; + +export class WizardSrv { + + /** @ngInject */ + constructor() { + } + +} + +export class WizardStep { + name: string; + fn: any; +} + +export class WizardFlow { + name: string; + steps: WizardStep[]; + + constructor(name) { + this.name = name; + this.steps = []; + } + + addStep(name, stepFn) { + this.steps.push({ + name: name, + fn: stepFn + }); + } + + start() { + appEvents.emit('show-modal', { + src: 'public/app/features/plugins/partials/wizard.html', + model: this + }); + } +} + +coreModule.service('wizardSrv', WizardSrv); From 1f9922a5aa429a734430881bef3d3a6e69b8b3ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 14 Apr 2016 16:53:19 -0400 Subject: [PATCH 002/350] refactor(): moved dashboard_srv and DashboardCtrl to typescript --- packaging/publish/publish.sh | 16 +- public/app/features/dashboard/all.js | 3 +- .../app/features/dashboard/dashboardCtrl.js | 147 ---------- .../app/features/dashboard/dashboard_ctrl.ts | 154 ++++++++++ .../features/dashboard/dynamicDashboardSrv.js | 181 ------------ .../dashboard/dynamic_dashboard_srv.ts | 175 ++++++++++++ .../app/features/dashboard/submenu/submenu.ts | 3 +- public/test/core/utils/emitter_specs.ts | 2 +- .../test/specs/dynamicDashboardSrv-specs.js | 267 ------------------ .../test/specs/dynamic_dashboard_srv_specs.ts | 264 +++++++++++++++++ 10 files changed, 604 insertions(+), 608 deletions(-) delete mode 100644 public/app/features/dashboard/dashboardCtrl.js create mode 100644 public/app/features/dashboard/dashboard_ctrl.ts delete mode 100644 public/app/features/dashboard/dynamicDashboardSrv.js create mode 100644 public/app/features/dashboard/dynamic_dashboard_srv.ts delete mode 100644 public/test/specs/dynamicDashboardSrv-specs.js create mode 100644 public/test/specs/dynamic_dashboard_srv_specs.ts diff --git a/packaging/publish/publish.sh b/packaging/publish/publish.sh index dd0c935d2cd..23d20b5a613 100755 --- a/packaging/publish/publish.sh +++ b/packaging/publish/publish.sh @@ -1,22 +1,22 @@ #! /usr/bin/env bash -deb_ver=3.0.0-beta41460581169 -rpm_ver=3.0.0-beta41460581169 +deb_ver=3.0.0-beta51460658374 +rpm_ver=3.0.0-beta51460658374 #rpm_ver=3.0.0-1 -wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb +#wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb #package_cloud push grafana/stable/debian/jessie grafana_${deb_ver}_amd64.deb #package_cloud push grafana/stable/debian/wheezy grafana_${deb_ver}_amd64.deb -package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb -package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb +#package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb +#package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb -wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm +#wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm -package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm -ackage_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm +#package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm +package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm # package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm # package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index d110019add6..9d370921332 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -1,5 +1,5 @@ define([ - './dashboardCtrl', + './dashboard_ctrl', './dashboardLoaderSrv', './dashnav/dashnav', './submenu/submenu', @@ -14,7 +14,6 @@ define([ './unsavedChangesSrv', './timepicker/timepicker', './graphiteImportCtrl', - './dynamicDashboardSrv', './importCtrl', './impression_store', ], function () {}); diff --git a/public/app/features/dashboard/dashboardCtrl.js b/public/app/features/dashboard/dashboardCtrl.js deleted file mode 100644 index b6702631155..00000000000 --- a/public/app/features/dashboard/dashboardCtrl.js +++ /dev/null @@ -1,147 +0,0 @@ -define([ - 'angular', - 'jquery', - 'app/core/config', - 'moment', -], -function (angular, $, config, moment) { - "use strict"; - - var module = angular.module('grafana.controllers'); - - module.controller('DashboardCtrl', function( - $scope, - $rootScope, - dashboardKeybindings, - timeSrv, - templateValuesSrv, - dynamicDashboardSrv, - dashboardSrv, - unsavedChangesSrv, - dashboardViewStateSrv, - contextSrv, - $timeout) { - - $scope.editor = { index: 0 }; - $scope.panels = config.panels; - - var resizeEventTimeout; - - this.init = function(dashboard) { - $scope.resetRow(); - $scope.registerWindowResizeEvent(); - $scope.onAppEvent('show-json-editor', $scope.showJsonEditor); - $scope.setupDashboard(dashboard); - }; - - $scope.setupDashboard = function(data) { - $rootScope.performance.dashboardLoadStart = new Date().getTime(); - $rootScope.performance.panelsInitialized = 0; - $rootScope.performance.panelsRendered = 0; - - var dashboard = dashboardSrv.create(data.dashboard, data.meta); - dashboardSrv.setCurrent(dashboard); - - // init services - timeSrv.init(dashboard); - - // template values service needs to initialize completely before - // the rest of the dashboard can load - templateValuesSrv.init(dashboard).finally(function() { - dynamicDashboardSrv.init(dashboard); - unsavedChangesSrv.init(dashboard, $scope); - - $scope.dashboard = dashboard; - $scope.dashboardMeta = dashboard.meta; - $scope.dashboardViewState = dashboardViewStateSrv.create($scope); - - dashboardKeybindings.shortcuts($scope); - - $scope.updateSubmenuVisibility(); - $scope.setWindowTitleAndTheme(); - - $scope.appEvent("dashboard-loaded", $scope.dashboard); - }).catch(function(err) { - if (err.data && err.data.message) { err.message = err.data.message; } - $scope.appEvent("alert-error", ['Dashboard init failed', 'Template variables could not be initialized: ' + err.message]); - }); - }; - - $scope.updateSubmenuVisibility = function() { - $scope.submenuEnabled = $scope.dashboard.isSubmenuFeaturesEnabled(); - }; - - $scope.setWindowTitleAndTheme = function() { - window.document.title = config.window_title_prefix + $scope.dashboard.title; - }; - - $scope.broadcastRefresh = function() { - $rootScope.performance.panelsRendered = 0; - $rootScope.$broadcast('refresh'); - }; - - $scope.addRow = function(dash, row) { - dash.rows.push(row); - }; - - $scope.addRowDefault = function() { - $scope.resetRow(); - $scope.row.title = 'New row'; - $scope.addRow($scope.dashboard, $scope.row); - }; - - $scope.resetRow = function() { - $scope.row = { - title: '', - height: '250px', - editable: true, - }; - }; - - $scope.showJsonEditor = function(evt, options) { - var editScope = $rootScope.$new(); - editScope.object = options.object; - editScope.updateHandler = options.updateHandler; - $scope.appEvent('show-dash-editor', { src: 'public/app/partials/edit_json.html', scope: editScope }); - }; - - $scope.onDrop = function(panelId, row, dropTarget) { - var info = $scope.dashboard.getPanelInfoById(panelId); - if (dropTarget) { - var dropInfo = $scope.dashboard.getPanelInfoById(dropTarget.id); - dropInfo.row.panels[dropInfo.index] = info.panel; - info.row.panels[info.index] = dropTarget; - var dragSpan = info.panel.span; - info.panel.span = dropTarget.span; - dropTarget.span = dragSpan; - } - else { - info.row.panels.splice(info.index, 1); - info.panel.span = 12 - $scope.dashboard.rowSpan(row); - row.panels.push(info.panel); - } - - $rootScope.$broadcast('render'); - }; - - $scope.registerWindowResizeEvent = function() { - angular.element(window).bind('resize', function() { - $timeout.cancel(resizeEventTimeout); - resizeEventTimeout = $timeout(function() { $scope.$broadcast('render'); }, 200); - }); - $scope.$on('$destroy', function() { - angular.element(window).unbind('resize'); - }); - }; - - $scope.timezoneChanged = function() { - $rootScope.$broadcast("refresh"); - }; - - $scope.formatDate = function(date) { - return moment(date).format('MMM Do YYYY, h:mm:ss a'); - }; - - }); - -}); diff --git a/public/app/features/dashboard/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts new file mode 100644 index 00000000000..f7acac4e1b7 --- /dev/null +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -0,0 +1,154 @@ +/// + +import config from 'app/core/config'; +import angular from 'angular'; +import moment from 'moment'; +import _ from 'lodash'; + +import coreModule from 'app/core/core_module'; +import {DynamicDashboardSrv} from './dynamic_dashboard_srv'; + +export class DashboardCtrl { + + /** @ngInject */ + constructor( + private $scope, + private $rootScope, + dashboardKeybindings, + timeSrv, + templateValuesSrv, + dashboardSrv, + unsavedChangesSrv, + dashboardViewStateSrv, + contextSrv, + $timeout) { + + $scope.editor = { index: 0 }; + $scope.panels = config.panels; + $scope.dynamicDashboardSrv = new DynamicDashboardSrv(); + + var resizeEventTimeout; + + $scope.setupDashboard = function(data) { + $rootScope.performance.dashboardLoadStart = new Date().getTime(); + $rootScope.performance.panelsInitialized = 0; + $rootScope.performance.panelsRendered = 0; + + var dashboard = dashboardSrv.create(data.dashboard, data.meta); + dashboardSrv.setCurrent(dashboard); + + // init services + timeSrv.init(dashboard); + + // template values service needs to initialize completely before + // the rest of the dashboard can load + templateValuesSrv.init(dashboard).finally(function() { + $scope.dynamicDashboardSrv.init(dashboard); + + unsavedChangesSrv.init(dashboard, $scope); + + $scope.dashboard = dashboard; + $scope.dashboardMeta = dashboard.meta; + $scope.dashboardViewState = dashboardViewStateSrv.create($scope); + + dashboardKeybindings.shortcuts($scope); + + $scope.updateSubmenuVisibility(); + $scope.setWindowTitleAndTheme(); + + $scope.appEvent("dashboard-loaded", $scope.dashboard); + }).catch(function(err) { + if (err.data && err.data.message) { err.message = err.data.message; } + $scope.appEvent("alert-error", ['Dashboard init failed', 'Template variables could not be initialized: ' + err.message]); + }); + }; + + $scope.templateVariableUpdated = function() { + $scope.dynamicDashboardSrv.update($scope.dashboard); + }; + + $scope.updateSubmenuVisibility = function() { + $scope.submenuEnabled = $scope.dashboard.isSubmenuFeaturesEnabled(); + }; + + $scope.setWindowTitleAndTheme = function() { + window.document.title = config.window_title_prefix + $scope.dashboard.title; + }; + + $scope.broadcastRefresh = function() { + $rootScope.performance.panelsRendered = 0; + $rootScope.$broadcast('refresh'); + }; + + $scope.addRow = function(dash, row) { + dash.rows.push(row); + }; + + $scope.addRowDefault = function() { + $scope.resetRow(); + $scope.row.title = 'New row'; + $scope.addRow($scope.dashboard, $scope.row); + }; + + $scope.resetRow = function() { + $scope.row = { + title: '', + height: '250px', + editable: true, + }; + }; + + $scope.showJsonEditor = function(evt, options) { + var editScope = $rootScope.$new(); + editScope.object = options.object; + editScope.updateHandler = options.updateHandler; + $scope.appEvent('show-dash-editor', { src: 'public/app/partials/edit_json.html', scope: editScope }); + }; + + $scope.onDrop = function(panelId, row, dropTarget) { + var info = $scope.dashboard.getPanelInfoById(panelId); + if (dropTarget) { + var dropInfo = $scope.dashboard.getPanelInfoById(dropTarget.id); + dropInfo.row.panels[dropInfo.index] = info.panel; + info.row.panels[info.index] = dropTarget; + var dragSpan = info.panel.span; + info.panel.span = dropTarget.span; + dropTarget.span = dragSpan; + } else { + info.row.panels.splice(info.index, 1); + info.panel.span = 12 - $scope.dashboard.rowSpan(row); + row.panels.push(info.panel); + } + + $rootScope.$broadcast('render'); + }; + + $scope.registerWindowResizeEvent = function() { + angular.element(window).bind('resize', function() { + $timeout.cancel(resizeEventTimeout); + resizeEventTimeout = $timeout(function() { $scope.$broadcast('render'); }, 200); + }); + $scope.$on('$destroy', function() { + angular.element(window).unbind('resize'); + }); + }; + + $scope.timezoneChanged = function() { + $rootScope.$broadcast("refresh"); + }; + + $scope.formatDate = function(date) { + return moment(date).format('MMM Do YYYY, h:mm:ss a'); + }; + } + + init(dashboard) { + this.$scope.resetRow(); + this.$scope.registerWindowResizeEvent(); + this.$scope.onAppEvent('show-json-editor', this.$scope.showJsonEditor); + this.$scope.onAppEvent('template-variable-value-updated', this.$scope.templateVariableUpdated); + this.$scope.setupDashboard(dashboard); + } +} + +coreModule.controller('DashboardCtrl', DashboardCtrl); diff --git a/public/app/features/dashboard/dynamicDashboardSrv.js b/public/app/features/dashboard/dynamicDashboardSrv.js deleted file mode 100644 index 9e369733f45..00000000000 --- a/public/app/features/dashboard/dynamicDashboardSrv.js +++ /dev/null @@ -1,181 +0,0 @@ -define([ - 'angular', - 'lodash', -], -function (angular, _) { - 'use strict'; - - var module = angular.module('grafana.services'); - - module.service('dynamicDashboardSrv', function() { - var self = this; - - this.init = function(dashboard) { - if (dashboard.snapshot) { return; } - - this.iteration = new Date().getTime(); - this.process(dashboard); - }; - - this.update = function(dashboard) { - if (dashboard.snapshot) { return; } - - this.iteration = this.iteration + 1; - this.process(dashboard); - }; - - this.process = function(dashboard) { - if (dashboard.templating.list.length === 0) { return; } - this.dashboard = dashboard; - - var i, j, row, panel; - for (i = 0; i < this.dashboard.rows.length; i++) { - row = this.dashboard.rows[i]; - // handle row repeats - if (row.repeat) { - this.repeatRow(row, i); - } - // clean up old left overs - else if (row.repeatRowId && row.repeatIteration !== this.iteration) { - this.dashboard.rows.splice(i, 1); - i = i - 1; - continue; - } - - // repeat panels - for (j = 0; j < row.panels.length; j++) { - panel = row.panels[j]; - if (panel.repeat) { - this.repeatPanel(panel, row); - } - // clean up old left overs - else if (panel.repeatPanelId && panel.repeatIteration !== this.iteration) { - row.panels = _.without(row.panels, panel); - j = j - 1; - } else if (!_.isEmpty(panel.scopedVars) && panel.repeatIteration !== this.iteration) { - panel.scopedVars = {}; - } - } - } - }; - - // returns a new row clone or reuses a clone from previous iteration - this.getRowClone = function(sourceRow, repeatIndex, sourceRowIndex) { - if (repeatIndex === 0) { - return sourceRow; - } - - var i, panel, row, copy; - var sourceRowId = sourceRowIndex + 1; - - // look for row to reuse - for (i = 0; i < this.dashboard.rows.length; i++) { - row = this.dashboard.rows[i]; - if (row.repeatRowId === sourceRowId && row.repeatIteration !== this.iteration) { - copy = row; - break; - } - } - - if (!copy) { - copy = angular.copy(sourceRow); - this.dashboard.rows.splice(sourceRowIndex + repeatIndex, 0, copy); - - // set new panel ids - for (i = 0; i < copy.panels.length; i++) { - panel = copy.panels[i]; - panel.id = this.dashboard.getNextPanelId(); - } - } - - copy.repeat = null; - copy.repeatRowId = sourceRowId; - copy.repeatIteration = this.iteration; - return copy; - }; - - // returns a new row clone or reuses a clone from previous iteration - this.repeatRow = function(row, rowIndex) { - var variables = this.dashboard.templating.list; - var variable = _.findWhere(variables, {name: row.repeat}); - if (!variable) { - return; - } - - var selected, copy, i, panel; - if (variable.current.text === 'All') { - selected = variable.options.slice(1, variable.options.length); - } else { - selected = _.filter(variable.options, {selected: true}); - } - - _.each(selected, function(option, index) { - copy = self.getRowClone(row, index, rowIndex); - copy.scopedVars = {}; - copy.scopedVars[variable.name] = option; - - for (i = 0; i < copy.panels.length; i++) { - panel = copy.panels[i]; - panel.scopedVars = {}; - panel.scopedVars[variable.name] = option; - panel.repeatIteration = this.iteration; - } - }, this); - }; - - this.getPanelClone = function(sourcePanel, row, index) { - // if first clone return source - if (index === 0) { - return sourcePanel; - } - - var i, tmpId, panel, clone; - - // first try finding an existing clone to use - for (i = 0; i < row.panels.length; i++) { - panel = row.panels[i]; - if (panel.repeatIteration !== this.iteration && panel.repeatPanelId === sourcePanel.id) { - clone = panel; - break; - } - } - - if (!clone) { - clone = { id: this.dashboard.getNextPanelId() }; - row.panels.push(clone); - } - - // save id - tmpId = clone.id; - // copy properties from source - angular.copy(sourcePanel, clone); - // restore id - clone.id = tmpId; - clone.repeatIteration = this.iteration; - clone.repeatPanelId = sourcePanel.id; - clone.repeat = null; - return clone; - }; - - this.repeatPanel = function(panel, row) { - var variables = this.dashboard.templating.list; - var variable = _.findWhere(variables, {name: panel.repeat}); - if (!variable) { return; } - - var selected; - if (variable.current.text === 'All') { - selected = variable.options.slice(1, variable.options.length); - } else { - selected = _.filter(variable.options, {selected: true}); - } - - _.each(selected, function(option, index) { - var copy = self.getPanelClone(panel, row, index); - copy.span = Math.max(12 / selected.length, panel.minSpan); - copy.scopedVars = copy.scopedVars || {}; - copy.scopedVars[variable.name] = option; - }); - }; - - }); -}); diff --git a/public/app/features/dashboard/dynamic_dashboard_srv.ts b/public/app/features/dashboard/dynamic_dashboard_srv.ts new file mode 100644 index 00000000000..340bca69b40 --- /dev/null +++ b/public/app/features/dashboard/dynamic_dashboard_srv.ts @@ -0,0 +1,175 @@ +/// + +import config from 'app/core/config'; +import angular from 'angular'; +import _ from 'lodash'; + +export class DynamicDashboardSrv { + iteration: number; + dashboard: any; + + init(dashboard) { + if (dashboard.snapshot) { return; } + + this.iteration = new Date().getTime(); + this.process(dashboard); + } + + update(dashboard) { + if (dashboard.snapshot) { return; } + + this.iteration = this.iteration + 1; + this.process(dashboard); + } + + process(dashboard) { + if (dashboard.templating.list.length === 0) { return; } + this.dashboard = dashboard; + + var i, j, row, panel; + for (i = 0; i < this.dashboard.rows.length; i++) { + row = this.dashboard.rows[i]; + // handle row repeats + if (row.repeat) { + this.repeatRow(row, i); + } else if (row.repeatRowId && row.repeatIteration !== this.iteration) { + // clean up old left overs + this.dashboard.rows.splice(i, 1); + i = i - 1; + continue; + } + + // repeat panels + for (j = 0; j < row.panels.length; j++) { + panel = row.panels[j]; + if (panel.repeat) { + this.repeatPanel(panel, row); + } else if (panel.repeatPanelId && panel.repeatIteration !== this.iteration) { + // clean up old left overs + row.panels = _.without(row.panels, panel); + j = j - 1; + } else if (!_.isEmpty(panel.scopedVars) && panel.repeatIteration !== this.iteration) { + panel.scopedVars = {}; + } + } + } + } + + // returns a new row clone or reuses a clone from previous iteration + getRowClone(sourceRow, repeatIndex, sourceRowIndex) { + if (repeatIndex === 0) { + return sourceRow; + } + + var i, panel, row, copy; + var sourceRowId = sourceRowIndex + 1; + + // look for row to reuse + for (i = 0; i < this.dashboard.rows.length; i++) { + row = this.dashboard.rows[i]; + if (row.repeatRowId === sourceRowId && row.repeatIteration !== this.iteration) { + copy = row; + break; + } + } + + if (!copy) { + copy = angular.copy(sourceRow); + this.dashboard.rows.splice(sourceRowIndex + repeatIndex, 0, copy); + + // set new panel ids + for (i = 0; i < copy.panels.length; i++) { + panel = copy.panels[i]; + panel.id = this.dashboard.getNextPanelId(); + } + } + + copy.repeat = null; + copy.repeatRowId = sourceRowId; + copy.repeatIteration = this.iteration; + return copy; + } + + // returns a new row clone or reuses a clone from previous iteration + repeatRow(row, rowIndex) { + var variables = this.dashboard.templating.list; + var variable = _.findWhere(variables, {name: row.repeat}); + if (!variable) { + return; + } + + var selected, copy, i, panel; + if (variable.current.text === 'All') { + selected = variable.options.slice(1, variable.options.length); + } else { + selected = _.filter(variable.options, {selected: true}); + } + + _.each(selected, (option, index) => { + copy = this.getRowClone(row, index, rowIndex); + copy.scopedVars = {}; + copy.scopedVars[variable.name] = option; + + for (i = 0; i < copy.panels.length; i++) { + panel = copy.panels[i]; + panel.scopedVars = {}; + panel.scopedVars[variable.name] = option; + panel.repeatIteration = this.iteration; + } + }); + } + + getPanelClone(sourcePanel, row, index) { + // if first clone return source + if (index === 0) { + return sourcePanel; + } + + var i, tmpId, panel, clone; + + // first try finding an existing clone to use + for (i = 0; i < row.panels.length; i++) { + panel = row.panels[i]; + if (panel.repeatIteration !== this.iteration && panel.repeatPanelId === sourcePanel.id) { + clone = panel; + break; + } + } + + if (!clone) { + clone = { id: this.dashboard.getNextPanelId() }; + row.panels.push(clone); + } + + // save id + tmpId = clone.id; + // copy properties from source + angular.copy(sourcePanel, clone); + // restore id + clone.id = tmpId; + clone.repeatIteration = this.iteration; + clone.repeatPanelId = sourcePanel.id; + clone.repeat = null; + return clone; + } + + repeatPanel(panel, row) { + var variables = this.dashboard.templating.list; + var variable = _.findWhere(variables, {name: panel.repeat}); + if (!variable) { return; } + + var selected; + if (variable.current.text === 'All') { + selected = variable.options.slice(1, variable.options.length); + } else { + selected = _.filter(variable.options, {selected: true}); + } + + _.each(selected, (option, index) => { + var copy = this.getPanelClone(panel, row, index); + copy.span = Math.max(12 / selected.length, panel.minSpan); + copy.scopedVars = copy.scopedVars || {}; + copy.scopedVars[variable.name] = option; + }); + } +} diff --git a/public/app/features/dashboard/submenu/submenu.ts b/public/app/features/dashboard/submenu/submenu.ts index a9899c3a4b8..8e7984a4085 100644 --- a/public/app/features/dashboard/submenu/submenu.ts +++ b/public/app/features/dashboard/submenu/submenu.ts @@ -8,7 +8,7 @@ export class SubmenuCtrl { dashboard: any; /** @ngInject */ - constructor(private $rootScope, private templateValuesSrv, private dynamicDashboardSrv) { + constructor(private $rootScope, private templateValuesSrv) { this.annotations = this.dashboard.templating.list; this.variables = this.dashboard.templating.list; } @@ -24,7 +24,6 @@ export class SubmenuCtrl { variableUpdated(variable) { this.templateValuesSrv.variableUpdated(variable).then(() => { - this.dynamicDashboardSrv.update(this.dashboard); this.$rootScope.$emit('template-variable-value-updated'); this.$rootScope.$broadcast('refresh'); }); diff --git a/public/test/core/utils/emitter_specs.ts b/public/test/core/utils/emitter_specs.ts index f7076c46719..fec4d02a649 100644 --- a/public/test/core/utils/emitter_specs.ts +++ b/public/test/core/utils/emitter_specs.ts @@ -24,7 +24,7 @@ describe("Emitter", () => { expect(sub2Called).to.be(true); }); - it.only('should handle errors', () => { + it('should handle errors', () => { var events = new Emitter(); var sub1Called = 0; var sub2Called = 0; diff --git a/public/test/specs/dynamicDashboardSrv-specs.js b/public/test/specs/dynamicDashboardSrv-specs.js deleted file mode 100644 index b988203009a..00000000000 --- a/public/test/specs/dynamicDashboardSrv-specs.js +++ /dev/null @@ -1,267 +0,0 @@ -define([ - 'app/features/dashboard/dynamicDashboardSrv', - 'app/features/dashboard/dashboardSrv' -], function() { - 'use strict'; - - function dynamicDashScenario(desc, func) { - - describe(desc, function() { - var ctx = {}; - - ctx.setup = function (setupFunc) { - - beforeEach(module('grafana.services')); - beforeEach(module(function($provide) { - $provide.value('contextSrv', { - user: { timezone: 'utc'} - }); - })); - - beforeEach(inject(function(dynamicDashboardSrv, dashboardSrv) { - ctx.dynamicDashboardSrv = dynamicDashboardSrv; - ctx.dashboardSrv = dashboardSrv; - - var model = { - rows: [], - templating: { list: [] } - }; - - setupFunc(model); - ctx.dash = ctx.dashboardSrv.create(model); - ctx.dynamicDashboardSrv.init(ctx.dash); - ctx.rows = ctx.dash.rows; - })); - }; - - func(ctx); - }); - } - - dynamicDashScenario('given dashboard with panel repeat', function(ctx) { - ctx.setup(function(dash) { - dash.rows.push({ - panels: [{id: 2, repeat: 'apps'}] - }); - dash.templating.list.push({ - name: 'apps', - current: { - text: 'se1, se2, se3', - value: ['se1', 'se2', 'se3'] - }, - options: [ - {text: 'se1', value: 'se1', selected: true}, - {text: 'se2', value: 'se2', selected: true}, - {text: 'se3', value: 'se3', selected: true}, - {text: 'se4', value: 'se4', selected: false} - ] - }); - }); - - it('should repeat panel one time', function() { - expect(ctx.rows[0].panels.length).to.be(3); - }); - - it('should mark panel repeated', function() { - expect(ctx.rows[0].panels[0].repeat).to.be('apps'); - expect(ctx.rows[0].panels[1].repeatPanelId).to.be(2); - }); - - it('should set scopedVars on panels', function() { - expect(ctx.rows[0].panels[0].scopedVars.apps.value).to.be('se1'); - expect(ctx.rows[0].panels[1].scopedVars.apps.value).to.be('se2'); - expect(ctx.rows[0].panels[2].scopedVars.apps.value).to.be('se3'); - }); - - describe('After a second iteration', function() { - var repeatedPanelAfterIteration1; - - beforeEach(function() { - repeatedPanelAfterIteration1 = ctx.rows[0].panels[1]; - ctx.rows[0].panels[0].fill = 10; - ctx.dynamicDashboardSrv.update(ctx.dash); - }); - - it('should have reused same panel instances', function() { - expect(ctx.rows[0].panels[1]).to.be(repeatedPanelAfterIteration1); - }); - - it('reused panel should copy properties from source', function() { - expect(ctx.rows[0].panels[1].fill).to.be(10); - }); - - it('should have same panel count', function() { - expect(ctx.rows[0].panels.length).to.be(3); - }); - }); - - describe('After a second iteration and selected values reduced', function() { - beforeEach(function() { - ctx.dash.templating.list[0].options[1].selected = false; - - ctx.dynamicDashboardSrv.update(ctx.dash); - }); - - it('should clean up repeated panel', function() { - expect(ctx.rows[0].panels.length).to.be(2); - }); - }); - - describe('After a second iteration and panel repeat is turned off', function() { - beforeEach(function() { - ctx.rows[0].panels[0].repeat = null; - ctx.dynamicDashboardSrv.update(ctx.dash); - }); - - it('should clean up repeated panel', function() { - expect(ctx.rows[0].panels.length).to.be(1); - }); - - it('should remove scoped vars from reused panel', function() { - expect(ctx.rows[0].panels[0].scopedVars).to.be.empty(); - }); - }); - - }); - - dynamicDashScenario('given dashboard with row repeat', function(ctx) { - ctx.setup(function(dash) { - dash.rows.push({ - repeat: 'servers', - panels: [{id: 2}] - }); - dash.rows.push({panels: []}); - dash.templating.list.push({ - name: 'servers', - current: { - text: 'se1, se2', - value: ['se1', 'se2'] - }, - options: [ - {text: 'se1', value: 'se1', selected: true}, - {text: 'se2', value: 'se2', selected: true}, - ] - }); - }); - - it('should repeat row one time', function() { - expect(ctx.rows.length).to.be(3); - }); - - it('should keep panel ids on first row', function() { - expect(ctx.rows[0].panels[0].id).to.be(2); - }); - - it('should keep first row as repeat', function() { - expect(ctx.rows[0].repeat).to.be('servers'); - }); - - it('should clear repeat field on repeated row', function() { - expect(ctx.rows[1].repeat).to.be(null); - }); - - it('should add scopedVars to rows', function() { - expect(ctx.rows[0].scopedVars.servers.value).to.be('se1'); - expect(ctx.rows[1].scopedVars.servers.value).to.be('se2'); - }); - - it('should generate a repeartRowId based on repeat row index', function() { - expect(ctx.rows[1].repeatRowId).to.be(1); - }); - - it('should set scopedVars on row panels', function() { - expect(ctx.rows[0].panels[0].scopedVars.servers.value).to.be('se1'); - expect(ctx.rows[1].panels[0].scopedVars.servers.value).to.be('se2'); - }); - - describe('After a second iteration', function() { - var repeatedRowAfterFirstIteration; - - beforeEach(function() { - repeatedRowAfterFirstIteration = ctx.rows[1]; - ctx.rows[0].height = 500; - ctx.dynamicDashboardSrv.update(ctx.dash); - }); - - it('should still only have 2 rows', function() { - expect(ctx.rows.length).to.be(3); - }); - - it.skip('should have updated props from source', function() { - expect(ctx.rows[1].height).to.be(500); - }); - - it('should reuse row instance', function() { - expect(ctx.rows[1]).to.be(repeatedRowAfterFirstIteration); - }); - }); - - describe('After a second iteration and selected values reduced', function() { - beforeEach(function() { - ctx.dash.templating.list[0].options[1].selected = false; - ctx.dynamicDashboardSrv.update(ctx.dash); - }); - - it('should remove repeated second row', function() { - expect(ctx.rows.length).to.be(2); - }); - }); - }); - - dynamicDashScenario('given dashboard with row repeat and panel repeat', function(ctx) { - ctx.setup(function(dash) { - dash.rows.push({ - repeat: 'servers', - panels: [{id: 2, repeat: 'metric'}] - }); - dash.templating.list.push({ - name: 'servers', - current: { text: 'se1, se2', value: ['se1', 'se2'] }, - options: [ - {text: 'se1', value: 'se1', selected: true}, - {text: 'se2', value: 'se2', selected: true}, - ] - }); - dash.templating.list.push({ - name: 'metric', - current: { text: 'm1, m2', value: ['m1', 'm2'] }, - options: [ - {text: 'm1', value: 'm1', selected: true}, - {text: 'm2', value: 'm2', selected: true}, - ] - }); - }); - - it('should repeat row one time', function() { - expect(ctx.rows.length).to.be(2); - }); - - it('should repeat panel on both rows', function() { - expect(ctx.rows[0].panels.length).to.be(2); - expect(ctx.rows[1].panels.length).to.be(2); - }); - - it('should keep panel ids on first row', function() { - expect(ctx.rows[0].panels[0].id).to.be(2); - }); - - it('should mark second row as repeated', function() { - expect(ctx.rows[0].repeat).to.be('servers'); - }); - - it('should clear repeat field on repeated row', function() { - expect(ctx.rows[1].repeat).to.be(null); - }); - - it('should generate a repeartRowId based on repeat row index', function() { - expect(ctx.rows[1].repeatRowId).to.be(1); - }); - - it('should set scopedVars on row panels', function() { - expect(ctx.rows[0].panels[0].scopedVars.servers.value).to.be('se1'); - expect(ctx.rows[1].panels[0].scopedVars.servers.value).to.be('se2'); - }); - - }); - -}); diff --git a/public/test/specs/dynamic_dashboard_srv_specs.ts b/public/test/specs/dynamic_dashboard_srv_specs.ts new file mode 100644 index 00000000000..6f233fcb9ff --- /dev/null +++ b/public/test/specs/dynamic_dashboard_srv_specs.ts @@ -0,0 +1,264 @@ +import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; + +import 'app/features/dashboard/dashboardSrv'; +import {DynamicDashboardSrv} from '../../app/features/dashboard/dynamic_dashboard_srv'; + +function dynamicDashScenario(desc, func) { + + describe(desc, function() { + var ctx: any = {}; + + ctx.setup = function (setupFunc) { + + beforeEach(angularMocks.module('grafana.services')); + beforeEach(angularMocks.module(function($provide) { + $provide.value('contextSrv', { + user: { timezone: 'utc'} + }); + })); + + beforeEach(angularMocks.inject(function(dashboardSrv) { + ctx.dashboardSrv = dashboardSrv; + var model = { + rows: [], + templating: { list: [] } + }; + + setupFunc(model); + ctx.dash = ctx.dashboardSrv.create(model); + ctx.dynamicDashboardSrv = new DynamicDashboardSrv(); + ctx.dynamicDashboardSrv.init(ctx.dash); + ctx.rows = ctx.dash.rows; + })); + }; + + func(ctx); + }); +} + +dynamicDashScenario('given dashboard with panel repeat', function(ctx) { + ctx.setup(function(dash) { + dash.rows.push({ + panels: [{id: 2, repeat: 'apps'}] + }); + dash.templating.list.push({ + name: 'apps', + current: { + text: 'se1, se2, se3', + value: ['se1', 'se2', 'se3'] + }, + options: [ + {text: 'se1', value: 'se1', selected: true}, + {text: 'se2', value: 'se2', selected: true}, + {text: 'se3', value: 'se3', selected: true}, + {text: 'se4', value: 'se4', selected: false} + ] + }); + }); + + it('should repeat panel one time', function() { + expect(ctx.rows[0].panels.length).to.be(3); + }); + + it('should mark panel repeated', function() { + expect(ctx.rows[0].panels[0].repeat).to.be('apps'); + expect(ctx.rows[0].panels[1].repeatPanelId).to.be(2); + }); + + it('should set scopedVars on panels', function() { + expect(ctx.rows[0].panels[0].scopedVars.apps.value).to.be('se1'); + expect(ctx.rows[0].panels[1].scopedVars.apps.value).to.be('se2'); + expect(ctx.rows[0].panels[2].scopedVars.apps.value).to.be('se3'); + }); + + describe('After a second iteration', function() { + var repeatedPanelAfterIteration1; + + beforeEach(function() { + repeatedPanelAfterIteration1 = ctx.rows[0].panels[1]; + ctx.rows[0].panels[0].fill = 10; + ctx.dynamicDashboardSrv.update(ctx.dash); + }); + + it('should have reused same panel instances', function() { + expect(ctx.rows[0].panels[1]).to.be(repeatedPanelAfterIteration1); + }); + + it('reused panel should copy properties from source', function() { + expect(ctx.rows[0].panels[1].fill).to.be(10); + }); + + it('should have same panel count', function() { + expect(ctx.rows[0].panels.length).to.be(3); + }); + }); + + describe('After a second iteration and selected values reduced', function() { + beforeEach(function() { + ctx.dash.templating.list[0].options[1].selected = false; + + ctx.dynamicDashboardSrv.update(ctx.dash); + }); + + it('should clean up repeated panel', function() { + expect(ctx.rows[0].panels.length).to.be(2); + }); + }); + + describe('After a second iteration and panel repeat is turned off', function() { + beforeEach(function() { + ctx.rows[0].panels[0].repeat = null; + ctx.dynamicDashboardSrv.update(ctx.dash); + }); + + it('should clean up repeated panel', function() { + expect(ctx.rows[0].panels.length).to.be(1); + }); + + it('should remove scoped vars from reused panel', function() { + expect(ctx.rows[0].panels[0].scopedVars).to.be.empty(); + }); + }); + +}); + +dynamicDashScenario('given dashboard with row repeat', function(ctx) { + ctx.setup(function(dash) { + dash.rows.push({ + repeat: 'servers', + panels: [{id: 2}] + }); + dash.rows.push({panels: []}); + dash.templating.list.push({ + name: 'servers', + current: { + text: 'se1, se2', + value: ['se1', 'se2'] + }, + options: [ + {text: 'se1', value: 'se1', selected: true}, + {text: 'se2', value: 'se2', selected: true}, + ] + }); + }); + + it('should repeat row one time', function() { + expect(ctx.rows.length).to.be(3); + }); + + it('should keep panel ids on first row', function() { + expect(ctx.rows[0].panels[0].id).to.be(2); + }); + + it('should keep first row as repeat', function() { + expect(ctx.rows[0].repeat).to.be('servers'); + }); + + it('should clear repeat field on repeated row', function() { + expect(ctx.rows[1].repeat).to.be(null); + }); + + it('should add scopedVars to rows', function() { + expect(ctx.rows[0].scopedVars.servers.value).to.be('se1'); + expect(ctx.rows[1].scopedVars.servers.value).to.be('se2'); + }); + + it('should generate a repeartRowId based on repeat row index', function() { + expect(ctx.rows[1].repeatRowId).to.be(1); + }); + + it('should set scopedVars on row panels', function() { + expect(ctx.rows[0].panels[0].scopedVars.servers.value).to.be('se1'); + expect(ctx.rows[1].panels[0].scopedVars.servers.value).to.be('se2'); + }); + + describe('After a second iteration', function() { + var repeatedRowAfterFirstIteration; + + beforeEach(function() { + repeatedRowAfterFirstIteration = ctx.rows[1]; + ctx.rows[0].height = 500; + ctx.dynamicDashboardSrv.update(ctx.dash); + }); + + it('should still only have 2 rows', function() { + expect(ctx.rows.length).to.be(3); + }); + + it.skip('should have updated props from source', function() { + expect(ctx.rows[1].height).to.be(500); + }); + + it('should reuse row instance', function() { + expect(ctx.rows[1]).to.be(repeatedRowAfterFirstIteration); + }); + }); + + describe('After a second iteration and selected values reduced', function() { + beforeEach(function() { + ctx.dash.templating.list[0].options[1].selected = false; + ctx.dynamicDashboardSrv.update(ctx.dash); + }); + + it('should remove repeated second row', function() { + expect(ctx.rows.length).to.be(2); + }); + }); +}); + +dynamicDashScenario('given dashboard with row repeat and panel repeat', function(ctx) { + ctx.setup(function(dash) { + dash.rows.push({ + repeat: 'servers', + panels: [{id: 2, repeat: 'metric'}] + }); + dash.templating.list.push({ + name: 'servers', + current: { text: 'se1, se2', value: ['se1', 'se2'] }, + options: [ + {text: 'se1', value: 'se1', selected: true}, + {text: 'se2', value: 'se2', selected: true}, + ] + }); + dash.templating.list.push({ + name: 'metric', + current: { text: 'm1, m2', value: ['m1', 'm2'] }, + options: [ + {text: 'm1', value: 'm1', selected: true}, + {text: 'm2', value: 'm2', selected: true}, + ] + }); + }); + + it('should repeat row one time', function() { + expect(ctx.rows.length).to.be(2); + }); + + it('should repeat panel on both rows', function() { + expect(ctx.rows[0].panels.length).to.be(2); + expect(ctx.rows[1].panels.length).to.be(2); + }); + + it('should keep panel ids on first row', function() { + expect(ctx.rows[0].panels[0].id).to.be(2); + }); + + it('should mark second row as repeated', function() { + expect(ctx.rows[0].repeat).to.be('servers'); + }); + + it('should clear repeat field on repeated row', function() { + expect(ctx.rows[1].repeat).to.be(null); + }); + + it('should generate a repeartRowId based on repeat row index', function() { + expect(ctx.rows[1].repeatRowId).to.be(1); + }); + + it('should set scopedVars on row panels', function() { + expect(ctx.rows[0].panels[0].scopedVars.servers.value).to.be('se1'); + expect(ctx.rows[1].panels[0].scopedVars.servers.value).to.be('se2'); + }); + +}); + From 4d0b14fbb45407caf2fa684215ca1d7d92b9ab9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 14 Apr 2016 17:31:34 -0400 Subject: [PATCH 003/350] feat(exporter): stared work on dashboard exporter that cleans up repeated panels etc --- .../app/features/dashboard/dashboard_ctrl.ts | 4 -- .../app/features/dashboard/dashnav/dashnav.ts | 7 +-- .../dashboard/dynamic_dashboard_srv.ts | 22 ++++++--- public/app/features/dashboard/exporter.ts | 28 ++++++++++++ .../features/dashboard/partials/settings.html | 4 +- .../specs/dynamic_dashboard_srv_specs.ts | 2 +- .../dashboard/specs/exporter_specs.ts | 45 +++++++++++++++++++ 7 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 public/app/features/dashboard/exporter.ts rename public/{test => app/features/dashboard}/specs/dynamic_dashboard_srv_specs.ts (98%) create mode 100644 public/app/features/dashboard/specs/exporter_specs.ts diff --git a/public/app/features/dashboard/dashboard_ctrl.ts b/public/app/features/dashboard/dashboard_ctrl.ts index f7acac4e1b7..7bb50f58bf1 100644 --- a/public/app/features/dashboard/dashboard_ctrl.ts +++ b/public/app/features/dashboard/dashboard_ctrl.ts @@ -136,10 +136,6 @@ export class DashboardCtrl { $scope.timezoneChanged = function() { $rootScope.$broadcast("refresh"); }; - - $scope.formatDate = function(date) { - return moment(date).format('MMM Do YYYY, h:mm:ss a'); - }; } init(dashboard) { diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts index 2d5b66cb13c..afad15afe87 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/dashnav/dashnav.ts @@ -4,6 +4,8 @@ import _ from 'lodash'; import moment from 'moment'; import angular from 'angular'; +import {DashboardExporter} from '../exporter'; + export class DashNavCtrl { /** @ngInject */ @@ -170,9 +172,8 @@ export class DashNavCtrl { $scope.exportDashboard = function() { var clone = $scope.dashboard.getSaveModelClone(); - var blob = new Blob([angular.toJson(clone, true)], { type: "application/json;charset=utf-8" }); - var wnd: any = window; - wnd.saveAs(blob, $scope.dashboard.title + '-' + new Date().getTime()); + var exporter = new DashboardExporter(); + exporter.export(clone); }; $scope.snapshot = function() { diff --git a/public/app/features/dashboard/dynamic_dashboard_srv.ts b/public/app/features/dashboard/dynamic_dashboard_srv.ts index 340bca69b40..a5fb73d099a 100644 --- a/public/app/features/dashboard/dynamic_dashboard_srv.ts +++ b/public/app/features/dashboard/dynamic_dashboard_srv.ts @@ -8,30 +8,36 @@ export class DynamicDashboardSrv { iteration: number; dashboard: any; + constructor() { + this.iteration = new Date().getTime(); + } + init(dashboard) { if (dashboard.snapshot) { return; } - - this.iteration = new Date().getTime(); - this.process(dashboard); + this.process(dashboard, {}); } update(dashboard) { if (dashboard.snapshot) { return; } this.iteration = this.iteration + 1; - this.process(dashboard); + this.process(dashboard, {}); } - process(dashboard) { + process(dashboard, options) { if (dashboard.templating.list.length === 0) { return; } this.dashboard = dashboard; + var cleanUpOnly = options.cleanUpOnly; + var i, j, row, panel; for (i = 0; i < this.dashboard.rows.length; i++) { row = this.dashboard.rows[i]; // handle row repeats if (row.repeat) { - this.repeatRow(row, i); + if (!cleanUpOnly) { + this.repeatRow(row, i); + } } else if (row.repeatRowId && row.repeatIteration !== this.iteration) { // clean up old left overs this.dashboard.rows.splice(i, 1); @@ -43,7 +49,9 @@ export class DynamicDashboardSrv { for (j = 0; j < row.panels.length; j++) { panel = row.panels[j]; if (panel.repeat) { - this.repeatPanel(panel, row); + if (!cleanUpOnly) { + this.repeatPanel(panel, row); + } } else if (panel.repeatPanelId && panel.repeatIteration !== this.iteration) { // clean up old left overs row.panels = _.without(row.panels, panel); diff --git a/public/app/features/dashboard/exporter.ts b/public/app/features/dashboard/exporter.ts new file mode 100644 index 00000000000..0a9ac2ff2ca --- /dev/null +++ b/public/app/features/dashboard/exporter.ts @@ -0,0 +1,28 @@ +/// + +import config from 'app/core/config'; +import angular from 'angular'; +import _ from 'lodash'; + +import {DynamicDashboardSrv} from './dynamic_dashboard_srv'; + +export class DashboardExporter { + + makeExportable(dashboard) { + var dynSrv = new DynamicDashboardSrv(); + dynSrv.process(dashboard, {cleanUpOnly: true}); + + return dashboard; + } + + export(dashboard) { + var clean = this.makeExportable(dashboard); + var blob = new Blob([angular.toJson(clean, true)], { type: "application/json;charset=utf-8" }); + var wnd: any = window; + wnd.saveAs(blob, clean.title + '-' + new Date().getTime()); + } + +} + + + diff --git a/public/app/features/dashboard/partials/settings.html b/public/app/features/dashboard/partials/settings.html index 2a2287613ae..d641e21cfdb 100644 --- a/public/app/features/dashboard/partials/settings.html +++ b/public/app/features/dashboard/partials/settings.html @@ -104,7 +104,7 @@
Last updated at: - {{formatDate(dashboardMeta.updated)}} + {{dashboard.formatDate(dashboardMeta.updated)}}
Last updated by: @@ -112,7 +112,7 @@
Created at: - {{formatDate(dashboardMeta.created)}}  + {{dashboard.formatDate(dashboardMeta.created)}} 
Created by: diff --git a/public/test/specs/dynamic_dashboard_srv_specs.ts b/public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts similarity index 98% rename from public/test/specs/dynamic_dashboard_srv_specs.ts rename to public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts index 6f233fcb9ff..ee2fac16ba1 100644 --- a/public/test/specs/dynamic_dashboard_srv_specs.ts +++ b/public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts @@ -1,7 +1,7 @@ import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; import 'app/features/dashboard/dashboardSrv'; -import {DynamicDashboardSrv} from '../../app/features/dashboard/dynamic_dashboard_srv'; +import {DynamicDashboardSrv} from '../dynamic_dashboard_srv'; function dynamicDashScenario(desc, func) { diff --git a/public/app/features/dashboard/specs/exporter_specs.ts b/public/app/features/dashboard/specs/exporter_specs.ts new file mode 100644 index 00000000000..7831905fda8 --- /dev/null +++ b/public/app/features/dashboard/specs/exporter_specs.ts @@ -0,0 +1,45 @@ +import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; + +import {DashboardExporter} from '../exporter'; + +describe('given dashboard with repeated panels', function() { + var dash, exported; + + beforeEach(() => { + dash = { + rows: [], + templating: { list: [] } + }; + dash.templating.list.push({ + name: 'apps', + current: {}, + options: [] + }); + + dash.rows.push({ + repeat: 'test', + panels: [ + {id: 2, repeat: 'apps'}, + {id: 2, repeat: null, repeatPanelId: 2}, + ] + }); + dash.rows.push({ + repeat: null, + repeatRowId: 1 + }); + + var exporter = new DashboardExporter(); + exported = exporter.makeExportable(dash); + }); + + + it('exported dashboard should not contain repeated panels', function() { + expect(exported.rows[0].panels.length).to.be(1); + }); + + it('exported dashboard should not contain repeated rows', function() { + expect(exported.rows.length).to.be(1); + }); + +}); + From 0f71838fdf7bfad029f16e371e98717f70ef759e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 14 Apr 2016 21:13:01 -0400 Subject: [PATCH 004/350] feat(dash export): dashboard export can now replace datasource names with variable and add inputs section --- .../app/features/dashboard/dashnav/dashnav.ts | 4 +- .../dashboard/dynamic_dashboard_srv.ts | 3 ++ public/app/features/dashboard/exporter.ts | 49 +++++++++++++++---- .../dashboard/specs/exporter_specs.ts | 23 +++++++-- 4 files changed, 63 insertions(+), 16 deletions(-) diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts index afad15afe87..e9ef96408ad 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/dashnav/dashnav.ts @@ -9,7 +9,7 @@ import {DashboardExporter} from '../exporter'; export class DashNavCtrl { /** @ngInject */ - constructor($scope, $rootScope, alertSrv, $location, playlistSrv, backendSrv, $timeout) { + constructor($scope, $rootScope, alertSrv, $location, playlistSrv, backendSrv, $timeout, datasourceSrv) { $scope.init = function() { $scope.onAppEvent('save-dashboard', $scope.saveDashboard); @@ -172,7 +172,7 @@ export class DashNavCtrl { $scope.exportDashboard = function() { var clone = $scope.dashboard.getSaveModelClone(); - var exporter = new DashboardExporter(); + var exporter = new DashboardExporter(datasourceSrv); exporter.export(clone); }; diff --git a/public/app/features/dashboard/dynamic_dashboard_srv.ts b/public/app/features/dashboard/dynamic_dashboard_srv.ts index a5fb73d099a..747c15479b2 100644 --- a/public/app/features/dashboard/dynamic_dashboard_srv.ts +++ b/public/app/features/dashboard/dynamic_dashboard_srv.ts @@ -4,6 +4,8 @@ import config from 'app/core/config'; import angular from 'angular'; import _ from 'lodash'; +import coreModule from 'app/core/core_module'; + export class DynamicDashboardSrv { iteration: number; dashboard: any; @@ -181,3 +183,4 @@ export class DynamicDashboardSrv { }); } } + diff --git a/public/app/features/dashboard/exporter.ts b/public/app/features/dashboard/exporter.ts index 0a9ac2ff2ca..04f4e0a203d 100644 --- a/public/app/features/dashboard/exporter.ts +++ b/public/app/features/dashboard/exporter.ts @@ -8,21 +8,50 @@ import {DynamicDashboardSrv} from './dynamic_dashboard_srv'; export class DashboardExporter { - makeExportable(dashboard) { - var dynSrv = new DynamicDashboardSrv(); - dynSrv.process(dashboard, {cleanUpOnly: true}); + constructor(private datasourceSrv) { + } - return dashboard; + makeExportable(dash) { + var dynSrv = new DynamicDashboardSrv(); + dynSrv.process(dash, {cleanUpOnly: true}); + + var inputs = []; + var datasources = {}; + var promises = []; + + for (let row of dash.rows) { + _.each(row.panels, (panel) => { + if (panel.datasource !== undefined) { + promises.push(this.datasourceSrv.get(panel.datasource).then(ds => { + var refName = 'DS_' + ds.name.toUpperCase(); + datasources[panel.datasource] = { + name: refName, + type: 'datasource', + pluginId: ds.meta.id, + }; + panel.datasource = '${' + refName +'}'; + })); + } + }); + } + + return Promise.all(promises).then(() => { + _.each(datasources, (value, key) => { + inputs.push(value); + }); + + dash["__inputs"] = inputs; + return dash; + }); } export(dashboard) { - var clean = this.makeExportable(dashboard); - var blob = new Blob([angular.toJson(clean, true)], { type: "application/json;charset=utf-8" }); - var wnd: any = window; - wnd.saveAs(blob, clean.title + '-' + new Date().getTime()); + return this.makeExportable(dashboard).then(clean => { + var blob = new Blob([angular.toJson(clean, true)], { type: "application/json;charset=utf-8" }); + var wnd: any = window; + wnd.saveAs(blob, clean.title + '-' + new Date().getTime()); + }); } } - - diff --git a/public/app/features/dashboard/specs/exporter_specs.ts b/public/app/features/dashboard/specs/exporter_specs.ts index 7831905fda8..190ad85743a 100644 --- a/public/app/features/dashboard/specs/exporter_specs.ts +++ b/public/app/features/dashboard/specs/exporter_specs.ts @@ -5,7 +5,7 @@ import {DashboardExporter} from '../exporter'; describe('given dashboard with repeated panels', function() { var dash, exported; - beforeEach(() => { + beforeEach((done) => { dash = { rows: [], templating: { list: [] } @@ -19,7 +19,7 @@ describe('given dashboard with repeated panels', function() { dash.rows.push({ repeat: 'test', panels: [ - {id: 2, repeat: 'apps'}, + {id: 2, repeat: 'apps', datasource: 'gfdb'}, {id: 2, repeat: null, repeatPanelId: 2}, ] }); @@ -28,8 +28,18 @@ describe('given dashboard with repeated panels', function() { repeatRowId: 1 }); - var exporter = new DashboardExporter(); - exported = exporter.makeExportable(dash); + var datasourceSrvStub = { + get: sinon.stub().returns(Promise.resolve({ + name: 'gfdb', + meta: {id: "testdb"} + })) + }; + + var exporter = new DashboardExporter(datasourceSrvStub); + exporter.makeExportable(dash).then(clean => { + exported = clean; + done(); + }); }); @@ -41,5 +51,10 @@ describe('given dashboard with repeated panels', function() { expect(exported.rows.length).to.be(1); }); + it('should replace datasource refs', function() { + var panel = exported.rows[0].panels[0]; + expect(panel.datasource).to.be("${DS_GFDB}"); + }); + }); From c555bbce3d531f50942e872ba959a248c6f2e4c7 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lespiau Date: Sun, 1 May 2016 01:32:36 +0200 Subject: [PATCH 005/350] Add optional tooltip ordering --- .../app/plugins/panel/graph/graph_tooltip.js | 18 +++++++++++++++--- public/app/plugins/panel/graph/module.ts | 1 + .../app/plugins/panel/graph/tab_display.html | 12 +++++++++--- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/panel/graph/graph_tooltip.js b/public/app/plugins/panel/graph/graph_tooltip.js index 9ab6369a6b2..0c2136f7d9d 100644 --- a/public/app/plugins/panel/graph/graph_tooltip.js +++ b/public/app/plugins/panel/graph/graph_tooltip.js @@ -81,9 +81,9 @@ function ($) { // Stacked series can increase its length on each new stacked serie if null points found, // to speed the index search we begin always on the last found hoverIndex. var newhoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex); - results.push({ value: value, hoverIndex: newhoverIndex }); + results.push({ value: value, hoverIndex: newhoverIndex, color: series.color, label: series.label }); } else { - results.push({ value: value, hoverIndex: hoverIndex }); + results.push({ value: value, hoverIndex: hoverIndex, color: series.color, label: series.label }); } } @@ -133,6 +133,18 @@ function ($) { absoluteTime = dashboard.formatDate(seriesHoverInfo.time, tooltipFormat); + // Dynamically reorder the hovercard for the current time point if the + // option is enabled. + if (panel.tooltip.ordering === 'decreasing') { + seriesHoverInfo.sort(function(a, b) { + return parseFloat(b.value) - parseFloat(a.value); + }); + } else if (panel.tooltip.ordering === 'increasing') { + seriesHoverInfo.sort(function(a, b) { + return parseFloat(a.value) - parseFloat(b.value); + }); + } + for (i = 0; i < seriesHoverInfo.length; i++) { hoverInfo = seriesHoverInfo[i]; @@ -150,7 +162,7 @@ function ($) { value = series.formatValue(hoverInfo.value); seriesHtml += '
'; - seriesHtml += ' ' + series.label + ':
'; + seriesHtml += ' ' + hoverInfo.label + ':
'; seriesHtml += '
' + value + '
'; plot.highlight(i, hoverInfo.hoverIndex); } diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index a11c537b178..2b2e75b5a33 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -92,6 +92,7 @@ class GraphCtrl extends MetricsPanelCtrl { tooltip : { value_type: 'cumulative', shared: true, + ordering: 'alphabetical', msResolution: false, }, // time overrides diff --git a/public/app/plugins/panel/graph/tab_display.html b/public/app/plugins/panel/graph/tab_display.html index 067651540e2..a33ae0bc67f 100644 --- a/public/app/plugins/panel/graph/tab_display.html +++ b/public/app/plugins/panel/graph/tab_display.html @@ -42,23 +42,29 @@
Misc options
- +
- +
- +
+
+ +
+ +
+
From de5a39f320555e5fce2bf190b06c5725059a06ab Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 2 May 2016 13:17:57 +0200 Subject: [PATCH 006/350] Revert "Revert "Merge branch 'utkarshcmu-rangeMaps'"" This reverts commit 58b91befdedfd5115f07710bc47ed4524bc0cb56. --- .../app/plugins/panel/singlestat/editor.html | 32 --------- .../plugins/panel/singlestat/mappings.html | 58 +++++++++++++++++ public/app/plugins/panel/singlestat/module.ts | 65 +++++++++++++++---- .../singlestat/specs/singlestat-specs.ts | 25 +++++++ 4 files changed, 136 insertions(+), 44 deletions(-) create mode 100644 public/app/plugins/panel/singlestat/mappings.html diff --git a/public/app/plugins/panel/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html index fc3c9f69542..0504277a5e1 100644 --- a/public/app/plugins/panel/singlestat/editor.html +++ b/public/app/plugins/panel/singlestat/editor.html @@ -204,35 +204,3 @@
- -
-
-
-
    -
  • - Value to text mapping -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • - -
  • - - - -
  • - -
-
-
-
-
diff --git a/public/app/plugins/panel/singlestat/mappings.html b/public/app/plugins/panel/singlestat/mappings.html new file mode 100644 index 00000000000..a1105a159dd --- /dev/null +++ b/public/app/plugins/panel/singlestat/mappings.html @@ -0,0 +1,58 @@ +
+
+
+ + Type + +
+ +
+
+
+
+
+
Set valuea mappings
+
+
+ + + + + + + + +
+ +
+ +
+
+
+
+
Set range mappings
+
+
+ + + + From + + To + + Text + +
+ +
+ +
+
+
diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index 3c0b9e5342a..e2e529d00c7 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -35,6 +35,14 @@ class SingleStatCtrl extends MetricsPanelCtrl { valueMaps: [ { value: 'null', op: '=', text: 'N/A' } ], + mappingTypes: [ + {name: 'value to text', value: 1}, + {name: 'range to text', value: 2}, + ], + rangeMaps: [ + { from: 'null', to: 'null', text: 'N/A' } + ], + mappingType: 1, nullPointMode: 'connected', valueName: 'avg', prefixFontSize: '50%', @@ -73,6 +81,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { onInitEditMode() { this.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%']; this.addEditorTab('Options', 'public/app/plugins/panel/singlestat/editor.html', 2); + this.addEditorTab('Value Mappings', 'public/app/plugins/panel/singlestat/mappings.html', 3); this.unitFormats = kbn.getUnitFormats(); } @@ -197,23 +206,45 @@ class SingleStatCtrl extends MetricsPanelCtrl { } } - // check value to text mappings - for (var i = 0; i < this.panel.valueMaps.length; i++) { - var map = this.panel.valueMaps[i]; - // special null case - if (map.value === 'null') { - if (data.value === null || data.value === void 0) { + // check value to text mappings if its enabled + if (this.panel.mappingType === 1) { + for (var i = 0; i < this.panel.valueMaps.length; i++) { + var map = this.panel.valueMaps[i]; + // special null case + if (map.value === 'null') { + if (data.value === null || data.value === void 0) { + data.valueFormated = map.text; + return; + } + continue; + } + + // value/number to text mapping + var value = parseFloat(map.value); + if (value === data.valueRounded) { data.valueFormated = map.text; return; } - continue; } + } else if (this.panel.mappingType === 2) { + for (var i = 0; i < this.panel.rangeMaps.length; i++) { + var map = this.panel.rangeMaps[i]; + // special null case + if (map.from === 'null' && map.to === 'null') { + if (data.value === null || data.value === void 0) { + data.valueFormated = map.text; + return; + } + continue; + } - // value/number to text mapping - var value = parseFloat(map.value); - if (value === data.valueRounded) { - data.valueFormated = map.text; - return; + // value/number to range mapping + var from = parseFloat(map.from); + var to = parseFloat(map.to); + if (to >= data.valueRounded && from <= data.valueRounded) { + data.valueFormated = map.text; + return; + } } } @@ -232,6 +263,16 @@ class SingleStatCtrl extends MetricsPanelCtrl { this.panel.valueMaps.push({value: '', op: '=', text: '' }); } + removeRangeMap(rangeMap) { + var index = _.indexOf(this.panel.rangeMaps, rangeMap); + this.panel.rangeMaps.splice(index, 1); + this.render(); + }; + + addRangeMap() { + this.panel.rangeMaps.push({from: '', to: '', text: ''}); + } + link(scope, elem, attrs, ctrl) { var $location = this.$location; var linkSrv = this.linkSrv; diff --git a/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts b/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts index dc85454b64a..3d6c565443b 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts @@ -84,4 +84,29 @@ describe('SingleStatCtrl', function() { expect(ctx.data.valueFormated).to.be('OK'); }); }); + + singleStatScenario('When range to text mapping is specifiedfor first range', function(ctx) { + ctx.setup(function() { + ctx.datapoints = [[41,50]]; + ctx.ctrl.panel.mappingType = 2; + ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}]; + }); + + it('Should replace value with text OK', function() { + expect(ctx.data.valueFormated).to.be('OK'); + }); + }); + + singleStatScenario('When range to text mapping is specified for other ranges', function(ctx) { + ctx.setup(function() { + ctx.datapoints = [[65,75]]; + ctx.ctrl.panel.mappingType = 2; + ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}]; + }); + + it('Should replace value with text NOT OK', function() { + expect(ctx.data.valueFormated).to.be('NOT OK'); + }); + }); + }); From de26a17dc7350f9c40e84857f4c999b2a6dd5e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 3 May 2016 09:00:58 +0200 Subject: [PATCH 007/350] feat(footer): began work on page footer --- pkg/api/dtos/index.go | 18 ++++++++------ pkg/api/frontendsettings.go | 8 +++--- pkg/api/index.go | 14 +++++++---- public/app/core/controllers/login_ctrl.js | 9 ------- public/app/partials/login.html | 9 ------- public/app/partials/signup_step2.html | 1 - public/sass/components/_footer.scss | 3 +++ public/views/index.html | 30 +++++++++++++++++++++++ 8 files changed, 56 insertions(+), 36 deletions(-) diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index 21201d30adf..b813c78f2bb 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -1,13 +1,17 @@ package dtos type IndexViewData struct { - User *CurrentUser - Settings map[string]interface{} - AppUrl string - AppSubUrl string - GoogleAnalyticsId string - GoogleTagManagerId string - MainNavLinks []*NavLink + User *CurrentUser + Settings map[string]interface{} + AppUrl string + AppSubUrl string + GoogleAnalyticsId string + GoogleTagManagerId string + MainNavLinks []*NavLink + BuildVersion string + BuildCommit string + NewGrafanaVersionExists bool + NewGrafanaVersion string } type PluginCss struct { diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index dd84f7827eb..f23f416b47a 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -137,11 +137,9 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro "allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin, "authProxyEnabled": setting.AuthProxyEnabled, "buildInfo": map[string]interface{}{ - "version": setting.BuildVersion, - "commit": setting.BuildCommit, - "buildstamp": setting.BuildStamp, - "latestVersion": plugins.GrafanaLatestVersion, - "hasUpdate": plugins.GrafanaHasUpdate, + "version": setting.BuildVersion, + "commit": setting.BuildCommit, + "buildstamp": setting.BuildStamp, }, } diff --git a/pkg/api/index.go b/pkg/api/index.go index 53538fd2775..4e4f4fadfeb 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -36,11 +36,15 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { LightTheme: prefs.Theme == "light", Timezone: prefs.Timezone, }, - Settings: settings, - AppUrl: setting.AppUrl, - AppSubUrl: setting.AppSubUrl, - GoogleAnalyticsId: setting.GoogleAnalyticsId, - GoogleTagManagerId: setting.GoogleTagManagerId, + Settings: settings, + AppUrl: setting.AppUrl, + AppSubUrl: setting.AppSubUrl, + GoogleAnalyticsId: setting.GoogleAnalyticsId, + GoogleTagManagerId: setting.GoogleTagManagerId, + BuildVersion: setting.BuildVersion, + BuildCommit: setting.BuildCommit, + NewGrafanaVersion: plugins.GrafanaLatestVersion, + NewGrafanaVersionExists: plugins.GrafanaHasUpdate, } if setting.DisableGravatar { diff --git a/public/app/core/controllers/login_ctrl.js b/public/app/core/controllers/login_ctrl.js index 4249d55a44f..45a47558ed7 100644 --- a/public/app/core/controllers/login_ctrl.js +++ b/public/app/core/controllers/login_ctrl.js @@ -35,15 +35,6 @@ function (angular, coreModule, config) { } }; - // build info view model - $scope.buildInfo = { - version: config.buildInfo.version, - commit: config.buildInfo.commit, - buildstamp: new Date(config.buildInfo.buildstamp * 1000), - latestVersion: config.buildInfo.latestVersion, - hasUpdate: config.buildInfo.hasUpdate, - }; - $scope.submit = function() { if ($scope.loginMode) { $scope.login(); diff --git a/public/app/partials/login.html b/public/app/partials/login.html index ff8d22451a8..8143ff35125 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -73,14 +73,5 @@ -
- - -
diff --git a/public/app/partials/signup_step2.html b/public/app/partials/signup_step2.html index 7b72c2357b9..afb6d0d4fc4 100644 --- a/public/app/partials/signup_step2.html +++ b/public/app/partials/signup_step2.html @@ -67,7 +67,6 @@ - diff --git a/public/sass/components/_footer.scss b/public/sass/components/_footer.scss index 9f2674980b0..3796275d7f9 100644 --- a/public/sass/components/_footer.scss +++ b/public/sass/components/_footer.scss @@ -7,3 +7,6 @@ a { color: darken($gray-1, 25%); } } +.footer { + padding: 5rem 0 1rem 0; +} diff --git a/public/views/index.html b/public/views/index.html index c4bffae5019..9bf1f9f2f0c 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -39,6 +39,36 @@
+ + From 02ae1f79f7ede9d90416029aeb31cd88bee13ffc Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 3 May 2016 10:42:01 +0200 Subject: [PATCH 008/350] docs(changelog): add node about range to text mappings in singlestat --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a67f45e69ec..3f9a7a7b299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 3.1.0 + +### Enhancements +* **Singlestat**: Add support for range to text mappings, closes [#1319](https://github.com/grafana/grafana/issues/1319) + # 3.0.0-beta7 (2016-05-02) ### Bug fixes From bb6f4fff87a588c4231ce9dcfd134fae498d1da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 3 May 2016 16:40:21 +0200 Subject: [PATCH 009/350] feat(export/import): minor progress --- pkg/api/api.go | 1 + public/app/features/dashboard/partials/import.html | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 684633e0bcd..2b1eaa714e3 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -55,6 +55,7 @@ func Register(r *macaron.Macaron) { r.Get("/dashboard/*", reqSignedIn, Index) r.Get("/dashboard-solo/*", reqSignedIn, Index) + r.Get("/import/dashboard", reqSignedIn, Index) r.Get("/playlists/", reqSignedIn, Index) r.Get("/playlists/*", reqSignedIn, Index) diff --git a/public/app/features/dashboard/partials/import.html b/public/app/features/dashboard/partials/import.html index 3f23e4cb682..ecf015b18cc 100644 --- a/public/app/features/dashboard/partials/import.html +++ b/public/app/features/dashboard/partials/import.html @@ -4,15 +4,19 @@
+
+ Upload .json file +
+
-
-
-
+
+
+ +
From 5ee1b6ca972278fd591809043f2da7e039900676 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 10 May 2016 12:54:25 +0200 Subject: [PATCH 010/350] docs(changelog): add note about merged PR --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52708480ac1..0aa8bc79b94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Enhancements * **Singlestat**: Add support for range to text mappings, closes [#1319](https://github.com/grafana/grafana/issues/1319) +* **Graph**: Adds sort order options for graph tooltip, closes [#1189](https://github.com/grafana/grafana/issues/1189) # 3.0.0 stable (unreleased) From d3bbc245c9fb8d8dd7d0e28005490c928663cf8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 May 2016 14:32:25 +0200 Subject: [PATCH 011/350] feat(query_part): began query part refactor to be able to reuse it in prometheus query editor --- .../core/components/query_part/query_part.ts | 123 +++++++++++ .../query_part/query_part_editor.ts | 0 .../plugins/datasource/influxdb/query_part.ts | 198 +++++------------- 3 files changed, 171 insertions(+), 150 deletions(-) create mode 100644 public/app/core/components/query_part/query_part.ts create mode 100644 public/app/core/components/query_part/query_part_editor.ts diff --git a/public/app/core/components/query_part/query_part.ts b/public/app/core/components/query_part/query_part.ts new file mode 100644 index 00000000000..90724f65d2d --- /dev/null +++ b/public/app/core/components/query_part/query_part.ts @@ -0,0 +1,123 @@ +/// + +import _ from 'lodash'; + +export class QueryPartDef { + type: string; + params: any[]; + defaultParams: any[]; + renderer: any; + category: any; + addStrategy: any; + + constructor(options: any) { + this.type = options.type; + this.params = options.params; + this.defaultParams = options.defaultParams; + this.renderer = options.renderer; + this.category = options.category; + this.addStrategy = options.addStrategy; + } +} + +export class QueryPart { + part: any; + def: QueryPartDef; + params: any[]; + text: string; + + constructor(part: any, def: any) { + this.part = part; + this.def = def; + if (!this.def) { + throw {message: 'Could not find query part ' + part.type}; + } + + part.params = part.params || _.clone(this.def.defaultParams); + this.params = part.params; + this.updateText(); + } + + render(innerExpr: string) { + return this.def.renderer(this, innerExpr); + } + + hasMultipleParamsInString (strValue, index) { + if (strValue.indexOf(',') === -1) { + return false; + } + + return this.def.params[index + 1] && this.def.params[index + 1].optional; + } + + updateParam (strValue, index) { + // handle optional parameters + // if string contains ',' and next param is optional, split and update both + if (this.hasMultipleParamsInString(strValue, index)) { + _.each(strValue.split(','), function(partVal: string, idx) { + this.updateParam(partVal.trim(), idx); + }, this); + return; + } + + if (strValue === '' && this.def.params[index].optional) { + this.params.splice(index, 1); + } else { + this.params[index] = strValue; + } + + this.part.params = this.params; + this.updateText(); + } + + updateText() { + if (this.params.length === 0) { + this.text = this.def.type + '()'; + return; + } + + var text = this.def.type + '('; + text += this.params.join(', '); + text += ')'; + this.text = text; + } +} + +export function functionRenderer(part, innerExpr) { + var str = part.def.type + '('; + var parameters = _.map(part.params, (value, index) => { + var paramType = part.def.params[index]; + if (paramType.type === 'time') { + if (value === 'auto') { + value = '$interval'; + } + } + if (paramType.quote === 'single') { + return "'" + value + "'"; + } else if (paramType.quote === 'double') { + return '"' + value + '"'; + } + + return value; + }); + + if (innerExpr) { + parameters.unshift(innerExpr); + } + return str + parameters.join(', ') + ')'; +} + + +export function suffixRenderer(part, innerExpr) { + return innerExpr + ' ' + part.params[0]; +} + +export function identityRenderer(part, innerExpr) { + return part.params[0]; +} + +export function quotedIdentityRenderer(part, innerExpr) { + return '"' + part.params[0] + '"'; +} + + diff --git a/public/app/core/components/query_part/query_part_editor.ts b/public/app/core/components/query_part/query_part_editor.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 63d70be5653..f22713a8057 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -1,6 +1,14 @@ /// import _ from 'lodash'; +import { + QueryPartDef, + QueryPart, + functionRenderer, + suffixRenderer, + identityRenderer, + quotedIdentityRenderer, +} from 'app/core/components/query_part/query_part'; var index = []; var categories = { @@ -12,71 +20,26 @@ var categories = { Fields: [], }; +function createPart(part): any { + var def = index[part.type]; + if (!def) { + throw {message: 'Could not find query part ' + part.type}; + } + + return new QueryPart(part, def); +}; + +function register(options: any) { + index[options.type] = new QueryPartDef(options); + options.category.push(index[options.type]); +} + var groupByTimeFunctions = []; -class QueryPartDef { - type: string; - params: any[]; - defaultParams: any[]; - renderer: any; - category: any; - addStrategy: any; - - constructor(options: any) { - this.type = options.type; - this.params = options.params; - this.defaultParams = options.defaultParams; - this.renderer = options.renderer; - this.category = options.category; - this.addStrategy = options.addStrategy; - } - - static register(options: any) { - index[options.type] = new QueryPartDef(options); - options.category.push(index[options.type]); - } -} - -function functionRenderer(part, innerExpr) { - var str = part.def.type + '('; - var parameters = _.map(part.params, (value, index) => { - var paramType = part.def.params[index]; - if (paramType.type === 'time') { - if (value === 'auto') { - value = '$interval'; - } - } - if (paramType.quote === 'single') { - return "'" + value + "'"; - } else if (paramType.quote === 'double') { - return '"' + value + '"'; - } - - return value; - }); - - if (innerExpr) { - parameters.unshift(innerExpr); - } - return str + parameters.join(', ') + ')'; -} - function aliasRenderer(part, innerExpr) { return innerExpr + ' AS ' + '"' + part.params[0] + '"'; } -function suffixRenderer(part, innerExpr) { - return innerExpr + ' ' + part.params[0]; -} - -function identityRenderer(part, innerExpr) { - return part.params[0]; -} - -function quotedIdentityRenderer(part, innerExpr) { - return '"' + part.params[0] + '"'; -} - function fieldRenderer(part, innerExpr) { if (part.params[0] === '*') { return '*'; @@ -149,13 +112,13 @@ function addAliasStrategy(selectParts, partModel) { function addFieldStrategy(selectParts, partModel, query) { // copy all parts var parts = _.map(selectParts, function(part: any) { - return new QueryPart({type: part.def.type, params: _.clone(part.params)}); + return createPart({type: part.def.type, params: _.clone(part.params)}); }); query.selectModels.push(parts); } -QueryPartDef.register({ +register({ type: 'field', addStrategy: addFieldStrategy, category: categories.Fields, @@ -165,7 +128,7 @@ QueryPartDef.register({ }); // Aggregations -QueryPartDef.register({ +register({ type: 'count', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -174,7 +137,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'distinct', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -183,7 +146,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'integral', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -192,7 +155,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'mean', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -201,7 +164,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'median', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -210,7 +173,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'sum', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -221,7 +184,7 @@ QueryPartDef.register({ // transformations -QueryPartDef.register({ +register({ type: 'derivative', addStrategy: addTransformationStrategy, category: categories.Transformations, @@ -230,7 +193,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'non_negative_derivative', addStrategy: addTransformationStrategy, category: categories.Transformations, @@ -239,7 +202,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'difference', addStrategy: addTransformationStrategy, category: categories.Transformations, @@ -248,7 +211,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'moving_average', addStrategy: addTransformationStrategy, category: categories.Transformations, @@ -257,7 +220,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'stddev', addStrategy: addTransformationStrategy, category: categories.Transformations, @@ -266,7 +229,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'time', category: groupByTimeFunctions, params: [{ name: "interval", type: "time", options: ['auto', '1s', '10s', '1m', '5m', '10m', '15m', '1h'] }], @@ -274,7 +237,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'fill', category: groupByTimeFunctions, params: [{ name: "fill", type: "string", options: ['none', 'null', '0', 'previous'] }], @@ -283,7 +246,7 @@ QueryPartDef.register({ }); // Selectors -QueryPartDef.register({ +register({ type: 'bottom', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -292,7 +255,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'first', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -301,7 +264,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'last', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -310,7 +273,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'max', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -319,7 +282,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'min', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -328,7 +291,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'percentile', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -337,7 +300,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'top', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -346,7 +309,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'tag', category: groupByTimeFunctions, params: [{name: 'tag', type: 'string', dynamicLookup: true}], @@ -354,7 +317,7 @@ QueryPartDef.register({ renderer: fieldRenderer, }); -QueryPartDef.register({ +register({ type: 'math', addStrategy: addMathStrategy, category: categories.Math, @@ -363,7 +326,7 @@ QueryPartDef.register({ renderer: suffixRenderer, }); -QueryPartDef.register({ +register({ type: 'alias', addStrategy: addAliasStrategy, category: categories.Aliasing, @@ -373,74 +336,9 @@ QueryPartDef.register({ renderer: aliasRenderer, }); -class QueryPart { - part: any; - def: QueryPartDef; - params: any[]; - text: string; - - constructor(part: any) { - this.part = part; - this.def = index[part.type]; - if (!this.def) { - throw {message: 'Could not find query part ' + part.type}; - } - - part.params = part.params || _.clone(this.def.defaultParams); - this.params = part.params; - this.updateText(); - } - - render(innerExpr: string) { - return this.def.renderer(this, innerExpr); - } - - hasMultipleParamsInString (strValue, index) { - if (strValue.indexOf(',') === -1) { - return false; - } - - return this.def.params[index + 1] && this.def.params[index + 1].optional; - } - - updateParam (strValue, index) { - // handle optional parameters - // if string contains ',' and next param is optional, split and update both - if (this.hasMultipleParamsInString(strValue, index)) { - _.each(strValue.split(','), function(partVal: string, idx) { - this.updateParam(partVal.trim(), idx); - }, this); - return; - } - - if (strValue === '' && this.def.params[index].optional) { - this.params.splice(index, 1); - } else { - this.params[index] = strValue; - } - - this.part.params = this.params; - this.updateText(); - } - - updateText() { - if (this.params.length === 0) { - this.text = this.def.type + '()'; - return; - } - - var text = this.def.type + '('; - text += this.params.join(', '); - text += ')'; - this.text = text; - } -} export default { - create: function(part): any { - return new QueryPart(part); - }, - + create: createPart, getCategories: function() { return categories; } From ae66c3f2894600e4ea0915f54af140e1b17b6b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 May 2016 14:35:50 +0200 Subject: [PATCH 012/350] fix(influxdb): minor editor display fix --- .../app/plugins/datasource/influxdb/partials/query.editor.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index c3a52531ef1..0d6dd5c5c3b 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -30,7 +30,7 @@
From cca37caf331ce62eb3cbdb16b02bbbeb1d9583c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 May 2016 15:48:07 +0200 Subject: [PATCH 013/350] feat(prometheus): began work on prometheus query model --- .../datasource/prometheus/prom_query.ts | 79 +++++++++++++++++++ .../prometheus/specs/query_specs.ts | 23 ++++++ 2 files changed, 102 insertions(+) create mode 100644 public/app/plugins/datasource/prometheus/prom_query.ts create mode 100644 public/app/plugins/datasource/prometheus/specs/query_specs.ts diff --git a/public/app/plugins/datasource/prometheus/prom_query.ts b/public/app/plugins/datasource/prometheus/prom_query.ts new file mode 100644 index 00000000000..e9ef6ddf40d --- /dev/null +++ b/public/app/plugins/datasource/prometheus/prom_query.ts @@ -0,0 +1,79 @@ +import { + QueryPartDef, + QueryPart, + functionRenderer, + suffixRenderer, + identityRenderer, + quotedIdentityRenderer, +} from 'app/core/components/query_part/query_part'; + +var index = []; +var categories = { + Functions: [], +}; + +export class PromQuery { + target: any; + metric: string; + range: string; + filters: any[]; + functions: any[]; + templateSrv: any; + scopedVars: any; + + constructor(target, templateSrv?, scopedVars?) { + this.target = target; + this.templateSrv = templateSrv; + this.scopedVars = scopedVars; + } + + render() { + var query = this.target.metric; + if (this.target.range) { + query += '[' + this.target.range + ']'; + } + + for (let funcModel of this.target.functions) { + var partDef = index[funcModel.type]; + if (!partDef) { + continue; + } + + var part = new QueryPart(funcModel, partDef); + query = part.render(query); + } + + return query; + } +} + +export function createPart(part): any { + var def = index[part.type]; + if (!def) { + throw {message: 'Could not find query part ' + part.type}; + } + + return new QueryPart(part, def); +} + +function register(options: any) { + index[options.type] = new QueryPartDef(options); + options.category.push(index[options.type]); +} + +function addFunctionStrategy(model, partModel) { + model.functions.push(partModel); +} + +register({ + type: 'rate', + addStrategy: addFunctionStrategy, + category: categories.Functions, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + +export function getCategories() { + return categories; +} diff --git a/public/app/plugins/datasource/prometheus/specs/query_specs.ts b/public/app/plugins/datasource/prometheus/specs/query_specs.ts new file mode 100644 index 00000000000..5bb164c306e --- /dev/null +++ b/public/app/plugins/datasource/prometheus/specs/query_specs.ts @@ -0,0 +1,23 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import {PromQuery} from '../prom_query'; + +describe.only('PromQuery', function() { + var templateSrv = {replace: val => val}; + + describe('render series with mesurement only', function() { + it('should generate correct query', function() { + var query = new PromQuery({ + metric: 'cpu', + range: '5m', + functions: [ + {type: 'rate', params: []} + ] + }, templateSrv, {}); + + var queryText = query.render(); + expect(queryText).to.be('rate(cpu[5m])'); + }); + }); + +}); From d6768402582c10e4bf5a7efddcfc914475eecd48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 May 2016 16:02:00 +0200 Subject: [PATCH 014/350] docs(): added homebrew info to mac install docs --- docs/sources/installation/mac.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index 7246033b640..e65c2823666 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -6,8 +6,33 @@ page_keywords: grafana, installation, mac, osx, guide # Installing on Mac -There is currently no binary build for Mac, but Grafana will happily build on Mac. Read the [build from -source](/project/building_from_source) page for instructions on how to -build it yourself. +Installation can be done using [homebrew](http://brew.sh/) + +Install latest stable: + +``` +brew install grafana/grafana/grafana +``` + +To start grafana look at the command printed after the homebrew install completes. + +You can also add the grafana as tap. + +``` +brew tap grafana/grafana +brew install grafana +``` + +Install latest unstable from master: + +``` +brew install --HEAD grafana/grafana/grafana +``` + +To upgrade use the reinstall command + +``` +brew reinstall --HEAD grafana/grafana/grafana +``` From 56e53d7da24488c8de4ef25df22a92e4a04ae89c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 May 2016 16:07:59 +0200 Subject: [PATCH 015/350] docs(): update readme dependency section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e678e48a14d..c6021182136 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ the latest master builds [here](http://grafana.org/download/builds) ### Dependencies - Go 1.5 -- NodeJS v0.12.0 +- NodeJS v4+ - [Godep](https://github.com/tools/godep) ### Get Code From c299b86983281bb9ea891d392fbc0f690fc4b9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 May 2016 16:33:05 +0200 Subject: [PATCH 016/350] docs(): updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6021182136..18a497092f2 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ go run build.go build ### Building frontend assets -To build less to css for the frontend you will need a recent version of of **node (v0.12.0)**, +To build less to css for the frontend you will need a recent version of of **node (v4+)**, npm (v2.5.0) and grunt (v0.4.5). Run the following: ```bash From 9b0da20d90429c1766d96c46c6a7a612bf5c875a Mon Sep 17 00:00:00 2001 From: Peggy Li Date: Tue, 10 May 2016 16:06:51 +0000 Subject: [PATCH 017/350] Fix typo in error message from update checker --- pkg/plugins/update_checker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/plugins/update_checker.go b/pkg/plugins/update_checker.go index 36169434096..ed43398357e 100644 --- a/pkg/plugins/update_checker.go +++ b/pkg/plugins/update_checker.go @@ -91,14 +91,14 @@ func checkForUpdates() { resp2, err := client.Get("https://raw.githubusercontent.com/grafana/grafana/master/latest.json") if err != nil { - log.Trace("Failed to get lates.json repo from github: %v", err.Error()) + log.Trace("Failed to get latest.json repo from github: %v", err.Error()) return } defer resp2.Body.Close() body, err = ioutil.ReadAll(resp2.Body) if err != nil { - log.Trace("Update check failed, reading response from github.net, %v", err.Error()) + log.Trace("Update check failed, reading response from github.com, %v", err.Error()) return } From 79a8017fe916698f1979e68d591ef2ef96bf1417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 May 2016 20:31:47 +0200 Subject: [PATCH 018/350] feat(export): more progress on dashboard export --- public/app/features/dashboard/exporter.ts | 14 ++++++++++++++ .../app/features/dashboard/specs/exporter_specs.ts | 9 ++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard/exporter.ts b/public/app/features/dashboard/exporter.ts index c1f5a13fd9b..8ed3f950c7f 100644 --- a/public/app/features/dashboard/exporter.ts +++ b/public/app/features/dashboard/exporter.ts @@ -16,6 +16,7 @@ export class DashboardExporter { dynSrv.process(dash, {cleanUpOnly: true}); var inputs = []; + var requires = {}; var datasources = {}; var promises = []; @@ -30,6 +31,13 @@ export class DashboardExporter { pluginId: ds.meta.id, }; panel.datasource = '${' + refName +'}'; + + requires['datasource' + ds.meta.id] = { + type: 'datasource', + id: ds.meta.id, + name: ds.meta.name, + version: ds.meta.info.version + }; })); } }); @@ -40,7 +48,13 @@ export class DashboardExporter { inputs.push(value); }); + requires = _.map(requires, req => { + return req; + }); + dash["__inputs"] = inputs; + dash["__requires"] = requires; + return dash; }); } diff --git a/public/app/features/dashboard/specs/exporter_specs.ts b/public/app/features/dashboard/specs/exporter_specs.ts index 190ad85743a..5b0576b729c 100644 --- a/public/app/features/dashboard/specs/exporter_specs.ts +++ b/public/app/features/dashboard/specs/exporter_specs.ts @@ -2,7 +2,7 @@ import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/co import {DashboardExporter} from '../exporter'; -describe('given dashboard with repeated panels', function() { +describe.only('given dashboard with repeated panels', function() { var dash, exported; beforeEach((done) => { @@ -39,6 +39,7 @@ describe('given dashboard with repeated panels', function() { exporter.makeExportable(dash).then(clean => { exported = clean; done(); + console.log('done'); }); }); @@ -56,5 +57,11 @@ describe('given dashboard with repeated panels', function() { expect(panel.datasource).to.be("${DS_GFDB}"); }); + it('should add datasource as input', function() { + expect(exported.__inputs[0].name).to.be("DS_GFDB"); + expect(exported.__inputs[0].pluginId).to.be("testdb"); + expect(exported.__inputs[0].type).to.be("datasource"); + }); + }); From 959714a6efc465219433e99ead3c8770f5896d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 May 2016 20:33:29 +0200 Subject: [PATCH 019/350] feat(docs): updated what's new article --- docs/sources/guides/whats-new-in-v3.md | 39 ++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/docs/sources/guides/whats-new-in-v3.md b/docs/sources/guides/whats-new-in-v3.md index eb1030f8f31..051691e0384 100644 --- a/docs/sources/guides/whats-new-in-v3.md +++ b/docs/sources/guides/whats-new-in-v3.md @@ -39,12 +39,13 @@ entire experience right within Grafana. -A preview of [Grafana.net](http://grafana.net) is launching along with this release. We -think it’s the perfect compliment to Grafana. +[Grafana.net](https://grafana.net) offers a central repository where the community can come together to discover, create and +share plugins (data sources, panels, apps) and dashboards. -Grafana.net currently offers a central repository where the community -can come together to discover and share plugins (Data Sources, Panels, -Apps) and Dashboards for Grafana 3.0 and above. +We are also working on a hosted Graphite-compatible data source that will be optimized for use with Grafana. +It’ll be easy to combine your existing data source(s) with this OpenSaaS option. Finally, Grafana.net can +also be a hub to manage all your Grafana instances. You’ll be able to monitor their health and availability, +perform dashboard backups, and more. We are also working on a hosted Graphite-compatible Data Source that will be optimized for use with Grafana. It’ll be easy to combine your @@ -65,7 +66,6 @@ Grafana 3.0 comes with a new command line tool called grafana-cli. You can easily install plugins from Grafana.net with it. For example: - ``` grafana-cli install grafana-pie-chart-panel ``` @@ -188,6 +188,33 @@ you can still install manually from [Grafana.net](http://grafana.net) * KairosDB: This data source has also no longer shipped with Grafana, you can install it manually from [Grafana.net](http://grafana.net) +## Plugin showcase + +Discovering and installing plugins is very quick and easy with Grafana 3.0 and [Grafana.net](https://grafana.net). Here +are a couple that I incurage you try! + +#### [Clock Panel](https://grafana.net/plugins/grafana-clock-panel) +Support's both current time and count down mode. + + +#### [Pie Chart Panel](https://grafana.net/plugins/grafana-piechart-panel) +A simple pie chart panel is now available as an external plugin. + + +#### [WorldPing App](https://grafana.net/plugins/raintank-worldping-app) +This is full blown Grafana App that adds new panels, data sources and pages to give +feature rich global performance monitoring directly from your on-prem Grafana. + + + +#### [Zabbix App](https://grafana.net/plugins/alexanderzobnin-zabbix-app) +This app contains the already very pouplar Zabbix data source plugin, 2 dashboards and a triggers panel. It is +created and maintained by [Alexander Zobnin](https://github.com/alexanderzobnin/grafana-zabbix). + + + +Checkout the full list of plugins on [Grafana.net](https://grafana.net/plugins) + ## CHANGELOG For a detailed list and link to github issues for everything included From 5b42753b8b135bcdc987ba6417b990c348106871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 May 2016 21:09:15 +0200 Subject: [PATCH 020/350] feat(export): progress on dashboard export --- karma.conf.js | 2 +- public/app/features/dashboard/exporter.ts | 25 ++++++++++++--- .../dashboard/specs/exporter_specs.ts | 31 ++++++++++++++++--- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index c803dda5eae..cdcea23a90b 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -26,7 +26,7 @@ module.exports = function(config) { browsers: ['PhantomJS'], captureTimeout: 20000, singleRun: true, - autoWatchBatchDelay: 10000, + autoWatchBatchDelay: 1000, browserNoActivityTimeout: 60000, }); diff --git a/public/app/features/dashboard/exporter.ts b/public/app/features/dashboard/exporter.ts index 8ed3f950c7f..9a8d5bae5b1 100644 --- a/public/app/features/dashboard/exporter.ts +++ b/public/app/features/dashboard/exporter.ts @@ -36,10 +36,20 @@ export class DashboardExporter { type: 'datasource', id: ds.meta.id, name: ds.meta.name, - version: ds.meta.info.version + version: ds.meta.info.version || "1.0.0", }; })); } + + var panelDef = config.panels[panel.type]; + if (panelDef) { + requires['panel' + panelDef.id] = { + type: 'panel', + id: panelDef.id, + name: panelDef.name, + version: panelDef.info.version, + }; + } }); } @@ -56,14 +66,21 @@ export class DashboardExporter { dash["__requires"] = requires; return dash; + }).catch(err => { + console.log('Export failed:', err); + return {}; }); } export(dashboard) { return this.makeExportable(dashboard).then(clean => { - var blob = new Blob([angular.toJson(clean, true)], { type: "application/json;charset=utf-8" }); - var wnd: any = window; - wnd.saveAs(blob, clean.title + '-' + new Date().getTime() + '.json'); + var html = angular.toJson(clean, true); + var uri = "data:application/json," + encodeURIComponent(html); + var newWindow = window.open(uri); + + // var blob = new Blob([angular.toJson(clean, true)], { type: "application/json;charset=utf-8" }); + // var wnd: any = window; + // wnd.saveAs(blob, clean.title + '-' + new Date().getTime() + '.json'); }); } diff --git a/public/app/features/dashboard/specs/exporter_specs.ts b/public/app/features/dashboard/specs/exporter_specs.ts index 5b0576b729c..62403efae7c 100644 --- a/public/app/features/dashboard/specs/exporter_specs.ts +++ b/public/app/features/dashboard/specs/exporter_specs.ts @@ -1,11 +1,13 @@ import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; +import _ from 'lodash'; +import config from 'app/core/config'; import {DashboardExporter} from '../exporter'; describe.only('given dashboard with repeated panels', function() { var dash, exported; - beforeEach((done) => { + beforeEach(done => { dash = { rows: [], templating: { list: [] } @@ -19,7 +21,7 @@ describe.only('given dashboard with repeated panels', function() { dash.rows.push({ repeat: 'test', panels: [ - {id: 2, repeat: 'apps', datasource: 'gfdb'}, + {id: 2, repeat: 'apps', datasource: 'gfdb', type: 'graph'}, {id: 2, repeat: null, repeatPanelId: 2}, ] }); @@ -31,19 +33,23 @@ describe.only('given dashboard with repeated panels', function() { var datasourceSrvStub = { get: sinon.stub().returns(Promise.resolve({ name: 'gfdb', - meta: {id: "testdb"} + meta: {id: "testdb", info: {version: "1.2.1"}, name: "TestDB"} })) }; + config.panels['graph'] = { + id: "graph", + name: "Graph", + info: {version: "1.1.0"} + }; + var exporter = new DashboardExporter(datasourceSrvStub); exporter.makeExportable(dash).then(clean => { exported = clean; done(); - console.log('done'); }); }); - it('exported dashboard should not contain repeated panels', function() { expect(exported.rows[0].panels.length).to.be(1); }); @@ -63,5 +69,20 @@ describe.only('given dashboard with repeated panels', function() { expect(exported.__inputs[0].type).to.be("datasource"); }); + it('should add datasource to required', function() { + var require = _.findWhere(exported.__requires, {name: 'TestDB'}); + expect(require.name).to.be("TestDB"); + expect(require.id).to.be("testdb"); + expect(require.type).to.be("datasource"); + expect(require.version).to.be("1.2.1"); + }); + + it('should add panel to required', function() { + var require = _.findWhere(exported.__requires, {name: 'Graph'}); + expect(require.name).to.be("Graph"); + expect(require.id).to.be("graph"); + expect(require.version).to.be("1.1.0"); + }); + }); From 995f6be2c45c4d246ca7525bacf5a5317caad8ed Mon Sep 17 00:00:00 2001 From: Trent White Date: Wed, 11 May 2016 03:08:25 -0400 Subject: [PATCH 021/350] old grafana wordmark was still showing on login and sidebar (#4972) --- public/fonts/grafana-icons.eot | Bin 28992 -> 29716 bytes public/fonts/grafana-icons.svg | 13 ++++++---- public/fonts/grafana-icons.ttf | Bin 28804 -> 29528 bytes public/fonts/grafana-icons.woff | Bin 28880 -> 29604 bytes public/sass/base/_grafana_icons.scss | 36 ++++++++++++++------------- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/public/fonts/grafana-icons.eot b/public/fonts/grafana-icons.eot index 4e594eca65de0def6ec31291285f897eff9a9873..88bc142e2056ec8daf240b07534d77205b05dfc6 100755 GIT binary patch delta 2520 zcmai0eQZVrS9qs$7C@sw9;3Dkm*w@Y3hUj;Tt*elr_2?#|a zk~b7>hn4^v({6dJ-?4hMpIbd1D?|zaqE@$?MhlZwyXCf?v%0lgsIx+}0Q5j8xrn~@ zqc0n2LvKD4RqeKF8V)6Cq@{t%l@6O3cwZw3`VeSIfuax5Pz}WNLEm=Lh0NuJuC9g4 znbQ0Y$2D;hH#UYt6Q|GrhMAR(bp2TVMv?3JD!Fucxyt_wm4VM6{F?DF?~)4QA&MR} zZ^yJ?LRtcHv$PTY3!n=f>2$|`66W2(qJB_&yTs5g<7!aXW11MU@?f1^Vb)3I>h-{< zj~!-7KRLr(qJJYiF%S!}6H;jcyJP^r7qBFX0VGhs3qx4rxOSVdaYIWN#u2PMf6{eR2NsM%pG|7?$ za+Eb>Yg~9hz#v&7Pa@`2XHhI>QuA)9UXyVTj};ec{tKK-nDgW*H~L*NIp23+1Z)N zbneVR`&(~K<@Tss`{gG3n?ZRfTBMD)mS`g*NTc7K+SwfH%pR!P9c>UvapG&2Um%Y9KHWNwmMp~k{;iv-Wa+oc4yT$An)(0Kjg=5;8l4!5f zkMi{_DB36VO8%Md>FI9zQuj17-F3DATZ= zr?>aCwgOY|$#nN46Zfwr`uh_!k^d%t92P&yrefOA>C-mF$(po}R@k*>4u-c@rxMBFmbB=|-bL zG&UUwnpjp4Eu2Z|aC-!n#eI*>#Z()NMne^K**y%$aAge32_8>}VrI$^QnX+WrkjkS z!PuA%nmA5CC2wF%%67L)6*!JD@RVbmb|>8=8ja#6>bAKA39XAxcSk@QG17`NF3IY; zRN-URH^f(n^Zq#(nqV!W%jTk+Fc5Jw2I4NBce@`Cn2VPvgrHv{9W+zmIMKqI@fNCw z@K_`AjyU2^(1gSg+dkzcTg(u4#=nobhv4_!dL!+_A|-tmf+xP zJ;=n z+{K5&=qg2MWJ;D&6!p$n4fD_vT4bGIh{p}U=VB*K>8DRNisnOnNhLI$G#xmMx||lc z(WgRLxAr~Sr#>k@pFDm1#A51svAcW2`gfuE@YuD5lvj6}9 delta 1654 zcma)6Z)_7~7=PcpwkLnwc5AQgVD0W-+d0tYh(J|ISyA55W-8vy= zCL|>R2^fu(BZ0(4O+;ceQIn0tm?awdpeBHT82vPnh+%#)d{81`htG8k2TEeD@AZDq zpWpNR?tR`nb{&6l6}WQ>^Y||x!~q|J8s`gRyZ0axJg6{i_z`KwZ)@&ezLOa()!zD}N3 z1glk0c*7D3E?C6sYEj{xbsh0!qd(c*!LlKaruiVtc62BGjrS~Eh}AC&`Yh%J%v2#yoE{`#?XSL=F(X04eYl1XV#YHaFpLlTx^uP@72w^JS+Zm!T9cI~iL! zRIvIeALXDNszdde(pFI}w+teZT}wnfmrMs2RLG)LXYcFY&;#AaJL(KQ)m)G*E4<=7 zf6jRhe&?O%@Rz}~!3W8AD*#w*IMMs2;q(sjT>yzV@Ewww(Fnnyfa zwBTRwTqIYKAEOnh26>T$qG&S+58D}=Mc}=P98q}-W3x-)h$4H15WU1BDN9{TJ@@4h z`=quu$ylxZ{jJ8jhr1s%+{NT;3kg!2u&tcYx=la4XyO-0TKn}^mzuU2kyFE-Iksp< zL+A)ON!&!0$c|Pa4*5V62i?-3gaawG>?N_sjTxF;hPg*Era>rnsF=g#uK5PWNENbacI5+SLt&m64yVo8Lys~@M_|wiyHFj2$;fs@9}i zh~@gvxQ@7?&*57piqvmy+NfW{wFpg3KWVlW!<9a=6UJUa&!T=biVh+TeTdGWZ=p|# zslWmZmoZsV*zQmR04oS%{K~TjN5i8Ca5&_M1TUI08-5E_fnl2*ZVWr6G7>E?WO@65 z0k#q|0@&bE1lZCIMXW*!^M5kouvnNO_4=TsnfdR!_m!Rcsqv+a(Xm7@m>7#T zjXw62dNiIW<%-d0u9R3i(9`bH&k*i`fmDlQO~FaL%=ZSQ?H4O^`-=l?^{b;nkq*)^zOihs8pCWDuWZooj)YYU=B2(*MF# zD9Q2svd90Gqz^Ch!|Z%Dz*h+vK2@-$M@sv5R5tyAR+x;B7y51u{73w4@2UFP bhJB5D{U-vwfeHDR^1e|IDO1Z|Tu%HAM9h#> diff --git a/public/fonts/grafana-icons.svg b/public/fonts/grafana-icons.svg index 319e1739324..9006ea24078 100755 --- a/public/fonts/grafana-icons.svg +++ b/public/fonts/grafana-icons.svg @@ -31,10 +31,10 @@ - - + + - + @@ -46,14 +46,17 @@ - + - + + + + \ No newline at end of file diff --git a/public/fonts/grafana-icons.ttf b/public/fonts/grafana-icons.ttf index 2cfefbff196599282a6cf7ad15b01e9b29138070..50b2a0303f644e212864c67f544a810c45501853 100755 GIT binary patch delta 2543 zcmai0Yiv{J8UEhyT%9&UsY1MuVANC#_k8It= z^7p;xdA{d+-}k$m*M9DI@X2jZ2oORl$s%D$>&}6epwTS+5}CKLjZMsD<_m9KI!_4H zAfBAg9G*vOLcEApoX&l7|7Q!QZzKK|A$ilxRA$mT5`UJE125rtbOs4apZOBvUm@N& zGk4^v#_xOHK>Sz4zs(((=*kQv<8eafKStuvT;{2HGQhq<$iZ!hAIN9srdDoU=tDe% zL2k?+ID7;X#n0h(ZJaRN+0$z&sQ5PQgo@Q&04@c{>fJxE2;1rh>_BDW;mnD~* zuLX-Wkji7vqQa( zoEf4;pk+bHd5pCiV_8TCMspjeB3tTcD43*?a5ME(TP;T5-7O$!oiCgMzt%;A^$^oK zZP-Q^GZzel+_+j<+vO>LxPs19I+r*HS zyKC$Uvqq{{Mc-!+OtWN=oMbN0zY(73@IJ|eR2x8+bl`OX9*JKE68OOdBMb*|^#)@q zcwr7+XIb?I;=#w&_ZYwW9{rMvT;VFj!ARAgKpQ7A>YvrCv_n1jsJaHJ)x}4+Y9(s) zGyNn$Vx*U(NfxjA3GynrL_WZ^8yJtf8ILN7M`pDN&EUZ!ET3S^6>)JM?e2J_keq}B z2;hkrP;-AuBqTAoZ{=MBIV5{f9*&||W5J{R?-^cAU373SN+?)I|6dr!&!lFSb`G4J zPNZ_D`nSK(nQED9e<-`Nx$)8IL}X-Jo2b4EKZJvcV7HRpZSC?M+C(=GdqyMelRcX@ z^-Od`Mm&3GGF!VdneMF_=zQ(9+58S=)1ar7elqMCiI!xO?Pb~67|Q5(XScQmyR&<0 zw+EVcCEPTB-{XC0$>UqQPVCom>+4re=Te;=I|8BRUD?iLc1M$U^H6Y)bRlkM#jJq-cR!-YSAZp--imhSO!$kvXikI{zR;}dP}!iWD=cT~Lw9c|s+ zZR#~@YI4+iI{VeL_tfUEx?TP86K+#&1vz{7efB%dJ6Qf~WCN+kf)OB1BuTcD$CtmT zs#78^8Lxos@@iIaNiGSnipW?%LRdQ@5?+^T3-58ceOOE+$rTAlall#&&|@{4WZ7i2 zj%u9-K8Hi<$#PR)p&u2RSn#W#(#wS>dvm#7`h0JW$@QEXOcb7s4>9NO>>X0#^!daf z6Vx26YlRp3Zw}n-zuDh^LS2Db_%zph-^HCPiNV1HO%#4g$Gq{Op}4pB_Pxu#q`iIQ z2joYkGF86gT9vByH-lh+dN6>X6#=bKHiJK=M16iAZ!A710Y$~fQl)5sB*biWfG8IA zwX8!)D56qL>4ZA43B0wWOcjv<)}hjylh8tAEL0Y=0L@BDsVqr2Kv%Mtm-_maE@!`9 zvI_=Q5=53Y1kx>fov3f!6ELu>AeuOXzsuniSQf{f7CTd?)9ZD$)Gj+2j^Qd8mJ^)L zF29kfKuFPpF_3Q6i#mNvI$+>90ZCrR82p!v~|=lcG-?=1QgkEzP1S*PocOA#Wnb=a1E%a@^v|A zsLEaWtok#_;gDeO%a!;`&yq9uDzTR|l5Ud6+u%>(iD^y1FK$ddo^!D&G=@J@wiM<3 zF&o&x>yK#))Zm%g1Z@mQ@uDyqgjZ)H7ml@4DyT2Nx>$%`OB5{Ui@U zvQq?pUO;X%yAb57id<3v{vZ$ABttrFkY;0fp(4IT7&VG5*)nAs6?6&YOfmhn5d|tS z<{vTE(^{;jQ%gsWKCzT62G)T3@suDWyM-w|A3Rv4A04eU%y;OgL@~KlL@qS{Pfq(^ zp#7)9cFQ)Cztgf^zLSeV`*L{hFt^GPa(?;ZTnq-6Z_RDgb4}zd zwiRq=mc@J`^|I+N(vWn@e5^Vvo8;fvj@iFq|H5&uX0i61n{~H)KMyz`e7oUN4RaW7*4 delta 1678 zcma)7Z)_7~7=PcpcE=yxc5AQgfOdE9-C7)L*Y@sq2%~kQjho`OcB5mgg>@S;p>!y%H`_Pw{i z=Xsys^Ly`kpSvs9@u91@fG|R6B^pIE@^^GK1S_hT3t*Xo(Lb0?=jYFUata~52Jq1C z^u9dIm4HWKUbZ`P@cB8(`Ul`62<`10-jyD*(edvQ%3J||<1iSkv8uBOWg!Mh!`b}@ zoYlf5zzX2YncQG=`q%Eq&LA|h3=ElU`am9anoa=T511cGXLlX@`GXkXw;{<)KDTc_ z2!&66$nYcD{O;HnVa$x<4$Mq;Vl1qq(Zt=KOey+3v=+rs3c`G}NXjXuoaU@9%I>1& zV2IK}w5p8n;RmtE3Y@^ote^_?1=5j7`UAa*}46Jp4JSft{kJdYBJ ziznpjLOCs3JdB62Gj`3cc}yvbq?F`=;6%q#!72ZOU`4X%`~yM_=^!-LR%1x768wB= zGe0_JrXADpn{iCzFa0Tl4ubC`0J>D7y^@HEaRU@l=t!jedQ0&Yg=cGkhI-3!{(LDM z7>X~5=L=={`rV84D*9tMS=GpmWE4hQpb|kFXDt(nTU8o0QpQfuy-te`Bd>yRmZe zv^2*T%xDlDr6;Icr~=v0DkLBe>`4H%EbK&pt+0xl#aYPAg@yy5=uq z`%RizW75m075Y_NL*3Np@NE-A>$f+z=+}(qocuFtwiJRD9=aXQy^5Ygy=Vj-Mn&`? zI)}aioibOBMdG7WB}BMJ&@AO{de96(oX(R|58gh z7x(+)xp33S6Hja7vG`b~5DsU?;thRWtxo+MduO$-_c5w@t{KAu4BB5b8#xC zVYT>MZl8I=jL<|elh^QI@nl{qUe0gSZm=WlrK$}U$!g_3wH7B~o3=^SCJkTq9y>1C zx)00mIrn+abKduy^PcAow|cnUoj$1LsY3*t>jm{l*dGJ|HAxVD{1C`A?=NQ#j}71Js0>Sw^DIPa{dld==s? z%uId8>(g^5of=|E~MZ*`wpA+r6+EiDS$L;?d|_>S+wT@DgIiZzco$E1A^X zByvB%;P;rVJh<97fAshX+{rgT*Kzp72^VuZNI+R`7wmzu)mH(WSs<&A{=koME5wbH zol2{>4qO_u;&f@M!DIg6Nq9f$E-Mu++fqr+i@Pys;LXt&dF zp);y??8Xa5yLtyXBZLb8;}c4*V66QZ%S75SnoFixv#Ev#192K^ZKUo>i%Aa}R}+Y= z@wO(w$C_}U79y-+!&bVKy0+BQvve(0>faQEWcU0 z(&XItw%pBf@X4cJa}Mq;Qb8QV#}=(IP<+j-u;SDnGNfGTtD%X2`Z<~YPa1b0YA&EqMdpu*v~l}-)JL9-j>tzi^17TnqYJ;TG)1qbUC(?Tw>z|s6g+_L?Na{QAeOQPEy0Yp0mQL@n zt#sS4doo&)!mg!b#zW(g_*+fVCZhx?GZ@MF%-reBY zHWV1;VgdCQHPoA}lj`7?-Tu0siMF=!o;tt#$^4%|yM1hId)L?)q^n2NU9@ih*m#R8 z|K5Mqom6i_drMbWi+YfhMZ=+0%oU&^`cX8Du7kQj0Gf!wIih9b*VII+)kGli;1E*L#<&P zuv7!|So8+7*`T+KvW9_=;h1{1Wa=yQ{d@xtKJ`O-BY&!QcD9$k&^yb`_M97xA?L1NZyy_xGPsS6~J{ob7$=;^D2>;9!i#@;{@8J<*|| zsHgDuy~BT-y?x{|VO4d*9cNXl($@&047DJG$cg|fl#SqvWW!#cSEDaH#{or!$6}?Z zgE&O2HGn7*_BL%maVVf-P3eFdu!QJE+p88)Een-gQ9F%~Kds{qz3*m;qNDe0F5K}1rcwMWD^ziE?MoJUiGk!R-CmdM%&d27k6{R`U>lOc)^A)c!Oj!+2|%*h*X6Ou@~pt z?N563#Z45Jz&4Te>nR9=WZ?CyNsfTF~X;j7p_syLh3hI<4Q*>FBP1VKZga0-Pz z_$qJ@sHpIE+G()DRsOvCbH#2~;NZ*UKnPDRtXEF~y@rwsji+L_s zmPYVr%9@~pFJc8NczhA2Kn>>9DzY&g#f!o*$|p8)B~J;8yfsqGt$RX`ycM+3s1`Jt z$Z0uwN{ zW)=gYZbAcMyh8%byolUrdNH7>C~$ERGz%Kop~%Uktjt6*Vp(*%II5SL(k03;Dr#fM z86vvdBUvcNn194r&$3w0CYDd0{OWSN5SRh=gGo`0cZriaO<-r-P&tApxxbjw|0!Rw7t6(VXka+xar*1GCIauSU3JG(sm^y z3W-TcL?T8L#(jY3f*(X;G*OeWgcuTyd{HxSAjWSKj41PqlBh(&4xhU*94Lv`dwTc$ zp1;3)pZD(c&v@!OcD&fRX%oT-j=~gLYF-Z`tc{M?MprqCQ-iro0U)YBg9L0>*jyo|kK8CSSMx(KNKUmWAJE#W5QCbT=zXB`T zHJKm8A}eqLE3<+s&{L!{nF@wdJspIoqRl3%#6Uv9IYg?ckW}KTYKtf9{K>91o(~Bu zOM*P#)|K?vJwSvIuU{7RX-q^+^l9ys(q5!;`ZU~QLZCDT81P64^*bAz^&5C4LgTSbR$DP# z=A%1c>{av}>P2~U1Zn64bOC)0eac)Z7Gb!I$&$kkXCwe>DP@eGIeV}*s#Os-hf*)Y zgJ!ITy@V;nuuM)5hLwtNGAnYlxqOfTmJ)L!VDM8#Skm=H9Dxw-|3t!ODPiWzK`x`N z*9Rr7+<(`7sOi+(Zrg*&8=?z0_ExJOKotj zF1V6ZTfmRNs3-qYHeI&q@Top5D0_ETRz-ZMrOe9u&+ zQd`zHbf-jt^sJ$7kS+dy^7^+FA@RGAG#1Nb@E2D?^})@e?bqBUKCi}!n#_g>(I+M3clGnF#E&gSlerg~V8_2Z81}NdkRy~i8Z+*q> z-~!KB)axkTibZ`p8W-&t9omXOrx9 y?i0shmsH7Do?PDQuJyE%56NBcnVRX^Lv=6tPX@XJ2b4SNd*(Mv9e;D*66$XV=b7UG diff --git a/public/sass/base/_grafana_icons.scss b/public/sass/base/_grafana_icons.scss index f7f3a0368ee..f3264d409cd 100644 --- a/public/sass/base/_grafana_icons.scss +++ b/public/sass/base/_grafana_icons.scss @@ -1,10 +1,10 @@ @font-face { font-family: 'grafana-icons'; - src: url('../fonts/grafana-icons.eot?h6rv8b'); - src: url('../fonts/grafana-icons.eot?h6rv8b#iefix') format('embedded-opentype'), - url('../fonts/grafana-icons.ttf?h6rv8b') format('truetype'), - url('../fonts/grafana-icons.woff?h6rv8b') format('woff'), - url('../fonts/grafana-icons.svg?h6rv8b#grafana-icons') format('svg'); + src: url('../fonts/grafana-icons.eot?okx5td'); + src: url('../fonts/grafana-icons.eot?okx5td#iefix') format('embedded-opentype'), + url('../fonts/grafana-icons.ttf?okx5td') format('truetype'), + url('../fonts/grafana-icons.woff?okx5td') format('woff'), + url('../fonts/grafana-icons.svg?okx5td#grafana-icons') format('svg'); font-weight: normal; font-style: normal; } @@ -61,6 +61,9 @@ .icon-gf-endpoint:before { content: "\e609"; } +.icon-gf-page:before { + content: "\e908"; +} .icon-gf-filter:before { content: "\e60a"; } @@ -112,9 +115,6 @@ .icon-gf-save:before { content: "\e614"; } -.icon-gf-settings:before { - content: "\e615"; -} .icon-gf-share:before { content: "\e616"; } @@ -124,10 +124,13 @@ .icon-gf-search:before { content: "\e618"; } -.icon-gf-tag-add:before { +.icon-gf-settings:before { + content: "\e615"; +} +.icon-gf-add:before { content: "\e619"; } -.icon-gf-tag-remove:before { +.icon-gf-remove:before { content: "\e61a"; } .icon-gf-video:before { @@ -169,6 +172,12 @@ .icon-gf-scale:before { content: "\e906"; } +.icon-gf-pending:before { + content: "\e909"; +} +.icon-gf-verified:before { + content: "\e90a"; +} .icon-gf-worldping:before { content: "\e627"; } @@ -176,10 +185,3 @@ content: "\e903"; } -.icon-gf-app:before { - content: "\e902"; -} -.icon-gf-datasource:before { - content: "\e607"; -} - From bbd52c0b713a5220ae0e27f8471aa9877a7e6589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 May 2016 09:35:22 +0200 Subject: [PATCH 022/350] changelog(): updated and updated version --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 930a91af1da..dea2b9c5f31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 3.0.0 stable (unreleased) +# 3.0.0 Stable (2016-05-11) * **Templating**: Fixed issue with new data source variable not persisting current selected value, fixes [#4934](https://github.com/grafana/grafana/issues/4934) diff --git a/package.json b/package.json index 8b9ec906ed9..eab8c01a977 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "company": "Coding Instinct AB" }, "name": "grafana", - "version": "3.0.0-beta7", + "version": "3.0.1", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git" From 03663c5b289fb5e684fd83549929fd11667599a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 May 2016 10:23:17 +0200 Subject: [PATCH 023/350] docs(): updated download links --- docs/sources/installation/debian.md | 13 +++---------- docs/sources/installation/rpm.md | 27 ++++----------------------- 2 files changed, 7 insertions(+), 33 deletions(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index cac6a7c92b8..363cd4dcf7e 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -10,20 +10,13 @@ page_keywords: grafana, installation, debian, ubuntu, guide Description | Download ------------ | ------------- -Stable .deb for Debian-based Linux | [grafana_2.6.0_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb) -Beta .deb for Debian-based Linux | [grafana_3.0.0-beta71462173753_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta71462173753_amd64.deb) +Stable .deb for Debian-based Linux | [grafana_3.0.1_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.1_amd64.deb) ## Install Stable - $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb + $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.1_amd64.deb $ sudo apt-get install -y adduser libfontconfig - $ sudo dpkg -i grafana_2.6.0_amd64.deb - -## Install 3.0 Beta - - $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta71462173753_amd64.deb - $ sudo apt-get install -y adduser libfontconfig - $ sudo dpkg -i grafana_3.0.0-beta71462173753_amd64.deb + $ sudo dpkg -i grafana_3.0.1_amd64.deb ## APT Repository diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index 744cafe93db..88ee5cd7862 100644 --- a/docs/sources/installation/rpm.md +++ b/docs/sources/installation/rpm.md @@ -10,43 +10,24 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide Description | Download ------------ | ------------- -Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.6.0-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm) -Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta71462173753.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta71462173753.x86_64.rpm) +Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.1-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.3.1-1.x86_64.rpm) ## Install Stable Release from package file You can install Grafana using Yum directly. - $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm + $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.1-1.x86_64.rpm Or install manually using `rpm`. #### On CentOS / Fedora / Redhat: $ sudo yum install initscripts fontconfig - $ sudo rpm -Uvh grafana-2.6.0-1.x86_64.rpm + $ sudo rpm -Uvh grafana-3.0.1-1.x86_64.rpm #### On OpenSuse: - $ sudo rpm -i --nodeps grafana-2.6.0-1.x86_64.rpm - -## Install Beta Release from package file - -You can install Grafana using Yum directly. - - $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta71462173753.x86_64.rpm - -Or install manually using `rpm`. - -#### On CentOS / Fedora / Redhat: - - $ sudo yum install initscripts fontconfig - $ sudo rpm -Uvh grafana-3.0.0-beta71462173753.x86_64.rpm - -#### On OpenSuse: - - $ sudo rpm -i --nodeps grafana-3.0.0-beta71462173753.x86_64.rpm - + $ sudo rpm -i --nodeps grafana-3.0.1-1.x86_64.rpm ## Install via YUM Repository From 549eb15c32991bdf0eb174b60d982b1b2c5d0fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 May 2016 10:37:21 +0200 Subject: [PATCH 024/350] Update latest.json --- latest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/latest.json b/latest.json index 8ddb446ec44..4afdb750b3f 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "2.6.0", - "testing": "3.0.0-beta7" + "stable": "3.0.1", + "testing": "3.0.1" } From 56bf9c56125b1deb317013754f07fc41ca04ab32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 May 2016 13:20:01 +0200 Subject: [PATCH 025/350] fix(): changed how package iteration/build is generated --- build.go | 8 +++----- packaging/publish/publish.sh | 10 +++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/build.go b/build.go index e1fc3599aa8..4347c486063 100644 --- a/build.go +++ b/build.go @@ -132,12 +132,10 @@ func readVersionFromPackageJson() { if len(parts) > 1 { linuxPackageVersion = parts[0] linuxPackageIteration = parts[1] - if linuxPackageIteration != "" { - // add timestamp to iteration - linuxPackageIteration = fmt.Sprintf("%s%v", linuxPackageIteration, time.Now().Unix()) - } - log.Println(fmt.Sprintf("Iteration %v", linuxPackageIteration)) } + + // add timestamp to iteration + linuxPackageIteration = fmt.Sprintf("%d%s", time.Now().Unix(), linuxPackageIteration) } type linuxPackageOptions struct { diff --git a/packaging/publish/publish.sh b/packaging/publish/publish.sh index 79303707231..d2826366627 100755 --- a/packaging/publish/publish.sh +++ b/packaging/publish/publish.sh @@ -1,7 +1,7 @@ #! /usr/bin/env bash -deb_ver=3.0.0-beta51460725904 -rpm_ver=3.0.0-beta51460725904 +deb_ver=3.0.1 +rpm_ver=3.0.1-1 #rpm_ver=3.0.0-1 @@ -16,7 +16,7 @@ rpm_ver=3.0.0-beta51460725904 #wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm #package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm -package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm +#package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm -# package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm -# package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm +package_cloud push grafana/stable/el/7 grafana-${version}.x86_64.rpm +package_cloud push grafana/stable/el/6 grafana-${version}.x86_64.rpm From 8a3bdb3685f996265d5521c18c5a312802921931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 May 2016 13:21:25 +0200 Subject: [PATCH 026/350] fix(typo): corrected spelling in error message, fixes #4982 --- public/app/features/templating/templateValuesSrv.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js index 472c24aef61..d44f07ed366 100644 --- a/public/app/features/templating/templateValuesSrv.js +++ b/public/app/features/templating/templateValuesSrv.js @@ -204,7 +204,7 @@ function (angular, _, kbn) { } if (options.length === 0) { - options.push({text: 'No datasurces found', value: ''}); + options.push({text: 'No data sources found', value: ''}); } variable.options = options; From b170b6ec8b91485370a3fc7f3f8c7b947a4a7cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 May 2016 14:52:44 +0200 Subject: [PATCH 027/350] feat(query part): moved query part editor from influxdb to core --- .../query_part/query_part_editor.ts | 183 ++++++++++++++++++ public/app/core/core.ts | 2 + .../influxdb/partials/query.editor.html | 8 +- .../influxdb/partials/query_part.html | 5 - .../plugins/datasource/influxdb/query_ctrl.ts | 3 - .../datasource/influxdb/query_part_editor.js | 178 ----------------- .../prometheus/partials/query.editor.html | 30 ++- .../datasource/prometheus/prom_query.ts | 33 +++- .../datasource/prometheus/query_ctrl.ts | 60 ++++-- 9 files changed, 277 insertions(+), 225 deletions(-) delete mode 100644 public/app/plugins/datasource/influxdb/partials/query_part.html delete mode 100644 public/app/plugins/datasource/influxdb/query_part_editor.js diff --git a/public/app/core/components/query_part/query_part_editor.ts b/public/app/core/components/query_part/query_part_editor.ts index e69de29bb2d..f9122ee283b 100644 --- a/public/app/core/components/query_part/query_part_editor.ts +++ b/public/app/core/components/query_part/query_part_editor.ts @@ -0,0 +1,183 @@ +/// + +import _ from 'lodash'; +import $ from 'jquery'; +import coreModule from 'app/core/core_module'; + +var template = ` +
+ +
+ +{{part.def.type}} +() +`; + + /** @ngInject */ +export function queryPartEditorDirective($compile, templateSrv) { + + var paramTemplate = ''; + return { + restrict: 'E', + template: template, + scope: { + part: "=", + removeAction: "&", + partUpdated: "&", + getOptions: "&", + }, + link: function postLink($scope, elem) { + var part = $scope.part; + var partDef = part.def; + var $paramsContainer = elem.find('.query-part-parameters'); + var $controlsContainer = elem.find('.tight-form-func-controls'); + + function clickFuncParam(paramIndex) { + /*jshint validthis:true */ + var $link = $(this); + var $input = $link.next(); + + $input.val(part.params[paramIndex]); + $input.css('width', ($link.width() + 16) + 'px'); + + $link.hide(); + $input.show(); + $input.focus(); + $input.select(); + + var typeahead = $input.data('typeahead'); + if (typeahead) { + $input.val(''); + typeahead.lookup(); + } + } + + function inputBlur(paramIndex) { + /*jshint validthis:true */ + var $input = $(this); + var $link = $input.prev(); + var newValue = $input.val(); + + if (newValue !== '' || part.def.params[paramIndex].optional) { + $link.html(templateSrv.highlightVariablesAsHtml(newValue)); + + part.updateParam($input.val(), paramIndex); + $scope.$apply($scope.partUpdated); + } + + $input.hide(); + $link.show(); + } + + function inputKeyPress(paramIndex, e) { + /*jshint validthis:true */ + if (e.which === 13) { + inputBlur.call(this, paramIndex); + } + } + + function inputKeyDown() { + /*jshint validthis:true */ + this.style.width = (3 + this.value.length) * 8 + 'px'; + } + + function addTypeahead($input, param, paramIndex) { + if (!param.options && !param.dynamicLookup) { + return; + } + + var typeaheadSource = function (query, callback) { + if (param.options) { return param.options; } + + $scope.$apply(function() { + $scope.getOptions().then(function(result) { + var dynamicOptions = _.map(result, function(op) { return op.value; }); + callback(dynamicOptions); + }); + }); + }; + + $input.attr('data-provide', 'typeahead'); + var options = param.options; + if (param.type === 'int') { + options = _.map(options, function(val) { return val.toString(); }); + } + + $input.typeahead({ + source: typeaheadSource, + minLength: 0, + items: 1000, + updater: function (value) { + setTimeout(function() { + inputBlur.call($input[0], paramIndex); + }, 0); + return value; + } + }); + + var typeahead = $input.data('typeahead'); + typeahead.lookup = function () { + this.query = this.$element.val() || ''; + var items = this.source(this.query, $.proxy(this.process, this)); + return items ? this.process(items) : items; + }; + } + + $scope.toggleControls = function() { + var targetDiv = elem.closest('.tight-form'); + + if (elem.hasClass('show-function-controls')) { + elem.removeClass('show-function-controls'); + targetDiv.removeClass('has-open-function'); + $controlsContainer.hide(); + return; + } + + elem.addClass('show-function-controls'); + targetDiv.addClass('has-open-function'); + $controlsContainer.show(); + }; + + $scope.removeActionInternal = function() { + $scope.toggleControls(); + $scope.removeAction(); + }; + + function addElementsAndCompile() { + _.each(partDef.params, function(param, index) { + if (param.optional && part.params.length <= index) { + return; + } + + if (index > 0) { + $(', ').appendTo($paramsContainer); + } + + var paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]); + var $paramLink = $('' + paramValue + ''); + var $input = $(paramTemplate); + + $paramLink.appendTo($paramsContainer); + $input.appendTo($paramsContainer); + + $input.blur(_.partial(inputBlur, index)); + $input.keyup(inputKeyDown); + $input.keypress(_.partial(inputKeyPress, index)); + $paramLink.click(_.partial(clickFuncParam, index)); + + addTypeahead($input, param, index); + }); + } + + function relink() { + $paramsContainer.empty(); + addElementsAndCompile(); + } + + relink(); + } + }; +} + +coreModule.directive('queryPartEditor', queryPartEditorDirective); diff --git a/public/app/core/core.ts b/public/app/core/core.ts index abebb5ce560..9c8ae9cdad2 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -33,6 +33,7 @@ import {Emitter} from './utils/emitter'; import {layoutSelector} from './components/layout_selector/layout_selector'; import {switchDirective} from './components/switch'; import {dashboardSelector} from './components/dashboard_selector'; +import {queryPartEditorDirective} from './components/query_part/query_part_editor'; import 'app/core/controllers/all'; import 'app/core/services/all'; import 'app/core/routes/routes'; @@ -56,4 +57,5 @@ export { Emitter, appEvents, dashboardSelector, + queryPartEditorDirective, }; diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 0d6dd5c5c3b..68b8ee60d98 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -35,13 +35,13 @@
- - +
@@ -62,12 +62,12 @@ GROUP BY - - +
diff --git a/public/app/plugins/datasource/influxdb/partials/query_part.html b/public/app/plugins/datasource/influxdb/partials/query_part.html deleted file mode 100644 index 478edfe5c29..00000000000 --- a/public/app/plugins/datasource/influxdb/partials/query_part.html +++ /dev/null @@ -1,5 +0,0 @@ -
- -
- -{{part.def.type}}() diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts index 8d6a03fc4a1..69895e53c25 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.ts +++ b/public/app/plugins/datasource/influxdb/query_ctrl.ts @@ -1,8 +1,5 @@ /// -import './query_part_editor'; -import './query_part_editor'; - import angular from 'angular'; import _ from 'lodash'; import InfluxQueryBuilder from './query_builder'; diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js deleted file mode 100644 index 4e044eca304..00000000000 --- a/public/app/plugins/datasource/influxdb/query_part_editor.js +++ /dev/null @@ -1,178 +0,0 @@ -define([ - 'angular', - 'lodash', - 'jquery', -], -function (angular, _, $) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('influxQueryPartEditor', function($compile, templateSrv) { - - var paramTemplate = ''; - return { - restrict: 'E', - templateUrl: 'public/app/plugins/datasource/influxdb/partials/query_part.html', - scope: { - part: "=", - removeAction: "&", - partUpdated: "&", - getOptions: "&", - }, - link: function postLink($scope, elem) { - var part = $scope.part; - var partDef = part.def; - var $paramsContainer = elem.find('.query-part-parameters'); - var $controlsContainer = elem.find('.tight-form-func-controls'); - - function clickFuncParam(paramIndex) { - /*jshint validthis:true */ - var $link = $(this); - var $input = $link.next(); - - $input.val(part.params[paramIndex]); - $input.css('width', ($link.width() + 16) + 'px'); - - $link.hide(); - $input.show(); - $input.focus(); - $input.select(); - - var typeahead = $input.data('typeahead'); - if (typeahead) { - $input.val(''); - typeahead.lookup(); - } - } - - function inputBlur(paramIndex) { - /*jshint validthis:true */ - var $input = $(this); - var $link = $input.prev(); - var newValue = $input.val(); - - if (newValue !== '' || part.def.params[paramIndex].optional) { - $link.html(templateSrv.highlightVariablesAsHtml(newValue)); - - part.updateParam($input.val(), paramIndex); - $scope.$apply($scope.partUpdated); - } - - $input.hide(); - $link.show(); - } - - function inputKeyPress(paramIndex, e) { - /*jshint validthis:true */ - if(e.which === 13) { - inputBlur.call(this, paramIndex); - } - } - - function inputKeyDown() { - /*jshint validthis:true */ - this.style.width = (3 + this.value.length) * 8 + 'px'; - } - - function addTypeahead($input, param, paramIndex) { - if (!param.options && !param.dynamicLookup) { - return; - } - - var typeaheadSource = function (query, callback) { - if (param.options) { return param.options; } - - $scope.$apply(function() { - $scope.getOptions().then(function(result) { - var dynamicOptions = _.map(result, function(op) { return op.value; }); - callback(dynamicOptions); - }); - }); - }; - - $input.attr('data-provide', 'typeahead'); - var options = param.options; - if (param.type === 'int') { - options = _.map(options, function(val) { return val.toString(); }); - } - - $input.typeahead({ - source: typeaheadSource, - minLength: 0, - items: 1000, - updater: function (value) { - setTimeout(function() { - inputBlur.call($input[0], paramIndex); - }, 0); - return value; - } - }); - - var typeahead = $input.data('typeahead'); - typeahead.lookup = function () { - this.query = this.$element.val() || ''; - var items = this.source(this.query, $.proxy(this.process, this)); - return items ? this.process(items) : items; - }; - } - - $scope.toggleControls = function() { - var targetDiv = elem.closest('.tight-form'); - - if (elem.hasClass('show-function-controls')) { - elem.removeClass('show-function-controls'); - targetDiv.removeClass('has-open-function'); - $controlsContainer.hide(); - return; - } - - elem.addClass('show-function-controls'); - targetDiv.addClass('has-open-function'); - $controlsContainer.show(); - }; - - $scope.removeActionInternal = function() { - $scope.toggleControls(); - $scope.removeAction(); - }; - - function addElementsAndCompile() { - _.each(partDef.params, function(param, index) { - if (param.optional && part.params.length <= index) { - return; - } - - if (index > 0) { - $(', ').appendTo($paramsContainer); - } - - var paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]); - var $paramLink = $('' + paramValue + ''); - var $input = $(paramTemplate); - - $paramLink.appendTo($paramsContainer); - $input.appendTo($paramsContainer); - - $input.blur(_.partial(inputBlur, index)); - $input.keyup(inputKeyDown); - $input.keypress(_.partial(inputKeyPress, index)); - $paramLink.click(_.partial(clickFuncParam, index)); - - addTypeahead($input, param, index); - }); - } - - function relink() { - $paramsContainer.empty(); - addElementsAndCompile(); - } - - relink(); - } - }; - - }); - -}); diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index 37c27ace58a..a67e3e9b188 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -1,18 +1,34 @@
-
- - +
+ +
-
- - + +
+ + +
+ +
+ +
+ +
+
- + diff --git a/public/app/plugins/datasource/prometheus/prom_query.ts b/public/app/plugins/datasource/prometheus/prom_query.ts index e9ef6ddf40d..83ac4f1615f 100644 --- a/public/app/plugins/datasource/prometheus/prom_query.ts +++ b/public/app/plugins/datasource/prometheus/prom_query.ts @@ -7,6 +7,8 @@ import { quotedIdentityRenderer, } from 'app/core/components/query_part/query_part'; +import _ from 'lodash'; + var index = []; var categories = { Functions: [], @@ -23,8 +25,21 @@ export class PromQuery { constructor(target, templateSrv?, scopedVars?) { this.target = target; + + this.target.expr = this.target.expr || ''; + this.target.intervalFactor = this.target.intervalFactor || 2; + this.target.functions = this.target.functions || []; + this.templateSrv = templateSrv; this.scopedVars = scopedVars; + + this.updateProjection(); + } + + updateProjection() { + this.functions = _.map(this.target.functions, function(func: any) { + return createPart(func); + }); } render() { @@ -33,18 +48,17 @@ export class PromQuery { query += '[' + this.target.range + ']'; } - for (let funcModel of this.target.functions) { - var partDef = index[funcModel.type]; - if (!partDef) { - continue; - } - - var part = new QueryPart(funcModel, partDef); - query = part.render(query); + for (let func of this.functions) { + query = func.render(query); } return query; } + + addQueryPart(category, item) { + var partModel = createPart({type: item.text}); + partModel.def.addStrategy(this, partModel); + } } export function createPart(part): any { @@ -63,6 +77,7 @@ function register(options: any) { function addFunctionStrategy(model, partModel) { model.functions.push(partModel); + model.target.functions.push(partModel.part); } register({ @@ -74,6 +89,6 @@ register({ renderer: functionRenderer, }); -export function getCategories() { +export function getQueryPartCategories() { return categories; } diff --git a/public/app/plugins/datasource/prometheus/query_ctrl.ts b/public/app/plugins/datasource/prometheus/query_ctrl.ts index 8284268ef39..25a803b7bb3 100644 --- a/public/app/plugins/datasource/prometheus/query_ctrl.ts +++ b/public/app/plugins/datasource/prometheus/query_ctrl.ts @@ -6,46 +6,68 @@ import moment from 'moment'; import * as dateMath from 'app/core/utils/datemath'; import {QueryCtrl} from 'app/plugins/sdk'; +import {PromQuery, getQueryPartCategories} from './prom_query'; class PrometheusQueryCtrl extends QueryCtrl { static templateUrl = 'partials/query.editor.html'; - metric: any; + query: any; + metricSegment: any; + addQueryPartMenu: any[]; resolutions: any; oldTarget: any; suggestMetrics: any; linkToPrometheus: any; /** @ngInject */ - constructor($scope, $injector, private templateSrv) { + constructor($scope, $injector, private templateSrv, private uiSegmentSrv) { super($scope, $injector); - var target = this.target; - target.expr = target.expr || ''; - target.intervalFactor = target.intervalFactor || 2; + this.query = new PromQuery(this.target, templateSrv); + + if (this.target.metric) { + this.metricSegment = uiSegmentSrv.newSegment(this.target.metric); + } else { + this.metricSegment = uiSegmentSrv.newSegment({value: 'select metric', fake: true}); + } - this.metric = ''; this.resolutions = _.map([1,2,3,4,5,10], function(f) { return {factor: f, label: '1/' + f}; }); - $scope.$on('typeahead-updated', () => { - this.$scope.$apply(() => { + this.updateLink(); + this.buildQueryPartMenu(); + } - this.target.expr += this.target.metric; - this.metric = ''; - this.refreshMetricData(); + buildQueryPartMenu() { + var categories = getQueryPartCategories(); + this.addQueryPartMenu = _.reduce(categories, function(memo, cat, key) { + var menu = { + text: key, + submenu: cat.map(item => { + return {text: item.type, value: item.type}; + }), + }; + memo.push(menu); + return memo; + }, []); + } + + addQueryPart(item, subItem) { + this.query.addQueryPart(item, subItem); + this.panelCtrl.refresh(); + } + + getMetricOptions() { + return this.datasource.performSuggestQuery('').then(res => { + return _.map(res, metric => { + return this.uiSegmentSrv.newSegment(metric); }); }); + } - // called from typeahead so need this - // here in order to ensure this ref - this.suggestMetrics = (query, callback) => { - console.log(this); - this.datasource.performSuggestQuery(query).then(callback); - }; - - this.updateLink(); + queryChanged() { + this.target.metric = this.metricSegment.value; } refreshMetricData() { From 2c7447eaca6ff14bb2413265c7e68efc0d27fd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 May 2016 16:18:52 +0200 Subject: [PATCH 028/350] feat(): started work on new import system --- public/app/core/components/search/search.html | 15 +++-- public/app/core/core.ts | 1 - public/app/core/directives/dash_upload.js | 46 ------------- public/app/features/dashboard/all.js | 1 + public/app/features/dashboard/upload.ts | 65 +++++++++++++++++++ 5 files changed, 76 insertions(+), 52 deletions(-) delete mode 100644 public/app/core/directives/dash_upload.js create mode 100644 public/app/features/dashboard/upload.ts diff --git a/public/app/core/components/search/search.html b/public/app/core/components/search/search.html index 35c4431d80c..953a769f7ee 100644 --- a/public/app/core/components/search/search.html +++ b/public/app/core/components/search/search.html @@ -64,12 +64,17 @@
- - - Import - + +
+ + +
+
diff --git a/public/app/core/core.ts b/public/app/core/core.ts index abebb5ce560..7c4ebc3d5ed 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -5,7 +5,6 @@ import "./directives/annotation_tooltip"; import "./directives/dash_class"; import "./directives/confirm_click"; import "./directives/dash_edit_link"; -import "./directives/dash_upload"; import "./directives/dropdown_typeahead"; import "./directives/grafana_version_check"; import "./directives/metric_segment"; diff --git a/public/app/core/directives/dash_upload.js b/public/app/core/directives/dash_upload.js deleted file mode 100644 index b03bc201e83..00000000000 --- a/public/app/core/directives/dash_upload.js +++ /dev/null @@ -1,46 +0,0 @@ -define([ - '../core_module', - 'app/core/utils/kbn', -], -function (coreModule, kbn) { - 'use strict'; - - coreModule.default.directive('dashUpload', function(timer, alertSrv, $location) { - return { - restrict: 'A', - link: function(scope) { - function file_selected(evt) { - var files = evt.target.files; // FileList object - var readerOnload = function() { - return function(e) { - scope.$apply(function() { - try { - window.grafanaImportDashboard = JSON.parse(e.target.result); - } catch (err) { - console.log(err); - scope.appEvent('alert-error', ['Import failed', 'JSON -> JS Serialization failed: ' + err.message]); - return; - } - var title = kbn.slugifyForUrl(window.grafanaImportDashboard.title); - window.grafanaImportDashboard.id = null; - $location.path('/dashboard-import/' + title); - }); - }; - }; - for (var i = 0, f; f = files[i]; i++) { - var reader = new FileReader(); - reader.onload = (readerOnload)(f); - reader.readAsText(f); - } - } - // Check for the various File API support. - if (window.File && window.FileReader && window.FileList && window.Blob) { - // Something - document.getElementById('dashupload').addEventListener('change', file_selected, false); - } else { - alertSrv.set('Oops','Sorry, the HTML5 File APIs are not fully supported in this browser.','error'); - } - } - }; - }); -}); diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index 9d370921332..926288a6e71 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -16,4 +16,5 @@ define([ './graphiteImportCtrl', './importCtrl', './impression_store', + './upload', ], function () {}); diff --git a/public/app/features/dashboard/upload.ts b/public/app/features/dashboard/upload.ts new file mode 100644 index 00000000000..90e811a43c1 --- /dev/null +++ b/public/app/features/dashboard/upload.ts @@ -0,0 +1,65 @@ +/// + +import kbn from 'app/core/utils/kbn'; +import coreModule from 'app/core/core_module'; + +var wnd: any = window; + +class DashboardImporter { + + prepareForImport(dash) { + dash.id = null; + return Promise.resolve(dash); + } + +} + + +/** @ngInject */ +function uploadDashboardDirective(timer, alertSrv, $location) { + return { + restrict: 'A', + link: function(scope) { + function file_selected(evt) { + var files = evt.target.files; // FileList object + var readerOnload = function() { + return function(e) { + var dash; + try { + dash = JSON.parse(e.target.result); + } catch (err) { + console.log(err); + scope.appEvent('alert-error', ['Import failed', 'JSON -> JS Serialization failed: ' + err.message]); + return; + } + + var importer = new DashboardImporter(); + importer.prepareForImport(dash).then(modified => { + wnd.grafanaImportDashboard = modified; + var title = kbn.slugifyForUrl(dash.title); + + scope.$apply(function() { + $location.path('/dashboard-import/' + title); + }); + }); + }; + }; + + for (var i = 0, f; f = files[i]; i++) { + var reader = new FileReader(); + reader.onload = readerOnload(); + reader.readAsText(f); + } + } + // Check for the various File API support. + if (wnd.File && wnd.FileReader && wnd.FileList && wnd.Blob) { + // Something + document.getElementById('dashupload').addEventListener('change', file_selected, false); + } else { + alertSrv.set('Oops','Sorry, the HTML5 File APIs are not fully supported in this browser.','error'); + } + } + }; +} + +coreModule.directive('dashUpload', uploadDashboardDirective); From dac62ec560f162e5d3e9e85d6c698918e76c87d1 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Wed, 11 May 2016 14:09:03 -0400 Subject: [PATCH 029/350] fix rpm download link --- docs/sources/installation/rpm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index 88ee5cd7862..a49378203c7 100644 --- a/docs/sources/installation/rpm.md +++ b/docs/sources/installation/rpm.md @@ -10,7 +10,7 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide Description | Download ------------ | ------------- -Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.1-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.3.1-1.x86_64.rpm) +Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.1-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.1-1.x86_64.rpm) ## Install Stable Release from package file From 731c35540f661713c09a15f862bc82d558d94354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 12 May 2016 07:29:33 +0200 Subject: [PATCH 030/350] packaging(): default RESTART_ON_UPGRADE to true, closes #4993 --- package.json | 2 +- packaging/deb/default/grafana-server | 2 +- packaging/rpm/sysconfig/grafana-server | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index eab8c01a977..b9b87ab6e75 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "company": "Coding Instinct AB" }, "name": "grafana", - "version": "3.0.1", + "version": "3.0.2", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git" diff --git a/packaging/deb/default/grafana-server b/packaging/deb/default/grafana-server index dd06906b903..cc5ee866f7d 100644 --- a/packaging/deb/default/grafana-server +++ b/packaging/deb/default/grafana-server @@ -14,6 +14,6 @@ CONF_DIR=/etc/grafana CONF_FILE=/etc/grafana/grafana.ini -RESTART_ON_UPGRADE=false +RESTART_ON_UPGRADE=true PLUGINS_DIR=/var/lib/grafana/plugins diff --git a/packaging/rpm/sysconfig/grafana-server b/packaging/rpm/sysconfig/grafana-server index dd06906b903..cc5ee866f7d 100644 --- a/packaging/rpm/sysconfig/grafana-server +++ b/packaging/rpm/sysconfig/grafana-server @@ -14,6 +14,6 @@ CONF_DIR=/etc/grafana CONF_FILE=/etc/grafana/grafana.ini -RESTART_ON_UPGRADE=false +RESTART_ON_UPGRADE=true PLUGINS_DIR=/var/lib/grafana/plugins From 0201ac24e768fa15c4d0cee46df5cadfbd283773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 12 May 2016 10:31:36 +0200 Subject: [PATCH 031/350] fix(templating): fixed issue with mixing repeated row and repeated panel, fixes #4988 --- CHANGELOG.md | 6 +++++- karma.conf.js | 2 +- packaging/publish/publish.sh | 4 ++-- public/app/features/dashboard/dynamicDashboardSrv.js | 3 ++- public/test/specs/dynamicDashboardSrv-specs.js | 1 + 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dea2b9c5f31..fd2a4dd50a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -# 3.0.0 Stable (2016-05-11) +# 3.0.2 Stable (unreleased) + +* **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988) + +# 3.0.1 Stable (2016-05-11) * **Templating**: Fixed issue with new data source variable not persisting current selected value, fixes [#4934](https://github.com/grafana/grafana/issues/4934) diff --git a/karma.conf.js b/karma.conf.js index c803dda5eae..cdcea23a90b 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -26,7 +26,7 @@ module.exports = function(config) { browsers: ['PhantomJS'], captureTimeout: 20000, singleRun: true, - autoWatchBatchDelay: 10000, + autoWatchBatchDelay: 1000, browserNoActivityTimeout: 60000, }); diff --git a/packaging/publish/publish.sh b/packaging/publish/publish.sh index d2826366627..79da73d441a 100755 --- a/packaging/publish/publish.sh +++ b/packaging/publish/publish.sh @@ -18,5 +18,5 @@ rpm_ver=3.0.1-1 #package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm #package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm -package_cloud push grafana/stable/el/7 grafana-${version}.x86_64.rpm -package_cloud push grafana/stable/el/6 grafana-${version}.x86_64.rpm +package_cloud push grafana/stable/el/7 grafana-${rpm_ver}.x86_64.rpm +package_cloud push grafana/stable/el/6 grafana-${rpm_ver}.x86_64.rpm diff --git a/public/app/features/dashboard/dynamicDashboardSrv.js b/public/app/features/dashboard/dynamicDashboardSrv.js index 9e369733f45..f131b47b557 100644 --- a/public/app/features/dashboard/dynamicDashboardSrv.js +++ b/public/app/features/dashboard/dynamicDashboardSrv.js @@ -52,6 +52,8 @@ function (angular, _) { else if (panel.repeatPanelId && panel.repeatIteration !== this.iteration) { row.panels = _.without(row.panels, panel); j = j - 1; + } else if (row.repeat || row.repeatRowId) { + continue; } else if (!_.isEmpty(panel.scopedVars) && panel.repeatIteration !== this.iteration) { panel.scopedVars = {}; } @@ -118,7 +120,6 @@ function (angular, _) { panel = copy.panels[i]; panel.scopedVars = {}; panel.scopedVars[variable.name] = option; - panel.repeatIteration = this.iteration; } }, this); }; diff --git a/public/test/specs/dynamicDashboardSrv-specs.js b/public/test/specs/dynamicDashboardSrv-specs.js index b988203009a..a09c3788377 100644 --- a/public/test/specs/dynamicDashboardSrv-specs.js +++ b/public/test/specs/dynamicDashboardSrv-specs.js @@ -167,6 +167,7 @@ define([ it('should generate a repeartRowId based on repeat row index', function() { expect(ctx.rows[1].repeatRowId).to.be(1); + expect(ctx.rows[1].repeatIteration).to.be(ctx.dynamicDashboardSrv.iteration); }); it('should set scopedVars on row panels', function() { From 35f55cabf0d8ed2fd24d07d07de0a7e398e24d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 12 May 2016 10:41:15 +0200 Subject: [PATCH 032/350] fix(templating): improved detection of nested template variables, fixes #4986, fixes #4987 --- CHANGELOG.md | 1 + public/app/features/templating/templateSrv.js | 3 ++- public/test/specs/templateSrv-specs.js | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd2a4dd50a1..e9beca0c761 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 3.0.2 Stable (unreleased) * **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988) +* **Templating**: Fixed issue detecting dependencies in nested variables, fixes [#4987](https://github.com/grafana/grafana/issues/4987), fixes [#4986](https://github.com/grafana/grafana/issues/4986) # 3.0.1 Stable (2016-05-11) diff --git a/public/app/features/templating/templateSrv.js b/public/app/features/templating/templateSrv.js index f60414eac43..7e96af22e2a 100644 --- a/public/app/features/templating/templateSrv.js +++ b/public/app/features/templating/templateSrv.js @@ -97,7 +97,8 @@ function (angular, _) { if (!str) { return false; } - return str.indexOf('$' + variableName) !== -1 || str.indexOf('[[' + variableName + ']]') !== -1; + var match = this._regex.exec(str); + return match && (match[1] === variableName || match[2] === variableName); }; this.highlightVariablesAsHtml = function(str) { diff --git a/public/test/specs/templateSrv-specs.js b/public/test/specs/templateSrv-specs.js index 3334d7a7bfe..fd7247cbd81 100644 --- a/public/test/specs/templateSrv-specs.js +++ b/public/test/specs/templateSrv-specs.js @@ -190,6 +190,11 @@ define([ expect(contains).to.be(true); }); + it('should not find it if only part matches with $var syntax', function() { + var contains = _templateSrv.containsVariable('this.$ServerDomain.filters', 'Server'); + expect(contains).to.be(false); + }); + it('should find it with [[var]] syntax', function() { var contains = _templateSrv.containsVariable('this.[[test]].filters', 'test'); expect(contains).to.be(true); From 2f6b00b6f74379eeffd0d87656f1d3d65564c944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denny=20Sch=C3=A4fer?= Date: Thu, 12 May 2016 10:41:36 +0200 Subject: [PATCH 033/350] Add What's New in Grafana 3.0 link into the readme (#5001) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 18a497092f2..6dbfc5388c2 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB. - [What's New in Grafana 2.0](http://docs.grafana.org/guides/whats-new-in-v2/) - [What's New in Grafana 2.1](http://docs.grafana.org/guides/whats-new-in-v2-1/) - [What's New in Grafana 2.5](http://docs.grafana.org/guides/whats-new-in-v2-5/) +- [What's New in Grafana 3.0](http://docs.grafana.org/guides/whats-new-in-v3/) ## Features ### Graphite Target Editor From 9f9f4e7fef934365dc05a2135e143f46ec7e116d Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Thu, 12 May 2016 04:43:31 -0400 Subject: [PATCH 034/350] use new plugin-specific repo route when installing or updating a single plugin (#4992) --- .../grafana-cli/commands/upgrade_command.go | 15 ++++++------- pkg/cmd/grafana-cli/services/services.go | 22 +++++++++++++------ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/pkg/cmd/grafana-cli/commands/upgrade_command.go b/pkg/cmd/grafana-cli/commands/upgrade_command.go index b9ca834be6d..e788b3bdfaa 100644 --- a/pkg/cmd/grafana-cli/commands/upgrade_command.go +++ b/pkg/cmd/grafana-cli/commands/upgrade_command.go @@ -1,6 +1,8 @@ package commands import ( + "github.com/fatih/color" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/log" s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" ) @@ -14,20 +16,17 @@ func upgradeCommand(c CommandLine) error { return err } - remotePlugins, err2 := s.ListAllPlugins(c.GlobalString("repo")) + v, err2 := s.GetPlugin(localPlugin.Id, c.GlobalString("repo")) if err2 != nil { return err2 } - for _, v := range remotePlugins.Plugins { - if localPlugin.Id == v.Id { - if ShouldUpgrade(localPlugin.Info.Version, v) { - s.RemoveInstalledPlugin(pluginsDir, pluginName) - return InstallPlugin(localPlugin.Id, "", c) - } - } + if ShouldUpgrade(localPlugin.Info.Version, v) { + s.RemoveInstalledPlugin(pluginsDir, pluginName) + return InstallPlugin(localPlugin.Id, "", c) } + log.Infof("%s %s is up to date \n", color.GreenString("✔"), localPlugin.Id) return nil } diff --git a/pkg/cmd/grafana-cli/services/services.go b/pkg/cmd/grafana-cli/services/services.go index f0ad460842d..2332511ed89 100644 --- a/pkg/cmd/grafana-cli/services/services.go +++ b/pkg/cmd/grafana-cli/services/services.go @@ -44,7 +44,7 @@ func ReadPlugin(pluginDir, pluginName string) (m.InstalledPlugin, error) { } if res.Id == "" { - return m.InstalledPlugin{}, errors.New("could not read find plugin " + pluginName) + return m.InstalledPlugin{}, errors.New("could not find plugin " + pluginName + " in " + pluginDir) } return res, nil @@ -69,13 +69,21 @@ func RemoveInstalledPlugin(pluginPath, id string) error { } func GetPlugin(pluginId, repoUrl string) (m.Plugin, error) { - resp, _ := ListAllPlugins(repoUrl) + fullUrl := repoUrl + "/repo/" + pluginId - for _, i := range resp.Plugins { - if i.Id == pluginId { - return i, nil - } + res, err := goreq.Request{Uri: fullUrl, MaxRedirects: 3}.Do() + if err != nil { + return m.Plugin{}, err + } + if res.StatusCode != 200 { + return m.Plugin{}, fmt.Errorf("Could not access %s statuscode %v", fullUrl, res.StatusCode) } - return m.Plugin{}, errors.New("could not find plugin named \"" + pluginId + "\"") + var resp m.Plugin + err = res.Body.FromJsonTo(&resp) + if err != nil { + return m.Plugin{}, errors.New("Could not load plugin data") + } + + return resp, nil } From 0d3e06e68add4b19cb53d4ae371bebc9acb154fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 12 May 2016 14:45:32 +0200 Subject: [PATCH 035/350] feat(wizard): merged wizard poc --- .../components/wizard}/wizard.html | 0 .../components/wizard}/wizard.ts | 20 +++++- public/app/core/core.ts | 2 + public/app/features/dashboard/upload.ts | 16 ++++- .../app/features/plugins/plugin_edit_ctrl.ts | 65 ++++--------------- 5 files changed, 46 insertions(+), 57 deletions(-) rename public/app/{features/plugins/partials => core/components/wizard}/wizard.html (100%) rename public/app/{features/plugins => core/components/wizard}/wizard.ts (64%) diff --git a/public/app/features/plugins/partials/wizard.html b/public/app/core/components/wizard/wizard.html similarity index 100% rename from public/app/features/plugins/partials/wizard.html rename to public/app/core/components/wizard/wizard.html diff --git a/public/app/features/plugins/wizard.ts b/public/app/core/components/wizard/wizard.ts similarity index 64% rename from public/app/features/plugins/wizard.ts rename to public/app/core/components/wizard/wizard.ts index 741b0f01b3e..943998f2e10 100644 --- a/public/app/features/plugins/wizard.ts +++ b/public/app/core/components/wizard/wizard.ts @@ -1,4 +1,4 @@ -/// +/// import config from 'app/core/config'; import _ from 'lodash'; @@ -23,6 +23,8 @@ export class WizardStep { export class WizardFlow { name: string; steps: WizardStep[]; + reject: any; + fulfill: any; constructor(name) { this.name = name; @@ -36,11 +38,25 @@ export class WizardFlow { }); } + next(index) { + var step = this.steps[0]; + + return step.fn().then(() => { + if (this.steps.length === index+1) { + return; + } + + return this.next(index+1); + }); + } + start() { appEvents.emit('show-modal', { - src: 'public/app/features/plugins/partials/wizard.html', + src: 'public/app/core/components/wizard/wizard.html', model: this }); + + return this.next(0); } } diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 7c4ebc3d5ed..4db7c7ad650 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -32,6 +32,7 @@ import {Emitter} from './utils/emitter'; import {layoutSelector} from './components/layout_selector/layout_selector'; import {switchDirective} from './components/switch'; import {dashboardSelector} from './components/dashboard_selector'; +import {WizardFlow} from './components/wizard/wizard'; import 'app/core/controllers/all'; import 'app/core/services/all'; import 'app/core/routes/routes'; @@ -55,4 +56,5 @@ export { Emitter, appEvents, dashboardSelector, + WizardFlow, }; diff --git a/public/app/features/dashboard/upload.ts b/public/app/features/dashboard/upload.ts index 90e811a43c1..35095fb6cb9 100644 --- a/public/app/features/dashboard/upload.ts +++ b/public/app/features/dashboard/upload.ts @@ -3,15 +3,27 @@ import kbn from 'app/core/utils/kbn'; import coreModule from 'app/core/core_module'; +import {WizardFlow} from 'app/core/core'; + var wnd: any = window; class DashboardImporter { prepareForImport(dash) { dash.id = null; - return Promise.resolve(dash); - } + var wizard = new WizardFlow('Import Dashboard'); + + wizard.addStep("Importing dashboard", function() { + return new Promise(done => { + setTimeout(done, 2000); + }); + }); + + return wizard.start().then(() => { + return dash; + }); + } } diff --git a/public/app/features/plugins/plugin_edit_ctrl.ts b/public/app/features/plugins/plugin_edit_ctrl.ts index 782c324a22b..a10b5eb6e7a 100644 --- a/public/app/features/plugins/plugin_edit_ctrl.ts +++ b/public/app/features/plugins/plugin_edit_ctrl.ts @@ -4,8 +4,6 @@ import angular from 'angular'; import _ from 'lodash'; import appEvents from 'app/core/app_events'; -import {WizardFlow} from './wizard'; - export class PluginEditCtrl { model: any; pluginIcon: string; @@ -83,58 +81,19 @@ export class PluginEditCtrl { } update() { - var wizard = new WizardFlow("Application Setup"); - - wizard.addStep("Validating form", () => { - return new Promise((resolve) => { - setTimeout(resolve, 2000); - }); + this.preUpdateHook().then(() => { + var updateCmd = _.extend({ + enabled: this.model.enabled, + pinned: this.model.pinned, + jsonData: this.model.jsonData, + secureJsonData: this.model.secureJsonData, + }, {}); + return this.backendSrv.post(`/api/plugins/${this.pluginId}/settings`, updateCmd); + }) + .then(this.postUpdateHook) + .then((res) => { + window.location.href = window.location.href; }); - - wizard.addStep("Saving application config", () => { - return new Promise((resolve) => { - setTimeout(resolve, 2000); - }); - }); - - wizard.addStep("Validing key", () => { - return new Promise((resolve) => { - setTimeout(resolve, 2000); - }); - }); - - wizard.addStep("Adding Raintank metric data source", () => { - return new Promise((resolve) => { - setTimeout(resolve, 2000); - }); - }); - - wizard.addStep("Adding Raintank event data source", () => { - return new Promise((resolve) => { - setTimeout(resolve, 2000); - }); - }); - - wizard.addStep("Importing worldPing dashboards", () => { - return new Promise((resolve) => { - setTimeout(resolve, 2000); - }); - }); - - wizard.start(); - // this.preUpdateHook().then(() => { - // var updateCmd = _.extend({ - // enabled: this.model.enabled, - // pinned: this.model.pinned, - // jsonData: this.model.jsonData, - // secureJsonData: this.model.secureJsonData, - // }, {}); - // return this.backendSrv.post(`/api/plugins/${this.pluginId}/settings`, updateCmd); - // }) - // .then(this.postUpdateHook) - // .then((res) => { - // window.location.href = window.location.href; - // }); } importDashboards() { From c6744925c469f43ba658c5c351c3a6c21bd1c0c6 Mon Sep 17 00:00:00 2001 From: Anton Chevychalov Date: Thu, 12 May 2016 15:11:10 +0300 Subject: [PATCH 036/350] Add new parameter to config: default_theme. --- conf/defaults.ini | 3 +++ conf/sample.ini | 3 +++ pkg/services/sqlstore/preferences.go | 4 +++- pkg/setting/setting.go | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index f78287619a3..0a4b61fbe78 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -172,6 +172,9 @@ verify_email_enabled = false # Background text for the user field on the login page login_hint = email or username +# Default UI theme ("dark" or "light") +default_theme = dark + #################################### Anonymous Auth ########################## [auth.anonymous] # enable anonymous access diff --git a/conf/sample.ini b/conf/sample.ini index 6a26589d40d..7f358b07199 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -155,6 +155,9 @@ check_for_updates = true # Background text for the user field on the login page ;login_hint = email or username +# Default UI theme ("dark" or "light") +;default_theme = dark + #################################### Anonymous Auth ########################## [auth.anonymous] # enable anonymous access diff --git a/pkg/services/sqlstore/preferences.go b/pkg/services/sqlstore/preferences.go index d120c485ed3..65609a9c57c 100644 --- a/pkg/services/sqlstore/preferences.go +++ b/pkg/services/sqlstore/preferences.go @@ -5,6 +5,8 @@ import ( "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" + + "github.com/grafana/grafana/pkg/setting" ) func init() { @@ -26,7 +28,7 @@ func GetPreferencesWithDefaults(query *m.GetPreferencesWithDefaultsQuery) error } res := &m.Preferences{ - Theme: "dark", + Theme: setting.DefaultTheme, Timezone: "browser", HomeDashboardId: 0, } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 2d1bad945eb..b883a8d995d 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -88,6 +88,7 @@ var ( AutoAssignOrgRole string VerifyEmailEnabled bool LoginHint string + DefaultTheme string // Http auth AdminUser string @@ -454,6 +455,7 @@ func NewConfigContext(args *CommandLineArgs) error { AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"}) VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false) LoginHint = users.Key("login_hint").String() + DefaultTheme = users.Key("default_theme").String() // anonymous access AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false) From 3feb6492e6d03de265e9c5c61b5e3d0eb0126aae Mon Sep 17 00:00:00 2001 From: Prajwal Rao Date: Thu, 12 May 2016 12:49:58 -0700 Subject: [PATCH 037/350] fixed datasources icon (#5015) --- public/app/features/panel/metrics_ds_selector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/panel/metrics_ds_selector.ts b/public/app/features/panel/metrics_ds_selector.ts index b281c43048b..c0e1b776062 100644 --- a/public/app/features/panel/metrics_ds_selector.ts +++ b/public/app/features/panel/metrics_ds_selector.ts @@ -10,7 +10,7 @@ var template = `
diff --git a/public/app/core/components/search/search.ts b/public/app/core/components/search/search.ts index e296acf56e1..30ad9792c31 100644 --- a/public/app/core/components/search/search.ts +++ b/public/app/core/components/search/search.ts @@ -5,6 +5,7 @@ import config from 'app/core/config'; import _ from 'lodash'; import $ from 'jquery'; import coreModule from '../../core_module'; +import {DashImporter} from '../dash_importer/dash_importer'; export class SearchCtrl { isOpen: boolean; @@ -151,6 +152,10 @@ export class SearchCtrl { newDashboard() { this.$location.url('dashboard/new'); }; + + import() { + new DashImporter(this.backendSrv, this.$location).run(); + } } export function searchDirective() { diff --git a/public/app/core/components/wizard/wizard.html b/public/app/core/components/wizard/wizard.html index 4a532e81a0f..9d3f680649a 100644 --- a/public/app/core/components/wizard/wizard.html +++ b/public/app/core/components/wizard/wizard.html @@ -11,19 +11,21 @@
diff --git a/public/app/core/components/wizard/wizard.ts b/public/app/core/components/wizard/wizard.ts index 943998f2e10..2ae38cf9e03 100644 --- a/public/app/core/components/wizard/wizard.ts +++ b/public/app/core/components/wizard/wizard.ts @@ -8,40 +8,50 @@ import coreModule from 'app/core/core_module'; import appEvents from 'app/core/app_events'; export class WizardSrv { - /** @ngInject */ constructor() { } - } -export class WizardStep { +export interface WizardStep { name: string; - fn: any; + type: string; + process: any; +} + +export class SelectOptionStep { + type: string; + name: string; + fulfill: any; + + constructor() { + this.type = 'select'; + } + + process() { + return new Promise((fulfill, reject) => { + + }); + } } export class WizardFlow { name: string; steps: WizardStep[]; - reject: any; - fulfill: any; constructor(name) { this.name = name; this.steps = []; } - addStep(name, stepFn) { - this.steps.push({ - name: name, - fn: stepFn - }); + addStep(step) { + this.steps.push(step); } next(index) { var step = this.steps[0]; - return step.fn().then(() => { + return step.process().then(() => { if (this.steps.length === index+1) { return; } diff --git a/public/app/core/services/util_srv.ts b/public/app/core/services/util_srv.ts index 0f15a65a0dc..595962cd3c6 100644 --- a/public/app/core/services/util_srv.ts +++ b/public/app/core/services/util_srv.ts @@ -34,6 +34,7 @@ export class UtilSrv { Promise.resolve(modal).then(function(modalEl) { modalEl.modal('show'); + options.scope.model.dismiss = options.scope.dismiss; }); } } diff --git a/public/app/features/dashboard/upload.ts b/public/app/features/dashboard/upload.ts index 35095fb6cb9..ddc4005546c 100644 --- a/public/app/features/dashboard/upload.ts +++ b/public/app/features/dashboard/upload.ts @@ -3,34 +3,22 @@ import kbn from 'app/core/utils/kbn'; import coreModule from 'app/core/core_module'; -import {WizardFlow} from 'app/core/core'; - -var wnd: any = window; - -class DashboardImporter { - - prepareForImport(dash) { - dash.id = null; - - var wizard = new WizardFlow('Import Dashboard'); - - wizard.addStep("Importing dashboard", function() { - return new Promise(done => { - setTimeout(done, 2000); - }); - }); - - return wizard.start().then(() => { - return dash; - }); - } -} - +var template = ` + + +`; /** @ngInject */ function uploadDashboardDirective(timer, alertSrv, $location) { return { - restrict: 'A', + restrict: 'E', + template: template, + scope: { + onUpload: '&', + }, link: function(scope) { function file_selected(evt) { var files = evt.target.files; // FileList object @@ -45,15 +33,7 @@ function uploadDashboardDirective(timer, alertSrv, $location) { return; } - var importer = new DashboardImporter(); - importer.prepareForImport(dash).then(modified => { - wnd.grafanaImportDashboard = modified; - var title = kbn.slugifyForUrl(dash.title); - - scope.$apply(function() { - $location.path('/dashboard-import/' + title); - }); - }); + scope.onUpload({dash: dash}); }; }; @@ -63,6 +43,8 @@ function uploadDashboardDirective(timer, alertSrv, $location) { reader.readAsText(f); } } + + var wnd: any = window; // Check for the various File API support. if (wnd.File && wnd.FileReader && wnd.FileList && wnd.Blob) { // Something diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index fed3d805b82..29e3cce1ee5 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -235,7 +235,7 @@ $paginationActiveBackground: $blue; $state-warning-text: darken(#c09853, 10%); $state-warning-bg: $brand-warning; -$errorText: #b94a48; +$errorText: #E84D4D; $errorBackground: $btn-danger-bg; $successText: #468847; diff --git a/public/sass/utils/_validation.scss b/public/sass/utils/_validation.scss index 1145212707e..c0dd44d59a0 100644 --- a/public/sass/utils/_validation.scss +++ b/public/sass/utils/_validation.scss @@ -1,8 +1,10 @@ input[type=text].ng-dirty.ng-invalid { } +input.validation-error, input.ng-dirty.ng-invalid { box-shadow: inset 0 0px 5px $red; } + From 9e5a8c3fc8e3ab7b4e7b151d627721f8072de792 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 13 May 2016 11:38:54 +0200 Subject: [PATCH 040/350] docs(changelog): add info about configurable theme --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e515c06670..05127b90c65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Enhancements * **Singlestat**: Add support for range to text mappings, closes [#1319](https://github.com/grafana/grafana/issues/1319) * **Graph**: Adds sort order options for graph tooltip, closes [#1189](https://github.com/grafana/grafana/issues/1189) +* **Theme**: Add default theme to config file [#5011](https://github.com/grafana/grafana/pull/5011) # 3.0.2 Stable (unreleased) From d5899aacfbf06f1d584e5f6e951b92143a660120 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 13 May 2016 12:20:39 +0200 Subject: [PATCH 041/350] fix(graph-panel): fixes broken PNG rendering When refactoring y-axies for graph panel these was forgotten and which caused the graphite datasource to send invalid data. closes #5025 --- public/app/plugins/panel/graph/graph.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 669fe1fbc91..8b67bc0b7f6 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -452,12 +452,21 @@ function (angular, $, moment, _, kbn, GraphTooltip) { url += panel.fill !== 0 ? ('&areaAlpha=' + (panel.fill/10).toFixed(1)) : ''; url += panel.linewidth !== 0 ? '&lineWidth=' + panel.linewidth : ''; url += panel.legend.show ? '&hideLegend=false' : '&hideLegend=true'; - url += panel.grid.leftMin !== null ? '&yMin=' + panel.grid.leftMin : ''; - url += panel.grid.leftMax !== null ? '&yMax=' + panel.grid.leftMax : ''; - url += panel.grid.rightMin !== null ? '&yMin=' + panel.grid.rightMin : ''; - url += panel.grid.rightMax !== null ? '&yMax=' + panel.grid.rightMax : ''; - url += panel['x-axis'] ? '' : '&hideAxes=true'; - url += panel['y-axis'] ? '' : '&hideYAxis=true'; + + if (panel.yaxes && panel.yaxes.length > 0) { + var showYaxis = false; + for(var i = 0; panel.yaxes.length > i; i++) { + if (panel.yaxes[i].show) { + url += (panel.yaxes[i].min !== null && panel.yaxes[i].min !== undefined) ? '&yMin=' + panel.yaxes[i].min : ''; + url += (panel.yaxes[i].max !== null && panel.yaxes[i].max !== undefined) ? '&yMax=' + panel.yaxes[i].max : ''; + showYaxis = true; + break; + } + } + url += showYaxis ? '' : '&hideYAxis=true'; + } + + url += panel.xaxis.show ? '' : '&hideAxes=true'; switch(panel.yaxes[0].format) { case 'bytes': From ac674bd405f96359beb2498e9064746f2c9f8472 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 13 May 2016 12:23:58 +0200 Subject: [PATCH 042/350] docs(changelog): add note about graph fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9beca0c761..d4aae6bdf29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988) * **Templating**: Fixed issue detecting dependencies in nested variables, fixes [#4987](https://github.com/grafana/grafana/issues/4987), fixes [#4986](https://github.com/grafana/grafana/issues/4986) +* **Graph**: Fixed broken PNG rendering in graph panel, fixes [#5025](https://github.com/grafana/grafana/issues/5025) # 3.0.1 Stable (2016-05-11) From c3f1d1a647456c0a7c8ce97d9c9dd26f4eff1290 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 13 May 2016 13:26:57 +0200 Subject: [PATCH 043/350] fix(influxdb): fixes extra semi colon due to hidden series closes #5005 --- CHANGELOG.md | 1 + public/app/plugins/datasource/influxdb/datasource.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4aae6bdf29..bd0b91ec52c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988) * **Templating**: Fixed issue detecting dependencies in nested variables, fixes [#4987](https://github.com/grafana/grafana/issues/4987), fixes [#4986](https://github.com/grafana/grafana/issues/4986) * **Graph**: Fixed broken PNG rendering in graph panel, fixes [#5025](https://github.com/grafana/grafana/issues/5025) +* **Influxdb**: Fixes crash when hiding middle serie, fixes [#5005](https://github.com/grafana/grafana/issues/5005) # 3.0.1 Stable (2016-05-11) diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 4037e963bcb..40c7ba16f53 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -45,7 +45,7 @@ export default class InfluxDatasource { var i, y; var allQueries = _.map(options.targets, (target) => { - if (target.hide) { return []; } + if (target.hide) { return ""; } queryTargets.push(target); @@ -54,8 +54,12 @@ export default class InfluxDatasource { var query = queryModel.render(true); query = query.replace(/\$interval/g, (target.interval || options.interval)); return query; - - }).join(";"); + }).reduce((acc, current) => { + if (current !== "") { + acc += ";" + current; + } + return acc; + }); // replace grafana variables allQueries = allQueries.replace(/\$timeFilter/g, timeFilter); From 6849bfae167b49c659f0c70c7424c3efcd9893a6 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 13 May 2016 13:52:57 +0200 Subject: [PATCH 044/350] fix(graph): fixes broken hide xaxis option closes #5024 --- public/app/plugins/panel/graph/graph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 8b67bc0b7f6..562bf71c947 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -282,7 +282,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { options.xaxis = { timezone: dashboard.getTimezone(), - show: panel['x-axis'], + show: panel.xaxis.show, mode: "time", min: min, max: max, From e50211572f16c58ff02c11a638b20c2de9a38b71 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 13 May 2016 13:54:39 +0200 Subject: [PATCH 045/350] docs(changelog): add note about fix for graph panel --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd0b91ec52c..0819ffb1f57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ * **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988) * **Templating**: Fixed issue detecting dependencies in nested variables, fixes [#4987](https://github.com/grafana/grafana/issues/4987), fixes [#4986](https://github.com/grafana/grafana/issues/4986) * **Graph**: Fixed broken PNG rendering in graph panel, fixes [#5025](https://github.com/grafana/grafana/issues/5025) +* **Graph**: Fixed broken xaxis on graph panel, fixes [#5024](https://github.com/grafana/grafana/issues/5024) + +5024 * **Influxdb**: Fixes crash when hiding middle serie, fixes [#5005](https://github.com/grafana/grafana/issues/5005) # 3.0.1 Stable (2016-05-11) From 41ed0e670af1dfc46e1ba4ba878cb6c51c9f7f79 Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 13 May 2016 16:25:03 +0200 Subject: [PATCH 046/350] feat(docker): fake data writer for graphite --- docker/blocks/graphite/fig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker/blocks/graphite/fig b/docker/blocks/graphite/fig index 84da45341e1..4268641d32f 100644 --- a/docker/blocks/graphite/fig +++ b/docker/blocks/graphite/fig @@ -8,3 +8,10 @@ graphite: - /etc/localtime:/etc/localtime:ro - /etc/timezone:/etc/timezone:ro +fake-data-gen: + image: grafana/fake-data-gen + net: bridge + environment: + FD_DATASOURCE: graphite + FD_PORT: 2003 + From da68f7d31a3d566144dce1e732eeebeac0acd73b Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 13 May 2016 16:53:28 +0200 Subject: [PATCH 047/350] feat(docker): fake data writer for opentsdb --- docker/blocks/opentsdb/fig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docker/blocks/opentsdb/fig b/docker/blocks/opentsdb/fig index 34bbf4b854c..705d746a864 100644 --- a/docker/blocks/opentsdb/fig +++ b/docker/blocks/opentsdb/fig @@ -2,4 +2,10 @@ opentsdb: image: opower/opentsdb:latest ports: - "4242:4242" - + +fake-data-gen: + image: grafana/fake-data-gen + net: bridge + environment: + FD_DATASOURCE: opentsdb + From d9d46096dd5c41600f96f9f32e29e53b47083f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 13 May 2016 17:39:22 +0200 Subject: [PATCH 048/350] feat(import): lots of work on dashboard import --- .../dash_importer/dash_importer.html | 76 ----------- .../components/dash_importer/dash_importer.ts | 77 ------------ public/app/core/components/search/search.html | 8 +- public/app/core/components/search/search.ts | 10 +- public/app/core/directives/dash_edit_link.js | 29 ++--- public/app/core/services/util_srv.ts | 2 +- public/app/features/dashboard/all.js | 1 + public/app/features/dashboard/exporter.ts | 1 + .../app/features/dashboard/import/import.html | 96 ++++++++++++++ .../app/features/dashboard/import/import.ts | 119 ++++++++++++++++++ public/app/features/dashboard/upload.ts | 4 +- public/sass/_variables.dark.scss | 4 +- public/vendor/angular-other/angular-strap.js | 11 +- 13 files changed, 247 insertions(+), 191 deletions(-) delete mode 100644 public/app/core/components/dash_importer/dash_importer.html delete mode 100644 public/app/core/components/dash_importer/dash_importer.ts create mode 100644 public/app/features/dashboard/import/import.html create mode 100644 public/app/features/dashboard/import/import.ts diff --git a/public/app/core/components/dash_importer/dash_importer.html b/public/app/core/components/dash_importer/dash_importer.html deleted file mode 100644 index 8b4e649dd50..00000000000 --- a/public/app/core/components/dash_importer/dash_importer.html +++ /dev/null @@ -1,76 +0,0 @@ - - diff --git a/public/app/core/components/dash_importer/dash_importer.ts b/public/app/core/components/dash_importer/dash_importer.ts deleted file mode 100644 index 2f04640ffc2..00000000000 --- a/public/app/core/components/dash_importer/dash_importer.ts +++ /dev/null @@ -1,77 +0,0 @@ -/// - -import kbn from 'app/core/utils/kbn'; -import coreModule from 'app/core/core_module'; - -import appEvents from 'app/core/app_events'; -import {WizardFlow} from 'app/core/core'; - -var wnd: any = window; - -export class DashImporter { - step: number; - jsonText: string; - parseError: string; - nameExists: boolean; - dash: any; - dismiss: any; - - constructor(private backendSrv, private $location) { - } - - onUpload(dash) { - this.dash = dash; - this.dash.id = null; - - this.backendSrv.saveDashboard(this.dash, {overwrite: false}).then(res => { - - }).catch(err => { - if (err.data.status === 'name-exists') { - err.isHandled = true; - this.step = 2; - this.nameExists = true; - } - console.log(err); - }); - } - - titleChanged() { - this.backendSrv.search({query: this.dash.title}).then(res => { - this.nameExists = false; - for (let hit of res) { - if (this.dash.title === hit.title) { - this.nameExists = true; - break; - } - } - }); - } - - saveDashboard() { - return this.backendSrv.saveDashboard(this.dash, {overwrite: true}).then(res => { - this.$location.url('dashboard/db/' + res.slug); - this.dismiss(); - }); - } - - loadJsonText() { - try { - this.parseError = ''; - var dash = JSON.parse(this.jsonText); - this.onUpload(dash); - } catch (err) { - console.log(err); - this.parseError = err.message; - return; - } - } - - run() { - this.step = 0; - - appEvents.emit('show-modal', { - src: 'public/app/core/components/dash_importer/dash_importer.html', - model: this - }); - } -} diff --git a/public/app/core/components/search/search.html b/public/app/core/components/search/search.html index ddfe22215a9..72ff0d15f83 100644 --- a/public/app/core/components/search/search.html +++ b/public/app/core/components/search/search.html @@ -62,15 +62,15 @@
diff --git a/public/app/core/components/search/search.ts b/public/app/core/components/search/search.ts index 30ad9792c31..9da95278c57 100644 --- a/public/app/core/components/search/search.ts +++ b/public/app/core/components/search/search.ts @@ -5,7 +5,7 @@ import config from 'app/core/config'; import _ from 'lodash'; import $ from 'jquery'; import coreModule from '../../core_module'; -import {DashImporter} from '../dash_importer/dash_importer'; +import appEvents from 'app/core/app_events'; export class SearchCtrl { isOpen: boolean; @@ -149,12 +149,10 @@ export class SearchCtrl { this.searchDashboards(); }; - newDashboard() { - this.$location.url('dashboard/new'); - }; - import() { - new DashImporter(this.backendSrv, this.$location).run(); + appEvents.emit('show-modal', { + templateHtml: '', + }); } } diff --git a/public/app/core/directives/dash_edit_link.js b/public/app/core/directives/dash_edit_link.js index 23855925541..eb0fe453741 100644 --- a/public/app/core/directives/dash_edit_link.js +++ b/public/app/core/directives/dash_edit_link.js @@ -6,27 +6,12 @@ function ($, coreModule) { 'use strict'; var editViewMap = { - 'settings': { src: 'public/app/features/dashboard/partials/settings.html', title: "Settings" }, - 'annotations': { src: 'public/app/features/annotations/partials/editor.html', title: "Annotations" }, - 'templating': { src: 'public/app/features/templating/partials/editor.html', title: "Templating" } + 'settings': { src: 'public/app/features/dashboard/partials/settings.html'}, + 'annotations': { src: 'public/app/features/annotations/partials/editor.html'}, + 'templating': { src: 'public/app/features/templating/partials/editor.html'}, + 'import': { src: '' } }; - coreModule.default.directive('dashEditorLink', function($timeout) { - return { - restrict: 'A', - link: function(scope, elem, attrs) { - var partial = attrs.dashEditorLink; - - elem.bind('click',function() { - $timeout(function() { - var editorScope = attrs.editorScope === 'isolated' ? null : scope; - scope.appEvent('show-dash-editor', { src: partial, scope: editorScope }); - }); - }); - } - }; - }); - coreModule.default.directive('dashEditorView', function($compile, $location) { return { restrict: 'A', @@ -72,8 +57,10 @@ function ($, coreModule) { } }; - var src = "'" + payload.src + "'"; - var view = $('
'); + var view = payload.src; + if (view.indexOf('.html') > 0) { + view = $('
'); + } elem.append(view); $compile(elem.contents())(editorScope); diff --git a/public/app/core/services/util_srv.ts b/public/app/core/services/util_srv.ts index 595962cd3c6..538527b5fda 100644 --- a/public/app/core/services/util_srv.ts +++ b/public/app/core/services/util_srv.ts @@ -26,6 +26,7 @@ export class UtilSrv { var modal = this.$modal({ modalClass: options.modalClass, template: options.src, + templateHtml: options.templateHtml, persist: false, show: false, scope: options.scope, @@ -34,7 +35,6 @@ export class UtilSrv { Promise.resolve(modal).then(function(modalEl) { modalEl.modal('show'); - options.scope.model.dismiss = options.scope.dismiss; }); } } diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index 926288a6e71..51a2b806e19 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -17,4 +17,5 @@ define([ './importCtrl', './impression_store', './upload', + './import/import', ], function () {}); diff --git a/public/app/features/dashboard/exporter.ts b/public/app/features/dashboard/exporter.ts index 9a8d5bae5b1..63067502605 100644 --- a/public/app/features/dashboard/exporter.ts +++ b/public/app/features/dashboard/exporter.ts @@ -29,6 +29,7 @@ export class DashboardExporter { name: refName, type: 'datasource', pluginId: ds.meta.id, + pluginName: ds.meta.name, }; panel.datasource = '${' + refName +'}'; diff --git a/public/app/features/dashboard/import/import.html b/public/app/features/dashboard/import/import.html new file mode 100644 index 00000000000..8a3a276ce53 --- /dev/null +++ b/public/app/features/dashboard/import/import.html @@ -0,0 +1,96 @@ + + diff --git a/public/app/features/dashboard/import/import.ts b/public/app/features/dashboard/import/import.ts new file mode 100644 index 00000000000..852a927ded8 --- /dev/null +++ b/public/app/features/dashboard/import/import.ts @@ -0,0 +1,119 @@ +/// + +import kbn from 'app/core/utils/kbn'; +import coreModule from 'app/core/core_module'; +import appEvents from 'app/core/app_events'; +import config from 'app/core/config'; +import _ from 'lodash'; + +export class DashImportCtrl { + step: number; + jsonText: string; + parseError: string; + nameExists: boolean; + dash: any; + dismiss: any; + inputs: any[]; + inputsValid: boolean; + + /** @ngInject */ + constructor(private backendSrv, private $location, private $scope) { + this.step = 1; + this.nameExists = false; + } + + onUpload(dash) { + this.dash = dash; + this.dash.id = null; + this.step = 2; + this.inputs = []; + + if (this.dash.__inputs) { + for (let input of this.dash.__inputs) { + var inputModel = { + name: input.name, + type: input.type, + options: [] + }; + + if (input.type === 'datasource') { + this.setDatasourceOptions(input, inputModel); + } + + this.inputs.push(inputModel); + } + } + + this.inputsValid = this.inputs.length === 0; + this.titleChanged(); + } + + setDatasourceOptions(input, inputModel) { + var sources = _.filter(config.datasources, val => { + return val.type === input.pluginId; + }); + + if (sources.length === 0) { + inputModel.error = "No data sources of type " + input.pluginName + " found"; + } else { + inputModel.info = "Select a " + input.pluginName + " data source"; + } + + inputModel.options = sources.map(val => { + return {text: val.name, value: val.name}; + }); + } + + inputOptionChanged() { + this.inputsValid = true; + for (let input of this.inputs) { + if (!input.value) { + this.inputsValid = false; + } + } + } + + titleChanged() { + this.backendSrv.search({query: this.dash.title}).then(res => { + this.nameExists = false; + for (let hit of res) { + if (this.dash.title === hit.title) { + this.nameExists = true; + break; + } + } + }); + } + + saveDashboard() { + return this.backendSrv.saveDashboard(this.dash, {overwrite: true}).then(res => { + this.$location.url('dashboard/db/' + res.slug); + this.dismiss(); + }); + } + + loadJsonText() { + try { + this.parseError = ''; + var dash = JSON.parse(this.jsonText); + this.onUpload(dash); + } catch (err) { + console.log(err); + this.parseError = err.message; + return; + } + } + +} + +export function dashImportDirective() { + return { + restrict: 'E', + templateUrl: 'public/app/features/dashboard/import/import.html', + controller: DashImportCtrl, + bindToController: true, + controllerAs: 'ctrl', + }; +} + +coreModule.directive('dashImport', dashImportDirective); diff --git a/public/app/features/dashboard/upload.ts b/public/app/features/dashboard/upload.ts index ddc4005546c..57be7b8fd11 100644 --- a/public/app/features/dashboard/upload.ts +++ b/public/app/features/dashboard/upload.ts @@ -33,7 +33,9 @@ function uploadDashboardDirective(timer, alertSrv, $location) { return; } - scope.onUpload({dash: dash}); + scope.$apply(function() { + scope.onUpload({dash: dash}); + }); }; }; diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 29e3cce1ee5..d977689f553 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -232,13 +232,13 @@ $paginationActiveBackground: $blue; // Form states and alerts // ------------------------- -$state-warning-text: darken(#c09853, 10%); +$state-warning-text: $warn; $state-warning-bg: $brand-warning; $errorText: #E84D4D; $errorBackground: $btn-danger-bg; -$successText: #468847; +$successText: #12D95A; $successBackground: $btn-success-bg; $infoText: $blue-dark; diff --git a/public/vendor/angular-other/angular-strap.js b/public/vendor/angular-other/angular-strap.js index d9721bda038..3670f3f8ac4 100644 --- a/public/vendor/angular-other/angular-strap.js +++ b/public/vendor/angular-other/angular-strap.js @@ -25,11 +25,16 @@ angular.module('$strap.directives').factory('$modal', [ function ($rootScope, $compile, $http, $timeout, $q, $templateCache, $strapConfig) { var ModalFactory = function ModalFactory(config) { function Modal(config) { - var options = angular.extend({ show: true }, $strapConfig.modal, config), scope = options.scope ? options.scope : $rootScope.$new(), templateUrl = options.template; - return $q.when($templateCache.get(templateUrl) || $http.get(templateUrl, { cache: true }).then(function (res) { + var options = angular.extend({ show: true }, $strapConfig.modal, config); + var scope = options.scope ? options.scope : $rootScope.$new() + var templateUrl = options.template; + return $q.when(options.templateHtml || $templateCache.get(templateUrl) || $http.get(templateUrl, { cache: true }).then(function (res) { return res.data; })).then(function onSuccess(template) { - var id = templateUrl.replace('.html', '').replace(/[\/|\.|:]/g, '-') + '-' + scope.$id; + var id = scope.$id; + if (templateUrl) { + id += templateUrl.replace('.html', '').replace(/[\/|\.|:]/g, '-'); + } // grafana change, removed fade var $modal = $('').attr('id', id).html(template); if (options.modalClass) From 7bc15ec6c18193f1e6ea3a71437812aa6ee7ec20 Mon Sep 17 00:00:00 2001 From: Utkarsh Bhatnagar Date: Fri, 13 May 2016 13:22:56 -0700 Subject: [PATCH 049/350] [Bug Fix] Opentsdb Alias issue (#4910) * Fixed Opentsdb Alias issue * Fixed Opentsdb query editor --- .../datasource/opentsdb/config_ctrl.ts | 3 +- .../plugins/datasource/opentsdb/datasource.js | 40 +++++++++++-------- .../opentsdb/partials/query.editor.html | 6 +-- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/public/app/plugins/datasource/opentsdb/config_ctrl.ts b/public/app/plugins/datasource/opentsdb/config_ctrl.ts index c7c90e9c17f..a9259337926 100644 --- a/public/app/plugins/datasource/opentsdb/config_ctrl.ts +++ b/public/app/plugins/datasource/opentsdb/config_ctrl.ts @@ -16,7 +16,8 @@ export class OpenTsConfigCtrl { tsdbVersions = [ {name: '<=2.1', value: 1}, - {name: '>=2.2', value: 2}, + {name: '==2.2', value: 2}, + {name: '==2.3', value: 3}, ]; tsdbResolutions = [ diff --git a/public/app/plugins/datasource/opentsdb/datasource.js b/public/app/plugins/datasource/opentsdb/datasource.js index 16f96ae060e..b683aa02068 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.js +++ b/public/app/plugins/datasource/opentsdb/datasource.js @@ -54,13 +54,12 @@ function (angular, _, dateMath) { }); return this.performTimeSeriesQuery(queries, start, end).then(function(response) { - var metricToTargetMapping = mapMetricsToTargets(response.data, options); + var metricToTargetMapping = mapMetricsToTargets(response.data, options, this.tsdbVersion); var result = _.map(response.data, function(metricData, index) { index = metricToTargetMapping[index]; if (index === -1) { index = 0; } - this._saveTagKeys(metricData); return transformMetricData(metricData, groupByTags, options.targets[index], options, this.tsdbResolution); @@ -114,6 +113,9 @@ function (angular, _, dateMath) { msResolution: msResolution, globalAnnotations: true }; + if (this.tsdbVersion === 3) { + reqBody.showQuery = true; + } // Relative queries (e.g. last hour) don't include an end time if (end) { @@ -393,23 +395,27 @@ function (angular, _, dateMath) { return query; } - function mapMetricsToTargets(metrics, options) { + function mapMetricsToTargets(metrics, options, tsdbVersion) { var interpolatedTagValue; return _.map(metrics, function(metricData) { - return _.findIndex(options.targets, function(target) { - if (target.filters && target.filters.length > 0) { - return target.metric === metricData.metric && - _.all(target.filters, function(filter) { - return filter.tagk === interpolatedTagValue === "*"; - }); - } else { - return target.metric === metricData.metric && - _.all(target.tags, function(tagV, tagK) { - interpolatedTagValue = templateSrv.replace(tagV, options.scopedVars, 'pipe'); - return metricData.tags[tagK] === interpolatedTagValue || interpolatedTagValue === "*"; - }); - } - }); + if (tsdbVersion === 3) { + return metricData.query.index; + } else { + return _.findIndex(options.targets, function(target) { + if (target.filters && target.filters.length > 0) { + return target.metric === metricData.metric && + _.all(target.filters, function(filter) { + return filter.tagk === interpolatedTagValue === "*"; + }); + } else { + return target.metric === metricData.metric && + _.all(target.tags, function(tagV, tagK) { + interpolatedTagValue = templateSrv.replace(tagV, options.scopedVars, 'pipe'); + return metricData.tags[tagK] === interpolatedTagValue || interpolatedTagValue === "*"; + }); + } + }); + } }); } diff --git a/public/app/plugins/datasource/opentsdb/partials/query.editor.html b/public/app/plugins/datasource/opentsdb/partials/query.editor.html index e4e8fc8e09f..255e23c5701 100644 --- a/public/app/plugins/datasource/opentsdb/partials/query.editor.html +++ b/public/app/plugins/datasource/opentsdb/partials/query.editor.html @@ -69,7 +69,7 @@
-
+
+ +
+
+ + + +
+
+ +

+ Dashboard data sources +

+ +
+
+
+ + + +
+
+ + + +
+
+
+ +
+ + Cancel +
+ +
+ + + diff --git a/public/app/features/dashboard/export/export_modal.ts b/public/app/features/dashboard/export/export_modal.ts new file mode 100644 index 00000000000..3857d8a1330 --- /dev/null +++ b/public/app/features/dashboard/export/export_modal.ts @@ -0,0 +1,40 @@ +/// + +import kbn from 'app/core/utils/kbn'; +import coreModule from 'app/core/core_module'; +import appEvents from 'app/core/app_events'; +import config from 'app/core/config'; +import _ from 'lodash'; + +import {DashboardExporter} from './exporter'; + +export class DashExportCtrl { + dash: any; + exporter: DashboardExporter; + + /** @ngInject */ + constructor(private backendSrv, dashboardSrv, datasourceSrv, $scope) { + this.exporter = new DashboardExporter(datasourceSrv); + + var current = dashboardSrv.getCurrent().getSaveModelClone(); + + this.exporter.makeExportable(current).then(dash => { + $scope.$apply(() => { + this.dash = dash; + }); + }); + } + +} + +export function dashExportDirective() { + return { + restrict: 'E', + templateUrl: 'public/app/features/dashboard/export/export_modal.html', + controller: DashExportCtrl, + bindToController: true, + controllerAs: 'ctrl', + }; +} + +coreModule.directive('dashExportModal', dashExportDirective); diff --git a/public/app/features/dashboard/exporter.ts b/public/app/features/dashboard/export/exporter.ts similarity index 95% rename from public/app/features/dashboard/exporter.ts rename to public/app/features/dashboard/export/exporter.ts index 63067502605..0e73cbcf038 100644 --- a/public/app/features/dashboard/exporter.ts +++ b/public/app/features/dashboard/export/exporter.ts @@ -1,10 +1,10 @@ -/// +/// import config from 'app/core/config'; import angular from 'angular'; import _ from 'lodash'; -import {DynamicDashboardSrv} from './dynamic_dashboard_srv'; +import {DynamicDashboardSrv} from '../dynamic_dashboard_srv'; export class DashboardExporter { diff --git a/public/app/features/dashboard/keybindings.js b/public/app/features/dashboard/keybindings.js index b07dd2fd848..429e5f5d667 100644 --- a/public/app/features/dashboard/keybindings.js +++ b/public/app/features/dashboard/keybindings.js @@ -68,10 +68,6 @@ function(angular, $) { scope.appEvent('shift-time-forward', evt); }, { inputDisabled: true }); - keyboardManager.bind('ctrl+e', function(evt) { - scope.appEvent('export-dashboard', evt); - }, { inputDisabled: true }); - keyboardManager.bind('ctrl+i', function(evt) { scope.appEvent('quick-snapshot', evt); }, { inputDisabled: true }); diff --git a/public/app/features/dashboard/specs/exporter_specs.ts b/public/app/features/dashboard/specs/exporter_specs.ts index 62403efae7c..408d8cc5bb3 100644 --- a/public/app/features/dashboard/specs/exporter_specs.ts +++ b/public/app/features/dashboard/specs/exporter_specs.ts @@ -2,7 +2,7 @@ import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/co import _ from 'lodash'; import config from 'app/core/config'; -import {DashboardExporter} from '../exporter'; +import {DashboardExporter} from '../export/exporter'; describe.only('given dashboard with repeated panels', function() { var dash, exported; diff --git a/public/app/partials/help_modal.html b/public/app/partials/help_modal.html index 6a8c888bda3..e6601001433 100644 --- a/public/app/partials/help_modal.html +++ b/public/app/partials/help_modal.html @@ -40,10 +40,6 @@ CTRL+S Save dashboard - - CTRL+E - Export dashboard - CTRL+H Hide row controls From df50fa233255b88a28a8e17299f8160ca2d56e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 17 May 2016 11:17:11 +0200 Subject: [PATCH 067/350] feat(export): export dashboard modal --- docs/sources/reference/dashboard.md | 2 - public/app/features/dashboard/dashboardSrv.js | 1 - .../features/dashboard/dashnav/dashnav.html | 2 +- .../dashboard/export/export_modal.html | 50 ++++++++----------- .../features/dashboard/export/export_modal.ts | 13 +++++ .../app/features/dashboard/export/exporter.ts | 12 +++-- public/dashboards/home.json | 1 - public/dashboards/template_vars.json | 1 - 8 files changed, 44 insertions(+), 38 deletions(-) diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index 93adf5cd789..831dbe3abdc 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -26,7 +26,6 @@ When a user creates a new dashboard, a new dashboard JSON object is initialized { "id": null, "title": "New dashboard", - "originalTitle": "New dashboard", "tags": [], "style": "dark", "timezone": "browser", @@ -59,7 +58,6 @@ Each field in the dashboard JSON is explained below with its usage: | ---- | ----- | | **id** | unique dashboard id, an integer | | **title** | current title of dashboard | -| **originalTitle** | title of dashboard when saved for the first time | | **tags** | tags associated with dashboard, an array of strings | | **style** | theme of dashboard, i.e. `dark` or `light` | | **timezone** | timezone of dashboard, i.e. `utc` or `browser` | diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js index 2e1cd1acbf0..8181af4566f 100644 --- a/public/app/features/dashboard/dashboardSrv.js +++ b/public/app/features/dashboard/dashboardSrv.js @@ -22,7 +22,6 @@ function (angular, $, _, moment) { this.id = data.id || null; this.title = data.title || 'No Title'; - this.originalTitle = this.title; this.tags = data.tags || []; this.style = data.style || "dark"; this.timezone = data.timezone || ''; diff --git a/public/app/features/dashboard/dashnav/dashnav.html b/public/app/features/dashboard/dashnav/dashnav.html index 1a0c8a56e21..342612d12ce 100644 --- a/public/app/features/dashboard/dashnav/dashnav.html +++ b/public/app/features/dashboard/dashnav/dashnav.html @@ -35,7 +35,7 @@
  • - Export + Export for sharing
  • diff --git a/public/app/features/dashboard/export/export_modal.html b/public/app/features/dashboard/export/export_modal.html index fe821887ca4..a4c40cc6889 100644 --- a/public/app/features/dashboard/export/export_modal.html +++ b/public/app/features/dashboard/export/export_modal.html @@ -12,10 +12,10 @@ -

    - Dashboard data sources -

    - -
    -
    -
    - - - -
    -
    - - - -
    -
    -
    + + + + + + + + + + + +
    - + Cancel
    - diff --git a/public/app/features/dashboard/export/export_modal.ts b/public/app/features/dashboard/export/export_modal.ts index 3857d8a1330..57af9d9caf8 100644 --- a/public/app/features/dashboard/export/export_modal.ts +++ b/public/app/features/dashboard/export/export_modal.ts @@ -1,6 +1,7 @@ /// import kbn from 'app/core/utils/kbn'; +import angular from 'angular'; import coreModule from 'app/core/core_module'; import appEvents from 'app/core/app_events'; import config from 'app/core/config'; @@ -25,6 +26,18 @@ export class DashExportCtrl { }); } + save() { + var blob = new Blob([angular.toJson(this.dash, true)], { type: "application/json;charset=utf-8" }); + var wnd: any = window; + wnd.saveAs(blob, this.dash.title + '-' + new Date().getTime() + '.json'); + } + + saveJson() { + var html = angular.toJson(this.dash, true); + var uri = "data:application/json," + encodeURIComponent(html); + var newWindow = window.open(uri); + } + } export function dashExportDirective() { diff --git a/public/app/features/dashboard/export/exporter.ts b/public/app/features/dashboard/export/exporter.ts index 0e73cbcf038..d3b94d7fae7 100644 --- a/public/app/features/dashboard/export/exporter.ts +++ b/public/app/features/dashboard/export/exporter.ts @@ -15,6 +15,8 @@ export class DashboardExporter { var dynSrv = new DynamicDashboardSrv(); dynSrv.process(dash, {cleanUpOnly: true}); + dash.id = null; + var inputs = []; var requires = {}; var datasources = {}; @@ -63,10 +65,14 @@ export class DashboardExporter { return req; }); - dash["__inputs"] = inputs; - dash["__requires"] = requires; + // make inputs and requires a top thing + var newObj = {}; + newObj["__inputs"] = inputs; + newObj["__requires"] = requires; - return dash; + _.defaults(newObj, dash); + + return newObj; }).catch(err => { console.log('Export failed:', err); return {}; diff --git a/public/dashboards/home.json b/public/dashboards/home.json index c3ed1017bad..393cbc5865c 100644 --- a/public/dashboards/home.json +++ b/public/dashboards/home.json @@ -1,7 +1,6 @@ { "id": null, "title": "Home", - "originalTitle": "Home", "tags": [], "style": "dark", "timezone": "browser", diff --git a/public/dashboards/template_vars.json b/public/dashboards/template_vars.json index 8ca81fbdff5..a3c25d00371 100644 --- a/public/dashboards/template_vars.json +++ b/public/dashboards/template_vars.json @@ -1,7 +1,6 @@ { "id": null, "title": "Templated Graphs Nested", - "originalTitle": "Templated Graphs Nested", "tags": [ "showcase", "templated" From 83f5080274098821dbc7fadf2849de82ea4c86a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 17 May 2016 15:00:48 +0200 Subject: [PATCH 068/350] feat(angular): disable debug in production for angular compiler --- pkg/api/frontendsettings.go | 1 + public/app/app.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index dd84f7827eb..9fcd5e567fb 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -142,6 +142,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro "buildstamp": setting.BuildStamp, "latestVersion": plugins.GrafanaLatestVersion, "hasUpdate": plugins.GrafanaHasUpdate, + "env": setting.Env, }, } diff --git a/public/app/app.ts b/public/app/app.ts index 3434814d3b6..6de7181f5a1 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -42,7 +42,9 @@ export class GrafanaApp { app.constant('grafanaVersion', "@grafanaVersion@"); app.config(($locationProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) => { - //$compileProvider.debugInfoEnabled(false); + if (config.buildInfo.env !== 'development') { + $compileProvider.debugInfoEnabled(false); + } this.registerFunctions.controller = $controllerProvider.register; this.registerFunctions.directive = $compileProvider.directive; From b5fc12fb80c9c7d481c598033c6ce26eee386232 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 17 May 2016 15:28:25 +0200 Subject: [PATCH 069/350] docs(opentsdb): add working image of add datasource for opentsdb closes #4984 --- docs/sources/datasources/opentsdb.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/datasources/opentsdb.md b/docs/sources/datasources/opentsdb.md index 28d90c19b00..6ef930c0cc0 100644 --- a/docs/sources/datasources/opentsdb.md +++ b/docs/sources/datasources/opentsdb.md @@ -7,10 +7,10 @@ page_keywords: grafana, opentsdb, documentation # OpenTSDB Guide The newest release of Grafana adds additional functionality when using an OpenTSDB Data source. -![](/img/v2/add_OpenTSDB.jpg) +![](/img/v2/add_OpenTSDB.png) -1. Open the side menu by clicking the the Grafana icon in the top header. -2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`. +1. Open the side menu by clicking the the Grafana icon in the top header. +2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`. > NOTE: If this link is missing in the side menu it means that your current user does not have the `Admin` role for the current organization. From 05d064ca8d56141f671249e1dd77aae307b85feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 17 May 2016 21:18:47 +0200 Subject: [PATCH 070/350] export(): moved to share modal --- .../features/dashboard/dashnav/dashnav.html | 8 +- .../app/features/dashboard/dashnav/dashnav.ts | 6 - .../dashboard/export/export_modal.html | 113 ++++++++---------- .../dashboard/partials/shareModal.html | 4 + .../app/features/dashboard/shareModalCtrl.js | 8 +- 5 files changed, 66 insertions(+), 73 deletions(-) diff --git a/public/app/features/dashboard/dashnav/dashnav.html b/public/app/features/dashboard/dashnav/dashnav.html index 342612d12ce..8a50c9d3bda 100644 --- a/public/app/features/dashboard/dashnav/dashnav.html +++ b/public/app/features/dashboard/dashnav/dashnav.html @@ -30,12 +30,12 @@
  • - Snapshot sharing + Snapshot
  • - - Export for sharing + + Export
  • @@ -49,8 +49,6 @@
  • Settings
  • Annotations
  • Templating
  • -
  • Export
  • -
  • View JSON
  • Make Editable
  • Save As...
  • Delete dashboard
  • diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts index 81f2d9ea8c6..ae7d81dfc58 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/dashnav/dashnav.ts @@ -59,12 +59,6 @@ export class DashNavCtrl { $scope.shareDashboard(1); }; - $scope.shareExport = function() { - $scope.appEvent('show-modal', { - templateHtml: '', - }); - }; - $scope.openSearch = function() { $scope.appEvent('show-dash-search'); }; diff --git a/public/app/features/dashboard/export/export_modal.html b/public/app/features/dashboard/export/export_modal.html index a4c40cc6889..d2643eb934d 100644 --- a/public/app/features/dashboard/export/export_modal.html +++ b/public/app/features/dashboard/export/export_modal.html @@ -1,66 +1,59 @@ -