AngularMigration: Migrate series Graph to BarGauge (#92609)

This commit is contained in:
Adela Almasan 2024-09-24 15:42:59 -06:00 committed by GitHub
parent 1cbe8772c2
commit 2b94a82baa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 381 additions and 24 deletions

View File

@ -389,9 +389,9 @@
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 2.260951647569786,\n 1.887442338051216,\n 2.1526144400893514,\n 1.7287721375237766,\n 1.7262902137793208\n ]\n ]\n }\n },\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod1\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 1.589539045095202,\n 1.5914283506847613,\n 1.8976990616650726,\n 1.758223085999124,\n 2.2294649594813816\n ]\n ]\n }\n },\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod2\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 2.0914263380328766,\n 1.8164545521094575,\n 1.621111084665713,\n 1.3902653996444705,\n 1.482803315949775\n ]\n ]\n }\n }\n]",
"refId": "A",
"scenarioId": "random_walk",
"seriesCount": 3
"scenarioId": "raw_frame"
}
],
"thresholds": [],
@ -462,6 +462,145 @@
"title": "Status + Notes",
"type": "text"
},
{
"aliasColors": {},
"bars": true,
"dashLength": 10,
"dashes": false,
"datasource": {
"default": false,
"type": "datasource",
"uid": "-- Dashboard --"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 11,
"w": 16,
"x": 0,
"y": 22
},
"hiddenSeries": false,
"id": 32,
"legend": {
"alignAsTable": true,
"avg": true,
"current": true,
"max": true,
"min": false,
"show": true,
"total": false,
"values": true
},
"lines": false,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"percentage": false,
"pluginVersion": "11.3.0-pre",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "-- Dashboard --"
},
"panelId": 28,
"refId": "A"
}
],
"thresholds": [],
"timeRegions": [],
"title": "Flot graph - x axis series mode (with legend calcs)",
"tooltip": {
"shared": false,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "series",
"show": true,
"values": [
"total"
]
},
"yaxes": [
{
"$$hashKey": "object:88",
"format": "short",
"logBase": 1,
"show": true
},
{
"$$hashKey": "object:89",
"format": "short",
"logBase": 1,
"show": true
}
],
"yaxis": {
"align": false
}
},
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"gridPos": {
"h": 11,
"w": 8,
"x": 16,
"y": 22
},
"id": 33,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"content": "# Graph panel >> Bar gauge panel\n",
"mode": "markdown"
},
"pluginVersion": "11.3.0-pre",
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"refId": "A"
}
],
"title": "Status + Notes",
"type": "text"
},
{
"aliasColors": {},
"bars": true,
@ -1183,4 +1322,4 @@
"uid": "cdd412c4",
"version": 68,
"weekStart": ""
}
}

View File

@ -249,3 +249,29 @@ describe('sharedSingleStatMigrationHandler', () => {
expect(panel.fieldConfig.defaults.max).toBe(1);
});
});
describe('BarGauge migrations', () => {
it('Should migrate from old graph', () => {
const old = {
angular: {
xaxis: {
mode: 'series',
},
legend: {
show: true,
values: true,
min: false,
max: true,
current: true,
total: false,
avg: false,
},
},
};
const panel = {} as PanelModel;
panel.options = sharedSingleStatPanelChangedHandler(panel, 'graph', old);
expect(panel.options.legend.showLegend).toBe(true);
expect(panel.options.legend.calcs).toHaveLength(2);
});
});

View File

