Commit c14c7b68 by Jack Westbrook Committed by GitHub

Table: migrate old-table config to new table config (#30142)

* feat(tablepanel): add migration button to old table panel config

* feat(tablepanel): migrate old table transformations

* feat(tablepanel): migrate old styles to config overrides

* feat(tablepanel): migrate catch all style override to panel defaults

* refactor(tablepanel): clean up typings

* refactor(tablepanel): base threshold as -Infinity

* feat(tablepanel): migrate align to new table config overrides

* feat(tablepanel): migrate links to new table overrides

* refactor(tabelpanel): clean up threshold migrations

* feat(tablepanel): introduce table transform to merge

* feat(tablepanel): add note informing user to manually update links with cell values
parent 5088e204
<div class="gf-form-group"> <div class="gf-form-group">
<div class="grafana-info-box">
<h5>Table migration</h5>
<p>
This panel is deprecated. Please migrate to the new Table panel.
</p>
<p>
<button class="btn btn-primary" ng-click="ctrl.migrateToPanel('table')">
Migrate to Table panel
</button>
</p>
<p><b>NOTE:</b> Sorting is not persisted after migration.</p>
<p ng-if="ctrl.panelHasRowColorMode">
<b>NOTE:</b> Row color mode is no longer supported and will fallback to cell color mode.
</p>
<p ng-if="ctrl.panelHasLinks">
<b>NOTE:</b> Links that specify cell values will need to be updated manually after migration.
</p>
</div>
<h5 class="section-heading">Data</h5> <h5 class="section-heading">Data</h5>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<label class="gf-form-label width-8">Table Transform</label> <label class="gf-form-label width-8">Table Transform</label>
......
...@@ -18,6 +18,8 @@ export class TablePanelCtrl extends MetricsPanelCtrl { ...@@ -18,6 +18,8 @@ export class TablePanelCtrl extends MetricsPanelCtrl {
dataRaw: any; dataRaw: any;
table: any; table: any;
renderer: any; renderer: any;
panelHasRowColorMode: boolean;
panelHasLinks: boolean;
panelDefaults: any = { panelDefaults: any = {
targets: [{}], targets: [{}],
...@@ -65,6 +67,9 @@ export class TablePanelCtrl extends MetricsPanelCtrl { ...@@ -65,6 +67,9 @@ export class TablePanelCtrl extends MetricsPanelCtrl {
_.defaults(this.panel, this.panelDefaults); _.defaults(this.panel, this.panelDefaults);
this.panelHasRowColorMode = Boolean(this.panel.styles.find((style: any) => style.colorMode === 'row'));
this.panelHasLinks = Boolean(this.panel.styles.find((style: any) => style.link));
this.events.on(PanelEvents.dataReceived, this.onDataReceived.bind(this)); this.events.on(PanelEvents.dataReceived, this.onDataReceived.bind(this));
this.events.on(PanelEvents.dataSnapshotLoad, this.onDataReceived.bind(this)); this.events.on(PanelEvents.dataSnapshotLoad, this.onDataReceived.bind(this));
this.events.on(PanelEvents.editModeInitialized, this.onInitEditMode.bind(this)); this.events.on(PanelEvents.editModeInitialized, this.onInitEditMode.bind(this));
...@@ -75,6 +80,10 @@ export class TablePanelCtrl extends MetricsPanelCtrl { ...@@ -75,6 +80,10 @@ export class TablePanelCtrl extends MetricsPanelCtrl {
this.addEditorTab('Column Styles', columnOptionsTab, 3); this.addEditorTab('Column Styles', columnOptionsTab, 3);
} }
migrateToPanel(type: string) {
this.onPluginTypeChange(config.panels[type]);
}
issueQueries(datasource: any) { issueQueries(datasource: any) {
this.pageIndex = 0; this.pageIndex = 0;
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Table Migrations migrates styles to field config overrides and defaults 1`] = `
Object {
"fieldConfig": Object {
"defaults": Object {
"custom": Object {
"align": "right",
"displayMode": undefined,
},
"decimals": 2,
"displayName": "",
"unit": "short",
},
"overrides": Array [
Object {
"matcher": Object {
"id": "byName",
"options": "Time",
},
"properties": Array [
Object {
"id": "displayName",
"value": "Time",
},
Object {
"id": "unit",
"value": "time: YYYY-MM-DD HH:mm:ss",
},
Object {
"id": "custom.align",
"value": null,
},
],
},
Object {
"matcher": Object {
"id": "byName",
"options": "ColorCell",
},
"properties": Array [
Object {
"id": "unit",
"value": "currencyUSD",
},
Object {
"id": "decimals",
"value": 2,
},
Object {
"id": "custom.displayMode",
"value": "color-background",
},
Object {
"id": "custom.align",
"value": "left",
},
Object {
"id": "thresholds",
"value": Object {
"mode": "absolute",
"steps": Array [
Object {
"color": "rgba(245, 54, 54, 0.9)",
"value": -Infinity,
},
Object {
"color": "rgba(237, 129, 40, 0.89)",
"value": 5,
},
Object {
"color": "rgba(50, 172, 45, 0.97)",
"value": 10,
},
],
},
},
],
},
Object {
"matcher": Object {
"id": "byName",
"options": "ColorValue",
},
"properties": Array [
Object {
"id": "unit",
"value": "Bps",
},
Object {
"id": "decimals",
"value": 2,
},
Object {
"id": "links",
"value": Array [
Object {
"targetBlank": true,
"title": "",
"url": "http://www.grafana.com",
},
],
},
Object {
"id": "custom.displayMode",
"value": "color-text",
},
Object {
"id": "custom.align",
"value": null,
},
Object {
"id": "thresholds",
"value": Object {
"mode": "absolute",
"steps": Array [
Object {
"color": "rgba(245, 54, 54, 0.9)",
"value": -Infinity,
},
Object {
"color": "rgba(237, 129, 40, 0.89)",
"value": 5,
},
Object {
"color": "rgba(50, 172, 45, 0.97)",
"value": 10,
},
],
},
},
],
},
],
},
"transformations": Array [],
}
`;
exports[`Table Migrations migrates transform out to core transforms 1`] = `
Object {
"fieldConfig": Object {
"defaults": Object {
"custom": Object {},
},
"overrides": Array [],
},
"transformations": Array [
Object {
"id": "seriesToColumns",
"options": Object {
"reducers": Array [],
},
},
],
}
`;
exports[`Table Migrations migrates transform out to core transforms 2`] = `
Object {
"fieldConfig": Object {
"defaults": Object {
"custom": Object {},
},
"overrides": Array [],
},
"transformations": Array [
Object {
"id": "seriesToRows",
"options": Object {
"reducers": Array [],
},
},
],
}
`;
exports[`Table Migrations migrates transform out to core transforms 3`] = `
Object {
"fieldConfig": Object {
"defaults": Object {
"custom": Object {},
},
"overrides": Array [],
},
"transformations": Array [
Object {
"id": "reduce",
"options": Object {
"includeTimeField": false,
"reducers": Array [
"mean",
"max",
"last",
],
},
},
],
}
`;
exports[`Table Migrations migrates transform out to core transforms 4`] = `
Object {
"fieldConfig": Object {
"defaults": Object {
"custom": Object {},
},
"overrides": Array [],
},
"transformations": Array [
Object {
"id": "merge",
"options": Object {
"reducers": Array [],
},
},
],
}
`;
import { PanelModel } from '@grafana/data';
import { tablePanelChangedHandler } from './migrations';
describe('Table Migrations', () => {
it('migrates transform out to core transforms', () => {
const toColumns = {
angular: {
columns: [],
styles: [],
transform: 'timeseries_to_columns',
options: {},
},
};
const toRows = {
angular: {
columns: [],
styles: [],
transform: 'timeseries_to_rows',
options: {},
},
};
const aggregations = {
angular: {
columns: [
{
text: 'Avg',
value: 'avg',
$$hashKey: 'object:82',
},
{
text: 'Max',
value: 'max',
$$hashKey: 'object:83',
},
{
text: 'Current',
value: 'current',
$$hashKey: 'object:84',
},
],
styles: [],
transform: 'timeseries_aggregations',
options: {},
},
};
const table = {
angular: {
columns: [],
styles: [],
transform: 'table',
options: {},
},
};
const columnsPanel = {} as PanelModel;
tablePanelChangedHandler(columnsPanel, 'table-old', toColumns);
expect(columnsPanel).toMatchSnapshot();
const rowsPanel = {} as PanelModel;
tablePanelChangedHandler(rowsPanel, 'table-old', toRows);
expect(rowsPanel).toMatchSnapshot();
const aggregationsPanel = {} as PanelModel;
tablePanelChangedHandler(aggregationsPanel, 'table-old', aggregations);
expect(aggregationsPanel).toMatchSnapshot();
const tablePanel = {} as PanelModel;
tablePanelChangedHandler(tablePanel, 'table-old', table);
expect(tablePanel).toMatchSnapshot();
});
it('migrates styles to field config overrides and defaults', () => {
const oldStyles = {
angular: {
columns: [],
styles: [
{
alias: 'Time',
align: 'auto',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
pattern: 'Time',
type: 'date',
$$hashKey: 'object:195',
},
{
alias: '',
align: 'left',
colorMode: 'cell',
colors: ['rgba(245, 54, 54, 0.9)', 'rgba(237, 129, 40, 0.89)', 'rgba(50, 172, 45, 0.97)'],
dateFormat: 'YYYY-MM-DD HH:mm:ss',
decimals: 2,
mappingType: 1,
pattern: 'ColorCell',
thresholds: ['5', '10'],
type: 'number',
unit: 'currencyUSD',
$$hashKey: 'object:196',
},
{
alias: '',
align: 'auto',
colorMode: 'value',
colors: ['rgba(245, 54, 54, 0.9)', 'rgba(237, 129, 40, 0.89)', 'rgba(50, 172, 45, 0.97)'],
dateFormat: 'YYYY-MM-DD HH:mm:ss',
decimals: 2,
link: true,
linkTargetBlank: true,
linkTooltip: '',
linkUrl: 'http://www.grafana.com',
mappingType: 1,
pattern: 'ColorValue',
thresholds: ['5', '10'],
type: 'number',
unit: 'Bps',
$$hashKey: 'object:197',
},
{
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: '/.*/',
thresholds: [],
align: 'right',
},
],
},
};
const panel = {} as PanelModel;
tablePanelChangedHandler(panel, 'table-old', oldStyles);
expect(panel).toMatchSnapshot();
});
});
import { PanelModel } from '@grafana/data'; import {
PanelModel,
FieldMatcherID,
ConfigOverrideRule,
ThresholdsMode,
ThresholdsConfig,
FieldConfig,
} from '@grafana/data';
import { ReduceTransformerOptions } from '@grafana/data/src/transformations/transformers/reduce';
import omitBy from 'lodash/omitBy';
import isNil from 'lodash/isNil';
import isNumber from 'lodash/isNumber';
import defaultTo from 'lodash/defaultTo';
import { Options } from './types'; import { Options } from './types';
/** /**
...@@ -16,6 +28,195 @@ export const tableMigrationHandler = (panel: PanelModel<Options>): Partial<Optio ...@@ -16,6 +28,195 @@ export const tableMigrationHandler = (panel: PanelModel<Options>): Partial<Optio
return panel.options; return panel.options;
}; };
const transformsMap = {
timeseries_to_rows: 'seriesToRows',
timeseries_to_columns: 'seriesToColumns',
timeseries_aggregations: 'reduce',
table: 'merge',
};
const columnsMap = {
avg: 'mean',
min: 'min',
max: 'max',
total: 'sum',
current: 'last',
count: 'count',
};
const colorModeMap = {
cell: 'color-background',
row: 'color-background',
value: 'color-text',
};
type Transformations = keyof typeof transformsMap;
type Transformation = {
id: string;
options: ReduceTransformerOptions;
};
type Columns = keyof typeof columnsMap;
type Column = {
value: Columns;
text: string;
};
type ColorModes = keyof typeof colorModeMap;
const generateThresholds = (thresholds: string[], colors: string[]) => {
return [-Infinity, ...thresholds].map((threshold, idx) => ({
color: colors[idx],
value: isNumber(threshold) ? threshold : parseInt(threshold, 10),
}));
};
const migrateTransformations = (
panel: PanelModel<Partial<Options>> | any,
oldOpts: { columns: any; transform: Transformations }
) => {
const transformations: Transformation[] = panel.transformations ?? [];
if (Object.keys(transformsMap).includes(oldOpts.transform)) {
const opts: ReduceTransformerOptions = {
reducers: [],
};
if (oldOpts.transform === 'timeseries_aggregations') {
opts.includeTimeField = false;
opts.reducers = oldOpts.columns.map((column: Column) => columnsMap[column.value]);
}
transformations.push({
id: transformsMap[oldOpts.transform],
options: opts,
});
}
return transformations;
};
type Style = {
unit: string;
type: string;
alias: string;
decimals: number;
colors: string[];
colorMode: ColorModes;
pattern: string;
thresholds: string[];
align?: string;
dateFormat: string;
link: boolean;
linkTargetBlank?: boolean;
linkTooltip?: string;
linkUrl?: string;
};
const migrateTableStyleToOverride = (style: Style) => {
const fieldMatcherId = /^\/.*\/$/.test(style.pattern) ? FieldMatcherID.byRegexp : FieldMatcherID.byName;
const override: ConfigOverrideRule = {
matcher: {
id: fieldMatcherId,
options: style.pattern,
},
properties: [],
};
if (style.alias) {
override.properties.push({
id: 'displayName',
value: style.alias,
});
}
if (style.unit) {
override.properties.push({
id: 'unit',
value: style.unit,
});
}
if (style.decimals) {
override.properties.push({
id: 'decimals',
value: style.decimals,
});
}
if (style.type === 'date') {
override.properties.push({
id: 'unit',
value: `time: ${style.dateFormat}`,
});
}
if (style.link) {
override.properties.push({
id: 'links',
value: [
{
title: defaultTo(style.linkTooltip, ''),
url: defaultTo(style.linkUrl, ''),
targetBlank: defaultTo(style.linkTargetBlank, false),
},
],
});
}
if (style.colorMode) {
override.properties.push({
id: 'custom.displayMode',
value: colorModeMap[style.colorMode],
});
}
if (style.align) {
override.properties.push({
id: 'custom.align',
value: style.align === 'auto' ? null : style.align,
});
}
if (style.thresholds?.length) {
override.properties.push({
id: 'thresholds',
value: {
mode: ThresholdsMode.Absolute,
steps: generateThresholds(style.thresholds, style.colors),
},
});
}
return override;
};
const migrateDefaults = (prevDefaults: Style) => {
let defaults: FieldConfig = {
custom: {},
};
if (prevDefaults) {
defaults = omitBy(
{
unit: prevDefaults.unit,
decimals: prevDefaults.decimals,
displayName: prevDefaults.alias,
custom: {
align: prevDefaults.align === 'auto' ? null : prevDefaults.align,
displayMode: colorModeMap[prevDefaults.colorMode],
},
},
isNil
);
if (prevDefaults.thresholds.length) {
const thresholds: ThresholdsConfig = {
mode: ThresholdsMode.Absolute,
steps: generateThresholds(prevDefaults.thresholds, prevDefaults.colors),
};
defaults.thresholds = thresholds;
}
}
return defaults;
};
/** /**
* This is called when the panel changes from another panel * This is called when the panel changes from another panel
*/ */
...@@ -26,7 +227,17 @@ export const tablePanelChangedHandler = ( ...@@ -26,7 +227,17 @@ export const tablePanelChangedHandler = (
) => { ) => {
// Changing from angular table panel // Changing from angular table panel
if (prevPluginId === 'table-old' && prevOptions.angular) { if (prevPluginId === 'table-old' && prevOptions.angular) {
// Todo write migration logic const oldOpts = prevOptions.angular;
const transformations = migrateTransformations(panel, oldOpts);
const prevDefaults = oldOpts.styles.find((style: any) => style.pattern === '/.*/');
const defaults = migrateDefaults(prevDefaults);
const overrides = oldOpts.styles.filter((style: any) => style.pattern !== '/.*/').map(migrateTableStyleToOverride);
panel.transformations = transformations;
panel.fieldConfig = {
defaults,
overrides,
};
} }
return {}; return {};
......
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