fix: remove sort values on stacked totals (#31333)
This commit is contained in:
parent
5867b87680
commit
15fbb195e9
|
|
@ -19,17 +19,23 @@
|
||||||
|
|
||||||
import { QueryFormMetric, isSavedMetric, isAdhocMetricSimple } from './types';
|
import { QueryFormMetric, isSavedMetric, isAdhocMetricSimple } from './types';
|
||||||
|
|
||||||
export default function getMetricLabel(metric: QueryFormMetric): string {
|
export default function getMetricLabel(
|
||||||
|
metric: QueryFormMetric,
|
||||||
|
index?: number,
|
||||||
|
queryFormMetrics?: QueryFormMetric[],
|
||||||
|
verboseMap?: Record<string, string>,
|
||||||
|
): string {
|
||||||
|
let label = '';
|
||||||
if (isSavedMetric(metric)) {
|
if (isSavedMetric(metric)) {
|
||||||
return metric;
|
label = metric;
|
||||||
}
|
} else if (metric.label) {
|
||||||
if (metric.label) {
|
({ label } = metric);
|
||||||
return metric.label;
|
} else if (isAdhocMetricSimple(metric)) {
|
||||||
}
|
label = `${metric.aggregate}(${
|
||||||
if (isAdhocMetricSimple(metric)) {
|
|
||||||
return `${metric.aggregate}(${
|
|
||||||
metric.column.columnName || metric.column.column_name
|
metric.column.columnName || metric.column.column_name
|
||||||
})`;
|
})`;
|
||||||
|
} else {
|
||||||
|
label = metric.sqlExpression;
|
||||||
}
|
}
|
||||||
return metric.sqlExpression;
|
return verboseMap?.[label] || label;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
ensureIsArray,
|
ensureIsArray,
|
||||||
GenericDataType,
|
GenericDataType,
|
||||||
getCustomFormatter,
|
getCustomFormatter,
|
||||||
|
getMetricLabel,
|
||||||
getNumberFormatter,
|
getNumberFormatter,
|
||||||
getXAxisLabel,
|
getXAxisLabel,
|
||||||
isDefined,
|
isDefined,
|
||||||
|
|
@ -291,12 +292,20 @@ export default function transformProps(
|
||||||
const showValueIndexesB = extractShowValueIndexes(rawSeriesB, {
|
const showValueIndexesB = extractShowValueIndexes(rawSeriesB, {
|
||||||
stack,
|
stack,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const metricsLabels = metrics
|
||||||
|
.map(metric => getMetricLabel(metric, undefined, undefined, verboseMap))
|
||||||
|
.filter((label): label is string => label !== undefined);
|
||||||
|
const metricsLabelsB = metricsB.map((metric: QueryFormMetric) =>
|
||||||
|
getMetricLabel(metric, undefined, undefined, verboseMap),
|
||||||
|
);
|
||||||
|
|
||||||
const { totalStackedValues, thresholdValues } = extractDataTotalValues(
|
const { totalStackedValues, thresholdValues } = extractDataTotalValues(
|
||||||
rebasedDataA,
|
rebasedDataA,
|
||||||
{
|
{
|
||||||
stack,
|
stack,
|
||||||
percentageThreshold,
|
percentageThreshold,
|
||||||
xAxisCol: xAxisLabel,
|
metricsLabels,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const {
|
const {
|
||||||
|
|
@ -305,7 +314,7 @@ export default function transformProps(
|
||||||
} = extractDataTotalValues(rebasedDataB, {
|
} = extractDataTotalValues(rebasedDataB, {
|
||||||
stack: Boolean(stackB),
|
stack: Boolean(stackB),
|
||||||
percentageThreshold,
|
percentageThreshold,
|
||||||
xAxisCol: xAxisLabel,
|
metricsLabels: metricsLabelsB,
|
||||||
});
|
});
|
||||||
|
|
||||||
annotationLayers
|
annotationLayers
|
||||||
|
|
|
||||||
|
|
@ -215,14 +215,18 @@ export default function transformProps(
|
||||||
) {
|
) {
|
||||||
xAxisLabel = verboseMap[xAxisLabel];
|
xAxisLabel = verboseMap[xAxisLabel];
|
||||||
}
|
}
|
||||||
|
const metricsLabels = metrics
|
||||||
|
.map(metric => getMetricLabel(metric, undefined, undefined, verboseMap))
|
||||||
|
.filter((label): label is string => label !== undefined);
|
||||||
const isHorizontal = orientation === OrientationType.Horizontal;
|
const isHorizontal = orientation === OrientationType.Horizontal;
|
||||||
|
|
||||||
const { totalStackedValues, thresholdValues } = extractDataTotalValues(
|
const { totalStackedValues, thresholdValues } = extractDataTotalValues(
|
||||||
rebasedData,
|
rebasedData,
|
||||||
{
|
{
|
||||||
stack,
|
stack,
|
||||||
percentageThreshold,
|
percentageThreshold,
|
||||||
xAxisCol: xAxisLabel,
|
|
||||||
legendState,
|
legendState,
|
||||||
|
metricsLabels,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const extraMetricLabels = extractExtraMetrics(chartProps.rawFormData).map(
|
const extraMetricLabels = extractExtraMetrics(chartProps.rawFormData).map(
|
||||||
|
|
@ -296,7 +300,6 @@ export default function transformProps(
|
||||||
const entryName = String(entry.name || '');
|
const entryName = String(entry.name || '');
|
||||||
const seriesName = inverted[entryName] || entryName;
|
const seriesName = inverted[entryName] || entryName;
|
||||||
const colorScaleKey = getOriginalSeries(seriesName, array);
|
const colorScaleKey = getOriginalSeries(seriesName, array);
|
||||||
|
|
||||||
const transformedSeries = transformSeries(
|
const transformedSeries = transformSeries(
|
||||||
entry,
|
entry,
|
||||||
colorScale,
|
colorScale,
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,8 @@ export function extractDataTotalValues(
|
||||||
opts: {
|
opts: {
|
||||||
stack: StackType;
|
stack: StackType;
|
||||||
percentageThreshold: number;
|
percentageThreshold: number;
|
||||||
xAxisCol: string;
|
|
||||||
legendState?: LegendState;
|
legendState?: LegendState;
|
||||||
|
metricsLabels: string[];
|
||||||
},
|
},
|
||||||
): {
|
): {
|
||||||
totalStackedValues: number[];
|
totalStackedValues: number[];
|
||||||
|
|
@ -69,11 +69,11 @@ export function extractDataTotalValues(
|
||||||
} {
|
} {
|
||||||
const totalStackedValues: number[] = [];
|
const totalStackedValues: number[] = [];
|
||||||
const thresholdValues: number[] = [];
|
const thresholdValues: number[] = [];
|
||||||
const { stack, percentageThreshold, xAxisCol, legendState } = opts;
|
const { stack, percentageThreshold, legendState, metricsLabels } = opts;
|
||||||
if (stack) {
|
if (stack) {
|
||||||
data.forEach(datum => {
|
data.forEach(datum => {
|
||||||
const values = Object.keys(datum).reduce((prev, curr) => {
|
const values = Object.keys(datum).reduce((prev, curr) => {
|
||||||
if (curr === xAxisCol) {
|
if (!metricsLabels.includes(curr)) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
if (legendState && !legendState[curr]) {
|
if (legendState && !legendState[curr]) {
|
||||||
|
|
|
||||||
|
|
@ -36,15 +36,25 @@ describe('EchartsTimeseries transformProps', () => {
|
||||||
colorScheme: 'bnbColors',
|
colorScheme: 'bnbColors',
|
||||||
datasource: '3__table',
|
datasource: '3__table',
|
||||||
granularity_sqla: 'ds',
|
granularity_sqla: 'ds',
|
||||||
metric: 'sum__num',
|
metrics: ['sum__num'],
|
||||||
groupby: ['foo', 'bar'],
|
groupby: ['foo', 'bar'],
|
||||||
viz_type: 'my_viz',
|
viz_type: 'my_viz',
|
||||||
};
|
};
|
||||||
const queriesData = [
|
const queriesData = [
|
||||||
{
|
{
|
||||||
data: [
|
data: [
|
||||||
{ 'San Francisco': 1, 'New York': 2, __timestamp: 599616000000 },
|
{
|
||||||
{ 'San Francisco': 3, 'New York': 4, __timestamp: 599916000000 },
|
'San Francisco': 1,
|
||||||
|
'New York': 2,
|
||||||
|
__timestamp: 599616000000,
|
||||||
|
sum__num: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'San Francisco': 3,
|
||||||
|
'New York': 4,
|
||||||
|
__timestamp: 599916000000,
|
||||||
|
sum__num: 8,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -64,7 +74,7 @@ describe('EchartsTimeseries transformProps', () => {
|
||||||
height: 600,
|
height: 600,
|
||||||
echartOptions: expect.objectContaining({
|
echartOptions: expect.objectContaining({
|
||||||
legend: expect.objectContaining({
|
legend: expect.objectContaining({
|
||||||
data: ['San Francisco', 'New York'],
|
data: ['sum__num', 'San Francisco', 'New York'],
|
||||||
}),
|
}),
|
||||||
series: expect.arrayContaining([
|
series: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|
@ -101,7 +111,7 @@ describe('EchartsTimeseries transformProps', () => {
|
||||||
height: 600,
|
height: 600,
|
||||||
echartOptions: expect.objectContaining({
|
echartOptions: expect.objectContaining({
|
||||||
legend: expect.objectContaining({
|
legend: expect.objectContaining({
|
||||||
data: ['San Francisco', 'New York'],
|
data: ['sum__num', 'San Francisco', 'New York'],
|
||||||
}),
|
}),
|
||||||
series: expect.arrayContaining([
|
series: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|
@ -146,7 +156,7 @@ describe('EchartsTimeseries transformProps', () => {
|
||||||
height: 600,
|
height: 600,
|
||||||
echartOptions: expect.objectContaining({
|
echartOptions: expect.objectContaining({
|
||||||
legend: expect.objectContaining({
|
legend: expect.objectContaining({
|
||||||
data: ['San Francisco', 'New York', 'My Formula'],
|
data: ['sum__num', 'San Francisco', 'New York', 'My Formula'],
|
||||||
}),
|
}),
|
||||||
series: expect.arrayContaining([
|
series: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|
@ -274,7 +284,7 @@ describe('EchartsTimeseries transformProps', () => {
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
echartOptions: expect.objectContaining({
|
echartOptions: expect.objectContaining({
|
||||||
legend: expect.objectContaining({
|
legend: expect.objectContaining({
|
||||||
data: ['San Francisco', 'New York', 'My Line'],
|
data: ['sum__num', 'San Francisco', 'New York', 'My Line'],
|
||||||
}),
|
}),
|
||||||
series: expect.arrayContaining([
|
series: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|
@ -420,7 +430,7 @@ describe('Does transformProps transform series correctly', () => {
|
||||||
colorScheme: 'bnbColors',
|
colorScheme: 'bnbColors',
|
||||||
datasource: '3__table',
|
datasource: '3__table',
|
||||||
granularity_sqla: 'ds',
|
granularity_sqla: 'ds',
|
||||||
metric: 'sum__num',
|
metrics: ['sum__num'],
|
||||||
groupby: ['foo', 'bar'],
|
groupby: ['foo', 'bar'],
|
||||||
showValue: true,
|
showValue: true,
|
||||||
stack: true,
|
stack: true,
|
||||||
|
|
@ -435,24 +445,28 @@ describe('Does transformProps transform series correctly', () => {
|
||||||
'New York': 2,
|
'New York': 2,
|
||||||
Boston: 1,
|
Boston: 1,
|
||||||
__timestamp: 599616000000,
|
__timestamp: 599616000000,
|
||||||
|
sum__num: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'San Francisco': 3,
|
'San Francisco': 3,
|
||||||
'New York': 4,
|
'New York': 4,
|
||||||
Boston: 1,
|
Boston: 1,
|
||||||
__timestamp: 599916000000,
|
__timestamp: 599916000000,
|
||||||
|
sum__num: 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'San Francisco': 5,
|
'San Francisco': 5,
|
||||||
'New York': 8,
|
'New York': 8,
|
||||||
Boston: 6,
|
Boston: 6,
|
||||||
__timestamp: 600216000000,
|
__timestamp: 600216000000,
|
||||||
|
sum__num: 19,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'San Francisco': 2,
|
'San Francisco': 2,
|
||||||
'New York': 7,
|
'New York': 7,
|
||||||
Boston: 2,
|
Boston: 2,
|
||||||
__timestamp: 600516000000,
|
__timestamp: 600516000000,
|
||||||
|
sum__num: 11,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -468,7 +482,7 @@ describe('Does transformProps transform series correctly', () => {
|
||||||
const totalStackedValues = queriesData[0].data.reduce(
|
const totalStackedValues = queriesData[0].data.reduce(
|
||||||
(totals, currentStack) => {
|
(totals, currentStack) => {
|
||||||
const total = Object.keys(currentStack).reduce((stackSum, key) => {
|
const total = Object.keys(currentStack).reduce((stackSum, key) => {
|
||||||
if (key === '__timestamp') return stackSum;
|
if (key === '__timestamp' || key === 'sum__num') return stackSum;
|
||||||
return stackSum + currentStack[key as keyof typeof currentStack];
|
return stackSum + currentStack[key as keyof typeof currentStack];
|
||||||
}, 0);
|
}, 0);
|
||||||
totals.push(total);
|
totals.push(total);
|
||||||
|
|
@ -561,7 +575,6 @@ describe('Does transformProps transform series correctly', () => {
|
||||||
const expectedThresholds = totalStackedValues.map(
|
const expectedThresholds = totalStackedValues.map(
|
||||||
total => ((formData.percentageThreshold || 0) / 100) * total,
|
total => ((formData.percentageThreshold || 0) / 100) * total,
|
||||||
);
|
);
|
||||||
|
|
||||||
transformedSeries.forEach((series, seriesIndex) => {
|
transformedSeries.forEach((series, seriesIndex) => {
|
||||||
expect(series.label.show).toBe(true);
|
expect(series.label.show).toBe(true);
|
||||||
series.data.forEach((value, dataIndex) => {
|
series.data.forEach((value, dataIndex) => {
|
||||||
|
|
@ -576,7 +589,6 @@ describe('Does transformProps transform series correctly', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not apply percentage threshold when showValue is true and stack is false', () => {
|
it('should not apply percentage threshold when showValue is true and stack is false', () => {
|
||||||
const updatedChartPropsConfig = {
|
const updatedChartPropsConfig = {
|
||||||
...chartPropsConfig,
|
...chartPropsConfig,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import {
|
||||||
calculateLowerLogTick,
|
calculateLowerLogTick,
|
||||||
dedupSeries,
|
dedupSeries,
|
||||||
extractGroupbyLabel,
|
extractGroupbyLabel,
|
||||||
|
extractDataTotalValues,
|
||||||
extractSeries,
|
extractSeries,
|
||||||
extractShowValueIndexes,
|
extractShowValueIndexes,
|
||||||
extractTooltipKeys,
|
extractTooltipKeys,
|
||||||
|
|
@ -1085,6 +1086,123 @@ const forecastValue = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
describe('extractDataTotalValues', () => {
|
||||||
|
it('test_extractDataTotalValues_withStack', () => {
|
||||||
|
const data: DataRecord[] = [
|
||||||
|
{ metric1: 10, metric2: 20, xAxisCol: '2021-01-01' },
|
||||||
|
{ metric1: 15, metric2: 25, xAxisCol: '2021-01-02' },
|
||||||
|
];
|
||||||
|
const metricsLabels = ['metric1', 'metric2'];
|
||||||
|
const opts = {
|
||||||
|
stack: true,
|
||||||
|
percentageThreshold: 10,
|
||||||
|
metricsLabels,
|
||||||
|
};
|
||||||
|
const result = extractDataTotalValues(data, opts);
|
||||||
|
expect(result.totalStackedValues).toEqual([30, 40]);
|
||||||
|
expect(result.thresholdValues).toEqual([3, 4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate total and threshold values with stack option enabled', () => {
|
||||||
|
const data: DataRecord[] = [
|
||||||
|
{ metric1: 10, metric2: 20, xAxisCol: '2021-01-01' },
|
||||||
|
{ metric1: 15, metric2: 25, xAxisCol: '2021-01-02' },
|
||||||
|
];
|
||||||
|
const metricsLabels = ['metric1', 'metric2'];
|
||||||
|
const opts = {
|
||||||
|
stack: true,
|
||||||
|
percentageThreshold: 10,
|
||||||
|
metricsLabels,
|
||||||
|
};
|
||||||
|
const result = extractDataTotalValues(data, opts);
|
||||||
|
expect(result.totalStackedValues).toEqual([30, 40]);
|
||||||
|
expect(result.thresholdValues).toEqual([3, 4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty data array', () => {
|
||||||
|
const data: DataRecord[] = [];
|
||||||
|
const metricsLabels: string[] = [];
|
||||||
|
const opts = {
|
||||||
|
stack: true,
|
||||||
|
percentageThreshold: 10,
|
||||||
|
metricsLabels,
|
||||||
|
};
|
||||||
|
const result = extractDataTotalValues(data, opts);
|
||||||
|
expect(result.totalStackedValues).toEqual([]);
|
||||||
|
expect(result.thresholdValues).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate total and threshold values with stack option disabled', () => {
|
||||||
|
const data: DataRecord[] = [
|
||||||
|
{ metric1: 10, metric2: 20, xAxisCol: '2021-01-01' },
|
||||||
|
{ metric1: 15, metric2: 25, xAxisCol: '2021-01-02' },
|
||||||
|
];
|
||||||
|
const metricsLabels = ['metric1', 'metric2'];
|
||||||
|
const opts = {
|
||||||
|
stack: false,
|
||||||
|
percentageThreshold: 10,
|
||||||
|
metricsLabels,
|
||||||
|
};
|
||||||
|
const result = extractDataTotalValues(data, opts);
|
||||||
|
expect(result.totalStackedValues).toEqual([]);
|
||||||
|
expect(result.thresholdValues).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle data with null or undefined values', () => {
|
||||||
|
const data: DataRecord[] = [
|
||||||
|
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 },
|
||||||
|
{ my_x_axis: 'foo', x: null, y: 10, z: 5 },
|
||||||
|
{ my_x_axis: null, x: 4, y: 3, z: 7 },
|
||||||
|
];
|
||||||
|
const metricsLabels = ['x', 'y', 'z'];
|
||||||
|
const opts = {
|
||||||
|
stack: true,
|
||||||
|
percentageThreshold: 10,
|
||||||
|
metricsLabels,
|
||||||
|
};
|
||||||
|
const result = extractDataTotalValues(data, opts);
|
||||||
|
expect(result.totalStackedValues).toEqual([3, 15, 14]);
|
||||||
|
expect(result.thresholdValues).toEqual([
|
||||||
|
0.30000000000000004, 1.5, 1.4000000000000001,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle different percentage thresholds', () => {
|
||||||
|
const data: DataRecord[] = [
|
||||||
|
{ metric1: 10, metric2: 20, xAxisCol: '2021-01-01' },
|
||||||
|
{ metric1: 15, metric2: 25, xAxisCol: '2021-01-02' },
|
||||||
|
];
|
||||||
|
const metricsLabels = ['metric1', 'metric2'];
|
||||||
|
const opts = {
|
||||||
|
stack: true,
|
||||||
|
percentageThreshold: 50,
|
||||||
|
metricsLabels,
|
||||||
|
};
|
||||||
|
const result = extractDataTotalValues(data, opts);
|
||||||
|
expect(result.totalStackedValues).toEqual([30, 40]);
|
||||||
|
expect(result.thresholdValues).toEqual([15, 20]);
|
||||||
|
});
|
||||||
|
it('should not add datum not in metrics to the total value when stacked', () => {
|
||||||
|
const data: DataRecord[] = [
|
||||||
|
{ xAxisCol: 'foo', xAxisSort: 10, val: 345 },
|
||||||
|
{ xAxisCol: 'bar', xAxisSort: 20, val: 2432 },
|
||||||
|
{ xAxisCol: 'baz', xAxisSort: 30, val: 4543 },
|
||||||
|
];
|
||||||
|
const metricsLabels = ['val'];
|
||||||
|
const opts = {
|
||||||
|
stack: true,
|
||||||
|
percentageThreshold: 50,
|
||||||
|
metricsLabels,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = extractDataTotalValues(data, opts);
|
||||||
|
|
||||||
|
// Assuming extractDataTotalValues returns the total value
|
||||||
|
// without including the 'xAxisCol' category
|
||||||
|
expect(result.totalStackedValues).toEqual([345, 2432, 4543]); // 10 + 20, excluding the 'xAxisCol' category
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('extractTooltipKeys with rich tooltip', () => {
|
test('extractTooltipKeys with rich tooltip', () => {
|
||||||
const result = extractTooltipKeys(forecastValue, 1, true, false);
|
const result = extractTooltipKeys(forecastValue, 1, true, false);
|
||||||
expect(result).toEqual(['foo', 'bar']);
|
expect(result).toEqual(['foo', 'bar']);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue