Dashboard schema simplifications, moved schema updates to dashboard model creation, removes irritating 'unsaved changes' dialogs that show for dashboard schema changes, Closes #532

This commit is contained in:
Torkel Ödegaard 2014-08-05 10:15:27 +02:00
parent 0a677449dc
commit 60f68abd31
18 changed files with 246 additions and 109 deletions

View File

@ -22,6 +22,7 @@
**Changes**
- [Issue #536](https://github.com/grafana/grafana/issues/536). Graphite: Use unix epoch for Graphite from/to for absolute time ranges
- [Issue #641](https://github.com/grafana/grafana/issues/536). General: Dashboard save temp copy feature settings moved from dashboard to config.js, default is enabled, and ttl to 30 days
- [Issue #532](https://github.com/grafana/grafana/issues/532). Schema: Dashboard schema changes, "Unsaved changes" should not appear for schema changes. All changes are backward compatible with old schema.
**Fixes**
- [Issue #545](https://github.com/grafana/grafana/issues/545). Chart: Fix formatting negative values (axis formats, legend values)

View File

@ -0,0 +1,56 @@
<br/>
<div class="row-fluid">
<div class="span6">
<ul>
<li>
<a href="http://localhost:4567/docs#configuration" target="_blank">Configuration</a>
</li>
<li>
<a href="http://localhost:4567/docs/troubleshooting" target="_blank">Troubleshooting</a>
</li>
<li>
<a href="http://localhost:4567/docs/support" target="_blank">Support</a>
</li>
<li>
<a href="http://localhost:4567/docs/features/intro" target="_blank">Getting started</a> (Must read!)
</li>
</ul>
</div>
<div class="span6">
<ul>
<li>
<a href="http://localhost:4567/docs/features/charts" target="_blank">Charts</a>
</li>
<li>
<a href="http://localhost:4567/docs/features/annotations" target="_blank">Annotations</a>
</li>
<li>
<a href="http://localhost:4567/docs/features/graphite" target="_blank">Graphite</a>
</li>
<li>
<a href="http://localhost:4567/docs/features/influxdb" target="_blank">InfluxDB</a>
</li>
<li>
<a href="http://localhost:4567/docs/features/opentsdb" target="_blank">OpenTSDB</a>
</li>
</ul>
</div>
</div>
<br/>
<div class="row-fluid">
<div class="span12">
<ul>
<li>Ctrl+S saves the current dashboard</li>
<li>Ctrl+F Opens the dashboard finder (searches elastic search)</li>
<li>Ctrl+H Hide/show row controls</li>
<li>Click and drag graph title to move panel</li>
<li>Hit Escape to exit graph when in fullscreen or edit mode</li>
<li>Click the colored icon in the legend to change series color</li>
<li>Ctrl or Shift + Click legend name to hide other series</li>
<li>Click the Save icon in the menu to save the dashboard with a new name</li>
</ul>
</div>
</div>

View File

@ -188,6 +188,10 @@ function (angular, _, config, gfunc, Parser) {
$scope.segments[segmentIndex].val = $scope.altSegments[altIndex].val;
$scope.segments[segmentIndex].html = $scope.altSegments[altIndex].html;
if ($scope.functions.length > 0 && $scope.functions[0].def.fake) {
$scope.functions = [];
}
if ($scope.altSegments[altIndex].expandable) {
return checkOtherSegments(segmentIndex + 1)
.then(function () {

View File

@ -13,10 +13,8 @@ function (angular, app, _) {
title: "Row",
height: "150px",
collapse: false,
collapsable: true,
editable: true,
panels: [],
notice: false
};
_.defaults($scope.row,_d);
@ -26,16 +24,11 @@ function (angular, app, _) {
};
$scope.toggle_row = function(row) {
if(!row.collapsable) {
return;
}
row.collapse = row.collapse ? false : true;
if (!row.collapse) {
$timeout(function() {
$scope.$broadcast('render');
});
} else {
row.notice = false;
}
};

View File

@ -1,13 +1,11 @@
{
"title": "Welcome to Grafana!",
"services": {
"filter": {
"list": [],
"time": {
"from": "now-6h",
"to": "now"
}
}
"time": {
"from": "now-6h",
"to": "now"
},
"templating": {
"list": []
},
"rows": [
{
@ -15,7 +13,6 @@
"height": "150px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"error": false,
@ -28,15 +25,13 @@
"style": {},
"title": "Welcome to Grafana"
}
],
"notice": false
]
},
{
"title": "test",
"height": "250px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"span": 12,
@ -86,8 +81,7 @@
"aliasYAxis": {},
"title": "Graphite test"
}
],
"notice": false
]
}
],
"editable": true,

View File

@ -1,13 +1,11 @@
{
"title": "New Dashboard",
"services": {
"filter": {
"list": [],
"time": {
"from": "now-6h",
"to": "now"
}
}
"time": {
"from": "now-6h",
"to": "now"
},
"templating": {
"list": []
},
"rows": [
{
@ -15,9 +13,7 @@
"height": "250px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [],
"notice": false
"panels": []
}
],
"editable": true,

View File

@ -28,16 +28,13 @@ timspan = '1d';
// Intialize a skeleton with nothing but a rows array and service object
dashboard = {
rows : [],
services : {}
};
// Set a title
dashboard.title = 'Scripted dash';
dashboard.services.filter = {
time: {
from: "now-" + (ARGS.from || timspan),
to: "now"
}
dashboard.time = {
from: "now-" + (ARGS.from || timspan),
to: "now"
};
var rows = 1;
@ -59,7 +56,7 @@ for (var i = 0; i < rows; i++) {
panels: [
{
title: 'Events',
type: 'graphite',
type: 'graph',
span: 12,
fill: 1,
linewidth: 2,

View File

@ -185,32 +185,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
_.defaults($scope.panel.tooltip, _d.tooltip);
_.defaults($scope.panel.annotate, _d.annotate);
_.defaults($scope.panel.grid, _d.grid);
// backward compatible stuff
if (_.isBoolean($scope.panel.legend)) {
$scope.panel.legend = { show: $scope.panel.legend };
_.defaults($scope.panel.legend, _d.legend);
}
if ($scope.panel.grid.min) {
$scope.panel.grid.leftMin = $scope.panel.grid.min;
delete $scope.panel.grid.min;
}
if ($scope.panel.grid.max) {
$scope.panel.grid.leftMax = $scope.panel.grid.max;
delete $scope.panel.grid.max;
}
if ($scope.panel.y_format) {
$scope.panel.y_formats[0] = $scope.panel.y_format;
delete $scope.panel.y_format;
}
if ($scope.panel.y2_format) {
$scope.panel.y_formats[1] = $scope.panel.y2_format;
delete $scope.panel.y2_format;
}
_.defaults($scope.panel.legend, _d.legend);
$scope.init = function() {
$scope.initBaseController(this, $scope);

View File

@ -36,7 +36,7 @@ function (angular, app, _, require) {
style: {},
};
_.defaults($scope.panel,_d);
_.defaults($scope.panel, _d);
$scope.init = function() {
$scope.initBaseController(this, $scope);

View File

@ -34,13 +34,13 @@
<span class="row-button bgWarning" config-modal="app/partials/roweditor.html" class="pointer">
<i bs-tooltip="'Configure row'" data-placement="right" ng-show="row.editable" class="icon-cog pointer"></i>
</span>
<span class="row-button bgPrimary" ng-click="toggle_row(row)" ng-show="row.collapsable">
<span class="row-button bgPrimary" ng-click="toggle_row(row)">
<i bs-tooltip="'Expand row'" data-placement="right" class="icon-caret-left pointer" ></i>
</span>
<span class="row-button row-text" ng-click="toggle_row(row)" ng-class="{'pointer':row.collapsable}">{{row.title || 'Row '+$index}}</span>
<span class="row-button row-text" ng-click="toggle_row(row)">{{row.title || 'Row '+$index}}</span>
</div>
<div class="row-open" ng-show="!row.collapse">
<div ng-show="row.collapsable" class='row-tab bgPrimary' ng-click="toggle_row(row)">
<div class='row-tab bgPrimary' ng-click="toggle_row(row)">
<span class="row-tab-button">
<i class="icon-caret-right"></i>
</span>

View File

@ -4,7 +4,10 @@
<h3 class="text-center"><i class="icon-warning-sign"></i> Unsaved changes</h3>
<div class="row-fluid">
<span class="span3"></span>
<span class="span3">
{{changes}}
</span>
<button type="button" class="btn btn-success span2" ng-click="dismiss()">Cancel</button>
<button type="button" class="btn btn-success span2" ng-click="save();dismiss();">Save</button>
<button type="button" class="btn btn-warning span2" ng-click="ignore();dismiss();">Ignore</button>

View File

@ -2,7 +2,8 @@ define([
'angular',
'jquery',
'kbn',
'underscore'
'underscore',
'../timer',
],
function (angular, $, kbn, _) {
'use strict';
@ -17,7 +18,7 @@ function (angular, $, kbn, _) {
data = {};
}
this.title = data.title;
this.title = data.title || 'No Title';
this.tags = data.tags || [];
this.style = data.style || "dark";
this.timezone = data.timezone || 'browser';
@ -25,7 +26,8 @@ function (angular, $, kbn, _) {
this.rows = data.rows || [];
this.pulldowns = data.pulldowns || [];
this.nav = data.nav || [];
this.services = data.services || {};
this.time = data.time || { from: 'now-6h', to: 'now' };
this.templating = data.templating || { list: [] };
if (this.nav.length === 0) {
this.nav.push({ type: 'timepicker' });
@ -39,13 +41,7 @@ function (angular, $, kbn, _) {
this.pulldowns.push({ type: 'annotations', enable: false });
}
_.each(this.rows, function(row) {
_.each(row.panels, function(panel) {
if (panel.type === 'graphite') {
panel.type = 'graph';
}
});
});
this.updateSchema(data);
}
var p = DashboardModel.prototype;
@ -76,6 +72,64 @@ function (angular, $, kbn, _) {
}
};
p.updateSchema = function(old) {
var i, j, row, panel;
var isChanged = false;
if (this.version === 2) {
return;
}
if (old.services) {
if (old.services.filter) {
this.time = old.services.filter.time;
this.templating.list = old.services.filter.list;
}
delete this.services;
}
for (i = 0; i < this.rows.length; i++) {
row = this.rows[i];
for (j = 0; j < row.panels.length; j++) {
panel = row.panels[j];
if (panel.type === 'graphite') {
panel.type = 'graph';
isChanged = true;
}
if (panel.type === 'graph') {
if (_.isBoolean(panel.legend)) {
panel.legend = { show: panel.legend };
}
if (panel.grid) {
if (panel.grid.min) {
panel.grid.leftMin = panel.grid.min;
delete panel.grid.min;
}
if (panel.grid.max) {
panel.grid.leftMax = panel.grid.max;
delete panel.grid.max;
}
}
if (panel.y_format) {
panel.y_formats[0] = panel.y_format;
delete panel.y_format;
}
if (panel.y2_format) {
panel.y_formats[1] = panel.y2_format;
delete panel.y2_format;
}
}
}
}
this.version = 2;
};
return {
create: function(dashboard) {
return new DashboardModel(dashboard);

View File

@ -9,12 +9,6 @@ define([
var module = angular.module('grafana.services');
module.factory('filterSrv', function($rootScope, $timeout, $routeParams) {
// defaults
var _d = {
templateParameters: [],
time: {}
};
var result = {
updateTemplateData: function(initial) {
@ -86,26 +80,14 @@ define([
removeTemplateParameter: function(templateParameter) {
this.templateParameters = _.without(this.templateParameters, templateParameter);
this.dashboard.services.filter.list = this.templateParameters;
this.dashboard.templating.list = this.templateParameters;
},
init: function(dashboard) {
_.defaults(this, _d);
this.dashboard = dashboard;
this.templateSettings = { interpolate : /\[\[([\s\S]+?)\]\]/g };
if (!this.dashboard.services.filter) {
this.dashboard.services.filter = {
list: [],
time: {
from: '1h',
to: 'now'
}
};
}
this.time = dashboard.services.filter.time;
this.templateParameters = dashboard.services.filter.list || [];
this.time = dashboard.time;
this.templateParameters = dashboard.templating.list;
this.updateTemplateData(true);
}
};

View File

@ -216,6 +216,7 @@ function (_) {
addFuncDef({
name: 'randomWalk',
fake: true,
category: categories.Special,
params: [{ name: "name", type: "string", }],
defaultParams: ['randomWalk']

View File

@ -74,7 +74,7 @@ function(angular, _, config) {
var original = self.original;
// ignore timespan changes
current.services.filter.time = original.services.filter.time = {};
current.time = original.time = {};
current.refresh = original.refresh;

View File

@ -18,11 +18,9 @@ define([],
rows: [],
pulldowns: [ { type: 'templating' }, { type: 'annotations' } ],
nav: [ { type: 'timepicker' } ],
services: {
filter: {
time: {},
list: []
}
time: {},
templating: {
list: []
},
refresh: true
};

View File

@ -0,0 +1,82 @@
define([
'services/dashboard/dashboardModel'
], function() {
'use strict';
describe('when creating new dashboard with defaults only', function() {
var model;
beforeEach(module('grafana.services'));
beforeEach(inject(function(dashboard) {
model = dashboard.create({});
}));
it('should have title', function() {
expect(model.title).to.be('No Title');
});
it('should have default properties', function() {
expect(model.rows.length).to.be(0);
expect(model.nav.length).to.be(1);
expect(model.pulldowns.length).to.be(2);
});
});
describe('when creating dashboard with old schema', function() {
var model;
var graph;
beforeEach(module('grafana.services'));
beforeEach(inject(function(dashboard) {
model = dashboard.create({
services: { filter: { time: { from: 'now-1d', to: 'now'}, list: [1] }},
rows: [
{
panels: [
{
type: 'graphite',
legend: true,
grid: { min: 1, max: 10 }
}
]
}
]
});
graph = model.rows[0].panels[0];
}));
it('should have title', function() {
expect(model.title).to.be('No Title');
});
it('should move time and filtering list', function() {
expect(model.time.from).to.be('now-1d');
expect(model.templating.list[0]).to.be(1);
});
it('graphite panel should change name too graph', function() {
expect(graph.type).to.be('graph');
});
it('update legend setting', function() {
expect(graph.legend.show).to.be(true);
});
it('update grid options', function() {
expect(graph.grid.leftMin).to.be(1);
expect(graph.grid.leftMax).to.be(10);
});
it('dashboard schema version should be set to latest', function() {
expect(model.version).to.be(2);
});
});
});

View File

@ -124,6 +124,7 @@ require([
'specs/gfunc-specs',
'specs/filterSrv-specs',
'specs/kbn-format-specs',
'specs/dashboardModel-specs',
'specs/influxSeries-specs'
], function () {
window.__karma__.start();