fix(plugin-chart-echarts): reorder totals and support multimetric sort (#23675)

This commit is contained in:
Ville Brofeldt 2023-04-14 20:43:15 +03:00 committed by GitHub
parent 47fd73255e
commit cbbcc8d2e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 244 additions and 120 deletions

View File

@ -54,27 +54,29 @@ export const contributionModeControl = {
}, },
}; };
function isTemporal(controls: ControlStateMapping): boolean {
return !(
isDefined(controls?.x_axis?.value) &&
!isTemporalColumn(
getColumnLabel(controls?.x_axis?.value as QueryFormColumn),
controls?.datasource?.datasource,
)
);
}
const xAxisSortVisibility = ({ controls }: { controls: ControlStateMapping }) => const xAxisSortVisibility = ({ controls }: { controls: ControlStateMapping }) =>
isDefined(controls?.x_axis?.value) && !isTemporal(controls) &&
!isTemporalColumn( ensureIsArray(controls?.groupby?.value).length === 0 &&
getColumnLabel(controls?.x_axis?.value as QueryFormColumn), ensureIsArray(controls?.metrics?.value).length === 1;
controls?.datasource?.datasource,
) &&
Array.isArray(controls?.groupby?.value) &&
controls.groupby.value.length === 0;
const xAxisMultiSortVisibility = ({ const xAxisMultiSortVisibility = ({
controls, controls,
}: { }: {
controls: ControlStateMapping; controls: ControlStateMapping;
}) => }) =>
isDefined(controls?.x_axis?.value) && !isTemporal(controls) &&
!isTemporalColumn( (!!ensureIsArray(controls?.groupby?.value).length ||
getColumnLabel(controls?.x_axis?.value as QueryFormColumn), ensureIsArray(controls?.metrics?.value).length > 1);
controls?.datasource?.datasource,
) &&
Array.isArray(controls?.groupby?.value) &&
!!controls.groupby.value.length;
export const xAxisSortControl = { export const xAxisSortControl = {
name: 'x_axis_sort', name: 'x_axis_sort',

View File

@ -170,12 +170,12 @@ export default function transformProps(
} }
const rebasedDataA = rebaseForecastDatum(data1, verboseMap); const rebasedDataA = rebaseForecastDatum(data1, verboseMap);
const rawSeriesA = extractSeries(rebasedDataA, { const [rawSeriesA] = extractSeries(rebasedDataA, {
fillNeighborValue: stack ? 0 : undefined, fillNeighborValue: stack ? 0 : undefined,
xAxis: xAxisLabel, xAxis: xAxisLabel,
}); });
const rebasedDataB = rebaseForecastDatum(data2, verboseMap); const rebasedDataB = rebaseForecastDatum(data2, verboseMap);
const rawSeriesB = extractSeries(rebasedDataB, { const [rawSeriesB] = extractSeries(rebasedDataB, {
fillNeighborValue: stackB ? 0 : undefined, fillNeighborValue: stackB ? 0 : undefined,
xAxis: xAxisLabel, xAxis: xAxisLabel,
}); });

View File

@ -126,6 +126,7 @@ export default function transformProps(
logAxis, logAxis,
markerEnabled, markerEnabled,
markerSize, markerSize,
metrics,
minorSplitLine, minorSplitLine,
onlyTotal, onlyTotal,
opacity, opacity,
@ -193,7 +194,9 @@ export default function transformProps(
getMetricLabel, getMetricLabel,
); );
const rawSeries = extractSeries(rebasedData, { const isMultiSeries = groupby.length || metrics.length > 1;
const [rawSeries, sortedTotalValues] = extractSeries(rebasedData, {
fillNeighborValue: stack && !forecastEnabled ? 0 : undefined, fillNeighborValue: stack && !forecastEnabled ? 0 : undefined,
xAxis: xAxisLabel, xAxis: xAxisLabel,
extraMetricLabels, extraMetricLabels,
@ -202,8 +205,8 @@ export default function transformProps(
isHorizontal, isHorizontal,
sortSeriesType, sortSeriesType,
sortSeriesAscending, sortSeriesAscending,
xAxisSortSeries: groupby.length ? xAxisSortSeries : undefined, xAxisSortSeries: isMultiSeries ? xAxisSortSeries : undefined,
xAxisSortSeriesAscending: groupby.length xAxisSortSeriesAscending: isMultiSeries
? xAxisSortSeriesAscending ? xAxisSortSeriesAscending
: undefined, : undefined,
}); });
@ -240,7 +243,7 @@ export default function transformProps(
formatter, formatter,
showValue, showValue,
onlyTotal, onlyTotal,
totalStackedValues, totalStackedValues: sortedTotalValues,
showValueIndexes, showValueIndexes,
thresholdValues, thresholdValues,
richTooltip, richTooltip,

View File

@ -23,6 +23,7 @@ import {
ContributionType, ContributionType,
QueryFormColumn, QueryFormColumn,
QueryFormData, QueryFormData,
QueryFormMetric,
TimeFormatter, TimeFormatter,
TimeGranularity, TimeGranularity,
} from '@superset-ui/core'; } from '@superset-ui/core';
@ -65,6 +66,7 @@ export type EchartsTimeseriesFormData = QueryFormData & {
logAxis: boolean; logAxis: boolean;
markerEnabled: boolean; markerEnabled: boolean;
markerSize: number; markerSize: number;
metrics: QueryFormMetric[];
minorSplitLine: boolean; minorSplitLine: boolean;
opacity: number; opacity: number;
orderDesc: boolean; orderDesc: boolean;

View File

@ -153,11 +153,12 @@ export function sortAndFilterSeries(
export function sortRows( export function sortRows(
rows: DataRecord[], rows: DataRecord[],
totalStackedValues: number[],
xAxis: string, xAxis: string,
xAxisSortSeries: SortSeriesType, xAxisSortSeries: SortSeriesType,
xAxisSortSeriesAscending: boolean, xAxisSortSeriesAscending: boolean,
) { ) {
const sortedRows = rows.map(row => { const sortedRows = rows.map((row, idx) => {
let sortKey: DataRecordValue = ''; let sortKey: DataRecordValue = '';
let aggregate: number | undefined; let aggregate: number | undefined;
let entries = 0; let entries = 0;
@ -219,6 +220,7 @@ export function sortRows(
key: sortKey, key: sortKey,
value, value,
row, row,
totalStackedValue: totalStackedValues[idx],
}; };
}); });
@ -226,7 +228,7 @@ export function sortRows(
sortedRows, sortedRows,
['value'], ['value'],
[xAxisSortSeriesAscending ? 'asc' : 'desc'], [xAxisSortSeriesAscending ? 'asc' : 'desc'],
).map(({ row }) => row); ).map(({ row, totalStackedValue }) => ({ row, totalStackedValue }));
} }
export function extractSeries( export function extractSeries(
@ -244,7 +246,7 @@ export function extractSeries(
xAxisSortSeries?: SortSeriesType; xAxisSortSeries?: SortSeriesType;
xAxisSortSeriesAscending?: boolean; xAxisSortSeriesAscending?: boolean;
} = {}, } = {},
): SeriesOption[] { ): [SeriesOption[], number[]] {
const { const {
fillNeighborValue, fillNeighborValue,
xAxis = DTTM_ALIAS, xAxis = DTTM_ALIAS,
@ -258,7 +260,7 @@ export function extractSeries(
xAxisSortSeries, xAxisSortSeries,
xAxisSortSeriesAscending, xAxisSortSeriesAscending,
} = opts; } = opts;
if (data.length === 0) return []; if (data.length === 0) return [[], []];
const rows: DataRecord[] = data.map(datum => ({ const rows: DataRecord[] = data.map(datum => ({
...datum, ...datum,
[xAxis]: datum[xAxis], [xAxis]: datum[xAxis],
@ -272,14 +274,23 @@ export function extractSeries(
); );
const sortedRows = const sortedRows =
isDefined(xAxisSortSeries) && isDefined(xAxisSortSeriesAscending) isDefined(xAxisSortSeries) && isDefined(xAxisSortSeriesAscending)
? sortRows(rows, xAxis, xAxisSortSeries!, xAxisSortSeriesAscending!) ? sortRows(
: rows; rows,
totalStackedValues,
xAxis,
xAxisSortSeries!,
xAxisSortSeriesAscending!,
)
: rows.map((row, idx) => ({
row,
totalStackedValue: totalStackedValues[idx],
}));
return sortedSeries.map(name => ({ const finalSeries = sortedSeries.map(name => ({
id: name, id: name,
name, name,
data: sortedRows data: sortedRows
.map((row, idx) => { .map(({ row, totalStackedValue }, idx) => {
const isNextToDefinedValue = const isNextToDefinedValue =
isDefined(rows[idx - 1]?.[name]) || isDefined(rows[idx + 1]?.[name]); isDefined(rows[idx - 1]?.[name]) || isDefined(rows[idx + 1]?.[name]);
const isFillNeighborValue = const isFillNeighborValue =
@ -291,15 +302,19 @@ export function extractSeries(
value = fillNeighborValue; value = fillNeighborValue;
} else if ( } else if (
stack === StackControlsValue.Expand && stack === StackControlsValue.Expand &&
totalStackedValues.length > 0 totalStackedValue !== undefined
) { ) {
value = ((value || 0) as number) / totalStackedValues[idx]; value = ((value || 0) as number) / totalStackedValue;
} }
return [row[xAxis], value]; return [row[xAxis], value];
}) })
.filter(obs => !removeNulls || (obs[0] !== null && obs[1] !== null)) .filter(obs => !removeNulls || (obs[0] !== null && obs[1] !== null))
.map(obs => (isHorizontal ? [obs[1], obs[0]] : obs)), .map(obs => (isHorizontal ? [obs[1], obs[0]] : obs)),
})); }));
return [
finalSeries,
sortedRows.map(({ totalStackedValue }) => totalStackedValue),
];
} }
export function formatSeriesName( export function formatSeriesName(

View File

@ -56,83 +56,165 @@ const sortData: DataRecord[] = [
{ my_x_axis: null, x: 4, y: 3, z: 7 }, { my_x_axis: null, x: 4, y: 3, z: 7 },
]; ];
const totalStackedValues = [3, 15, 14];
test('sortRows by name ascending', () => { test('sortRows by name ascending', () => {
expect(sortRows(sortData, 'my_x_axis', SortSeriesType.Name, true)).toEqual([ expect(
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 }, sortRows(
{ my_x_axis: 'foo', x: null, y: 10, z: 5 }, sortData,
{ my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValues,
'my_x_axis',
SortSeriesType.Name,
true,
),
).toEqual([
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
]); ]);
}); });
test('sortRows by name descending', () => { test('sortRows by name descending', () => {
expect(sortRows(sortData, 'my_x_axis', SortSeriesType.Name, false)).toEqual([ expect(
{ my_x_axis: null, x: 4, y: 3, z: 7 }, sortRows(
{ my_x_axis: 'foo', x: null, y: 10, z: 5 }, sortData,
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValues,
'my_x_axis',
SortSeriesType.Name,
false,
),
).toEqual([
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
]); ]);
}); });
test('sortRows by sum ascending', () => { test('sortRows by sum ascending', () => {
expect(sortRows(sortData, 'my_x_axis', SortSeriesType.Sum, true)).toEqual([ expect(
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 }, sortRows(
{ my_x_axis: null, x: 4, y: 3, z: 7 }, sortData,
{ my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValues,
'my_x_axis',
SortSeriesType.Sum,
true,
),
).toEqual([
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
]); ]);
}); });
test('sortRows by sum descending', () => { test('sortRows by sum descending', () => {
expect(sortRows(sortData, 'my_x_axis', SortSeriesType.Sum, false)).toEqual([ expect(
{ my_x_axis: 'foo', x: null, y: 10, z: 5 }, sortRows(
{ my_x_axis: null, x: 4, y: 3, z: 7 }, sortData,
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValues,
'my_x_axis',
SortSeriesType.Sum,
false,
),
).toEqual([
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
]); ]);
}); });
test('sortRows by avg ascending', () => { test('sortRows by avg ascending', () => {
expect(sortRows(sortData, 'my_x_axis', SortSeriesType.Avg, true)).toEqual([ expect(
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 }, sortRows(
{ my_x_axis: null, x: 4, y: 3, z: 7 }, sortData,
{ my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValues,
'my_x_axis',
SortSeriesType.Avg,
true,
),
).toEqual([
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
]); ]);
}); });
test('sortRows by avg descending', () => { test('sortRows by avg descending', () => {
expect(sortRows(sortData, 'my_x_axis', SortSeriesType.Avg, false)).toEqual([ expect(
{ my_x_axis: 'foo', x: null, y: 10, z: 5 }, sortRows(
{ my_x_axis: null, x: 4, y: 3, z: 7 }, sortData,
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValues,
'my_x_axis',
SortSeriesType.Avg,
false,
),
).toEqual([
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
]); ]);
}); });
test('sortRows by min ascending', () => { test('sortRows by min ascending', () => {
expect(sortRows(sortData, 'my_x_axis', SortSeriesType.Min, true)).toEqual([ expect(
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 }, sortRows(
{ my_x_axis: null, x: 4, y: 3, z: 7 }, sortData,
{ my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValues,
'my_x_axis',
SortSeriesType.Min,
true,
),
).toEqual([
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
]); ]);
}); });
test('sortRows by min descending', () => { test('sortRows by min descending', () => {
expect(sortRows(sortData, 'my_x_axis', SortSeriesType.Min, false)).toEqual([ expect(
{ my_x_axis: 'foo', x: null, y: 10, z: 5 }, sortRows(
{ my_x_axis: null, x: 4, y: 3, z: 7 }, sortData,
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValues,
'my_x_axis',
SortSeriesType.Min,
false,
),
).toEqual([
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
]); ]);
}); });
test('sortRows by max ascending', () => { test('sortRows by max ascending', () => {
expect(sortRows(sortData, 'my_x_axis', SortSeriesType.Min, true)).toEqual([ expect(
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 }, sortRows(
{ my_x_axis: null, x: 4, y: 3, z: 7 }, sortData,
{ my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValues,
'my_x_axis',
SortSeriesType.Min,
true,
),
).toEqual([
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
]); ]);
}); });
test('sortRows by max descending', () => { test('sortRows by max descending', () => {
expect(sortRows(sortData, 'my_x_axis', SortSeriesType.Min, false)).toEqual([ expect(
{ my_x_axis: 'foo', x: null, y: 10, z: 5 }, sortRows(
{ my_x_axis: null, x: 4, y: 3, z: 7 }, sortData,
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValues,
'my_x_axis',
SortSeriesType.Min,
false,
),
).toEqual([
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
]); ]);
}); });
@ -215,25 +297,29 @@ describe('extractSeries', () => {
abc: 5, abc: 5,
}, },
]; ];
expect(extractSeries(data)).toEqual([ const totalStackedValues = [2, 12, 6];
{ expect(extractSeries(data, { totalStackedValues })).toEqual([
id: 'Hulk', [
name: 'Hulk', {
data: [ id: 'Hulk',
['2000-01-01', null], name: 'Hulk',
['2000-02-01', 2], data: [
['2000-03-01', 1], ['2000-01-01', null],
], ['2000-02-01', 2],
}, ['2000-03-01', 1],
{ ],
id: 'abc', },
name: 'abc', {
data: [ id: 'abc',
['2000-01-01', 2], name: 'abc',
['2000-02-01', 10], data: [
['2000-03-01', 5], ['2000-01-01', 2],
], ['2000-02-01', 10],
}, ['2000-03-01', 5],
],
},
],
totalStackedValues,
]); ]);
}); });
@ -255,20 +341,30 @@ describe('extractSeries', () => {
abc: 5, abc: 5,
}, },
]; ];
expect(extractSeries(data, { xAxis: 'x', removeNulls: true })).toEqual([ const totalStackedValues = [3, 12, 8];
{ expect(
id: 'Hulk', extractSeries(data, {
name: 'Hulk', totalStackedValues,
data: [[2, 1]], xAxis: 'x',
}, removeNulls: true,
{ }),
id: 'abc', ).toEqual([
name: 'abc', [
data: [ {
[1, 2], id: 'Hulk',
[2, 5], name: 'Hulk',
], data: [[2, 1]],
}, },
{
id: 'abc',
name: 'abc',
data: [
[1, 2],
[2, 5],
],
},
],
totalStackedValues,
]); ]);
}); });
@ -315,23 +411,29 @@ describe('extractSeries', () => {
abc: null, abc: null,
}, },
]; ];
expect(extractSeries(data, { fillNeighborValue: 0 })).toEqual([ const totalStackedValues = [0, 0, 1, 0, 0, 0, 2, 3, 0, 0];
{ expect(
id: 'abc', extractSeries(data, { totalStackedValues, fillNeighborValue: 0 }),
name: 'abc', ).toEqual([
data: [ [
['2000-01-01', null], {
['2000-02-01', 0], id: 'abc',
['2000-03-01', 1], name: 'abc',
['2000-04-01', 0], data: [
['2000-05-01', null], ['2000-01-01', null],
['2000-06-01', 0], ['2000-02-01', 0],
['2000-07-01', 2], ['2000-03-01', 1],
['2000-08-01', 3], ['2000-04-01', 0],
['2000-09-01', 0], ['2000-05-01', null],
['2000-10-01', null], ['2000-06-01', 0],
], ['2000-07-01', 2],
}, ['2000-08-01', 3],
['2000-09-01', 0],
['2000-10-01', null],
],
},
],
totalStackedValues,
]); ]);
}); });
}); });