This is an automated email from the ASF dual-hosted git repository.
msyavuz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 02cb1f1a28a fix(table): resolve auto-detected currency on time
comparison columns (#44649)
02cb1f1a28a is described below
commit 02cb1f1a28a55827256c520a0c99fe6a6b276bef
Author: Mehmet Salih Yavuz <[email protected]>
AuthorDate: Fri Sep 25 15:29:52 2026 +0300
fix(table): resolve auto-detected currency on time comparison columns
(#44649)
---
.../superset-ui-core/src/currency-format/utils.ts | 22 +++++++++++
.../test/currency-format/utils.test.ts | 33 ++++++++++++++++
.../src/transformProps.ts | 44 ++++++++++++---------
.../test/transformProps.test.ts | 45 ++++++++++++++++++++++
.../plugin-chart-table/src/transformProps.ts | 44 ++++++++++++---------
.../plugin-chart-table/test/TableChart.test.tsx | 32 +++++++++++++++
6 files changed, 184 insertions(+), 36 deletions(-)
diff --git
a/superset-frontend/packages/superset-ui-core/src/currency-format/utils.ts
b/superset-frontend/packages/superset-ui-core/src/currency-format/utils.ts
index 53a704ab97f..0036bd9e8dd 100644
--- a/superset-frontend/packages/superset-ui-core/src/currency-format/utils.ts
+++ b/superset-frontend/packages/superset-ui-core/src/currency-format/utils.ts
@@ -75,6 +75,28 @@ export const resolveAutoCurrency = (
return null; // Mixed currencies
};
+/**
+ * Resolves an AUTO currency to the backend-detected one when the currency
+ * code column isn't part of the query results, since CurrencyFormatter can
+ * only detect AUTO per row from that column.
+ */
+export const resolveDetectedCurrency = (
+ currency: Currency | undefined,
+ detectedCurrency: string | null | undefined,
+ currencyCodeColumn: string | undefined,
+ colnames: string[] | undefined,
+): Currency | undefined => {
+ if (
+ currency?.symbol !== AUTO_CURRENCY_SYMBOL ||
+ !detectedCurrency ||
+ (currencyCodeColumn && colnames?.includes(currencyCodeColumn))
+ ) {
+ return currency;
+ }
+ const symbol = normalizeCurrency(detectedCurrency);
+ return symbol ? { ...currency, symbol } : currency;
+};
+
const getEffectiveCurrencyFormat = (
resolvedCurrencyFormat: Currency | undefined | null,
savedFormat: Currency | undefined,
diff --git
a/superset-frontend/packages/superset-ui-core/test/currency-format/utils.test.ts
b/superset-frontend/packages/superset-ui-core/test/currency-format/utils.test.ts
index facf7183806..0f428f3ab23 100644
---
a/superset-frontend/packages/superset-ui-core/test/currency-format/utils.test.ts
+++
b/superset-frontend/packages/superset-ui-core/test/currency-format/utils.test.ts
@@ -30,6 +30,7 @@ import {
import {
analyzeCurrencyInData,
resolveAutoCurrency,
+ resolveDetectedCurrency,
} from '../../src/currency-format/utils';
test('buildCustomFormatters without saved metrics returns empty object', () =>
{
@@ -412,3 +413,35 @@ test('getValueFormatter returns NumberFormatter via line
205 when AUTO resolves
);
expect(formatter).toBeInstanceOf(NumberFormatter);
});
+
+test('resolveDetectedCurrency uses the detected currency when the currency
column is not in the results', () => {
+ expect(
+ resolveDetectedCurrency(
+ { symbol: 'AUTO', symbolPosition: 'prefix' },
+ 'gbp',
+ 'currency_code',
+ ['metric'],
+ ),
+ ).toEqual({ symbol: 'GBP', symbolPosition: 'prefix' });
+});
+
+test('resolveDetectedCurrency keeps AUTO when the currency column is in the
results', () => {
+ const currency: Currency = { symbol: 'AUTO', symbolPosition: 'prefix' };
+ expect(
+ resolveDetectedCurrency(currency, 'GBP', 'currency_code', [
+ 'metric',
+ 'currency_code',
+ ]),
+ ).toBe(currency);
+});
+
+test('resolveDetectedCurrency leaves non-AUTO and unresolvable currencies
untouched', () => {
+ const usd: Currency = { symbol: 'USD', symbolPosition: 'prefix' };
+ const auto: Currency = { symbol: 'AUTO', symbolPosition: 'prefix' };
+ expect(resolveDetectedCurrency(usd, 'GBP', 'currency_code', [])).toBe(usd);
+ expect(resolveDetectedCurrency(auto, null, 'currency_code', [])).toBe(auto);
+ expect(resolveDetectedCurrency(auto, 'nope', 'currency_code',
[])).toBe(auto);
+ expect(resolveDetectedCurrency(undefined, 'GBP', undefined, [])).toBe(
+ undefined,
+ );
+});
diff --git
a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/transformProps.ts
b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/transformProps.ts
index fde606dae12..231c5f73464 100644
--- a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/transformProps.ts
@@ -29,7 +29,7 @@ import {
getNumberFormatter,
getTimeFormatter,
getTimeFormatterForGranularity,
- normalizeCurrency,
+ resolveDetectedCurrency,
NumberFormats,
QueryMode,
SMART_DATE_ID,
@@ -147,6 +147,7 @@ const getComparisonColFormatter = (
columnConfig: Record<string, TableColumnConfig>,
savedFormat: string | undefined,
savedCurrency: Currency | undefined,
+ resolveCurrency: (currency: Currency | undefined) => Currency | undefined,
) => {
const currentColConfig = getComparisonColConfig(
label,
@@ -161,7 +162,9 @@ const getComparisonColFormatter = (
if (label === '%') {
formatter = getNumberFormatter(currentColNumberFormat || PERCENT_3_POINT);
} else if (currentColNumberFormat || hasCurrency) {
- const currency = currentColConfig.currencyFormat || savedCurrency;
+ const currency = resolveCurrency(
+ currentColConfig.currencyFormat || savedCurrency,
+ );
const numberFormat = currentColNumberFormat || savedFormat;
formatter = currency
? new CurrencyFormatter({
@@ -254,9 +257,19 @@ const processComparisonColumns = (
) =>
columns.flatMap(col => {
const {
- datasource: { columnFormats, currencyFormats },
+ datasource: { columnFormats, currencyFormats, currencyCodeColumn },
rawFormData: { column_config: columnConfig = {} },
+ queriesData,
} = props;
+ const { detected_currency: detectedCurrency, colnames } =
+ queriesData[0] || {};
+ const resolveCurrency = (currency: Currency | undefined) =>
+ resolveDetectedCurrency(
+ currency,
+ detectedCurrency,
+ currencyCodeColumn,
+ colnames,
+ );
const savedFormat = columnFormats?.[col.key];
const savedCurrency = currencyFormats?.[col.key];
const originalLabel = col.label;
@@ -279,6 +292,7 @@ const processComparisonColumns = (
columnConfig,
savedFormat,
savedCurrency,
+ resolveCurrency,
),
},
{
@@ -294,6 +308,7 @@ const processComparisonColumns = (
columnConfig,
savedFormat,
savedCurrency,
+ resolveCurrency,
),
},
{
@@ -309,6 +324,7 @@ const processComparisonColumns = (
columnConfig,
savedFormat,
savedCurrency,
+ resolveCurrency,
),
},
{
@@ -324,6 +340,7 @@ const processComparisonColumns = (
columnConfig,
savedFormat,
savedCurrency,
+ resolveCurrency,
),
},
];
@@ -477,21 +494,12 @@ const processColumns = memoizePerChart(function
processColumns(
// percent metrics have a default format
formatter = getNumberFormatter(numberFormat || PERCENT_3_POINT);
} else if (isMetric || (isNumber && (numberFormat || currency))) {
- // Resolve AUTO currency when currency column isn't in query results
- let resolvedCurrency = currency;
- if (
- currency?.symbol === 'AUTO' &&
- detectedCurrency &&
- (!currencyCodeColumn || !colnames?.includes(currencyCodeColumn))
- ) {
- const normalizedCurrency = normalizeCurrency(detectedCurrency);
- if (normalizedCurrency) {
- resolvedCurrency = {
- ...currency,
- symbol: normalizedCurrency,
- };
- }
- }
+ const resolvedCurrency = resolveDetectedCurrency(
+ currency,
+ detectedCurrency,
+ currencyCodeColumn,
+ colnames,
+ );
formatter = resolvedCurrency?.symbol
? new CurrencyFormatter({
d3Format: numberFormat,
diff --git
a/superset-frontend/plugins/plugin-chart-ag-grid-table/test/transformProps.test.ts
b/superset-frontend/plugins/plugin-chart-ag-grid-table/test/transformProps.test.ts
index 61c12b3bc06..bcfefaa1861 100644
---
a/superset-frontend/plugins/plugin-chart-ag-grid-table/test/transformProps.test.ts
+++
b/superset-frontend/plugins/plugin-chart-ag-grid-table/test/transformProps.test.ts
@@ -329,6 +329,51 @@ test('does not mistake the all_records percent-metric
query for the totals query
expect(result.totals).toEqual({ sum__num: 42 });
});
+test('resolves AUTO currency on comparison columns from detected_currency', ()
=> {
+ const autoCurrency = { symbol: 'AUTO', symbolPosition: 'prefix' };
+ const comparisonKeys = ['Main metric_1', '# metric_1', '△ metric_1'];
+ const props = createMockChartProps({
+ rawFormData: {
+ viz_type: 'table',
+ datasource: '1__table',
+ slice_id: 1,
+ query_mode: QueryMode.Aggregate,
+ metrics: ['metric_1'],
+ percent_metrics: [],
+ column_config: Object.fromEntries(
+ comparisonKeys.map(key => [key, { currencyFormat: autoCurrency }]),
+ ),
+ table_timestamp_format: '',
+ time_compare: ['1 year ago'],
+ comparison_type: 'values',
+ },
+ datasource: {
+ columns: [],
+ metrics: [],
+ columnFormats: {},
+ currencyFormats: {},
+ verboseMap: {},
+ currencyCodeColumn: 'currency_code',
+ },
+ queriesData: [
+ {
+ data: [{ metric_1: 100, 'metric_1__1 year ago': 80 }],
+ colnames: ['metric_1', 'metric_1__1 year ago'],
+ coltypes: [GenericDataType.Numeric, GenericDataType.Numeric],
+ rowcount: 1,
+ detected_currency: 'GBP',
+ },
+ { data: [{ rowcount: 1 }] },
+ ] as unknown as TableChartProps['queriesData'],
+ } as unknown as Partial<TableChartProps>);
+
+ const { columns } = transformProps(props);
+ comparisonKeys.forEach(key => {
+ const formatted = columns.find(col => col.key === key)?.formatter?.(100);
+ expect(formatted).toContain('£');
+ });
+});
+
test('excludes Green/Red color-scheme rules from columnColorFormatters', () =>
{
// Green/Red rules are rendered via the increase/decrease path, so they must
// not reach getColorFormatters, which would treat the scheme name as a hex
diff --git a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
index 21442b9f0fa..b3e3e62bf99 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
@@ -30,7 +30,7 @@ import {
getTimeFormatter,
getTimeFormatterForGranularity,
isAdhocColumn,
- normalizeCurrency,
+ resolveDetectedCurrency,
NumberFormats,
QueryMode,
SMART_DATE_ID,
@@ -299,21 +299,12 @@ const processColumns = memoizeOne(function processColumns(
// percent metrics have a default format
formatter = getNumberFormatter(numberFormat || PERCENT_3_POINT);
} else if (isMetric || (isNumber && (numberFormat || currency))) {
- // Resolve AUTO currency when currency column isn't in query results
- let resolvedCurrency = currency;
- if (
- currency?.symbol === 'AUTO' &&
- detectedCurrency &&
- (!currencyCodeColumn || !colnames?.includes(currencyCodeColumn))
- ) {
- const normalizedCurrency = normalizeCurrency(detectedCurrency);
- if (normalizedCurrency) {
- resolvedCurrency = {
- ...currency,
- symbol: normalizedCurrency,
- };
- }
- }
+ const resolvedCurrency = resolveDetectedCurrency(
+ currency,
+ detectedCurrency,
+ currencyCodeColumn,
+ colnames,
+ );
formatter = resolvedCurrency?.symbol
? new CurrencyFormatter({
d3Format: numberFormat,
@@ -358,6 +349,7 @@ const getComparisonColFormatter = (
columnConfig: Record<string, TableColumnConfig>,
savedFormat: string | undefined,
savedCurrency: Currency | undefined,
+ resolveCurrency: (currency: Currency | undefined) => Currency | undefined,
) => {
const currentColConfig = getComparisonColConfig(
label,
@@ -372,7 +364,9 @@ const getComparisonColFormatter = (
if (label === '%') {
formatter = getNumberFormatter(currentColNumberFormat || PERCENT_3_POINT);
} else if (currentColNumberFormat || hasCurrency) {
- const currency = currentColConfig.currencyFormat || savedCurrency;
+ const currency = resolveCurrency(
+ currentColConfig.currencyFormat || savedCurrency,
+ );
const numberFormat = currentColNumberFormat || savedFormat;
formatter = currency
? new CurrencyFormatter({
@@ -391,9 +385,19 @@ const processComparisonColumns = (
) =>
columns.flatMap(col => {
const {
- datasource: { columnFormats, currencyFormats },
+ datasource: { columnFormats, currencyFormats, currencyCodeColumn },
rawFormData: { column_config: columnConfig = {} },
+ queriesData,
} = props;
+ const { detected_currency: detectedCurrency, colnames } =
+ queriesData[0] || {};
+ const resolveCurrency = (currency: Currency | undefined) =>
+ resolveDetectedCurrency(
+ currency,
+ detectedCurrency,
+ currencyCodeColumn,
+ colnames,
+ );
const savedFormat = columnFormats?.[col.key];
const savedCurrency = currencyFormats?.[col.key];
const originalLabel = col.label;
@@ -415,6 +419,7 @@ const processComparisonColumns = (
columnConfig,
savedFormat,
savedCurrency,
+ resolveCurrency,
),
},
{
@@ -429,6 +434,7 @@ const processComparisonColumns = (
columnConfig,
savedFormat,
savedCurrency,
+ resolveCurrency,
),
},
{
@@ -443,6 +449,7 @@ const processComparisonColumns = (
columnConfig,
savedFormat,
savedCurrency,
+ resolveCurrency,
),
},
{
@@ -457,6 +464,7 @@ const processComparisonColumns = (
columnConfig,
savedFormat,
savedCurrency,
+ resolveCurrency,
),
},
];
diff --git
a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
index 9e67f24fd80..01ab2b7651f 100644
--- a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
@@ -335,6 +335,38 @@ describe('plugin-chart-table', () => {
expect(formattedPercentMetric).toBe('0.123');
});
+ test('resolves AUTO currency on comparison columns from
detected_currency', () => {
+ const autoCurrency = { symbol: 'AUTO', symbolPosition: 'prefix' };
+ const comparisonKeys = ['Main metric_1', '# metric_1', '△ metric_1'];
+ const transformedProps = transformProps({
+ ...testData.comparisonWithConfig,
+ rawFormData: {
+ ...testData.comparisonWithConfig.rawFormData,
+ column_config: Object.fromEntries(
+ comparisonKeys.map(key => [key, { currencyFormat: autoCurrency }]),
+ ),
+ },
+ datasource: {
+ ...testData.comparisonWithConfig.datasource,
+ currencyCodeColumn: 'currency_code',
+ },
+ queriesData: [
+ {
+ ...testData.comparisonWithConfig.queriesData[0],
+ detected_currency: 'GBP',
+ },
+ testData.comparisonWithConfig.queriesData[1],
+ ],
+ });
+
+ comparisonKeys.forEach(key => {
+ const formatted = transformedProps.columns
+ .find(col => col.key === key)
+ ?.formatter?.(100);
+ expect(formatted).toContain('£');
+ });
+ });
+
test('should set originalLabel for comparison columns when time_compare
and comparison_type are set', () => {
const transformedProps = transformProps(testData.comparison);