From e0a0a2254265cce88e5dfca1c3b3ca8b8e155122 Mon Sep 17 00:00:00 2001 From: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:38:07 +0200 Subject: [PATCH] fix(charts): add X Axis Number Format control for numeric X-axis columns (#38809) Co-authored-by: codeant-ai-for-open-source[bot] <244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com> --- .../src/Timeseries/Area/controlPanel.tsx | 27 ++++ .../Timeseries/Regular/Bar/controlPanel.tsx | 34 +++++- .../Timeseries/Regular/Line/controlPanel.tsx | 27 ++++ .../Regular/Scatter/controlPanel.tsx | 60 +++------ .../Regular/SmoothLine/controlPanel.tsx | 27 ++++ .../src/Timeseries/Step/controlPanel.tsx | 27 ++++ .../test/Timeseries/Area/controlPanel.test.ts | 115 ++++++++++++++++++ .../test/Timeseries/Bar/controlPanel.test.ts | 74 +++++++++++ .../test/Timeseries/Line/controlPanel.test.ts | 115 ++++++++++++++++++ .../Timeseries/Scatter/controlPanel.test.ts | 54 ++++---- .../SmoothLine/controlPanel.test.ts | 101 +++++++++++++++ .../test/Timeseries/Step/controlPanel.test.ts | 115 ++++++++++++++++++ .../Chart/DrillBy/DrillBySubmenu.test.tsx | 6 +- .../src/core/sqlLab/sqlLab.test.ts | 25 ++-- .../DashboardBuilder/DashboardBuilder.tsx | 51 ++++---- .../ControlPanelsContainer.test.tsx | 113 +++++++++++++++++ 16 files changed, 865 insertions(+), 106 deletions(-) create mode 100644 superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Area/controlPanel.test.ts create mode 100644 superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Line/controlPanel.test.ts create mode 100644 superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/SmoothLine/controlPanel.test.ts create mode 100644 superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Step/controlPanel.test.ts diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx index 4909ea38a81..5c12e526e9b 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx @@ -17,7 +17,10 @@ * under the License. */ import { t } from '@apache-superset/core/translation'; +import { getColumnLabel, QueryFormColumn } from '@superset-ui/core'; +import { GenericDataType } from '@apache-superset/core/common'; import { + checkColumnType, ControlPanelConfig, ControlPanelsContainerProps, ControlSubSectionHeader, @@ -181,6 +184,30 @@ const config: ControlPanelConfig = { ...sharedControls.x_axis_time_format, default: 'smart_date', description: `${D3_TIME_FORMAT_DOCS}. ${TIME_SERIES_DESCRIPTION_TEXT}`, + visibility: ({ controls }: ControlPanelsContainerProps) => + checkColumnType( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + [GenericDataType.Temporal], + ), + disableStash: true, + resetOnHide: false, + }, + }, + ], + [ + { + name: 'x_axis_number_format', + config: { + ...sharedControls.x_axis_number_format, + default: '~g', + mapStateToProps: undefined, + visibility: ({ controls }: ControlPanelsContainerProps) => + checkColumnType( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + [GenericDataType.Numeric], + ), }, }, ], diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx index 8ab2b643809..3eb0f814f93 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx @@ -17,8 +17,15 @@ * under the License. */ import { t } from '@apache-superset/core/translation'; -import { ensureIsArray, JsonArray } from '@superset-ui/core'; import { + ensureIsArray, + getColumnLabel, + JsonArray, + QueryFormColumn, +} from '@superset-ui/core'; +import { GenericDataType } from '@apache-superset/core/common'; +import { + checkColumnType, ControlPanelConfig, ControlPanelsContainerProps, ControlSetRow, @@ -154,6 +161,13 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] { Boolean(controls?.orientation.value === OrientationType.Vertical); const isHorizontal = (controls: ControlStateMapping) => Boolean(controls?.orientation.value === OrientationType.Horizontal); + const isNumericXAxis = (controls: ControlStateMapping) => + checkColumnType( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + [GenericDataType.Numeric], + ); + return [ [ { @@ -163,7 +177,23 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] { default: 'smart_date', description: `${D3_TIME_FORMAT_DOCS}. ${TIME_SERIES_DESCRIPTION_TEXT}`, visibility: ({ controls }: ControlPanelsContainerProps) => - isXAxis ? isVertical(controls) : isHorizontal(controls), + (isXAxis ? isVertical(controls) : isHorizontal(controls)) && + !isNumericXAxis(controls), + disableStash: true, + resetOnHide: false, + }, + }, + ], + [ + { + name: 'x_axis_number_format', + config: { + ...sharedControls.x_axis_number_format, + default: '~g', + mapStateToProps: undefined, + visibility: ({ controls }: ControlPanelsContainerProps) => + (isXAxis ? isVertical(controls) : isHorizontal(controls)) && + isNumericXAxis(controls), disableStash: true, resetOnHide: false, }, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx index 5d2ca46e3f3..ad1ad61e05f 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx @@ -17,7 +17,10 @@ * under the License. */ import { t } from '@apache-superset/core/translation'; +import { getColumnLabel, QueryFormColumn } from '@superset-ui/core'; +import { GenericDataType } from '@apache-superset/core/common'; import { + checkColumnType, ControlPanelConfig, ControlPanelsContainerProps, ControlSubSectionHeader, @@ -146,6 +149,30 @@ const config: ControlPanelConfig = { ...sharedControls.x_axis_time_format, default: 'smart_date', description: `${D3_TIME_FORMAT_DOCS}. ${TIME_SERIES_DESCRIPTION_TEXT}`, + visibility: ({ controls }: ControlPanelsContainerProps) => + checkColumnType( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + [GenericDataType.Temporal], + ), + disableStash: true, + resetOnHide: false, + }, + }, + ], + [ + { + name: 'x_axis_number_format', + config: { + ...sharedControls.x_axis_number_format, + default: '~g', + mapStateToProps: undefined, + visibility: ({ controls }: ControlPanelsContainerProps) => + checkColumnType( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + [GenericDataType.Numeric], + ), }, }, ], diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx index 97a6114b367..a955da37d52 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx @@ -17,7 +17,10 @@ * under the License. */ import { t } from '@apache-superset/core/translation'; +import { getColumnLabel, QueryFormColumn } from '@superset-ui/core'; +import { GenericDataType } from '@apache-superset/core/common'; import { + checkColumnType, ControlPanelConfig, ControlPanelsContainerProps, ControlSubSectionHeader, @@ -112,53 +115,28 @@ const config: ControlPanelConfig = { ...sharedControls.x_axis_time_format, default: 'smart_date', description: `${D3_TIME_FORMAT_DOCS}. ${TIME_SERIES_DESCRIPTION_TEXT}`, - visibility: ({ controls }: ControlPanelsContainerProps) => { - // check if x axis is a time column - const xAxisColumn = controls?.x_axis?.value; - const xAxisOptions = controls?.x_axis?.options; - - if (!xAxisColumn || !Array.isArray(xAxisOptions)) { - return false; - } - - const xAxisType = xAxisOptions.find( - option => option.column_name === xAxisColumn, - )?.type; - - return ( - typeof xAxisType === 'string' && - xAxisType.toUpperCase().includes('TIME') - ); - }, + visibility: ({ controls }: ControlPanelsContainerProps) => + checkColumnType( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + [GenericDataType.Temporal], + ), + disableStash: true, + resetOnHide: false, }, }, { name: 'x_axis_number_format', config: { ...sharedControls.x_axis_number_format, - visibility: ({ controls }: ControlPanelsContainerProps) => { - // check if x axis is a floating-point column - const xAxisColumn = controls?.x_axis?.value; - const xAxisOptions = controls?.x_axis?.options; - - if (!xAxisColumn || !Array.isArray(xAxisOptions)) { - return false; - } - - const xAxisType = xAxisOptions.find( - option => option.column_name === xAxisColumn, - )?.type; - - if (typeof xAxisType !== 'string') { - return false; - } - - const typeUpper = xAxisType.toUpperCase(); - - return ['FLOAT', 'DOUBLE', 'REAL', 'NUMERIC', 'DECIMAL'].some( - t => typeUpper.includes(t), - ); - }, + default: '~g', + mapStateToProps: undefined, + visibility: ({ controls }: ControlPanelsContainerProps) => + checkColumnType( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + [GenericDataType.Numeric], + ), }, }, ], diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx index c13d89787c3..45128037fca 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx @@ -17,7 +17,10 @@ * under the License. */ import { t } from '@apache-superset/core/translation'; +import { getColumnLabel, QueryFormColumn } from '@superset-ui/core'; +import { GenericDataType } from '@apache-superset/core/common'; import { + checkColumnType, ControlPanelConfig, ControlPanelsContainerProps, ControlSubSectionHeader, @@ -111,6 +114,30 @@ const config: ControlPanelConfig = { ...sharedControls.x_axis_time_format, default: 'smart_date', description: `${D3_TIME_FORMAT_DOCS}. ${TIME_SERIES_DESCRIPTION_TEXT}`, + visibility: ({ controls }: ControlPanelsContainerProps) => + checkColumnType( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + [GenericDataType.Temporal], + ), + disableStash: true, + resetOnHide: false, + }, + }, + ], + [ + { + name: 'x_axis_number_format', + config: { + ...sharedControls.x_axis_number_format, + default: '~g', + mapStateToProps: undefined, + visibility: ({ controls }: ControlPanelsContainerProps) => + checkColumnType( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + [GenericDataType.Numeric], + ), }, }, ], diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx index cbb17d7e0b4..87bcb0adc21 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx @@ -17,7 +17,10 @@ * under the License. */ import { t } from '@apache-superset/core/translation'; +import { getColumnLabel, QueryFormColumn } from '@superset-ui/core'; +import { GenericDataType } from '@apache-superset/core/common'; import { + checkColumnType, ControlPanelConfig, ControlPanelsContainerProps, ControlSubSectionHeader, @@ -163,6 +166,30 @@ const config: ControlPanelConfig = { ...sharedControls.x_axis_time_format, default: 'smart_date', description: `${D3_TIME_FORMAT_DOCS}. ${TIME_SERIES_DESCRIPTION_TEXT}`, + visibility: ({ controls }: ControlPanelsContainerProps) => + checkColumnType( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + [GenericDataType.Temporal], + ), + disableStash: true, + resetOnHide: false, + }, + }, + ], + [ + { + name: 'x_axis_number_format', + config: { + ...sharedControls.x_axis_number_format, + default: '~g', + mapStateToProps: undefined, + visibility: ({ controls }: ControlPanelsContainerProps) => + checkColumnType( + getColumnLabel(controls?.x_axis?.value as QueryFormColumn), + controls?.datasource?.datasource, + [GenericDataType.Numeric], + ), }, }, ], diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Area/controlPanel.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Area/controlPanel.test.ts new file mode 100644 index 00000000000..20a36341694 --- /dev/null +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Area/controlPanel.test.ts @@ -0,0 +1,115 @@ +/** + * 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 { ControlPanelsContainerProps } from '@superset-ui/chart-controls/types'; +import { GenericDataType } from '@apache-superset/core/common'; +import controlPanel from '../../../src/Timeseries/Area/controlPanel'; + +const config = controlPanel; + +const getControl = (controlName: string) => { + for (const section of config.controlPanelSections) { + if (section && section.controlSetRows) { + for (const row of section.controlSetRows) { + for (const control of row) { + if ( + typeof control === 'object' && + control !== null && + 'name' in control && + control.name === controlName + ) { + return control; + } + } + } + } + } + + return null; +}; + +const mockControls = ( + xAxisColumn: string | null, + typeGeneric: GenericDataType | null, +): ControlPanelsContainerProps => { + const columns = + xAxisColumn && typeGeneric !== null + ? [{ column_name: xAxisColumn, type_generic: typeGeneric }] + : []; + + return { + controls: { + // @ts-expect-error + x_axis: { + value: xAxisColumn, + }, + // @ts-expect-error + datasource: { + datasource: { columns }, + }, + }, + }; +}; + +const timeFormatControl: any = getControl('x_axis_time_format'); +const numberFormatControl: any = getControl('x_axis_number_format'); + +test('should include x_axis_time_format control', () => { + expect(timeFormatControl).toBeDefined(); + expect(timeFormatControl.config.default).toBe('smart_date'); +}); + +test('should include x_axis_number_format control', () => { + expect(numberFormatControl).toBeDefined(); + expect(numberFormatControl.config.default).toBe('~g'); +}); + +test('x_axis_time_format should be visible for temporal columns', () => { + const visibilityFn = timeFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('date', GenericDataType.Temporal))).toBe( + true, + ); +}); + +test('x_axis_time_format should be hidden for numeric columns', () => { + const visibilityFn = timeFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('year', GenericDataType.Numeric))).toBe( + false, + ); +}); + +test('x_axis_number_format should be visible for numeric columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('year', GenericDataType.Numeric))).toBe( + true, + ); +}); + +test('x_axis_number_format should be hidden for temporal columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('date', GenericDataType.Temporal))).toBe( + false, + ); +}); + +test('x_axis_number_format should be hidden for string columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('name', GenericDataType.String))).toBe( + false, + ); +}); diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Bar/controlPanel.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Bar/controlPanel.test.ts index 2b56d6ef0ad..01bb1db740c 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Bar/controlPanel.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Bar/controlPanel.test.ts @@ -16,11 +16,14 @@ * specific language governing permissions and limitations * under the License. */ +import { ControlPanelsContainerProps } from '@superset-ui/chart-controls/types'; +import { GenericDataType } from '@apache-superset/core/common'; import controlPanel from '../../../src/Timeseries/Regular/Bar/controlPanel'; import { StackControlOptionsWithoutStream, StackControlsValue, } from '../../../src/constants'; +import { OrientationType } from '../../../src/Timeseries/types'; const config = controlPanel; @@ -218,3 +221,74 @@ test('should preserve stack value when formData does not have stack property', ( expect(result).not.toHaveProperty('stack'); }); + +// x_axis_number_format visibility tests + +const mockBarControls = ( + xAxisColumn: string | null, + typeGeneric: GenericDataType | null, + orientation: string = OrientationType.Vertical, +): ControlPanelsContainerProps => { + const columns = + xAxisColumn && typeGeneric !== null + ? [{ column_name: xAxisColumn, type_generic: typeGeneric }] + : []; + + return { + controls: { + // @ts-expect-error + x_axis: { + value: xAxisColumn, + }, + // @ts-expect-error + orientation: { + value: orientation, + }, + // @ts-expect-error + datasource: { + datasource: { columns }, + }, + }, + }; +}; + +const numberFormatControl: any = getControl('x_axis_number_format'); +const timeFormatControl: any = getControl('x_axis_time_format'); + +test('should include x_axis_number_format control in the panel', () => { + expect(numberFormatControl).toBeDefined(); +}); + +test('x_axis_number_format should be visible for numeric columns in vertical orientation', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockBarControls('year', GenericDataType.Numeric))).toBe( + true, + ); + expect(visibilityFn(mockBarControls('price', GenericDataType.Numeric))).toBe( + true, + ); +}); + +test('x_axis_number_format should be hidden for time columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockBarControls('date', GenericDataType.Temporal))).toBe( + false, + ); +}); + +test('x_axis_number_format should be hidden for non-numeric columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockBarControls('name', GenericDataType.String))).toBe( + false, + ); + expect(visibilityFn(mockBarControls('flag', GenericDataType.Boolean))).toBe( + false, + ); +}); + +test('x_axis_time_format should be hidden for numeric columns', () => { + const visibilityFn = timeFormatControl?.config?.visibility; + expect(visibilityFn(mockBarControls('year', GenericDataType.Numeric))).toBe( + false, + ); +}); diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Line/controlPanel.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Line/controlPanel.test.ts new file mode 100644 index 00000000000..4183d1dba7e --- /dev/null +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Line/controlPanel.test.ts @@ -0,0 +1,115 @@ +/** + * 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 { ControlPanelsContainerProps } from '@superset-ui/chart-controls/types'; +import { GenericDataType } from '@apache-superset/core/common'; +import controlPanel from '../../../src/Timeseries/Regular/Line/controlPanel'; + +const config = controlPanel; + +const getControl = (controlName: string) => { + for (const section of config.controlPanelSections) { + if (section && section.controlSetRows) { + for (const row of section.controlSetRows) { + for (const control of row) { + if ( + typeof control === 'object' && + control !== null && + 'name' in control && + control.name === controlName + ) { + return control; + } + } + } + } + } + + return null; +}; + +const mockControls = ( + xAxisColumn: string | null, + typeGeneric: GenericDataType | null, +): ControlPanelsContainerProps => { + const columns = + xAxisColumn && typeGeneric !== null + ? [{ column_name: xAxisColumn, type_generic: typeGeneric }] + : []; + + return { + controls: { + // @ts-expect-error + x_axis: { + value: xAxisColumn, + }, + // @ts-expect-error + datasource: { + datasource: { columns }, + }, + }, + }; +}; + +const timeFormatControl: any = getControl('x_axis_time_format'); +const numberFormatControl: any = getControl('x_axis_number_format'); + +test('should include x_axis_time_format control', () => { + expect(timeFormatControl).toBeDefined(); + expect(timeFormatControl.config.default).toBe('smart_date'); +}); + +test('should include x_axis_number_format control', () => { + expect(numberFormatControl).toBeDefined(); + expect(numberFormatControl.config.default).toBe('~g'); +}); + +test('x_axis_time_format should be visible for temporal columns', () => { + const visibilityFn = timeFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('date', GenericDataType.Temporal))).toBe( + true, + ); +}); + +test('x_axis_time_format should be hidden for numeric columns', () => { + const visibilityFn = timeFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('year', GenericDataType.Numeric))).toBe( + false, + ); +}); + +test('x_axis_number_format should be visible for numeric columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('year', GenericDataType.Numeric))).toBe( + true, + ); +}); + +test('x_axis_number_format should be hidden for temporal columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('date', GenericDataType.Temporal))).toBe( + false, + ); +}); + +test('x_axis_number_format should be hidden for string columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('name', GenericDataType.String))).toBe( + false, + ); +}); diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/controlPanel.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/controlPanel.test.ts index 4c560ea9ff3..943badfe02f 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/controlPanel.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Scatter/controlPanel.test.ts @@ -17,6 +17,7 @@ * under the License. */ import { ControlPanelsContainerProps } from '@superset-ui/chart-controls/types'; +import { GenericDataType } from '@apache-superset/core/common'; import controlPanel from '../../../src/Timeseries/Regular/Scatter/controlPanel'; const config = controlPanel; @@ -44,18 +45,22 @@ const getControl = (controlName: string) => { const mockControls = ( xAxisColumn: string | null, - xAxisType: string | null, + typeGeneric: GenericDataType | null, ): ControlPanelsContainerProps => { - const options = xAxisType - ? [{ column_name: xAxisColumn, type: xAxisType }] - : []; + const columns = + xAxisColumn && typeGeneric !== null + ? [{ column_name: xAxisColumn, type_generic: typeGeneric }] + : []; return { controls: { // @ts-expect-error x_axis: { value: xAxisColumn, - options: options, + }, + // @ts-expect-error + datasource: { + datasource: { columns }, }, }, }; @@ -85,26 +90,21 @@ test('scatter chart control panel should have visibility function for x_axis_tim const isTimeVisible = ( xAxisColumn: string | null, - xAxisType: string | null, + xAxisType: GenericDataType | null, ): boolean => { const props = mockControls(xAxisColumn, xAxisType); const visibilityFn = timeFormatControl?.config?.visibility; return visibilityFn ? visibilityFn(props) : false; }; -test('x_axis_time_format control should be visible for any data types include TIME', () => { - expect(isTimeVisible('time_column', 'TIME')).toBe(true); - expect(isTimeVisible('time_column', 'TIME WITH TIME ZONE')).toBe(true); - expect(isTimeVisible('time_column', 'TIMESTAMP WITH TIME ZONE')).toBe(true); - expect(isTimeVisible('time_column', 'TIMESTAMP WITHOUT TIME ZONE')).toBe( - true, - ); +test('x_axis_time_format control should be visible for temporal data types', () => { + expect(isTimeVisible('time_column', GenericDataType.Temporal)).toBe(true); }); -test('x_axis_time_format control should be hidden for data types that do NOT include TIME', () => { - expect(isTimeVisible('null', 'null')).toBe(false); +test('x_axis_time_format control should be hidden for non-temporal data types', () => { expect(isTimeVisible(null, null)).toBe(false); - expect(isTimeVisible('float_column', 'FLOAT')).toBe(false); + expect(isTimeVisible('float_column', GenericDataType.Numeric)).toBe(false); + expect(isTimeVisible('name_column', GenericDataType.String)).toBe(false); }); // tests for x_axis_number_format control @@ -117,7 +117,7 @@ test('scatter chart control panel should include x_axis_number_format control in test('scatter chart control panel should have correct default value for x_axis_number_format', () => { expect(numberFormatControl).toBeDefined(); expect(numberFormatControl.config).toBeDefined(); - expect(numberFormatControl.config.default).toBe('SMART_NUMBER'); + expect(numberFormatControl.config.default).toBe('~g'); }); test('scatter chart control panel should have visibility function for x_axis_number_format', () => { @@ -131,26 +131,20 @@ test('scatter chart control panel should have visibility function for x_axis_num const isNumberVisible = ( xAxisColumn: string | null, - xAxisType: string | null, + xAxisType: GenericDataType | null, ): boolean => { const props = mockControls(xAxisColumn, xAxisType); const visibilityFn = numberFormatControl?.config?.visibility; return visibilityFn ? visibilityFn(props) : false; }; -test('x_axis_number_format control should be visible for any floating-point data types', () => { - expect(isNumberVisible('float_column', 'FLOAT')).toBe(true); - expect(isNumberVisible('double_column', 'DOUBLE')).toBe(true); - expect(isNumberVisible('real_column', 'REAL')).toBe(true); - expect(isNumberVisible('numeric_column', 'NUMERIC')).toBe(true); - expect(isNumberVisible('decimal_column', 'DECIMAL')).toBe(true); +test('x_axis_number_format control should be visible for numeric data types', () => { + expect(isNumberVisible('float_column', GenericDataType.Numeric)).toBe(true); + expect(isNumberVisible('int_column', GenericDataType.Numeric)).toBe(true); }); -test('x_axis_number_format control should be hidden for any non-floating-point data types', () => { - expect(isNumberVisible('string_column', 'VARCHAR')).toBe(false); - expect(isNumberVisible('null', 'null')).toBe(false); +test('x_axis_number_format control should be hidden for non-numeric data types', () => { + expect(isNumberVisible('string_column', GenericDataType.String)).toBe(false); expect(isNumberVisible(null, null)).toBe(false); - expect(isNumberVisible('time_column', 'TIMESTAMP WITHOUT TIME ZONE')).toBe( - false, - ); + expect(isNumberVisible('time_column', GenericDataType.Temporal)).toBe(false); }); diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/SmoothLine/controlPanel.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/SmoothLine/controlPanel.test.ts new file mode 100644 index 00000000000..1c1a634db3d --- /dev/null +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/SmoothLine/controlPanel.test.ts @@ -0,0 +1,101 @@ +/** + * 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 { ControlPanelsContainerProps } from '@superset-ui/chart-controls/types'; +import { GenericDataType } from '@apache-superset/core/common'; +import controlPanel from '../../../src/Timeseries/Regular/SmoothLine/controlPanel'; + +const config = controlPanel; + +const getControl = (controlName: string) => { + for (const section of config.controlPanelSections) { + if (section && section.controlSetRows) { + for (const row of section.controlSetRows) { + for (const control of row) { + if ( + typeof control === 'object' && + control !== null && + 'name' in control && + control.name === controlName + ) { + return control; + } + } + } + } + } + + return null; +}; + +const mockControls = ( + xAxisColumn: string | null, + typeGeneric: GenericDataType | null, +): ControlPanelsContainerProps => { + const columns = + xAxisColumn && typeGeneric !== null + ? [{ column_name: xAxisColumn, type_generic: typeGeneric }] + : []; + + return { + controls: { + // @ts-expect-error + x_axis: { + value: xAxisColumn, + }, + // @ts-expect-error + datasource: { + datasource: { columns }, + }, + }, + }; +}; + +const timeFormatControl: any = getControl('x_axis_time_format'); +const numberFormatControl: any = getControl('x_axis_number_format'); + +test('should include x_axis_time_format control', () => { + expect(timeFormatControl).toBeDefined(); + expect(timeFormatControl.config.default).toBe('smart_date'); +}); + +test('should include x_axis_number_format control', () => { + expect(numberFormatControl).toBeDefined(); + expect(numberFormatControl.config.default).toBe('~g'); +}); + +test('x_axis_number_format should be visible for numeric columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('year', GenericDataType.Numeric))).toBe( + true, + ); +}); + +test('x_axis_number_format should be hidden for temporal columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('date', GenericDataType.Temporal))).toBe( + false, + ); +}); + +test('x_axis_number_format should be hidden for string columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('name', GenericDataType.String))).toBe( + false, + ); +}); diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Step/controlPanel.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Step/controlPanel.test.ts new file mode 100644 index 00000000000..dcd36ebacd2 --- /dev/null +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/Step/controlPanel.test.ts @@ -0,0 +1,115 @@ +/** + * 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 { ControlPanelsContainerProps } from '@superset-ui/chart-controls/types'; +import { GenericDataType } from '@apache-superset/core/common'; +import controlPanel from '../../../src/Timeseries/Step/controlPanel'; + +const config = controlPanel; + +const getControl = (controlName: string) => { + for (const section of config.controlPanelSections) { + if (section && section.controlSetRows) { + for (const row of section.controlSetRows) { + for (const control of row) { + if ( + typeof control === 'object' && + control !== null && + 'name' in control && + control.name === controlName + ) { + return control; + } + } + } + } + } + + return null; +}; + +const mockControls = ( + xAxisColumn: string | null, + typeGeneric: GenericDataType | null, +): ControlPanelsContainerProps => { + const columns = + xAxisColumn && typeGeneric !== null + ? [{ column_name: xAxisColumn, type_generic: typeGeneric }] + : []; + + return { + controls: { + // @ts-expect-error + x_axis: { + value: xAxisColumn, + }, + // @ts-expect-error + datasource: { + datasource: { columns }, + }, + }, + }; +}; + +const timeFormatControl: any = getControl('x_axis_time_format'); +const numberFormatControl: any = getControl('x_axis_number_format'); + +test('should include x_axis_time_format control', () => { + expect(timeFormatControl).toBeDefined(); + expect(timeFormatControl.config.default).toBe('smart_date'); +}); + +test('should include x_axis_number_format control', () => { + expect(numberFormatControl).toBeDefined(); + expect(numberFormatControl.config.default).toBe('~g'); +}); + +test('x_axis_time_format should be visible for temporal columns', () => { + const visibilityFn = timeFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('date', GenericDataType.Temporal))).toBe( + true, + ); +}); + +test('x_axis_time_format should be hidden for numeric columns', () => { + const visibilityFn = timeFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('year', GenericDataType.Numeric))).toBe( + false, + ); +}); + +test('x_axis_number_format should be visible for numeric columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('year', GenericDataType.Numeric))).toBe( + true, + ); +}); + +test('x_axis_number_format should be hidden for temporal columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('date', GenericDataType.Temporal))).toBe( + false, + ); +}); + +test('x_axis_number_format should be hidden for string columns', () => { + const visibilityFn = numberFormatControl?.config?.visibility; + expect(visibilityFn(mockControls('name', GenericDataType.String))).toBe( + false, + ); +}); diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.test.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.test.tsx index b723c13d570..02c2d11a22b 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.test.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillBySubmenu.test.tsx @@ -274,7 +274,11 @@ test('When menu item is clicked, call onSelection with clicked column and drill test('matrixify_mode_rows enabled should not render component', () => { const { container } = renderSubmenu({ - formData: { ...defaultFormData, matrixify_enable: true, matrixify_mode_rows: 'metrics' }, + formData: { + ...defaultFormData, + matrixify_enable: true, + matrixify_mode_rows: 'metrics', + }, }); expect(container).toBeEmptyDOMElement(); }); diff --git a/superset-frontend/src/core/sqlLab/sqlLab.test.ts b/superset-frontend/src/core/sqlLab/sqlLab.test.ts index e5e7219805e..5b7356c939b 100644 --- a/superset-frontend/src/core/sqlLab/sqlLab.test.ts +++ b/superset-frontend/src/core/sqlLab/sqlLab.test.ts @@ -16,10 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { - configureStore, - createListenerMiddleware, -} from '@reduxjs/toolkit'; +import { configureStore, createListenerMiddleware } from '@reduxjs/toolkit'; import type { QueryEditor } from 'src/SqlLab/types'; import sqlLabReducer from 'src/SqlLab/reducers/sqlLab'; import { @@ -369,7 +366,10 @@ test('onDidCloseTab fires with Tab on REMOVE_QUERY_EDITOR', async () => { test('onDidChangeActiveTab fires with Tab on SET_ACTIVE_QUERY_EDITOR', () => { // Add a second editor so switching back is a real change - mockStore.dispatch({ type: ADD_QUERY_EDITOR, queryEditor: makeSecondEditor() }); + mockStore.dispatch({ + type: ADD_QUERY_EDITOR, + queryEditor: makeSecondEditor(), + }); const listener = jest.fn(); const disposable = sqlLab.onDidChangeActiveTab(listener); @@ -418,7 +418,10 @@ test('onDidCreateTab fires with Tab on ADD_QUERY_EDITOR', () => { test('editor-scoped listener does not fire for a different editor', () => { // Add a second editor (ADD_QUERY_EDITOR makes it active) - mockStore.dispatch({ type: ADD_QUERY_EDITOR, queryEditor: makeSecondEditor() }); + mockStore.dispatch({ + type: ADD_QUERY_EDITOR, + queryEditor: makeSecondEditor(), + }); // Switch back to editor-1 so the predicate captures immutable-1 mockStore.dispatch({ @@ -444,7 +447,10 @@ test('editor-scoped listener does not fire for a different editor', () => { test('editor-scoped predicate filters tab events via queryEditor lookup', () => { // Add a second editor and switch back to editor-1 - mockStore.dispatch({ type: ADD_QUERY_EDITOR, queryEditor: makeSecondEditor() }); + mockStore.dispatch({ + type: ADD_QUERY_EDITOR, + queryEditor: makeSecondEditor(), + }); mockStore.dispatch({ type: SET_ACTIVE_QUERY_EDITOR, queryEditor: { id: EDITOR_ID }, @@ -468,7 +474,10 @@ test('editor-scoped predicate filters tab events via queryEditor lookup', () => test('globalPredicate listener fires for a non-active tab', () => { // Add editor-2 and switch to it - mockStore.dispatch({ type: ADD_QUERY_EDITOR, queryEditor: makeSecondEditor() }); + mockStore.dispatch({ + type: ADD_QUERY_EDITOR, + queryEditor: makeSecondEditor(), + }); mockStore.dispatch({ type: SET_ACTIVE_QUERY_EDITOR, queryEditor: { id: 'editor-2' }, diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx index a706f89b7de..a507c65318d 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx @@ -523,30 +523,33 @@ const DashboardBuilder = () => { ({ dropIndicatorProps }: { dropIndicatorProps: JsonObject }) => (