Merge branch 'master' of github.com:grafana/grafana

This commit is contained in:
Torkel Ödegaard
2017-05-09 08:33:43 +02:00
14 changed files with 233 additions and 160 deletions

View File

@@ -77,7 +77,7 @@ function (_, $, coreModule) {
$scope.source = function(query, callback) {
$scope.$apply(function() {
$scope.getOptions({ measurementFilter: query }).then(function(altSegments) {
$scope.getOptions({ $query: query }).then(function(altSegments) {
$scope.altSegments = altSegments;
options = _.map($scope.altSegments, function(alt) { return alt.value; });

View File

@@ -53,7 +53,7 @@ export function exportTableDataToCsv(table) {
var text = 'sep=;\n';
// add header
_.each(table.columns, function(column) {
text += column.text + ';';
text += (column.title || column.text) + ';';
});
text += '\n';
// process data

View File

@@ -119,7 +119,7 @@ function (queryDef) {
}
query.script_fields = {},
query.fielddata_fields = [this.timeField];
query.docvalue_fields = [this.timeField];
return query;
};

View File

@@ -11,7 +11,7 @@
<label class="gf-form-label query-keyword width-7">FROM</label>
<metric-segment segment="ctrl.policySegment" get-options="ctrl.getPolicySegments()" on-change="ctrl.policyChanged()"></metric-segment>
<metric-segment segment="ctrl.measurementSegment" get-options="ctrl.getMeasurements(measurementFilter)" on-change="ctrl.measurementChanged()"></metric-segment>
<metric-segment segment="ctrl.measurementSegment" get-options="ctrl.getMeasurements($query)" on-change="ctrl.measurementChanged()"></metric-segment>
</div>
<div class="gf-form">

View File

@@ -81,8 +81,8 @@ describe('grafanaHeatmap', function () {
getTimezone: sinon.stub().returns('utc')
},
range: {
from: moment.utc("01 Mar 2017 10:00:00"),
to: moment.utc("01 Mar 2017 11:00:00"),
from: moment.utc("01 Mar 2017 10:00:00", 'DD MMM YYYY HH:mm:ss'),
to: moment.utc("01 Mar 2017 11:00:00", 'DD MMM YYYY HH:mm:ss'),
},
};
@@ -263,5 +263,5 @@ function getTicks(element, axisSelector) {
function formatLocalTime(timeStr) {
let format = "HH:mm";
return moment.utc(timeStr).local().format(format);
return moment.utc(timeStr, 'DD MMM YYYY HH:mm:ss').local().format(format);
}

View File

@@ -29,112 +29,122 @@
<div class="section gf-form-group">
<h5 class="section-heading">Table Display</h5>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-8">Rows per page</label>
<input type="number" class="gf-form-input width-6"
placeholder="100" data-placement="right"
ng-model="editor.panel.pageSize"
ng-change="editor.render()"
ng-model-onblur>
</div>
<gf-form-switch class="gf-form" label-class="width-4" label="Scroll" checked="editor.panel.scroll" on-change="editor.render()"></gf-form-switch>
<div class="gf-form max-width-17">
<label class="gf-form-label width-6">Font size</label>
<div class="gf-form-select-wrapper max-width-15">
<select class="gf-form-input"
ng-model="editor.panel.fontSize"
ng-options="f for f in editor.fontSizes"
ng-change="editor.render()"></select>
</div>
<div class="gf-form">
<label class="gf-form-label width-8">Rows per page</label>
<input type="number" class="gf-form-input width-6"
placeholder="100" data-placement="right"
ng-model="editor.panel.pageSize"
ng-change="editor.render()"
ng-model-onblur>
</div>
<gf-form-switch class="gf-form" label-class="width-8" switch-class="max-width-6" label="Scroll" checked="editor.panel.scroll" on-change="editor.render()"></gf-form-switch>
<div class="gf-form max-width-17">
<label class="gf-form-label width-8">Font size</label>
<div class="gf-form-select-wrapper width-6">
<select class="gf-form-input"
ng-model="editor.panel.fontSize"
ng-options="f for f in editor.fontSizes"
ng-change="editor.render()"></select>
</div>
</div>
</div>
</div>
<h5 class="section-heading">
<span style="padding-right: 10px;">Column Style Rules</span>
<button class="btn btn-secondary btn-small" ng-click="editor.addColumnStyle()">
<i class="fa fa-plus"></i>&nbsp;Add
</button>
</h5>
<div class="form-tabs-wrapper">
<ul class="gf-tabs">
<li class="gf-tabs-item" ng-repeat="style in editor.panel.styles">
<a class="gf-tabs-link" ng-click="editor.activeStyleIndex = $index" ng-class="{active: editor.activeStyleIndex === $index}">{{style.pattern || 'New rule'}}</a>
</li>
</ul>
</div>
<div class="form-tabs-content" ng-repeat="style in editor.panel.styles" ng-if="editor.activeStyleIndex === $index">
<div class="section gf-form-group">
<h5 class="section-heading">Options</h5>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-13">Apply to columns named</label>
<input type="text" placeholder="Name or regex" class="gf-form-input width-13" ng-model="style.pattern" bs-tooltip="'Specify regex using /my.*regex/ syntax'" bs-typeahead="editor.getColumnNames" ng-blur="editor.render()" data-min-length=0 data-items=100 ng-model-onblur data-placement="right">
</div>
</div>
<div class="gf-form" ng-if="style.type !== 'hidden'">
<label class="gf-form-label width-13">Column Header</label>
<input type="text" class="gf-form-input width-13" ng-model="style.alias" ng-change="editor.render()" ng-model-onblur placeholder="Override header label">
</div>
</div>
<div class="section gf-form-group">
<h5 class="section-heading">Type</h5>
<div class="gf-form">
<label class="gf-form-label width-8">Type</label>
<div class="gf-form-select-wrapper width-12">
<select class="gf-form-input" ng-model="style.type" ng-options="c.value as c.text for c in editor.columnTypes" ng-change="editor.render()"></select>
</div>
</div>
<div class="gf-form" ng-if="style.type === 'date'">
<label class="gf-form-label width-8">Date Format</label>
<metric-segment-model property="style.dateFormat" options="editor.dateFormats" on-change="editor.render()" custom="true"></metric-segment-model>
</div>
<div ng-if="style.type === 'string'">
<gf-form-switch class="gf-form" label-class="width-8" ng-if="style.type === 'string'" label="Sanitize HTML" checked="style.sanitize" change="editor.render()"></gf-form-switch>
</div>
<div ng-if="style.type === 'number'">
<div class="gf-form">
<label class="gf-form-label width-8">Unit</label>
<div class="gf-form-dropdown-typeahead width-12" ng-model="style.unit" dropdown-typeahead2="editor.unitFormats" dropdown-typeahead-on-select="editor.setUnitFormat(style, $subItem)"></div>
</div>
<div class="gf-form">
<label class="gf-form-label width-8">Decimals</label>
<input type="number" class="gf-form-input width-4" data-placement="right" ng-model="style.decimals" ng-change="editor.render()" ng-model-onblur>
</div>
</div>
</div>
<div class="section gf-form-group" ng-if="style.type === 'number'">
<h5 class="section-heading">Thresholds</h5>
<div class="gf-form">
<label class="gf-form-label width-8">Thresholds<tip>Comma separated values</tip></label>
<input type="text" class="gf-form-input width-10" ng-model="style.thresholds" placeholder="50,80" ng-blur="editor.render()" array-join ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label width-8">Color Mode</label>
<div class="gf-form-select-wrapper width-10">
<select class="gf-form-input" ng-model="style.colorMode" ng-options="c.value as c.text for c in editor.colorModes" ng-change="editor.render()"></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-8">Colors</label>
<span class="gf-form-label">
<spectrum-picker ng-model="style.colors[0]" ng-change="editor.render()"></spectrum-picker>
</span>
<span class="gf-form-label">
<spectrum-picker ng-model="style.colors[1]" ng-change="editor.render()"></spectrum-picker>
</span>
<span class="gf-form-label">
<spectrum-picker ng-model="style.colors[2]" ng-change="editor.render()"></spectrum-picker>
</span>
<div class="gf-form-label">
<a class="pointer" ng-click="editor.invertColorOrder($index)">Invert</a>
</div>
</div>
</div>
</div>
<div class="editor-row">
<div class="section gf-form-group">
<h5 class="section-heading">Column Styles</h5>
<div ng-repeat="style in editor.panel.styles">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label">Name or regex</label>
<input type="text" class="gf-form-input" ng-model="style.pattern" bs-typeahead="editor.getColumnNames" ng-blur="editor.render()" data-min-length=0 data-items=100 ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label">Type</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="style.type" ng-options="c.value as c.text for c in editor.columnTypes" ng-change="editor.render()"></select>
</div>
</div>
<div class="gf-form" ng-if="style.type === 'date'">
<label class="gf-form-label">Format</label>
<metric-segment-model property="style.dateFormat" options="editor.dateFormats" on-change="editor.render()" custom="true"></metric-segment-model>
</div>
<gf-form-switch class="gf-form" label-class="width-8" ng-if="style.type === 'string'" label="Sanitize HTML" checked="style.sanitize" change="editor.render()"></gf-form-switch>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
<div class="gf-form">
<label class="gf-form-label">
<a class="pointer" ng-click="editor.removeColumnStyle(style)">
<i class="fa fa-trash"></i>
</a>
</label>
</div>
</div>
<div class="gf-form-inline" ng-if="style.type === 'number'">
<div class="gf-form offset-width-8">
<label class="gf-form-label width-8">Unit</label>
</div>
<div class="gf-form">
<div class="gf-form-dropdown-typeahead" ng-model="style.unit" dropdown-typeahead2="editor.unitFormats" dropdown-typeahead-on-select="editor.setUnitFormat(style, $subItem)"></div>
</div>
<div class="gf-form">
<label class="gf-form-label">Decimals</label>
<input type="number" class="gf-form-input width-4" data-placement="right" ng-model="style.decimals" ng-change="editor.render()" ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label">Coloring</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="style.colorMode" ng-options="c.value as c.text for c in editor.colorModes" ng-change="editor.render()"></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="style.type === 'number'">
<div class="gf-form offset-width-8">
<label class="gf-form-label width-8">Thresholds<tip>Comma separated values</tip></label>
<input type="text" class="gf-form-input width-10" ng-model="style.thresholds" placeholder="50,80" ng-blur="editor.render()" array-join ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label width-5">Colors</label>
<span class="gf-form-label">
<spectrum-picker ng-model="style.colors[0]" ng-change="editor.render()"></spectrum-picker>
</span>
<span class="gf-form-label">
<spectrum-picker ng-model="style.colors[1]" ng-change="editor.render()"></spectrum-picker>
</span>
<span class="gf-form-label">
<spectrum-picker ng-model="style.colors[2]" ng-change="editor.render()"></spectrum-picker>
</span>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow">
<a class="pointer" ng-click="editor.invertColorOrder($index)">Invert</a>
</div>
</div>
</div>
</div>
</div>
<div class="gf-form-button">
<button class="btn btn-inverse" ng-click="editor.addColumnStyle()">
<i class="fa fa-plus"></i>&nbsp;Add column style rule
</button>
</div>
<div class="clearfix"></div>
<button class="btn btn-danger btn-small" ng-click="editor.removeColumnStyle(style)">
<i class="fa fa-trash"></i> Remove Rule
</button>
<br />
<br />
</div>

View File

@@ -21,10 +21,12 @@ export class TablePanelEditorCtrl {
addColumnSegment: any;
unitFormats: any;
getColumnNames: any;
activeStyleIndex: number;
/** @ngInject */
constructor($scope, private $q, private uiSegmentSrv) {
$scope.editor = this;
this.activeStyleIndex = 0;
this.panelCtrl = $scope.ctrl;
this.panel = this.panelCtrl.panel;
this.transformers = transformers;
@@ -104,18 +106,32 @@ export class TablePanelEditorCtrl {
}
addColumnStyle() {
var columnStyleDefaults = {
var newStyleRule = {
unit: 'short',
type: 'number',
alias: '',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
pattern: '/.*/',
pattern: '',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
thresholds: [],
};
this.panel.styles.push(angular.copy(columnStyleDefaults));
var styles = this.panel.styles;
var stylesCount = styles.length;
var indexToInsert = stylesCount;
// check if last is a catch all rule, then add it before that one
if (stylesCount > 0) {
var last = styles[stylesCount-1];
if (last.pattern === '/.*/') {
indexToInsert = stylesCount-1;
}
}
styles.splice(indexToInsert, 0, newStyleRule);
this.activeStyleIndex = indexToInsert;
}
removeColumnStyle(style) {

View File

@@ -7,7 +7,7 @@
<tr>
<th ng-repeat="col in ctrl.table.columns" ng-hide="col.hidden">
<div class="table-panel-table-header-inner pointer" ng-click="ctrl.toggleColumnSort(col, $index)">
{{col.text}}
{{col.title}}
<span class="table-panel-table-header-controls" ng-if="col.sort">
<i class="fa fa-caret-down" ng-show="col.desc"></i>
<i class="fa fa-caret-up" ng-hide="col.desc"></i>

View File

@@ -16,6 +16,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
pageIndex: number;
dataRaw: any;
table: any;
renderer: any;
panelDefaults = {
targets: [{}],
@@ -26,11 +27,13 @@ class TablePanelCtrl extends MetricsPanelCtrl {
{
type: 'date',
pattern: 'Time',
alias: 'Time',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
},
{
unit: 'short',
type: 'number',
alias: '',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
@@ -118,6 +121,9 @@ class TablePanelCtrl extends MetricsPanelCtrl {
render() {
this.table = transformDataToTable(this.dataRaw, this.panel);
this.table.sort(this.panel.sort);
this.renderer = new TableRenderer(this.panel, this.table, this.dashboard.isTimezoneUtc(), this.$sanitize);
return super.render(this.table);
}
@@ -141,8 +147,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
}
exportCsv() {
var renderer = new TableRenderer(this.panel, this.table, this.dashboard.isTimezoneUtc(), this.$sanitize);
FileExport.exportTableDataToCsv(renderer.render_values());
FileExport.exportTableDataToCsv(this.renderer.render_values());
}
link(scope, elem, attrs, ctrl) {
@@ -162,9 +167,9 @@ class TablePanelCtrl extends MetricsPanelCtrl {
}
function appendTableRows(tbodyElem) {
var renderer = new TableRenderer(panel, data, ctrl.dashboard.isTimezoneUtc(), ctrl.$sanitize);
ctrl.renderer.setTable(data);
tbodyElem.empty();
tbodyElem.html(renderer.render(ctrl.pageIndex));
tbodyElem.html(ctrl.renderer.render(ctrl.pageIndex));
}
function switchPage(e) {

View File

@@ -5,12 +5,44 @@ import moment from 'moment';
import kbn from 'app/core/utils/kbn';
export class TableRenderer {
formaters: any[];
formatters: any[];
colorState: any;
constructor(private panel, private table, private isUtc, private sanitize) {
this.formaters = [];
this.initColumns();
}
setTable(table) {
this.table = table;
this.initColumns();
}
initColumns() {
this.formatters = [];
this.colorState = {};
for (let colIndex = 0; colIndex < this.table.columns.length; colIndex++) {
let column = this.table.columns[colIndex];
column.title = column.text;
for (let i = 0; i < this.panel.styles.length; i++) {
let style = this.panel.styles[i];
var regex = kbn.stringToJsRegex(style.pattern);
if (column.text.match(regex)) {
column.style = style;
if (style.alias) {
column.title = column.text.replace(regex, style.alias);
}
break;
}
}
this.formatters[colIndex] = this.createColumnFormatter(column);
}
}
getColorForValue(value, style) {
@@ -24,7 +56,7 @@ export class TableRenderer {
return _.first(style.colors);
}
defaultCellFormater(v, style) {
defaultCellFormatter(v, style) {
if (v === null || v === void 0 || v === undefined) {
return '';
}
@@ -40,18 +72,18 @@ export class TableRenderer {
}
}
createColumnFormater(style, column) {
if (!style) {
return this.defaultCellFormater;
createColumnFormatter(column) {
if (!column.style) {
return this.defaultCellFormatter;
}
if (style.type === 'hidden') {
if (column.style.type === 'hidden') {
return v => {
return undefined;
};
}
if (style.type === 'date') {
if (column.style.type === 'date') {
return v => {
if (v === undefined || v === null) {
return '-';
@@ -62,12 +94,12 @@ export class TableRenderer {
if (this.isUtc) {
date = date.utc();
}
return date.format(style.dateFormat);
return date.format(column.style.dateFormat);
};
}
if (style.type === 'number') {
let valueFormater = kbn.valueFormats[column.unit || style.unit];
if (column.style.type === 'number') {
let valueFormatter = kbn.valueFormats[column.unit || column.style.unit];
return v => {
if (v === null || v === void 0) {
@@ -75,39 +107,24 @@ export class TableRenderer {
}
if (_.isString(v)) {
return this.defaultCellFormater(v, style);
return this.defaultCellFormatter(v, column.style);
}
if (style.colorMode) {
this.colorState[style.colorMode] = this.getColorForValue(v, style);
if (column.style.colorMode) {
this.colorState[column.style.colorMode] = this.getColorForValue(v, column.style);
}
return valueFormater(v, style.decimals, null);
return valueFormatter(v, column.style.decimals, null);
};
}
return (value) => {
return this.defaultCellFormater(value, style);
return this.defaultCellFormatter(value, column.style);
};
}
formatColumnValue(colIndex, value) {
if (this.formaters[colIndex]) {
return this.formaters[colIndex](value);
}
for (let i = 0; i < this.panel.styles.length; i++) {
let style = this.panel.styles[i];
let column = this.table.columns[colIndex];
var regex = kbn.stringToJsRegex(style.pattern);
if (column.text.match(regex)) {
this.formaters[colIndex] = this.createColumnFormater(style, column);
return this.formaters[colIndex](value);
}
}
this.formaters[colIndex] = this.defaultCellFormater;
return this.formaters[colIndex](value);
return this.formatters[colIndex] ? this.formatters[colIndex](value) : value;
}
renderCell(columnIndex, value, addWidthHack = false) {
@@ -126,7 +143,7 @@ export class TableRenderer {
// this hack adds header content to cell (not visible)
var widthHack = '';
if (addWidthHack) {
widthHack = '<div class="table-panel-width-hack">' + this.table.columns[columnIndex].text + '</div>';
widthHack = '<div class="table-panel-width-hack">' + this.table.columns[columnIndex].title + '</div>';
}
if (value === undefined) {

View File

@@ -22,13 +22,15 @@ describe('when rendering table', () => {
{
pattern: 'Time',
type: 'date',
format: 'LLL'
format: 'LLL',
alias: 'Timestamp'
},
{
pattern: 'Value',
pattern: '/(Val)ue/',
type: 'number',
unit: 'ms',
decimals: 3,
alias: '$1'
},
{
pattern: 'Colored',
@@ -132,6 +134,18 @@ describe('when rendering table', () => {
var html = renderer.renderCell(6, 'text <a href="http://google.com">link</a>');
expect(html).to.be('<td>sanitized</td>');
});
it('Time column title should be Timestamp', () => {
expect(table.columns[0].title).to.be('Timestamp');
});
it('Value column title should be Val', () => {
expect(table.columns[1].title).to.be('Val');
});
it('Colored column title should be Colored', () => {
expect(table.columns[2].title).to.be('Colored');
});
});
});

View File

@@ -229,7 +229,7 @@ function transformDataToTable(data, panel) {
var transformer = transformers[panel.transform];
if (!transformer) {
throw {message: 'Transformer ' + panel.transformer + ' not found'};
throw {message: 'Transformer ' + panel.transform + ' not found'};
}
if (panel.filterNull) {
@@ -239,6 +239,7 @@ function transformDataToTable(data, panel) {
}
transformer.transform(copyData, panel, model);
return model;
}

View File

@@ -68,3 +68,12 @@
top: 1px;
}
}
.form-tabs-wrapper {
@include brand-bottom-border();
@include clearfix();
}
.form-tabs-content {
padding: $spacer*2 $spacer;
}