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

View File

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

View File

@ -128,7 +128,7 @@ describe('DashboardModel', () => {
}); });
it('dashboard schema version should be set to latest', () => { 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', () => { it('graph thresholds should be migrated', () => {

View File

@ -33,7 +33,7 @@ export class DashboardMigrator {
let i, j, k, n; let i, j, k, n;
const oldVersion = this.dashboard.schemaVersion; const oldVersion = this.dashboard.schemaVersion;
const panelUpgrades = []; const panelUpgrades = [];
this.dashboard.schemaVersion = 21; this.dashboard.schemaVersion = 22;
if (oldVersion === this.dashboard.schemaVersion) { if (oldVersion === this.dashboard.schemaVersion) {
return; 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) { if (panelUpgrades.length === 0) {
return; 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" <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> on-change="editor.render()"></gf-form-switch>
</div> </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'"> <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" <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> on-change="editor.render()"></gf-form-switch>

View File

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

View File

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

View File

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

View File

@ -40,9 +40,26 @@ describe('when rendering table', () => {
{ text: 'MappingColored' }, { text: 'MappingColored' },
{ text: 'RangeMappingColored' }, { text: 'RangeMappingColored' },
{ text: 'HiddenType' }, { text: 'HiddenType' },
{ text: 'RightAligned' },
]; ];
table.rows = [ 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 = { const panel = {
@ -185,13 +202,17 @@ describe('when rendering table', () => {
pattern: 'HiddenType', pattern: 'HiddenType',
type: 'hidden', type: 'hidden',
}, },
{
pattern: 'RightAligned',
align: 'right',
},
], ],
}; };
//@ts-ignore //@ts-ignore
const renderer = new TableRenderer(panel, table, 'utc', sanitize, templateSrv); 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); const html = renderer.renderCell(0, 0, 1388556366666);
expect(html).toBe('<td>2014-01-01T06:06:06Z</td>'); expect(html).toBe('<td>2014-01-01T06:06:06Z</td>');
}); });
@ -396,6 +417,11 @@ describe('when rendering table', () => {
expect(html).toBe(''); 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', () => { it('render_values should ignore hidden columns', () => {
renderer.render(0); // this computes the hidden markers on the columns renderer.render(0); // this computes the hidden markers on the columns
const { columns, rows } = renderer.render_values(); 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) { function normalize(str: string) {
return str.replace(/\s+/gm, ' ').trim(); return str.replace(/\s+/gm, ' ').trim();
} }

View File

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