Skip to content

Commit

Permalink
feat(explorer): Add configs and formatting to discrete comparison col…
Browse files Browse the repository at this point in the history
…umns (#29553)
  • Loading branch information
rtexelm authored Jul 25, 2024
1 parent 72caec1 commit dac69e2
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 17 deletions.
49 changes: 45 additions & 4 deletions superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
* under the License.
*/
import {
ChartDataResponseResult,
ensureIsArray,
GenericDataType,
isAdhocColumn,
Expand Down Expand Up @@ -145,6 +144,21 @@ const percentMetricsControl: typeof sharedControls.metrics = {
validators: [],
};

/**
* Generate comparison column names for a given column.
*/
const generateComparisonColumns = (colname: string) => [
`${t('Main')} ${colname}`,
`# ${colname}`,
`△ ${colname}`,
`% ${colname}`,
];
/**
* Generate column types for the comparison columns.
*/
const generateComparisonColumnTypes = (count: number) =>
Array(count).fill(GenericDataType.Numeric);

const processComparisonColumns = (columns: any[], suffix: string) =>
columns
.map(col => {
Expand Down Expand Up @@ -470,10 +484,37 @@ const config: ControlPanelConfig = {
return true;
},
mapStateToProps(explore, _, chart) {
const timeComparisonStatus =
!!explore?.controls?.time_compare?.value;

const { colnames: _colnames, coltypes: _coltypes } =
chart?.queriesResponse?.[0] ?? {};
let colnames: string[] = _colnames || [];
let coltypes: GenericDataType[] = _coltypes || [];

if (timeComparisonStatus) {
/**
* Replace numeric columns with sets of comparison columns.
*/
const updatedColnames: string[] = [];
const updatedColtypes: GenericDataType[] = [];
colnames.forEach((colname, index) => {
if (coltypes[index] === GenericDataType.Numeric) {
updatedColnames.push(
...generateComparisonColumns(colname),
);
updatedColtypes.push(...generateComparisonColumnTypes(4));
} else {
updatedColnames.push(colname);
updatedColtypes.push(coltypes[index]);
}
});

colnames = updatedColnames;
coltypes = updatedColtypes;
}
return {
queryResponse: chart?.queriesResponse?.[0] as
| ChartDataResponseResult
| undefined,
columnsPropsObject: { colnames, coltypes },
};
},
},
Expand Down
82 changes: 78 additions & 4 deletions superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import memoizeOne from 'memoize-one';
import {
ComparisonType,
CurrencyFormatter,
Currency,
DataRecord,
ensureIsArray,
extractTimegrain,
Expand Down Expand Up @@ -53,6 +54,7 @@ import {
DataColumnMeta,
TableChartProps,
TableChartTransformedProps,
TableColumnConfig,
} from './types';

const { PERCENT_3_POINT } = NumberFormats;
Expand Down Expand Up @@ -293,6 +295,48 @@ const processColumns = memoizeOne(function processColumns(
];
}, isEqualColumns);

const getComparisonColConfig = (
label: string,
parentColKey: string,
columnConfig: Record<string, TableColumnConfig>,
) => {
const comparisonKey = `${label} ${parentColKey}`;
const comparisonColConfig = columnConfig[comparisonKey] || {};
return comparisonColConfig;
};

const getComparisonColFormatter = (
label: string,
parentCol: DataColumnMeta,
columnConfig: Record<string, TableColumnConfig>,
savedFormat: string | undefined,
savedCurrency: Currency | undefined,
) => {
const currentColConfig = getComparisonColConfig(
label,
parentCol.key,
columnConfig,
);
const hasCurrency = currentColConfig.currencyFormat?.symbol;
const currentColNumberFormat =
// fallback to parent's number format if not set
currentColConfig.d3NumberFormat || parentCol.config?.d3NumberFormat;
let { formatter } = parentCol;
if (label === '%') {
formatter = getNumberFormatter(currentColNumberFormat || PERCENT_3_POINT);
} else if (currentColNumberFormat || hasCurrency) {
const currency = currentColConfig.currencyFormat || savedCurrency;
const numberFormat = currentColNumberFormat || savedFormat;
formatter = currency
? new CurrencyFormatter({
d3Format: numberFormat,
currency,
})
: getNumberFormatter(numberFormat);
}
return formatter;
};

const processComparisonColumns = (
columns: DataColumnMeta[],
props: TableChartProps,
Expand All @@ -301,12 +345,11 @@ const processComparisonColumns = (
columns
.map(col => {
const {
datasource: { columnFormats },
datasource: { columnFormats, currencyFormats },
rawFormData: { column_config: columnConfig = {} },
} = props;
const config = columnConfig[col.key] || {};
const savedFormat = columnFormats?.[col.key];
const numberFormat = config.d3NumberFormat || savedFormat;
const savedCurrency = currencyFormats?.[col.key];
if (
(col.isMetric || col.isPercentMetric) &&
!col.key.includes(comparisonSuffix) &&
Expand All @@ -317,22 +360,53 @@ const processComparisonColumns = (
...col,
label: t('Main'),
key: `${t('Main')} ${col.key}`,
config: getComparisonColConfig(t('Main'), col.key, columnConfig),
formatter: getComparisonColFormatter(
t('Main'),
col,
columnConfig,
savedFormat,
savedCurrency,
),
},
{
...col,
label: `#`,
key: `# ${col.key}`,
config: getComparisonColConfig(`#`, col.key, columnConfig),
formatter: getComparisonColFormatter(
`#`,
col,
columnConfig,
savedFormat,
savedCurrency,
),
},
{
...col,
label: `△`,
key: `△ ${col.key}`,
config: getComparisonColConfig(`△`, col.key, columnConfig),
formatter: getComparisonColFormatter(
`△`,
col,
columnConfig,
savedFormat,
savedCurrency,
),
},
{
...col,
formatter: getNumberFormatter(numberFormat || PERCENT_3_POINT),
label: `%`,
key: `% ${col.key}`,
config: getComparisonColConfig(`%`, col.key, columnConfig),
formatter: getComparisonColFormatter(
`%`,
col,
columnConfig,
savedFormat,
savedCurrency,
),
},
];
}
Expand Down
110 changes: 110 additions & 0 deletions superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,116 @@ describe('plugin-chart-table', () => {
expect(String(parsedDate)).toBe('2020-01-01 12:34:56');
expect(parsedDate.getTime()).toBe(1577882096000);
});
it('should process comparison columns when time_compare and comparison_type are set', () => {
const transformedProps = transformProps(testData.comparison);

// Check if comparison columns are processed
const comparisonColumns = transformedProps.columns.filter(
col =>
col.label === 'Main' ||
col.label === '#' ||
col.label === '△' ||
col.label === '%',
);

expect(comparisonColumns.length).toBeGreaterThan(0);
expect(comparisonColumns.some(col => col.label === 'Main')).toBe(true);
expect(comparisonColumns.some(col => col.label === '#')).toBe(true);
expect(comparisonColumns.some(col => col.label === '△')).toBe(true);
expect(comparisonColumns.some(col => col.label === '%')).toBe(true);
});

it('should not process comparison columns when time_compare is empty', () => {
const propsWithoutTimeCompare = {
...testData.comparison,
rawFormData: {
...testData.comparison.rawFormData,
time_compare: [],
},
};

const transformedProps = transformProps(propsWithoutTimeCompare);

// Check if comparison columns are not processed
const comparisonColumns = transformedProps.columns.filter(
col =>
col.label === 'Main' ||
col.label === '#' ||
col.label === '△' ||
col.label === '%',
);

expect(comparisonColumns.length).toBe(0);
});

it('should correctly apply column configuration for comparison columns', () => {
const transformedProps = transformProps(testData.comparisonWithConfig);

const comparisonColumns = transformedProps.columns.filter(
col =>
col.key.startsWith('Main') ||
col.key.startsWith('#') ||
col.key.startsWith('△') ||
col.key.startsWith('%'),
);

expect(comparisonColumns).toHaveLength(4);

const mainMetricConfig = comparisonColumns.find(
col => col.key === 'Main metric_1',
);
expect(mainMetricConfig).toBeDefined();
expect(mainMetricConfig?.config).toEqual({ d3NumberFormat: '.2f' });

const hashMetricConfig = comparisonColumns.find(
col => col.key === '# metric_1',
);
expect(hashMetricConfig).toBeDefined();
expect(hashMetricConfig?.config).toEqual({ d3NumberFormat: '.1f' });

const deltaMetricConfig = comparisonColumns.find(
col => col.key === '△ metric_1',
);
expect(deltaMetricConfig).toBeDefined();
expect(deltaMetricConfig?.config).toEqual({ d3NumberFormat: '.0f' });

const percentMetricConfig = comparisonColumns.find(
col => col.key === '% metric_1',
);
expect(percentMetricConfig).toBeDefined();
expect(percentMetricConfig?.config).toEqual({ d3NumberFormat: '.3f' });
});

it('should correctly format comparison columns using getComparisonColFormatter', () => {
const transformedProps = transformProps(testData.comparisonWithConfig);
const comparisonColumns = transformedProps.columns.filter(
col =>
col.key.startsWith('Main') ||
col.key.startsWith('#') ||
col.key.startsWith('△') ||
col.key.startsWith('%'),
);

const formattedMainMetric = comparisonColumns
.find(col => col.key === 'Main metric_1')
?.formatter?.(12345.678);
expect(formattedMainMetric).toBe('12345.68');

const formattedHashMetric = comparisonColumns
.find(col => col.key === '# metric_1')
?.formatter?.(12345.678);
expect(formattedHashMetric).toBe('12345.7');

const formattedDeltaMetric = comparisonColumns
.find(col => col.key === '△ metric_1')
?.formatter?.(12345.678);
expect(formattedDeltaMetric).toBe('12346');

const formattedPercentMetric = comparisonColumns
.find(col => col.key === '% metric_1')
?.formatter?.(0.123456);
expect(formattedPercentMetric).toBe('0.123');
});
});

describe('TableChart', () => {
Expand Down
Loading

0 comments on commit dac69e2

Please sign in to comment.