Commit 60f68abd by Torkel Ödegaard

Dashboard schema simplifications, moved schema updates to dashboard model…

Dashboard schema simplifications, moved schema updates to dashboard model creation, removes irritating 'unsaved changes' dialogs that show for dashboard schema changes, Closes #532
parent 0a677449
......@@ -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)
......
<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>
......@@ -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 () {
......
......@@ -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;
}
};
......
{
"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,
......
{
"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,
......
......@@ -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,
......@@ -77,4 +74,4 @@ for (var i = 0; i < rows; i++) {
}
return dashboard;
\ No newline at end of file
return dashboard;
......@@ -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);
......
......@@ -36,7 +36,7 @@ function (angular, app, _, require) {
style: {},
};
_.defaults($scope.panel,_d);
_.defaults($scope.panel, _d);
$scope.init = function() {
$scope.initBaseController(this, $scope);
......
......@@ -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>
......
......@@ -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>
......@@ -13,4 +16,4 @@
</div>
<div class="modal-footer">
</div>
\ No newline at end of file
</div>
......@@ -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);
......
......@@ -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);
}
};
......
......@@ -216,6 +216,7 @@ function (_) {
addFuncDef({
name: 'randomWalk',
fake: true,
category: categories.Special,
params: [{ name: "name", type: "string", }],
defaultParams: ['randomWalk']
......
......@@ -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;
......
......@@ -18,11 +18,9 @@ define([],
rows: [],
pulldowns: [ { type: 'templating' }, { type: 'annotations' } ],
nav: [ { type: 'timepicker' } ],
services: {
filter: {
time: {},
list: []
}
time: {},
templating: {
list: []
},
refresh: true
};
......
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);
});
});
});
......@@ -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();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment