mirror of
https://github.com/apache/superset.git
synced 2026-04-18 07:35:09 +00:00
feat(chart): add toggle for percentage metric calculation mode in Table chart (#33656)
This commit is contained in:
@@ -84,7 +84,7 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
return buildQueryContext(formDataCopy, baseQueryObject => {
|
||||
let { metrics, orderby = [], columns = [] } = baseQueryObject;
|
||||
const { extras = {} } = baseQueryObject;
|
||||
let postProcessing: PostProcessingRule[] = [];
|
||||
const postProcessing: PostProcessingRule[] = [];
|
||||
const nonCustomNorInheritShifts = ensureIsArray(
|
||||
formData.time_compare,
|
||||
).filter((shift: string) => shift !== 'custom' && shift !== 'inherit');
|
||||
@@ -129,6 +129,12 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
orderby = [[metrics[0], false]];
|
||||
}
|
||||
// add postprocessing for percent metrics only when in aggregation mode
|
||||
type PercentMetricCalculationMode = 'row_limit' | 'all_records';
|
||||
|
||||
const calculationMode: PercentMetricCalculationMode =
|
||||
(formData.percent_metric_calculation as PercentMetricCalculationMode) ||
|
||||
'row_limit';
|
||||
|
||||
if (percentMetrics && percentMetrics.length > 0) {
|
||||
const percentMetricsLabelsWithTimeComparison = isTimeComparison(
|
||||
formData,
|
||||
@@ -139,6 +145,7 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
timeOffsets,
|
||||
)
|
||||
: percentMetrics.map(getMetricLabel);
|
||||
|
||||
const percentMetricLabels = removeDuplicates(
|
||||
percentMetricsLabelsWithTimeComparison,
|
||||
);
|
||||
@@ -146,16 +153,26 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
metrics.concat(percentMetrics),
|
||||
getMetricLabel,
|
||||
);
|
||||
postProcessing = [
|
||||
{
|
||||
|
||||
if (calculationMode === 'all_records') {
|
||||
postProcessing.push({
|
||||
operation: 'contribution',
|
||||
options: {
|
||||
columns: percentMetricLabels,
|
||||
rename_columns: percentMetricLabels.map(x => `%${x}`),
|
||||
rename_columns: percentMetricLabels.map(m => `%${m}`),
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
} else {
|
||||
postProcessing.push({
|
||||
operation: 'contribution',
|
||||
options: {
|
||||
columns: percentMetricLabels,
|
||||
rename_columns: percentMetricLabels.map(m => `%${m}`),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add the operator for the time comparison if some is selected
|
||||
if (!isEmpty(timeOffsets)) {
|
||||
postProcessing.push(timeCompareOperator(formData, baseQueryObject));
|
||||
@@ -252,6 +269,26 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
});
|
||||
|
||||
const extraQueries: QueryObject[] = [];
|
||||
|
||||
const calculationMode = formData.percent_metric_calculation || 'row_limit';
|
||||
|
||||
if (
|
||||
calculationMode === 'all_records' &&
|
||||
percentMetrics &&
|
||||
percentMetrics.length > 0
|
||||
) {
|
||||
extraQueries.push({
|
||||
...queryObject,
|
||||
columns: [],
|
||||
metrics: percentMetrics,
|
||||
post_processing: [],
|
||||
row_limit: 0,
|
||||
row_offset: 0,
|
||||
orderby: [],
|
||||
is_timeseries: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
metrics?.length &&
|
||||
formData.show_totals &&
|
||||
@@ -263,8 +300,8 @@ const buildQuery: BuildQuery<TableChartFormData> = (
|
||||
row_limit: 0,
|
||||
row_offset: 0,
|
||||
post_processing: [],
|
||||
order_desc: undefined, // we don't need orderby stuff here,
|
||||
orderby: undefined, // because this query will be used for get total aggregation.
|
||||
order_desc: undefined,
|
||||
orderby: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -161,12 +161,30 @@ const generateComparisonColumns = (colname: string) => [
|
||||
`△ ${colname}`,
|
||||
`% ${colname}`,
|
||||
];
|
||||
|
||||
/**
|
||||
* Generate column types for the comparison columns.
|
||||
*/
|
||||
const generateComparisonColumnTypes = (count: number) =>
|
||||
Array(count).fill(GenericDataType.Numeric);
|
||||
|
||||
const percentMetricCalculationControl: ControlConfig<'SelectControl'> = {
|
||||
type: 'SelectControl',
|
||||
label: t('Percentage metric calculation'),
|
||||
description: t(
|
||||
'Row Limit: percentages are calculated based on the subset of data retrieved, respecting the row limit. ' +
|
||||
'All Records: Percentages are calculated based on the total dataset, ignoring the row limit.',
|
||||
),
|
||||
default: 'row_limit',
|
||||
clearable: false,
|
||||
choices: [
|
||||
['row_limit', t('Row limit')],
|
||||
['all_records', t('All records')],
|
||||
],
|
||||
visibility: isAggMode,
|
||||
renderTrigger: false,
|
||||
};
|
||||
|
||||
const processComparisonColumns = (columns: any[], suffix: string) =>
|
||||
columns
|
||||
.map(col => {
|
||||
@@ -433,6 +451,13 @@ const config: ControlPanelConfig = {
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'percent_metric_calculation',
|
||||
config: percentMetricCalculationControl,
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
{
|
||||
name: 'show_totals',
|
||||
|
||||
@@ -148,5 +148,92 @@ describe('plugin-chart-table', () => {
|
||||
expect(queries[1].extras?.time_grain_sqla).toEqual(TimeGranularity.MONTH);
|
||||
expect(queries[1].extras?.where).toEqual("(status IN ('In Process'))");
|
||||
});
|
||||
|
||||
describe('Percent Metric Calculation Modes', () => {
|
||||
const baseFormDataWithPercents: TableChartFormData = {
|
||||
...basicFormData,
|
||||
query_mode: QueryMode.Aggregate,
|
||||
metrics: ['count'],
|
||||
percent_metrics: ['sum_sales'],
|
||||
groupby: ['category'],
|
||||
};
|
||||
|
||||
it('should default to row_limit mode with single query', () => {
|
||||
const { queries } = buildQuery(baseFormDataWithPercents);
|
||||
|
||||
expect(queries).toHaveLength(1);
|
||||
expect(queries[0].metrics).toEqual(['count', 'sum_sales']);
|
||||
expect(queries[0].post_processing).toEqual([
|
||||
{
|
||||
operation: 'contribution',
|
||||
options: {
|
||||
columns: ['sum_sales'],
|
||||
rename_columns: ['%sum_sales'],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create extra query in all_records mode', () => {
|
||||
const formData = {
|
||||
...baseFormDataWithPercents,
|
||||
percent_metric_calculation: 'all_records',
|
||||
};
|
||||
|
||||
const { queries } = buildQuery(formData);
|
||||
|
||||
expect(queries).toHaveLength(2);
|
||||
|
||||
expect(queries[0].post_processing).toEqual([
|
||||
{
|
||||
operation: 'contribution',
|
||||
options: {
|
||||
columns: ['sum_sales'],
|
||||
rename_columns: ['%sum_sales'],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(queries[1]).toMatchObject({
|
||||
columns: [],
|
||||
metrics: ['sum_sales'],
|
||||
post_processing: [],
|
||||
row_limit: 0,
|
||||
row_offset: 0,
|
||||
orderby: [],
|
||||
is_timeseries: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with show_totals in all_records mode', () => {
|
||||
const formData = {
|
||||
...baseFormDataWithPercents,
|
||||
percent_metric_calculation: 'all_records',
|
||||
show_totals: true,
|
||||
};
|
||||
|
||||
const { queries } = buildQuery(formData);
|
||||
|
||||
expect(queries).toHaveLength(3);
|
||||
expect(queries[1].metrics).toEqual(['sum_sales']);
|
||||
expect(queries[2].metrics).toEqual(['count', 'sum_sales']);
|
||||
});
|
||||
|
||||
it('should handle empty percent_metrics in all_records mode', () => {
|
||||
const formData = {
|
||||
...basicFormData,
|
||||
query_mode: QueryMode.Aggregate,
|
||||
metrics: ['count'],
|
||||
percent_metrics: [],
|
||||
percent_metric_calculation: 'all_records',
|
||||
groupby: ['category'],
|
||||
};
|
||||
|
||||
const { queries } = buildQuery(formData);
|
||||
|
||||
expect(queries).toHaveLength(1);
|
||||
expect(queries[0].post_processing).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user