Table: Added text align option to column styles (#21175)

* Added text align option to column styles, tests

* table panel migrations

* added tests

* default column style is now auto

* tests and fixtures...

* wrong comments need removing

* xss guard

* test

* Some test for invalid options, formatting.

* Remote branch tracking.
This commit is contained in:
teodoryantcheff 2020-01-09 17:01:12 +02:00 committed by Torkel Ödegaard
parent 7784b519a0
commit 0bf9e9bc28
10 changed files with 168 additions and 22 deletions

View File

@ -78,7 +78,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 21,
"schemaVersion": 22,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
@ -191,7 +191,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 21,
"schemaVersion": 22,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
@ -283,7 +283,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 21,
"schemaVersion": 22,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
@ -407,7 +407,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 21,
"schemaVersion": 22,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
@ -518,7 +518,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 21,
"schemaVersion": 22,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
@ -613,7 +613,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 21,
"schemaVersion": 22,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
@ -705,7 +705,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 21,
"schemaVersion": 22,
"snapshot": undefined,
"style": "dark",
"tags": Array [],

View File

@ -232,7 +232,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 21,
"schemaVersion": 22,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
@ -469,7 +469,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 21,
"schemaVersion": 22,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
@ -706,7 +706,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 21,
"schemaVersion": 22,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
@ -943,7 +943,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 21,
"schemaVersion": 22,
"snapshot": undefined,
"style": "dark",
"tags": Array [],

View File

@ -128,7 +128,7 @@ describe('DashboardModel', () => {
});
it('dashboard schema version should be set to latest', () => {
expect(model.schemaVersion).toBe(21);
expect(model.schemaVersion).toBe(22);
});
it('graph thresholds should be migrated', () => {

View File

@ -33,7 +33,7 @@ export class DashboardMigrator {
let i, j, k, n;
const oldVersion = this.dashboard.schemaVersion;
const panelUpgrades = [];
this.dashboard.schemaVersion = 21;
this.dashboard.schemaVersion = 22;
if (oldVersion === this.dashboard.schemaVersion) {
return;
@ -487,6 +487,18 @@ export class DashboardMigrator {
});
}
if (oldVersion < 22) {
panelUpgrades.push((panel: any) => {
if (panel.type !== 'table') {
return;
}
_.each(panel.styles, style => {
style.align = 'auto';
});
});
}
if (panelUpgrades.length === 0) {
return;
}

View File

@ -36,6 +36,20 @@
<gf-form-switch class="gf-form" label-class="width-10" ng-if="style.type === 'string'" label="Sanitize HTML" checked="style.sanitize"
on-change="editor.render()"></gf-form-switch>
</div>
<div ng-if="style.type !== 'hidden'">
<div class="gf-form">
<label class="gf-form-label width-10">Align</label>
<gf-form-dropdown model="style.align"
css-class="gf-form-input width-16"
lookup-text="true"
get-options="editor.alignTypes"
allow-custom="false"
on-change="editor.render()"
allow-custom="false"/>
</div>
</div>
<div ng-if="style.type === 'string'">
<gf-form-switch class="gf-form" label-class="width-10" ng-if="style.type === 'string'" label="Preserve Formatting" checked="style.preserveFormat"
on-change="editor.render()"></gf-form-switch>

View File

@ -15,6 +15,14 @@ export class ColumnOptionsCtrl {
activeStyleIndex: number;
mappingTypes: any;
alignTypes: any;
static readonly alignTypesEnum = [
{ text: 'auto', value: '' },
{ text: 'left', value: 'left' },
{ text: 'center', value: 'center' },
{ text: 'right', value: 'right' },
];
/** @ngInject */
constructor($scope: any) {
$scope.editor = this;
@ -47,6 +55,7 @@ export class ColumnOptionsCtrl {
{ text: 'Value to text', value: 1 },
{ text: 'Range to text', value: 2 },
];
this.alignTypes = ColumnOptionsCtrl.alignTypesEnum;
this.getColumnNames = () => {
if (!this.panelCtrl.table) {
@ -81,6 +90,7 @@ export class ColumnOptionsCtrl {
dateFormat: 'YYYY-MM-DD HH:mm:ss',
thresholds: [],
mappingType: 1,
align: 'auto',
};
const styles = this.panel.styles;

View File

@ -30,6 +30,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
pattern: 'Time',
alias: 'Time',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
align: 'auto',
},
{
unit: 'short',
@ -40,6 +41,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
colorMode: null,
pattern: '/.*/',
thresholds: [],
align: 'right',
},
],
columns: [],

View File

@ -13,6 +13,7 @@ import {
} from '@grafana/data';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { ColumnRender, TableRenderModel, ColumnStyle } from './types';
import { ColumnOptionsCtrl } from './column_options';
export class TableRenderer {
formatters: any[];
@ -243,17 +244,17 @@ export class TableRenderer {
value = this.formatColumnValue(columnIndex, value);
const column = this.table.columns[columnIndex];
const cellStyles = [];
let cellStyle = '';
let textStyle = '';
const cellClasses = [];
let cellClass = '';
if (this.colorState.cell) {
cellStyle = ' style="background-color:' + this.colorState.cell + '"';
cellStyles.push('background-color:' + this.colorState.cell);
cellClasses.push('table-panel-color-cell');
this.colorState.cell = null;
} else if (this.colorState.value) {
textStyle = ' style="color:' + this.colorState.value + '"';
cellStyles.push('color:' + this.colorState.value);
this.colorState.value = null;
}
// because of the fixed table headers css only solution
@ -265,7 +266,7 @@ export class TableRenderer {
}
if (value === undefined) {
cellStyle = ' style="display:none;"';
cellStyles.push('display:none');
column.hidden = true;
} else {
column.hidden = false;
@ -279,6 +280,17 @@ export class TableRenderer {
cellClasses.push('table-panel-cell-pre');
}
if (column.style && column.style.align) {
const textAlign = _.find(ColumnOptionsCtrl.alignTypesEnum, ['text', column.style.align]);
if (textAlign && textAlign['value']) {
cellStyles.push(`text-align:${textAlign['value']}`);
}
}
if (cellStyles.length) {
cellStyle = ' style="' + cellStyles.join(';') + '"';
}
if (column.style && column.style.link) {
// Render cell as link
const scopedVars = this.renderRowVariables(rowIndex);
@ -291,7 +303,7 @@ export class TableRenderer {
cellClasses.push('table-panel-cell-link');
columnHtml += `
<a href="${cellLink}" target="${cellTarget}" data-link-tooltip data-original-title="${cellLinkTooltip}" data-placement="right"${textStyle}>
<a href="${cellLink}" target="${cellTarget}" data-link-tooltip data-original-title="${cellLinkTooltip}" data-placement="right"${cellStyle}>
${value}
</a>
`;
@ -316,7 +328,7 @@ export class TableRenderer {
cellClass = ' class="' + cellClasses.join(' ') + '"';
}
columnHtml = '<td' + cellClass + cellStyle + textStyle + '>' + columnHtml + '</td>';
columnHtml = '<td' + cellClass + cellStyle + '>' + columnHtml + '</td>';
return columnHtml;
}

View File

@ -40,9 +40,26 @@ describe('when rendering table', () => {
{ text: 'MappingColored' },
{ text: 'RangeMappingColored' },
{ text: 'HiddenType' },
{ text: 'RightAligned' },
];
table.rows = [
[1388556366666, 1230, 40, undefined, '', '', 'my.host.com', 'host1', ['value1', 'value2'], 1, 2, 1, 2, 'ignored'],
[
1388556366666,
1230,
40,
undefined,
'',
'',
'my.host.com',
'host1',
['value1', 'value2'],
1,
2,
1,
2,
'ignored',
42,
],
];
const panel = {
@ -185,13 +202,17 @@ describe('when rendering table', () => {
pattern: 'HiddenType',
type: 'hidden',
},
{
pattern: 'RightAligned',
align: 'right',
},
],
};
//@ts-ignore
const renderer = new TableRenderer(panel, table, 'utc', sanitize, templateSrv);
it('time column should be formated', () => {
it('time column should be formatted', () => {
const html = renderer.renderCell(0, 0, 1388556366666);
expect(html).toBe('<td>2014-01-01T06:06:06Z</td>');
});
@ -396,6 +417,11 @@ describe('when rendering table', () => {
expect(html).toBe('');
});
it('right aligned column should have correct text-align style', () => {
const html = renderer.renderCell(14, 0, 42);
expect(html).toBe('<td style="text-align:right">42</td>');
});
it('render_values should ignore hidden columns', () => {
renderer.render(0); // this computes the hidden markers on the columns
const { columns, rows } = renderer.render_values();
@ -451,6 +477,76 @@ describe('when rendering table with different patterns', () => {
);
});
describe('when rendering cells with different alignment options', () => {
const cases = [
//align, preserve fmt, color mode, expected
['', false, null, '<td>42</td>'],
['invalid_option', false, null, '<td>42</td>'],
['alert("no xss");', false, null, '<td>42</td>'],
['auto', false, null, '<td>42</td>'],
['justify', false, null, '<td>42</td>'],
['auto', true, null, '<td class="table-panel-cell-pre">42</td>'],
['left', false, null, '<td style="text-align:left">42</td>'],
['left', true, null, '<td class="table-panel-cell-pre" style="text-align:left">42</td>'],
['center', false, null, '<td style="text-align:center">42</td>'],
[
'center',
true,
'cell',
'<td class="table-panel-color-cell table-panel-cell-pre" style="background-color:rgba(50, 172, 45, 0.97);text-align:center">42</td>',
],
[
'right',
false,
'cell',
'<td class="table-panel-color-cell" style="background-color:rgba(50, 172, 45, 0.97);text-align:right">42</td>',
],
[
'right',
true,
'cell',
'<td class="table-panel-color-cell table-panel-cell-pre" style="background-color:rgba(50, 172, 45, 0.97);text-align:right">42</td>',
],
];
it.each(cases)(
'align option:"%s", preformatted:%s columns should be formatted with correct style',
(align: string, preserveFormat: boolean, colorMode, expected: string) => {
const table = new TableModel();
table.columns = [{ text: 'Time' }, { text: align }];
table.rows = [[0, 42]];
const panel = {
pageSize: 10,
styles: [
{
pattern: 'Time',
type: 'date',
format: 'LLL',
alias: 'Timestamp',
},
{
pattern: `/${align}/`,
align: align,
type: 'number',
unit: 'none',
preserveFormat: preserveFormat,
colorMode: colorMode,
thresholds: [1, 2],
colors: ['rgba(245, 54, 54, 0.9)', 'rgba(237, 129, 40, 0.89)', 'rgba(50, 172, 45, 0.97)'],
},
],
};
//@ts-ignore
const renderer = new TableRenderer(panel, table, 'utc', sanitize, templateSrv);
const html = renderer.renderCell(1, 0, 42);
expect(html).toBe(expected);
}
);
});
function normalize(str: string) {
return str.replace(/\s+/gm, ' ').trim();
}

View File

@ -33,7 +33,7 @@ export interface ColumnStyle {
mappingType?: any;
valueMaps?: any;
rangeMaps?: any;
align?: 'auto' | 'left' | 'center' | 'right';
link?: any;
linkUrl?: any;
linkTooltip?: any;