Commit e8ae6fd0 by Ryan McKinley Committed by GitHub

GraphNG: simple settings migration from flot panel (#29599)

parent 3ce93050
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Graph Migrations simple bars 1`] = `
Object {
"fieldConfig": Object {
"defaults": Object {
"custom": Object {
"drawStyle": "bars",
"fillOpacity": 1,
"showPoints": "never",
"spanNulls": false,
},
},
"overrides": Array [],
},
"options": Object {
"graph": Object {},
"legend": Object {
"displayMode": "list",
"placement": "bottom",
},
"tooltipOptions": Object {
"mode": "single",
},
},
}
`;
exports[`Graph Migrations stairscase 1`] = `
Object {
"fieldConfig": Object {
"defaults": Object {
"custom": Object {
"axisPlacement": "hidden",
"drawStyle": "line",
"fillOpacity": 0.5,
"lineInterpolation": "stepAfter",
"pointSize": 2,
"showPoints": "never",
"spanNulls": true,
},
"displayName": "DISPLAY NAME",
"nullValueMode": "null",
"unit": "short",
},
"overrides": Array [],
},
"options": Object {
"graph": Object {},
"legend": Object {
"displayMode": "list",
"placement": "bottom",
},
"tooltipOptions": Object {
"mode": "single",
},
},
}
`;
exports[`Graph Migrations twoYAxis 1`] = `
Object {
"fieldConfig": Object {
"defaults": Object {
"custom": Object {
"axisLabel": "Y111",
"axisPlacement": "auto",
"drawStyle": "line",
"fillOpacity": 0.1,
"pointSize": 2,
"showPoints": "never",
"spanNulls": true,
},
"decimals": 3,
"max": 1000,
"min": 0,
"nullValueMode": "null",
"unit": "areaMI2",
},
"overrides": Array [
Object {
"matcher": Object {
"id": "byName",
"options": "B-series",
},
"properties": Array [
Object {
"id": "unit",
"value": "degree",
},
Object {
"id": "decimals",
"value": 2,
},
Object {
"id": "min",
"value": -10,
},
Object {
"id": "max",
"value": 25,
},
Object {
"id": "custom.axisLabel",
"value": "Y222",
},
],
},
],
},
"options": Object {
"graph": Object {},
"legend": Object {
"displayMode": "list",
"placement": "bottom",
},
"tooltipOptions": Object {
"mode": "single",
},
},
}
`;
import { PanelModel } from '@grafana/data';
import { graphPanelChangedHandler } from './migrations';
describe('Graph Migrations', () => {
it('simple bars', () => {
const old: any = {
angular: {
bars: true,
},
};
const panel = {} as PanelModel;
panel.options = graphPanelChangedHandler(panel, 'graph', old);
expect(panel).toMatchSnapshot();
});
it('stairscase', () => {
const old: any = {
angular: stairscase,
};
const panel = {} as PanelModel;
panel.options = graphPanelChangedHandler(panel, 'graph', old);
expect(panel).toMatchSnapshot();
});
it('twoYAxis', () => {
const old: any = {
angular: twoYAxis,
};
const panel = {} as PanelModel;
panel.options = graphPanelChangedHandler(panel, 'graph', old);
expect(panel).toMatchSnapshot();
});
});
const stairscase = {
fieldConfig: {
defaults: {
custom: {},
unit: 'areaF2',
displayName: 'DISPLAY NAME',
},
overrides: [],
},
aliasColors: {},
dashLength: 10,
fill: 5,
fillGradient: 6,
legend: {
avg: true,
current: true,
max: true,
min: true,
show: true,
total: true,
values: true,
alignAsTable: true,
},
lines: true,
linewidth: 1,
nullPointMode: 'null',
options: {
alertThreshold: true,
},
pointradius: 2,
seriesOverrides: [],
spaceLength: 10,
steppedLine: true,
thresholds: [],
timeRegions: [],
title: 'Panel Title',
tooltip: {
shared: true,
sort: 0,
value_type: 'individual',
},
type: 'graph',
xaxis: {
buckets: null,
mode: 'time',
name: null,
show: true,
values: [],
},
yaxes: [
{
$$hashKey: 'object:42',
format: 'short',
label: null,
logBase: 1,
max: null,
min: null,
show: false,
},
{
$$hashKey: 'object:43',
format: 'short',
label: null,
logBase: 1,
max: null,
min: null,
show: true,
},
],
yaxis: {
align: false,
alignLevel: null,
},
timeFrom: null,
timeShift: null,
bars: false,
dashes: false,
hiddenSeries: false,
percentage: false,
points: false,
stack: false,
decimals: 1,
datasource: null,
};
const twoYAxis = {
yaxes: [
{
label: 'Y111',
show: true,
logBase: 10,
min: '0',
max: '1000',
format: 'areaMI2',
$$hashKey: 'object:19',
decimals: 3,
},
{
label: 'Y222',
show: true,
logBase: 1,
min: '-10',
max: '25',
format: 'degree',
$$hashKey: 'object:20',
decimals: 2,
},
],
xaxis: {
show: true,
mode: 'time',
name: null,
values: [],
buckets: null,
},
yaxis: {
align: false,
alignLevel: null,
},
lines: true,
fill: 1,
linewidth: 1,
dashLength: 10,
spaceLength: 10,
pointradius: 2,
legend: {
show: true,
values: false,
min: false,
max: false,
current: false,
total: false,
avg: false,
},
nullPointMode: 'null',
tooltip: {
value_type: 'individual',
shared: true,
sort: 0,
},
aliasColors: {},
seriesOverrides: [
{
alias: 'B-series',
yaxis: 2,
},
],
thresholds: [],
timeRegions: [],
targets: [
{
refId: 'A',
},
{
refId: 'B',
},
],
fillGradient: 0,
dashes: false,
hiddenSeries: false,
points: false,
bars: false,
stack: false,
percentage: false,
steppedLine: false,
timeFrom: null,
timeShift: null,
datasource: null,
};
import {
FieldConfig,
FieldConfigSource,
NullValueMode,
PanelModel,
fieldReducers,
ConfigOverrideRule,
FieldMatcherID,
DynamicConfigValue,
} from '@grafana/data';
import { GraphFieldConfig, LegendDisplayMode } from '@grafana/ui';
import { AxisPlacement, DrawStyle, LineInterpolation, PointVisibility } from '@grafana/ui/src/components/uPlot/config';
import { Options } from './types';
import omitBy from 'lodash/omitBy';
import isNil from 'lodash/isNil';
import { isNumber, isString } from 'lodash';
/**
* This is called when the panel changes from another panel
*/
export const graphPanelChangedHandler = (
panel: PanelModel<Partial<Options>> | any,
prevPluginId: string,
prevOptions: any
) => {
// Changing from angular/flot panel to react/uPlot
if (prevPluginId === 'graph' && prevOptions.angular) {
const { fieldConfig, options } = flotToGraphOptions(prevOptions.angular);
panel.fieldConfig = fieldConfig; // Mutates the incoming panel
return options;
}
return {};
};
export function flotToGraphOptions(angular: any): { fieldConfig: FieldConfigSource; options: Options } {
const overrides: ConfigOverrideRule[] = angular.fieldConfig?.overrides ?? [];
const yaxes = angular.yaxes ?? [];
let y1 = getFieldConfigFromOldAxis(yaxes[0]);
if (angular.fieldConfig?.defaults) {
y1 = {
...angular.fieldConfig?.defaults,
...y1, // Keep the y-axis unit and custom
};
}
// "seriesOverrides": [
// {
// "$$hashKey": "object:183",
// "alias": "B-series",
// "fill": 3,
// "nullPointMode": "null as zero",
// "lines": true,
// "linewidth": 2
// }
// ],
if (angular.seriesOverrides?.length) {
for (const seriesOverride of angular.seriesOverrides) {
if (!seriesOverride.alias) {
continue; // the matcher config
}
const rule: ConfigOverrideRule = {
matcher: {
id: FieldMatcherID.byName,
options: seriesOverride.alias,
},
properties: [],
};
for (const p of Object.keys(seriesOverride)) {
const v = seriesOverride[p];
switch (p) {
// Ignore
case 'alias':
case '$$hashKey':
break;
// Link to y axis settings
case 'yaxis':
if (2 === v) {
const y2 = getFieldConfigFromOldAxis(yaxes[1]);
fillY2DynamicValues(y1, y2, rule.properties);
}
break;
case 'fill':
rule.properties.push({
id: 'custom.fillOpacity',
value: v / 10.0, // was 0-10
});
break;
case 'points':
rule.properties.push({
id: 'custom.showPoints',
value: v ? PointVisibility.Always : PointVisibility.Never,
});
break;
case 'bars':
if (v) {
rule.properties.push({
id: 'custom.drawStyle',
value: DrawStyle.Bars,
});
rule.properties.push({
id: 'custom.fillOpacity',
value: 1, // solid bars
});
} else {
rule.properties.push({
id: 'custom.drawStyle',
value: DrawStyle.Line, // Change from bars
});
}
break;
case 'lineWidth':
rule.properties.push({
id: 'custom.lineWidth',
value: v,
});
break;
case 'pointradius':
rule.properties.push({
id: 'custom.pointSize',
value: v,
});
break;
default:
console.log('Ignore override migration:', seriesOverride.alias, p, v);
}
}
if (rule.properties.length) {
overrides.push(rule);
}
}
}
const graph = y1.custom ?? ({} as GraphFieldConfig);
graph.drawStyle = angular.bars ? DrawStyle.Bars : angular.lines ? DrawStyle.Line : DrawStyle.Points;
if (angular.points) {
graph.showPoints = PointVisibility.Always;
} else if (graph.drawStyle !== DrawStyle.Points) {
graph.showPoints = PointVisibility.Never;
}
if (graph.drawStyle === DrawStyle.Bars) {
graph.fillOpacity = 1.0; // bars were always
}
graph.lineWidth = angular.lineWidth;
graph.pointSize = angular.pointradius;
if (isNumber(angular.fill)) {
graph.fillOpacity = angular.fill / 10; // fill is 0-10
}
graph.spanNulls = angular.nullPointMode === NullValueMode.Null;
if (angular.steppedLine) {
graph.lineInterpolation = LineInterpolation.StepAfter;
}
y1.custom = omitBy(graph, isNil);
y1.nullValueMode = angular.nullPointMode as NullValueMode;
const options: Options = {
graph: {},
legend: {
displayMode: LegendDisplayMode.List,
placement: 'bottom',
},
tooltipOptions: {
mode: 'single',
},
};
if (angular.legend?.values) {
const show = getReducersFromLegend(angular.legend?.values);
console.log('Migrate Legend', show);
}
return {
fieldConfig: {
defaults: omitBy(y1, isNil),
overrides,
},
options,
};
}
// {
// "label": "Y111",
// "show": true,
// "logBase": 10,
// "min": "0",
// "max": "1000",
// "format": "areaMI2",
// "$$hashKey": "object:19",
// "decimals": 3
// },
function getFieldConfigFromOldAxis(obj: any): FieldConfig<GraphFieldConfig> {
if (!obj) {
return {};
}
const graph: GraphFieldConfig = {
axisPlacement: obj.show ? AxisPlacement.Auto : AxisPlacement.Hidden,
};
if (obj.label) {
graph.axisLabel = obj.label;
}
return omitBy(
{
unit: obj.format,
decimals: validNumber(obj.decimals),
min: validNumber(obj.min),
max: validNumber(obj.max),
custom: graph,
},
isNil
);
}
function fillY2DynamicValues(
y1: FieldConfig<GraphFieldConfig>,
y2: FieldConfig<GraphFieldConfig>,
props: DynamicConfigValue[]
) {
// The standard properties
for (const key of Object.keys(y2)) {
const value = (y2 as any)[key];
if (key !== 'custom' && value !== (y1 as any)[key]) {
props.push({
id: key,
value,
});
}
}
// Add any custom property
const y1G = y1.custom ?? {};
const y2G = y2.custom ?? {};
for (const key of Object.keys(y2G)) {
const value = (y2G as any)[key];
if (value !== (y1G as any)[key]) {
props.push({
id: `custom.${key}`,
value,
});
}
}
}
function validNumber(val: any): number | undefined {
if (isNumber(val)) {
return val;
}
if (isString(val)) {
const num = Number(val);
if (!isNaN(num)) {
return num;
}
}
return undefined;
}
function getReducersFromLegend(obj: Record<string, any>): string[] {
const ids: string[] = [];
for (const key of Object.keys(obj)) {
const r = fieldReducers.getIfExists(key);
if (r) {
ids.push(r.id);
}
}
return ids;
}
...@@ -8,9 +8,11 @@ import { ...@@ -8,9 +8,11 @@ import {
graphFieldOptions, graphFieldOptions,
} from '@grafana/ui/src/components/uPlot/config'; } from '@grafana/ui/src/components/uPlot/config';
import { GraphPanel } from './GraphPanel'; import { GraphPanel } from './GraphPanel';
import { graphPanelChangedHandler } from './migrations';
import { Options } from './types'; import { Options } from './types';
export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel) export const plugin = new PanelPlugin<Options, GraphFieldConfig>(GraphPanel)
.setPanelChangeHandler(graphPanelChangedHandler)
.useFieldConfig({ .useFieldConfig({
standardOptions: { standardOptions: {
[FieldConfigProperty.Color]: { [FieldConfigProperty.Color]: {
......
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