This is an automated email from the ASF dual-hosted git repository.
enzomartellucci 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 3347b9bf6c fix(table): only show increase/decrease color options when
time comparison enabled (#37362)
3347b9bf6c is described below
commit 3347b9bf6cfcf4525f2af283c668f4d36febc33f
Author: Jamile Celento <[email protected]>
AuthorDate: Wed Jan 28 09:32:30 2026 -0300
fix(table): only show increase/decrease color options when time comparison
enabled (#37362)
---
.../src/controlPanel.tsx | 33 ++--
.../test/controlPanel.test.tsx | 208 +++++++++++++++++++++
.../plugin-chart-table/src/controlPanel.tsx | 35 ++--
.../plugin-chart-table/test/controlPanel.test.tsx | 208 +++++++++++++++++++++
4 files changed, 455 insertions(+), 29 deletions(-)
diff --git
a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/controlPanel.tsx
b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/controlPanel.tsx
index 50d40f9429..1277a8f7f3 100644
--- a/superset-frontend/plugins/plugin-chart-ag-grid-table/src/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-ag-grid-table/src/controlPanel.tsx
@@ -683,16 +683,6 @@ const config: ControlPanelConfig = {
type: 'ConditionalFormattingControl',
renderTrigger: true,
label: t('Custom conditional formatting'),
- extraColorChoices: [
- {
- value: ColorSchemeEnum.Green,
- label: t('Green for increase, red for decrease'),
- },
- {
- value: ColorSchemeEnum.Red,
- label: t('Red for increase, green for decrease'),
- },
- ],
description: t(
'Apply conditional color formatting to numeric columns',
),
@@ -705,6 +695,22 @@ const config: ControlPanelConfig = {
)
? (explore?.datasource as Dataset)?.verbose_map
: (explore?.datasource?.columns ?? {});
+ const timeCompareValue =
explore?.controls?.time_compare?.value;
+ const hasTimeComparison = !isEmpty(timeCompareValue);
+
+ const extraColorChoices = hasTimeComparison
+ ? [
+ {
+ value: ColorSchemeEnum.Green,
+ label: t('Green for increase, red for decrease'),
+ },
+ {
+ value: ColorSchemeEnum.Red,
+ label: t('Red for increase, green for decrease'),
+ },
+ ]
+ : [];
+
const chartStatus = chart?.chartStatus;
const { colnames, coltypes } =
chart?.queriesResponse?.[0] ?? {};
@@ -724,12 +730,10 @@ const config: ControlPanelConfig = {
colnames && coltypes[colnames?.indexOf(colname)],
}))
: [];
- const columnOptions = explore?.controls?.time_compare?.value
+ const columnOptions = hasTimeComparison
? processComparisonColumns(
numericColumns || [],
- ensureIsArray(
- explore?.controls?.time_compare?.value,
- )[0]?.toString() || '',
+ ensureIsArray(timeCompareValue)[0]?.toString() || '',
)
: numericColumns;
@@ -737,6 +741,7 @@ const config: ControlPanelConfig = {
removeIrrelevantConditions: chartStatus === 'success',
columnOptions,
verboseMap,
+ extraColorChoices,
};
},
},
diff --git
a/superset-frontend/plugins/plugin-chart-ag-grid-table/test/controlPanel.test.tsx
b/superset-frontend/plugins/plugin-chart-ag-grid-table/test/controlPanel.test.tsx
new file mode 100644
index 0000000000..70501b17bd
--- /dev/null
+++
b/superset-frontend/plugins/plugin-chart-ag-grid-table/test/controlPanel.test.tsx
@@ -0,0 +1,208 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { GenericDataType } from '@apache-superset/core/api/core';
+import { QueryFormData } from '@superset-ui/core';
+import {
+ Dataset,
+ isCustomControlItem,
+ ControlConfig,
+ ControlPanelState,
+ ControlState,
+} from '@superset-ui/chart-controls';
+import config from '../src/controlPanel';
+import { ColorSchemeEnum } from '../src/types';
+
+const findConditionalFormattingControl = (): ControlConfig | null => {
+ for (const section of config.controlPanelSections) {
+ if (!section) continue;
+ for (const row of section.controlSetRows) {
+ for (const control of row) {
+ if (
+ isCustomControlItem(control) &&
+ control.name === 'conditional_formatting'
+ ) {
+ return control.config;
+ }
+ }
+ }
+ }
+ return null;
+};
+
+const createMockControlState = (value: string[] | undefined): ControlState =>
({
+ type: 'SelectControl',
+ value,
+ label: '',
+ default: undefined,
+ renderTrigger: false,
+});
+
+const createMockExplore = (
+ timeCompareValue: string[] | undefined,
+): ControlPanelState => ({
+ slice: { slice_id: 123 },
+ datasource: {
+ verbose_map: { col1: 'Column 1', col2: 'Column 2' },
+ columns: [],
+ } as Partial<Dataset> as Dataset,
+ controls: {
+ time_compare: createMockControlState(timeCompareValue),
+ },
+ form_data: {
+ time_compare: timeCompareValue,
+ datasource: 'test',
+ viz_type: 'table',
+ } as QueryFormData,
+ common: {},
+ metadata: {},
+});
+
+const createMockChart = () => ({
+ chartStatus: 'success' as const,
+ queriesResponse: [
+ {
+ colnames: ['col1', 'col2'],
+ coltypes: [GenericDataType.Numeric, GenericDataType.Numeric],
+ },
+ ],
+});
+
+const createMockControlStateForConditionalFormatting = (): ControlState => ({
+ type: 'CollectionControl',
+ value: [],
+ label: '',
+ default: undefined,
+ renderTrigger: false,
+});
+
+test('extraColorChoices not included when time comparison is disabled', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+ expect(controlConfig?.mapStateToProps).toBeTruthy();
+
+ const explore = createMockExplore(undefined);
+ const chart = createMockChart();
+ const result = controlConfig!.mapStateToProps!(
+ explore,
+ createMockControlStateForConditionalFormatting(),
+ chart,
+ );
+
+ expect(result.extraColorChoices).toEqual([]);
+ expect(result.columnOptions).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ value: 'col1' }),
+ expect.objectContaining({ value: 'col2' }),
+ ]),
+ );
+});
+
+test('extraColorChoices included when time comparison is enabled', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+
+ const explore = createMockExplore(['P1D']);
+ const chart = createMockChart();
+ const result = controlConfig!.mapStateToProps!(
+ explore,
+ createMockControlStateForConditionalFormatting(),
+ chart,
+ );
+
+ expect(result.extraColorChoices).toEqual([
+ {
+ value: ColorSchemeEnum.Green,
+ label: expect.stringContaining('Green for increase'),
+ },
+ {
+ value: ColorSchemeEnum.Red,
+ label: expect.stringContaining('Red for increase'),
+ },
+ ]);
+ expect(result.columnOptions).not.toEqual(
+ expect.arrayContaining([expect.objectContaining({ value: 'col1' })]),
+ );
+});
+
+test('extraColorChoices not included when time_compare is empty array', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+
+ const explore = createMockExplore([]);
+ const chart = createMockChart();
+ const result = controlConfig!.mapStateToProps!(
+ explore,
+ createMockControlStateForConditionalFormatting(),
+ chart,
+ );
+
+ expect(result.extraColorChoices).toEqual([]);
+});
+
+test('consistency between extraColorChoices and columnOptions', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+
+ const explore = createMockExplore(['P1D']);
+ const chart = createMockChart();
+ const result = controlConfig!.mapStateToProps!(
+ explore,
+ createMockControlStateForConditionalFormatting(),
+ chart,
+ );
+
+ const hasExtraColorChoices = result.extraColorChoices.length > 0;
+ const hasComparisonColumns = result.columnOptions.some(
+ (col: { value: string }) =>
+ col.value.includes('Main') ||
+ col.value.includes('#') ||
+ col.value.includes('△'),
+ );
+
+ expect(hasExtraColorChoices).toBe(true);
+ expect(hasComparisonColumns).toBe(true);
+});
+
+test('uses controls.time_compare.value not form_data.time_compare', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+
+ const explore: ControlPanelState = {
+ ...createMockExplore(undefined),
+ form_data: {
+ ...createMockExplore(undefined).form_data,
+ time_compare: ['P1D'],
+ },
+ };
+ const chart = createMockChart();
+ const result = controlConfig!.mapStateToProps!(
+ explore,
+ createMockControlStateForConditionalFormatting(),
+ chart,
+ );
+
+ expect(result.extraColorChoices).toEqual([]);
+});
+
+test('static extraColorChoices removed from config', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+
+ expect(controlConfig?.extraColorChoices).toBeUndefined();
+});
diff --git a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
index cdff93eb8e..2d051cf97c 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
@@ -746,16 +746,6 @@ const config: ControlPanelConfig = {
type: 'ConditionalFormattingControl',
renderTrigger: true,
label: t('Custom conditional formatting'),
- extraColorChoices: [
- {
- value: ColorSchemeEnum.Green,
- label: t('Green for increase, red for decrease'),
- },
- {
- value: ColorSchemeEnum.Red,
- label: t('Red for increase, green for decrease'),
- },
- ],
description: t(
'Apply conditional color formatting to numeric columns',
),
@@ -768,6 +758,22 @@ const config: ControlPanelConfig = {
)
? (explore?.datasource as Dataset)?.verbose_map
: (explore?.datasource?.columns ?? {});
+ const timeCompareValue =
explore?.controls?.time_compare?.value;
+ const hasTimeComparison = !isEmpty(timeCompareValue);
+
+ const extraColorChoices = hasTimeComparison
+ ? [
+ {
+ value: ColorSchemeEnum.Green,
+ label: t('Green for increase, red for decrease'),
+ },
+ {
+ value: ColorSchemeEnum.Red,
+ label: t('Red for increase, green for decrease'),
+ },
+ ]
+ : [];
+
const chartStatus = chart?.chartStatus;
const value = _?.value ?? [];
if (value && Array.isArray(value)) {
@@ -796,7 +802,7 @@ const config: ControlPanelConfig = {
? colnames.reduce((acc, colname, index) => {
if (
coltypes[index] === GenericDataType.Numeric ||
- (!explore?.controls?.time_compare?.value &&
+ (!hasTimeComparison &&
(coltypes[index] === GenericDataType.String ||
coltypes[index] === GenericDataType.Boolean))
) {
@@ -811,12 +817,10 @@ const config: ControlPanelConfig = {
return acc;
}, [])
: [];
- const columnOptions = explore?.controls?.time_compare?.value
+ const columnOptions = hasTimeComparison
? processComparisonColumns(
numericColumns || [],
- ensureIsArray(
- explore?.controls?.time_compare?.value,
- )[0]?.toString() || '',
+ ensureIsArray(timeCompareValue)[0]?.toString() || '',
)
: numericColumns;
@@ -828,6 +832,7 @@ const config: ControlPanelConfig = {
toAllRowCheck: true,
toColorTextCheck: true,
},
+ extraColorChoices,
};
},
},
diff --git
a/superset-frontend/plugins/plugin-chart-table/test/controlPanel.test.tsx
b/superset-frontend/plugins/plugin-chart-table/test/controlPanel.test.tsx
new file mode 100644
index 0000000000..70501b17bd
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-table/test/controlPanel.test.tsx
@@ -0,0 +1,208 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { GenericDataType } from '@apache-superset/core/api/core';
+import { QueryFormData } from '@superset-ui/core';
+import {
+ Dataset,
+ isCustomControlItem,
+ ControlConfig,
+ ControlPanelState,
+ ControlState,
+} from '@superset-ui/chart-controls';
+import config from '../src/controlPanel';
+import { ColorSchemeEnum } from '../src/types';
+
+const findConditionalFormattingControl = (): ControlConfig | null => {
+ for (const section of config.controlPanelSections) {
+ if (!section) continue;
+ for (const row of section.controlSetRows) {
+ for (const control of row) {
+ if (
+ isCustomControlItem(control) &&
+ control.name === 'conditional_formatting'
+ ) {
+ return control.config;
+ }
+ }
+ }
+ }
+ return null;
+};
+
+const createMockControlState = (value: string[] | undefined): ControlState =>
({
+ type: 'SelectControl',
+ value,
+ label: '',
+ default: undefined,
+ renderTrigger: false,
+});
+
+const createMockExplore = (
+ timeCompareValue: string[] | undefined,
+): ControlPanelState => ({
+ slice: { slice_id: 123 },
+ datasource: {
+ verbose_map: { col1: 'Column 1', col2: 'Column 2' },
+ columns: [],
+ } as Partial<Dataset> as Dataset,
+ controls: {
+ time_compare: createMockControlState(timeCompareValue),
+ },
+ form_data: {
+ time_compare: timeCompareValue,
+ datasource: 'test',
+ viz_type: 'table',
+ } as QueryFormData,
+ common: {},
+ metadata: {},
+});
+
+const createMockChart = () => ({
+ chartStatus: 'success' as const,
+ queriesResponse: [
+ {
+ colnames: ['col1', 'col2'],
+ coltypes: [GenericDataType.Numeric, GenericDataType.Numeric],
+ },
+ ],
+});
+
+const createMockControlStateForConditionalFormatting = (): ControlState => ({
+ type: 'CollectionControl',
+ value: [],
+ label: '',
+ default: undefined,
+ renderTrigger: false,
+});
+
+test('extraColorChoices not included when time comparison is disabled', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+ expect(controlConfig?.mapStateToProps).toBeTruthy();
+
+ const explore = createMockExplore(undefined);
+ const chart = createMockChart();
+ const result = controlConfig!.mapStateToProps!(
+ explore,
+ createMockControlStateForConditionalFormatting(),
+ chart,
+ );
+
+ expect(result.extraColorChoices).toEqual([]);
+ expect(result.columnOptions).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ value: 'col1' }),
+ expect.objectContaining({ value: 'col2' }),
+ ]),
+ );
+});
+
+test('extraColorChoices included when time comparison is enabled', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+
+ const explore = createMockExplore(['P1D']);
+ const chart = createMockChart();
+ const result = controlConfig!.mapStateToProps!(
+ explore,
+ createMockControlStateForConditionalFormatting(),
+ chart,
+ );
+
+ expect(result.extraColorChoices).toEqual([
+ {
+ value: ColorSchemeEnum.Green,
+ label: expect.stringContaining('Green for increase'),
+ },
+ {
+ value: ColorSchemeEnum.Red,
+ label: expect.stringContaining('Red for increase'),
+ },
+ ]);
+ expect(result.columnOptions).not.toEqual(
+ expect.arrayContaining([expect.objectContaining({ value: 'col1' })]),
+ );
+});
+
+test('extraColorChoices not included when time_compare is empty array', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+
+ const explore = createMockExplore([]);
+ const chart = createMockChart();
+ const result = controlConfig!.mapStateToProps!(
+ explore,
+ createMockControlStateForConditionalFormatting(),
+ chart,
+ );
+
+ expect(result.extraColorChoices).toEqual([]);
+});
+
+test('consistency between extraColorChoices and columnOptions', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+
+ const explore = createMockExplore(['P1D']);
+ const chart = createMockChart();
+ const result = controlConfig!.mapStateToProps!(
+ explore,
+ createMockControlStateForConditionalFormatting(),
+ chart,
+ );
+
+ const hasExtraColorChoices = result.extraColorChoices.length > 0;
+ const hasComparisonColumns = result.columnOptions.some(
+ (col: { value: string }) =>
+ col.value.includes('Main') ||
+ col.value.includes('#') ||
+ col.value.includes('△'),
+ );
+
+ expect(hasExtraColorChoices).toBe(true);
+ expect(hasComparisonColumns).toBe(true);
+});
+
+test('uses controls.time_compare.value not form_data.time_compare', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+
+ const explore: ControlPanelState = {
+ ...createMockExplore(undefined),
+ form_data: {
+ ...createMockExplore(undefined).form_data,
+ time_compare: ['P1D'],
+ },
+ };
+ const chart = createMockChart();
+ const result = controlConfig!.mapStateToProps!(
+ explore,
+ createMockControlStateForConditionalFormatting(),
+ chart,
+ );
+
+ expect(result.extraColorChoices).toEqual([]);
+});
+
+test('static extraColorChoices removed from config', () => {
+ const controlConfig = findConditionalFormattingControl();
+ expect(controlConfig).toBeTruthy();
+
+ expect(controlConfig?.extraColorChoices).toBeUndefined();
+});