diff --git a/public/app/plugins/panel/table-old/module.ts b/public/app/plugins/panel/table-old/module.ts
index 2b3660c99d5..37bcba40e87 100644
--- a/public/app/plugins/panel/table-old/module.ts
+++ b/public/app/plugins/panel/table-old/module.ts
@@ -18,6 +18,8 @@ export class TablePanelCtrl extends MetricsPanelCtrl {
dataRaw: any;
table: any;
renderer: any;
+ panelHasRowColorMode: boolean;
+ panelHasLinks: boolean;
panelDefaults: any = {
targets: [{}],
@@ -65,6 +67,9 @@ export class TablePanelCtrl extends MetricsPanelCtrl {
_.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.dataSnapshotLoad, this.onDataReceived.bind(this));
this.events.on(PanelEvents.editModeInitialized, this.onInitEditMode.bind(this));
@@ -75,6 +80,10 @@ export class TablePanelCtrl extends MetricsPanelCtrl {
this.addEditorTab('Column Styles', columnOptionsTab, 3);
}
+ migrateToPanel(type: string) {
+ this.onPluginTypeChange(config.panels[type]);
+ }
+
issueQueries(datasource: any) {
this.pageIndex = 0;
diff --git a/public/app/plugins/panel/table/__snapshots__/migrations.test.ts.snap b/public/app/plugins/panel/table/__snapshots__/migrations.test.ts.snap
new file mode 100644
index 00000000000..992a95e9554
--- /dev/null
+++ b/public/app/plugins/panel/table/__snapshots__/migrations.test.ts.snap
@@ -0,0 +1,219 @@
+// 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 [],
+ },
+ },
+ ],
+}
+`;
diff --git a/public/app/plugins/panel/table/migrations.test.ts b/public/app/plugins/panel/table/migrations.test.ts
new file mode 100644
index 00000000000..2f767d0f95d
--- /dev/null
+++ b/public/app/plugins/panel/table/migrations.test.ts
@@ -0,0 +1,132 @@
+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();
+ });
+});
diff --git a/public/app/plugins/panel/table/migrations.ts b/public/app/plugins/panel/table/migrations.ts
index 557b819a9d2..dc2fac31529 100644
--- a/public/app/plugins/panel/table/migrations.ts
+++ b/public/app/plugins/panel/table/migrations.ts
@@ -1,4 +1,16 @@
-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';
/**
@@ -16,6 +28,195 @@ export const tableMigrationHandler = (panel: PanelModel
): Partial {
+ return [-Infinity, ...thresholds].map((threshold, idx) => ({
+ color: colors[idx],
+ value: isNumber(threshold) ? threshold : parseInt(threshold, 10),
+ }));
+};
+
+const migrateTransformations = (
+ panel: PanelModel> | 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
*/
@@ -26,7 +227,17 @@ export const tablePanelChangedHandler = (
) => {
// Changing from angular table panel
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 {};