@ -1,4 +1,4 @@
import { cloneDeep, isNumber, omit } from 'lodash';
import { cloneDeep, identity, isNumber, omit, pickBy } from 'lodash';
import {
convertOldAngularValueMappings,
@ -16,7 +16,7 @@ import {
ValueMapping,
VizOrientation,
} from '@grafana/data';
import { OptionsWithTextFormatting } from '@grafana/schema';
import { LegendDisplayMode, OptionsWithLegend, OptionsWithTextFormatting } from '@grafana/schema';
export interface SingleStatBaseOptions extends OptionsWithTextFormatting {
reduceOptions: ReduceDataOptions;
@ -25,6 +25,7 @@ export interface SingleStatBaseOptions extends OptionsWithTextFormatting {
const optionsToKeep = ['reduceOptions', 'orientation'];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function sharedSingleStatPanelChangedHandler(
panel: PanelModel<Partial<SingleStatBaseOptions>> | any,
prevPluginId: string,
@ -40,6 +41,9 @@ export function sharedSingleStatPanelChangedHandler(
// Migrating from angular singlestat
if (prevPluginId === 'singlestat' && prevOptions.angular) {
return migrateFromAngularSinglestat(panel, prevOptions);
} else if (prevPluginId === 'graph') {
// Migrating from Graph panel
return migrateFromGraphPanel(panel, prevOptions);
}
for (const k of optionsToKeep) {
@ -51,6 +55,65 @@ export function sharedSingleStatPanelChangedHandler(
return options;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function migrateFromGraphPanel(panel: PanelModel<Partial<SingleStatBaseOptions>> | any, prevOptions: any) {
const graphOptions: GraphOptions = prevOptions.angular;
const options: SingleStatBaseOptions & OptionsWithLegend = {
orientation: VizOrientation.Auto,
reduceOptions: {
values: false,
calcs: [],
},
legend: {
displayMode: LegendDisplayMode.List,
showLegend: true,
placement: 'bottom',
calcs: [],
},
};
if (graphOptions.xaxis?.mode === 'series') {
panel.fieldConfig = {
...panel.fieldConfig,
defaults: {
...panel.fieldConfig.defaults,
color: { mode: 'palette-classic' },
},
};
// Value options calculation migration
if (graphOptions.xaxis.values) {
options.reduceOptions.calcs = getReducerForMigration(graphOptions.xaxis.values);
}
// Legend migration
const legendConfig = graphOptions.legend;
if (legendConfig) {
if (legendConfig.show) {
options.legend.displayMode = legendConfig.alignAsTable ? LegendDisplayMode.Table : LegendDisplayMode.List;
} else {
options.legend.showLegend = false;
}
if (legendConfig.rightSide) {
options.legend.placement = 'right';
}
if (legendConfig.values) {
const enabledLegendValues = pickBy(legendConfig, identity);
options.legend.calcs = getReducersFromLegend(enabledLegendValues);
}
if (legendConfig.sideWidth) {
options.legend.width = legendConfig.sideWidth;
}
}
}
return options;
}
function migrateFromAngularSinglestat(panel: PanelModel<Partial<SingleStatBaseOptions>> | any, prevOptions: any) {
const prevPanel = prevOptions.angular;
const reducer = fieldReducers.getIfExists(prevPanel.valueName);
@ -331,3 +394,55 @@ export function migrateOldThresholds(thresholds?: any[]): Threshold[] | undefine
export function convertOldAngularValueMapping(panel: any): ValueMapping[] {
return convertOldAngularValueMappings(panel);
}
interface GraphOptions {
xaxis: {
mode: 'series' | 'time' | 'histogram';
values?: string[];
};
legend: {
show: boolean;
alignAsTable: boolean;
rightSide: boolean;
values: boolean;
min?: boolean;
max?: boolean;
avg?: boolean;
current?: boolean;
total?: boolean;
sideWidth?: number;
};
}
function getReducersFromLegend(obj: Record<string, unknown>): string[] {
const ids: string[] = [];
for (const key in obj) {
const reducer = fieldReducers.getIfExists(key);
if (reducer) {
ids.push(reducer.id);
}
}
return ids;
}
// same as public/app/plugins/panel/barchart/migrations.ts
function getReducerForMigration(reducers: string[] | undefined) {
const transformReducers: string[] = [];
reducers?.forEach((reducer) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
if (!Object.values(ReducerID).includes(reducer as ReducerID)) {
if (reducer === 'current') {
transformReducers.push(ReducerID.lastNotNull);
} else if (reducer === 'total') {
transformReducers.push(ReducerID.sum);
} else if (reducer === 'avg') {
transformReducers.push(ReducerID.mean);
}
} else {
transformReducers.push(reducer);
}
});
return reducers ? transformReducers : [ReducerID.sum];
}

View File

@ -32,6 +32,10 @@ export function getPanelPluginToMigrateTo(panel: any, forceMigration?: boolean):
isUrlFeatureFlagEnabled('autoMigrateGraphPanel'))
) {
if (panel.xaxis?.mode === 'series') {
if (panel.legend?.values) {
return 'bargauge';
}
return 'barchart';
}

View File

@ -1,4 +1,4 @@
import { FieldConfigSource, PanelModel } from '@grafana/data';
import { FieldConfigSource, FieldMatcherID, PanelModel } from '@grafana/data';
import { changeToBarChartPanelMigrationHandler } from './migrations';
@ -17,16 +17,41 @@ describe('Bar chart Migrations', () => {
angular: {
xaxis: {
mode: 'series',
values: 'avg',
values: ['avg'],
},
},
};
const panel = {} as PanelModel;
panel.options = changeToBarChartPanelMigrationHandler(panel, 'graph', old, prevFieldConfig);
const transformations = panel.transformations || [];
expect(transformations).toHaveLength(2);
const transform = panel.transformations![0];
expect(transform.id).toBe('reduce');
expect(transform.options.reducers).toBe('avg');
const reduceTransform = transformations[0];
expect(reduceTransform.id).toBe('reduce');
expect(reduceTransform.options.reducers).toHaveLength(1);
expect(reduceTransform.options.reducers[0]).toBe('mean');
const transposeTransform = transformations[1];
expect(transposeTransform.id).toBe('transpose');
expect(panel.fieldConfig.overrides).toHaveLength(1);
expect(panel.fieldConfig.overrides[0].matcher.id).toBe(FieldMatcherID.byName);
expect(panel.fieldConfig.overrides).toMatchInlineSnapshot(`
[
{
"matcher": {
"id": "byName",
"options": "Field",
},
"properties": [
{
"id": "custom.axisPlacement",
"value": "hidden",
},
],
},
]
`);
});
});

View File

@ -1,33 +1,79 @@
import { PanelTypeChangedHandler } from '@grafana/data';
import { FieldMatcherID, PanelTypeChangedHandler, ReducerID } from '@grafana/data';
import { AxisPlacement } from '@grafana/ui';
/*
* This is called when the panel changes from another panel
*/
export const changeToBarChartPanelMigrationHandler: PanelTypeChangedHandler = (
panel,
prevPluginId,
prevOptions,
prevFieldConfig
) => {
export const changeToBarChartPanelMigrationHandler: PanelTypeChangedHandler = (panel, prevPluginId, prevOptions) => {
if (prevPluginId === 'graph') {
const graphOptions: GraphOptions = prevOptions.angular;
const fieldConfig = panel.fieldConfig ?? { defaults: {}, overrides: [] };
if (graphOptions.xaxis?.mode === 'series') {
const tranformations = panel.transformations || [];
tranformations.push({
id: 'reduce',
options: {
reducers: graphOptions.xaxis?.values ?? ['sum'],
const transformations = panel.transformations || [];
transformations.push(
{
id: 'reduce',
options: {
reducers: getReducer(graphOptions.xaxis?.values),
},
},
{
id: 'transpose',
options: {},
}
);
panel.transformations = transformations;
// temporary, until we have a bar chart with per bar labels
fieldConfig.overrides.push({
matcher: {
id: FieldMatcherID.byName,
options: 'Field',
},
properties: [
{
id: 'custom.axisPlacement',
value: AxisPlacement.Hidden,
},
],
});
panel.transformations = tranformations;
panel.fieldConfig = fieldConfig;
panel.options = {
...panel.options,
groupWidth: 1,
};
}
}
return {};
};
// same as grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts
const getReducer = (reducers: string[] | undefined) => {
const transformReducers: string[] = [];
reducers?.forEach((reducer) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
if (!Object.values(ReducerID).includes(reducer as ReducerID)) {
if (reducer === 'current') {
transformReducers.push(ReducerID.lastNotNull);
} else if (reducer === 'total') {
transformReducers.push(ReducerID.sum);
} else if (reducer === 'avg') {
transformReducers.push(ReducerID.mean);
}
} else {
transformReducers.push(reducer);
}
});
return reducers ? transformReducers : [ReducerID.sum];
};
interface GraphOptions {
xaxis: {
mode: 'series' | 'time' | 'histogram';

View File

@ -1,6 +1,6 @@
import { memo } from 'react';
import { DataFrame, FieldType, getFieldSeriesColor } from '@grafana/data';
import { cacheFieldDisplayNames, DataFrame, FieldType, getFieldSeriesColor } from '@grafana/data';
import { Field } from '@grafana/data/';
import { AxisPlacement, VizLegendOptions } from '@grafana/schema';
import { useTheme2, VizLayout, VizLayoutLegendProps, VizLegend, VizLegendItem } from '@grafana/ui';
@ -16,6 +16,8 @@ export const BarGaugeLegend = memo(
const theme = useTheme2();
let legendItems: VizLegendItem[] = [];
cacheFieldDisplayNames(data);
data.forEach((series, frameIndex) => {
series.fields.forEach((field, i) => {
const fieldIndex = i + 1;