Commit 90cca939 by Torkel Ödegaard

feat(tablepanel): lots of work on table panel

parent 4e37290a
...@@ -45,16 +45,25 @@ function (_, $, coreModule) { ...@@ -45,16 +45,25 @@ function (_, $, coreModule) {
} }
var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) { var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) {
_.each(value.submenu, function(item, subIndex) { if (!value.submenu) {
item.click = 'menuItemSelected(' + index + ',' + subIndex + ')'; value.click = 'menuItemSelected(' + index + ')';
memo.push(value.text + ' ' + item.text); memo.push(value.text);
}); } else {
_.each(value.submenu, function(item, subIndex) {
item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
memo.push(value.text + ' ' + item.text);
});
}
return memo; return memo;
}, []); }, []);
$scope.menuItemSelected = function(index, subIndex) { $scope.menuItemSelected = function(index, subIndex) {
var item = $scope.menuItems[index]; var menuItem = $scope.menuItems[index];
$scope.dropdownTypeaheadOnSelect({$item: item, $subItem: item.submenu[subIndex]}); var payload = {$item: menuItem};
if (menuItem.submenu && subIndex !== void 0) {
payload.$subItem = menuItem.submenu[subIndex];
}
$scope.dropdownTypeaheadOnSelect(payload);
}; };
$input.attr('data-provide', 'typeahead'); $input.attr('data-provide', 'typeahead');
...@@ -65,9 +74,10 @@ function (_, $, coreModule) { ...@@ -65,9 +74,10 @@ function (_, $, coreModule) {
updater: function (value) { updater: function (value) {
var result = {}; var result = {};
_.each($scope.menuItems, function(menuItem) { _.each($scope.menuItems, function(menuItem) {
result.$item = menuItem;
_.each(menuItem.submenu, function(submenuItem) { _.each(menuItem.submenu, function(submenuItem) {
if (value === (menuItem.text + ' ' + submenuItem.text)) { if (value === (menuItem.text + ' ' + submenuItem.text)) {
result.$item = menuItem;
result.$subItem = submenuItem; result.$subItem = submenuItem;
} }
}); });
......
...@@ -32,9 +32,9 @@ function (angular, _, $, kbn, dateMath, rangeUtil) { ...@@ -32,9 +32,9 @@ function (angular, _, $, kbn, dateMath, rangeUtil) {
scope.timing.renderEnd = new Date().getTime(); scope.timing.renderEnd = new Date().getTime();
}; };
this.broadcastRender = function(scope, data) { this.broadcastRender = function(scope, arg1, arg2) {
this.setTimeRenderStart(scope); this.setTimeRenderStart(scope);
scope.$broadcast('render', data); scope.$broadcast('render', arg1, arg2);
this.setTimeRenderEnd(scope); this.setTimeRenderEnd(scope);
if ($rootScope.profilingEnabled) { if ($rootScope.profilingEnabled) {
......
...@@ -3,24 +3,15 @@ ...@@ -3,24 +3,15 @@
import angular = require('angular'); import angular = require('angular');
import _ = require('lodash'); import _ = require('lodash');
import moment = require('moment'); import moment = require('moment');
import kbn = require('app/core/utils/kbn');
import PanelMeta = require('app/features/panel/panel_meta'); import PanelMeta = require('app/features/panel/panel_meta');
import {TableModel} from './table_model'; import {TableModel} from './table_model';
import {transformers} from './transformers';
export class TablePanelCtrl { export class TablePanelCtrl {
constructor($scope, $rootScope, $q, panelSrv, panelHelper) { constructor($scope, $rootScope, $q, panelSrv, panelHelper) {
$scope.ctrl = this; $scope.ctrl = this;
$scope.transformers = transformers;
$scope.pageIndex = 0; $scope.pageIndex = 0;
$scope.unitFormats = kbn.getUnitFormats();
$scope.colorModes = {
'cell': {text: 'Cell'},
'value': {text: 'Value'},
'row': {text: 'Row'},
};
$scope.panelMeta = new PanelMeta({ $scope.panelMeta = new PanelMeta({
panelName: 'Table', panelName: 'Table',
...@@ -38,23 +29,18 @@ export class TablePanelCtrl { ...@@ -38,23 +29,18 @@ export class TablePanelCtrl {
pageSize: 50, pageSize: 50,
showHeader: true, showHeader: true,
columns: [], columns: [],
fields: []
}; };
$scope.init = function() { $scope.init = function() {
_.defaults($scope.panel, panelDefaults); _.defaults($scope.panel, panelDefaults);
if ($scope.panel.columns.length === 0) { if ($scope.panel.columns.length === 0) {
$scope.addColumnStyle();
} }
panelSrv.init($scope); panelSrv.init($scope);
}; };
$scope.setUnitFormat = function(column, subItem) {
column.unit = subItem.value;
$scope.render();
};
$scope.refreshData = function(datasource) { $scope.refreshData = function(datasource) {
panelHelper.updateTimeRange($scope); panelHelper.updateTimeRange($scope);
...@@ -73,32 +59,7 @@ export class TablePanelCtrl { ...@@ -73,32 +59,7 @@ export class TablePanelCtrl {
$scope.render = function() { $scope.render = function() {
$scope.table = TableModel.transform($scope.dataRaw, $scope.panel); $scope.table = TableModel.transform($scope.dataRaw, $scope.panel);
panelHelper.broadcastRender($scope, $scope.table); panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw);
};
$scope.getColumnNames = function() {
if (!$scope.table) {
return [];
}
return _.map($scope.table.columns, function(col: any) {
return col.text;
});
};
$scope.addColumnStyle = function() {
var columnStyleDefaults = {
unit: 'short',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
pattern: '/.*/',
colorMode: 'value',
};
$scope.panel.columns.push(angular.copy(columnStyleDefaults));
};
$scope.removeColumnStyle = function(col) {
$scope.panel.columns = _.without($scope.panel.columns, col);
}; };
$scope.init(); $scope.init();
......
<div class="editor-row">
<div class="section">
<h5>Data</h5>
<div class="tight-form-container">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 140px">
To Table Transform
</li>
<li>
<select class="input-large tight-form-input"
ng-model="panel.transform"
ng-options="k as v.description for (k, v) in transformers"
ng-change="render()"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="panel.transform === 'json'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 140px">
Fields
</li>
<li class="tight-form-item" ng-repeat="field in panel.fields">
<i class="pointer fa fa-remove" ng-click="removeJsonField(field)"></i>
<span>
{{field.name}}
</span>
</li>
<li class="dropdown" dropdown-typeahead="jsonFieldsMenu" dropdown-typeahead-on-select="addJsonField($item, $subItem)">
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="section">
<h5>Table Display</h5>
<div class="tight-form-container">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item">
Pagination (Page size)
</li>
<li>
<input type="text" class="input-small tight-form-input" placeholder="50"
empty-to-null ng-model="panel.pageSize" ng-change="render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="editor-row" style="margin-top: 20px">
<h5>Column Styles</h5>
<div class="tight-form-container">
<div ng-repeat="column in panel.columns">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item">
<i class="fa fa-remove pointer" ng-click="removeColumnStyle(column)"></i>
</li>
<li class="tight-form-item">
Name or regex
</li>
<li>
<input type="text" ng-model="column.pattern" bs-typeahead="getColumnNames" ng-blur="render()" data-min-length=0 data-items=100 class="input-medium tight-form-input">
</li>
<li class="tight-form-item" style="width: 86px">
Type
</li>
<li>
<select class="input-small tight-form-input"
ng-model="column.type"
ng-options="k as v.text for (k, v) in columnTypes"
ng-change="render()"
style="width: 150px"
></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="column.type === 'date'">
<ul class="tight-form-list">
<li class="tight-form-item">
<i class="fa fa-remove pointer invisible"></i>
</li>
<li class="tight-form-item text-right" style="width: 93px">
Format
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="column.dateFormat" ng-change="render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="column.type === 'number'">
<ul class="tight-form-list">
<li class="tight-form-item">
<i class="fa fa-remove pointer invisible"></i>
</li>
<li class="tight-form-item text-right" style="width: 93px">
Coloring
</li>
<li>
<select class="input-small tight-form-input"
ng-model="column.colorMode"
ng-options="k as v.text for (k, v) in colorModes"
ng-change="render()"
style="width: 150px"
></select>
</li>
<li class="tight-form-item">
Thresholds<tip>Comma seperated values</tip>
</li>
<li>
<input type="text" class="input-small tight-form-input" style="width: 150px" ng-model="column.thresholds" ng-blur="render()" placeholder="0,50,80"></input>
</li>
<li class="tight-form-item" style="width: 60px">
Colors
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="column.colors[0]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="column.colors[1]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="column.colors[2]" ng-change="render()" ></spectrum-picker>
</li>
<li class="tight-form-item last">
<a class="pointer" ng-click="invertColorOrder()">invert order</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form" ng-if="column.type === 'number'">
<ul class="tight-form-list">
<li class="tight-form-item">
<i class="fa fa-remove pointer invisible"></i>
</li>
<li class="tight-form-item text-right" style="width: 93px">
Unit
</li>
<li class="dropdown" style="width: 150px"
ng-model="column.unit"
dropdown-typeahead="unitFormats"
dropdown-typeahead-on-select="setUnitFormat(column, $subItem)">
</li>
<li class="tight-form-item" style="width: 86px">
Decimals
</li>
<li style="width: 105px">
<input type="number" class="input-mini tight-form-input" ng-model="column.decimals" ng-change="render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
<button class="btn btn-inverse" style="margin-top: 20px" ng-click="addColumnStyle()">
Add column display rule
</button>
</div>
///<reference path="../../headers/common.d.ts" />
import angular = require('angular');
import $ = require('jquery');
import _ = require('lodash');
import kbn = require('app/core/utils/kbn');
import moment = require('moment');
import {transformers} from './transformers';
export function tablePanelEditor() {
'use strict';
return {
restrict: 'E',
scope: true,
templateUrl: 'app/panels/table/editor.html',
link: function(scope, elem) {
scope.transformers = transformers;
scope.unitFormats = kbn.getUnitFormats();
scope.colorModes = {
'cell': {text: 'Cell'},
'value': {text: 'Value'},
'row': {text: 'Row'},
};
scope.columnTypes = {
'number': {text: 'Number'},
'string': {text: 'String'},
'date': {text: 'Date'},
};
scope.updateJsonFieldsMenu = function(data) {
scope.jsonFieldsMenu = [];
if (!data || data.length === 0) {
return;
}
var names = {};
for (var i = 0; i < data.length; i++) {
var series = data[i];
if (series.type !== 'docs') {
continue;
}
for (var y = 0; y < series.datapoints.length; y++) {
var doc = series.datapoints[y];
for (var propName in doc) {
names[propName] = true;
}
}
}
_.each(names, function(value, key) {
scope.jsonFieldsMenu.push({text: key});
});
};
scope.updateJsonFieldsMenu(scope.dataRaw);
scope.$on('render', function(event, table, rawData) {
scope.updateJsonFieldsMenu(rawData);
});
scope.addJsonField = function(menuItem) {
scope.panel.fields.push({name: menuItem.text});
};
scope.removeJsonField = function(field) {
scope.panel.fields = _.without(scope.panel.fields, field);
};
scope.setUnitFormat = function(column, subItem) {
column.unit = subItem.value;
scope.render();
};
scope.addColumnStyle = function() {
var columnStyleDefaults = {
unit: 'short',
type: 'number',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: 'value',
pattern: '/.*/',
};
scope.panel.columns.push(angular.copy(columnStyleDefaults));
};
scope.removeColumnStyle = function(col) {
scope.panel.columns = _.without(scope.panel.columns, col);
};
scope.getColumnNames = function() {
if (!scope.table) {
return [];
}
return _.map(scope.table.columns, function(col: any) {
return col.text;
});
};
}
};
}
...@@ -4,10 +4,12 @@ import angular = require('angular'); ...@@ -4,10 +4,12 @@ import angular = require('angular');
import $ = require('jquery'); import $ = require('jquery');
import _ = require('lodash'); import _ = require('lodash');
import kbn = require('app/core/utils/kbn'); import kbn = require('app/core/utils/kbn');
import moment = require('moment');
import {TablePanelCtrl} from './controller'; import {TablePanelCtrl} from './controller';
import {tablePanelEditor} from './editor';
export function tablePanelDirective() { export function tablePanel() {
'use strict'; 'use strict';
return { return {
restrict: 'E', restrict: 'E',
...@@ -45,9 +47,13 @@ export function tablePanelDirective() { ...@@ -45,9 +47,13 @@ export function tablePanelDirective() {
if (v === null || v === void 0) { if (v === null || v === void 0) {
return '-'; return '-';
} }
if (_.isString(v) || style) { if (_.isString(v) || !style) {
return v; return v;
} }
if (style.type === 'date') {
var date = moment(v);
return date.format(style.dateFormat);
}
let valueFormater = kbn.valueFormats[style.unit]; let valueFormater = kbn.valueFormats[style.unit];
return valueFormater(v, style.decimals); return valueFormater(v, style.decimals);
}; };
...@@ -136,4 +142,5 @@ export function tablePanelDirective() { ...@@ -136,4 +142,5 @@ export function tablePanelDirective() {
}; };
} }
angular.module('grafana.directives').directive('grafanaPanelTable', tablePanelDirective); angular.module('grafana.directives').directive('grafanaPanelTable', tablePanel);
angular.module('grafana.directives').directive('grafanaPanelTableEditor', tablePanelEditor);
<div class="editor-row"> <grafana-panel-table-editor>
<div class="section"> </grafana-panel-table-editor>
<h5>Data</h5>
<div class="tight-form-container">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 170px">
To Table Transform
</li>
<li>
<select class="input-xlarge tight-form-input"
ng-model="panel.transform"
ng-options="k as v.description for (k, v) in transformers"
ng-change="render()"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="section">
<h5>Table Display</h5>
<div class="tight-form-container">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item">
Pagination (Page size)
</li>
<li>
<input type="text" class="input-small tight-form-input" placeholder="50"
empty-to-null ng-model="panel.pageSize" ng-change="render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="editor-row" style="margin-top: 20px">
<h5>Column Styles</h5>
<div class="tight-form-container">
<div ng-repeat="column in panel.columns">
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item">
<i class="fa fa-remove pointer" ng-click="removeColumnStyle(column)"></i>
</li>
<li class="tight-form-item">
Name or regex
</li>
<li>
<input type="text" ng-model="column.pattern" bs-typeahead="getColumnNames" ng-blur="render()" data-min-length=0 data-items=100 class="input-medium tight-form-input">
</li>
<li class="tight-form-item" style="width: 86px">
Unit
</li>
<li class="dropdown" style="width: 150px"
ng-model="column.unit"
dropdown-typeahead="unitFormats"
dropdown-typeahead-on-select="setUnitFormat(column, $subItem)">
</li>
<li class="tight-form-item" style="width: 60px">
Decimals
</li>
<li style="width: 105px">
<input type="number" class="input-mini tight-form-input" ng-model="column.decimals" ng-change="render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item">
<i class="fa fa-remove pointer invisible"></i>
</li>
<li class="tight-form-item text-right" style="width: 93px">
Coloring
</li>
<li>
<select class="input-small tight-form-input"
ng-model="column.colorMode"
ng-options="k as v.text for (k, v) in colorModes"
ng-change="render()"
style="width: 150px"
></select>
</li>
<li class="tight-form-item">
Thresholds<tip>Comma seperated values</tip>
</li>
<li>
<input type="text" class="input-small tight-form-input" style="width: 150px" ng-model="column.thresholds" ng-blur="render()" placeholder="0,50,80"></input>
</li>
<li class="tight-form-item" style="width: 60px">
Colors
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="column.colors[0]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="column.colors[1]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="column.colors[2]" ng-change="render()" ></spectrum-picker>
</li>
<li class="tight-form-item last">
<a class="pointer" ng-click="invertColorOrder()">invert order</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
<button class="btn btn-inverse" style="margin-top: 20px" ng-click="addColumnStyle()">
Add column display rule
</button>
</div>
...@@ -67,6 +67,41 @@ describe('when transforming time series table', () => { ...@@ -67,6 +67,41 @@ describe('when transforming time series table', () => {
expect(table.rows[1][2]).to.be(undefined); expect(table.rows[1][2]).to.be(undefined);
}); });
}); });
describe('JSON Data', () => {
var panel = {
transform: 'json',
fields: [{name: 'timestamp'}, {name: 'message'}]
};
var rawData = [
{
type: 'docs',
datapoints: [
{
timestamp: 'time',
message: 'message'
}
]
}
];
beforeEach(() => {
table = TableModel.transform(rawData, panel);
});
it ('should return 2 columns', () => {
expect(table.columns.length).to.be(2);
expect(table.columns[0].text).to.be('timestamp');
expect(table.columns[1].text).to.be('message');
});
it ('should return 2 rows', () => {
expect(table.rows.length).to.be(2);
expect(table.rows[0][0]).to.be('time');
expect(table.rows[0][1]).to.be('message');
});
});
}); });
}); });
...@@ -9,7 +9,7 @@ transformers['timeseries_to_rows'] = { ...@@ -9,7 +9,7 @@ transformers['timeseries_to_rows'] = {
description: 'Time series to rows', description: 'Time series to rows',
transform: function(data, panel, model) { transform: function(data, panel, model) {
model.columns = [ model.columns = [
{text: 'Time'}, {text: 'Time', type: 'date'},
{text: 'Series'}, {text: 'Series'},
{text: 'Value'}, {text: 'Value'},
]; ];
...@@ -18,9 +18,7 @@ transformers['timeseries_to_rows'] = { ...@@ -18,9 +18,7 @@ transformers['timeseries_to_rows'] = {
var series = data[i]; var series = data[i];
for (var y = 0; y < series.datapoints.length; y++) { for (var y = 0; y < series.datapoints.length; y++) {
var dp = series.datapoints[y]; var dp = series.datapoints[y];
var time = moment(dp[1]).format('LLL'); model.rows.push([dp[1], series.target, dp[0]]);
var value = dp[0];
model.rows.push([time, series.target, value]);
} }
} }
}, },
...@@ -29,7 +27,7 @@ transformers['timeseries_to_rows'] = { ...@@ -29,7 +27,7 @@ transformers['timeseries_to_rows'] = {
transformers['timeseries_to_columns'] = { transformers['timeseries_to_columns'] = {
description: 'Time series to columns', description: 'Time series to columns',
transform: function(data, panel, model) { transform: function(data, panel, model) {
model.columns.push({text: 'Time'}); model.columns.push({text: 'Time', type: 'date'});
// group by time // group by time
var points = {}; var points = {};
...@@ -54,7 +52,7 @@ transformers['timeseries_to_columns'] = { ...@@ -54,7 +52,7 @@ transformers['timeseries_to_columns'] = {
for (var time in points) { for (var time in points) {
var point = points[time]; var point = points[time];
var values = [moment(point.time).format('LLL')]; var values = [point.time];
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
var value = point[i]; var value = point[i];
...@@ -71,17 +69,31 @@ transformers['annotations'] = { ...@@ -71,17 +69,31 @@ transformers['annotations'] = {
}; };
transformers['json'] = { transformers['json'] = {
description: 'JSON', description: 'JSON Data',
transform: function(data, panel, model) { transform: function(data, panel, model) {
model.columns.push({text: 'JSON'}); var i, y, z;
debugger; for (i = 0; i < panel.fields.length; i++) {
model.columns.push({text: panel.fields[i].name});
}
for (var i = 0; i < data.length; i++) { if (model.columns.length === 0) {
model.columns.push({text: 'JSON'});
}
for (i = 0; i < data.length; i++) {
var series = data[i]; var series = data[i];
for (var y = 0; y < series.datapoints.length; y++) { for (y = 0; y < series.datapoints.length; y++) {
var dp = series.datapoints[y]; var dp = series.datapoints[y];
model.rows.push([JSON.stringify(dp)]); var values = [];
for (z = 0; z < panel.fields.length; z++) {
values.push(dp[panel.fields[z].name]);
}
if (values.length === 0) {
values.push([JSON.stringify(dp)]);
}
model.rows.push(values);
} }
} }
} }
......
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