From d5c5e58583771a35d8870ce3694b2a3f1b688159 Mon Sep 17 00:00:00 2001 From: Ville Brofeldt <33317356+villebro@users.noreply.github.com> Date: Thu, 19 May 2022 13:51:52 +0300 Subject: [PATCH] feat(plugin-chart-echarts): add support for generic axis to mixed chart (#20097) * feat(plugin-chart-echarts): add support for generic axis to mixed chart * fix tests + add new tests * address review comments * simplify control panel * fix types and tests --- .../superset-ui-chart-controls/src/types.ts | 8 +- .../test/types.test.ts | 61 ++++---- .../src/MixedTimeseries/buildQuery.ts | 32 +++-- .../src/MixedTimeseries/controlPanel.tsx | 11 +- .../src/MixedTimeseries/index.ts | 25 ++-- .../src/MixedTimeseries/transformProps.ts | 38 ++++- .../src/Timeseries/buildQuery.ts | 6 +- .../src/Timeseries/transformProps.ts | 27 +--- .../plugin-chart-echarts/src/constants.ts | 9 ++ .../plugin-chart-echarts/src/utils/series.ts | 12 ++ .../test/MixedTimeseries/buildQuery.test.ts | 131 +++++++++++++++--- .../controlUtils/getSectionsToRender.ts | 4 +- 12 files changed, 263 insertions(+), 101 deletions(-) diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts index 73985bfc743b..53e3a198bd7c 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts @@ -341,7 +341,7 @@ export interface ControlPanelSectionConfig { } export interface ControlPanelConfig { - controlPanelSections: ControlPanelSectionConfig[]; + controlPanelSections: (ControlPanelSectionConfig | null)[]; controlOverrides?: ControlOverrides; sectionOverrides?: SectionOverrides; onInit?: (state: ControlStateMapping) => void; @@ -413,3 +413,9 @@ export function isAdhocColumn( ): column is AdhocColumn { return 'label' in column && 'sqlExpression' in column; } + +export function isControlPanelSectionConfig( + section: ControlPanelSectionConfig | null, +): section is ControlPanelSectionConfig { + return section !== null; +} diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/types.test.ts b/superset-frontend/packages/superset-ui-chart-controls/test/types.test.ts index bd3197210e72..5c53fbcf10c6 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/test/types.test.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/test/types.test.ts @@ -18,10 +18,12 @@ */ import { AdhocColumn } from '@superset-ui/core'; import { + ColumnMeta, + ControlPanelSectionConfig, isAdhocColumn, isColumnMeta, + isControlPanelSectionConfig, isSavedExpression, - ColumnMeta, } from '../src'; const ADHOC_COLUMN: AdhocColumn = { @@ -37,37 +39,46 @@ const SAVED_EXPRESSION: ColumnMeta = { column_name: 'Saved expression', expression: 'case when 1 = 1 then 1 else 2 end', }; +const CONTROL_PANEL_SECTION_CONFIG: ControlPanelSectionConfig = { + label: 'My Section', + description: 'My Description', + controlSetRows: [], +}; -describe('isColumnMeta', () => { - it('returns false for AdhocColumn', () => { - expect(isColumnMeta(ADHOC_COLUMN)).toEqual(false); - }); +test('isColumnMeta returns false for AdhocColumn', () => { + expect(isColumnMeta(ADHOC_COLUMN)).toEqual(false); +}); - it('returns true for ColumnMeta', () => { - expect(isColumnMeta(COLUMN_META)).toEqual(true); - }); +test('isColumnMeta returns true for ColumnMeta', () => { + expect(isColumnMeta(COLUMN_META)).toEqual(true); }); -describe('isAdhocColumn', () => { - it('returns true for AdhocColumn', () => { - expect(isAdhocColumn(ADHOC_COLUMN)).toEqual(true); - }); +test('isAdhocColumn returns true for AdhocColumn', () => { + expect(isAdhocColumn(ADHOC_COLUMN)).toEqual(true); +}); - it('returns false for ColumnMeta', () => { - expect(isAdhocColumn(COLUMN_META)).toEqual(false); - }); +test('isAdhocColumn returns false for ColumnMeta', () => { + expect(isAdhocColumn(COLUMN_META)).toEqual(false); }); -describe('isSavedExpression', () => { - it('returns false for AdhocColumn', () => { - expect(isSavedExpression(ADHOC_COLUMN)).toEqual(false); - }); +test('isSavedExpression returns false for AdhocColumn', () => { + expect(isSavedExpression(ADHOC_COLUMN)).toEqual(false); +}); - it('returns false for ColumnMeta without expression', () => { - expect(isSavedExpression(COLUMN_META)).toEqual(false); - }); +test('isSavedExpression returns false for ColumnMeta without expression', () => { + expect(isSavedExpression(COLUMN_META)).toEqual(false); +}); + +test('isSavedExpression returns true for ColumnMeta with expression', () => { + expect(isSavedExpression(SAVED_EXPRESSION)).toEqual(true); +}); + +test('isControlPanelSectionConfig returns true for section', () => { + expect(isControlPanelSectionConfig(CONTROL_PANEL_SECTION_CONFIG)).toEqual( + true, + ); +}); - it('returns true for ColumnMeta with expression', () => { - expect(isSavedExpression(SAVED_EXPRESSION)).toEqual(true); - }); +test('isControlPanelSectionConfig returns true for null value', () => { + expect(isControlPanelSectionConfig(null)).toEqual(false); }); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/buildQuery.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/buildQuery.ts index a8255b7d999f..ac3a96b2c737 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/buildQuery.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/buildQuery.ts @@ -18,10 +18,12 @@ */ import { buildQueryContext, - QueryFormData, - QueryObject, + DTTM_ALIAS, + ensureIsArray, normalizeOrderBy, PostProcessingPivot, + QueryFormData, + QueryObject, } from '@superset-ui/core'; import { pivotOperator, @@ -39,12 +41,13 @@ import { } from '../utils/formDataSuffix'; export default function buildQuery(formData: QueryFormData) { + const { x_axis: index } = formData; + const is_timeseries = index === DTTM_ALIAS || !index; const baseFormData = { ...formData, - is_timeseries: true, - columns: formData.groupby, - columns_b: formData.groupby_b, + is_timeseries, }; + const formData1 = removeFormDataSuffix(baseFormData, '_b'); const formData2 = retainFormDataSuffix(baseFormData, '_b'); @@ -52,7 +55,9 @@ export default function buildQuery(formData: QueryFormData) { buildQueryContext(fd, baseQueryObject => { const queryObject = { ...baseQueryObject, - is_timeseries: true, + columns: [...ensureIsArray(index), ...ensureIsArray(fd.groupby)], + series_columns: fd.groupby, + is_timeseries, }; const pivotOperatorInRuntime: PostProcessingPivot = isTimeComparison( @@ -60,7 +65,12 @@ export default function buildQuery(formData: QueryFormData) { queryObject, ) ? timeComparePivotOperator(fd, queryObject) - : pivotOperator(fd, queryObject); + : pivotOperator(fd, { + ...queryObject, + columns: fd.groupby, + index, + is_timeseries, + }); const tmpQueryObject = { ...queryObject, @@ -70,9 +80,13 @@ export default function buildQuery(formData: QueryFormData) { rollingWindowOperator(fd, queryObject), timeCompareOperator(fd, queryObject), resampleOperator(fd, queryObject), - renameOperator(fd, queryObject), + renameOperator(fd, { + ...queryObject, + columns: fd.groupby, + is_timeseries, + }), flattenOperator(fd, queryObject), - ], + ].filter(Boolean), } as QueryObject; return [normalizeOrderBy(tmpQueryObject)]; }), diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx index e839637885e4..e39c02366169 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { t } from '@superset-ui/core'; +import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core'; import { cloneDeep } from 'lodash'; import { ControlPanelConfig, @@ -31,7 +31,7 @@ import { import { DEFAULT_FORM_DATA } from './types'; import { EchartsTimeseriesSeriesType } from '../Timeseries/types'; -import { legendSection, richTooltipSection } from '../controls'; +import { legendSection, richTooltipSection, xAxisControl } from '../controls'; const { area, @@ -278,6 +278,13 @@ function createAdvancedAnalyticsSection( const config: ControlPanelConfig = { controlPanelSections: [ sections.legacyTimeseriesTime, + isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) + ? { + label: t('Shared query fields'), + expanded: true, + controlSetRows: [[xAxisControl]], + } + : null, createQuerySection(t('Query A'), ''), createAdvancedAnalyticsSection(t('Advanced analytics Query A'), ''), createQuerySection(t('Query B'), '_b'), diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/index.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/index.ts index 4729a8174c71..a5bd7ddf95a3 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/index.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/index.ts @@ -17,19 +17,21 @@ * under the License. */ import { - t, - ChartMetadata, - ChartPlugin, AnnotationType, Behavior, + ChartMetadata, + ChartPlugin, + FeatureFlag, + isFeatureEnabled, + t, } from '@superset-ui/core'; import buildQuery from './buildQuery'; import controlPanel from './controlPanel'; import transformProps from './transformProps'; import thumbnail from './images/thumbnail.png'; import { - EchartsMixedTimeseriesProps, EchartsMixedTimeseriesFormData, + EchartsMixedTimeseriesProps, } from './types'; export default class EchartsTimeseriesChartPlugin extends ChartPlugin< @@ -55,16 +57,22 @@ export default class EchartsTimeseriesChartPlugin extends ChartPlugin< behaviors: [Behavior.INTERACTIVE_CHART], category: t('Evolution'), credits: ['https://echarts.apache.org'], - description: t( - 'Visualize two different time series using the same x-axis time range. Note that each time series can be visualized differently (e.g. 1 using bars and 1 using a line).', - ), + description: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) + ? t( + 'Visualize two different series using the same x-axis. Note that both series can be visualized with a different chart type (e.g. 1 using bars and 1 using a line).', + ) + : t( + 'Visualize two different time series using the same x-axis. Note that each time series can be visualized differently (e.g. 1 using bars and 1 using a line).', + ), supportedAnnotationTypes: [ AnnotationType.Event, AnnotationType.Formula, AnnotationType.Interval, AnnotationType.Timeseries, ], - name: t('Mixed Time-Series'), + name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) + ? t('Mixed Chart') + : t('Mixed Time-Series'), thumbnail, tags: [ t('Advanced-Analytics'), @@ -73,7 +81,6 @@ export default class EchartsTimeseriesChartPlugin extends ChartPlugin< t('Experimental'), t('Line'), t('Multi-Variables'), - t('Predictive'), t('Time'), t('Transformable'), ], diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts index 5072aedf7141..10dd33ff1ac4 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts @@ -21,12 +21,15 @@ import { AnnotationLayer, CategoricalColorNamespace, DataRecordValue, - TimeseriesDataRecord, + DTTM_ALIAS, + GenericDataType, + getColumnLabel, getNumberFormatter, isEventAnnotationLayer, isFormulaAnnotationLayer, isIntervalAnnotationLayer, isTimeseriesAnnotationLayer, + TimeseriesDataRecord, } from '@superset-ui/core'; import { EChartsCoreOption, SeriesOption } from 'echarts'; import { @@ -41,6 +44,8 @@ import { currentSeries, dedupSeries, extractSeries, + getAxisType, + getColtypesMapping, getLegendProps, } from '../utils/series'; import { extractAnnotationLabels } from '../utils/annotation'; @@ -62,7 +67,7 @@ import { transformSeries, transformTimeseriesAnnotation, } from '../Timeseries/transformers'; -import { TIMESERIES_CONSTANTS } from '../constants'; +import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants'; export default function transformProps( chartProps: EchartsMixedTimeseriesProps, @@ -124,24 +129,35 @@ export default function transformProps( groupbyB, emitFilter, emitFilterB, + xAxis: xAxisOrig, xAxisTitle, yAxisTitle, xAxisTitleMargin, yAxisTitleMargin, yAxisTitlePosition, sliceId, + timeGrainSqla, }: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData }; const colorScale = CategoricalColorNamespace.getScale(colorScheme as string); + + const xAxisCol = + verboseMap[xAxisOrig] || getColumnLabel(xAxisOrig || DTTM_ALIAS); + const rebasedDataA = rebaseForecastDatum(data1, verboseMap); const rawSeriesA = extractSeries(rebasedDataA, { fillNeighborValue: stack ? 0 : undefined, + xAxis: xAxisCol, }); const rebasedDataB = rebaseForecastDatum(data2, verboseMap); const rawSeriesB = extractSeries(rebasedDataB, { fillNeighborValue: stackB ? 0 : undefined, + xAxis: xAxisCol, }); + const dataTypes = getColtypesMapping(queriesData[0]); + const xAxisDataType = dataTypes?.[xAxisCol]; + const xAxisType = getAxisType(xAxisDataType); const series: SeriesOption[] = []; const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat); const formatterSecondary = getNumberFormatter( @@ -255,8 +271,14 @@ export default function transformProps( if (max === undefined) max = 1; } - const tooltipTimeFormatter = getTooltipTimeFormatter(tooltipTimeFormat); - const xAxisFormatter = getXAxisFormatter(xAxisTimeFormat); + const tooltipFormatter = + xAxisDataType === GenericDataType.TEMPORAL + ? getTooltipTimeFormatter(tooltipTimeFormat) + : String; + const xAxisFormatter = + xAxisDataType === GenericDataType.TEMPORAL + ? getXAxisFormatter(xAxisTimeFormat) + : String; const addYAxisTitleOffset = !!(yAxisTitle || yAxisTitleSecondary); const addXAxisTitleOffset = !!xAxisTitle; @@ -298,7 +320,7 @@ export default function transformProps( ...chartPadding, }, xAxis: { - type: 'time', + type: xAxisType, name: xAxisTitle, nameGap: convertInteger(xAxisTitleMargin), nameLocation: 'middle', @@ -306,6 +328,10 @@ export default function transformProps( formatter: xAxisFormatter, rotate: xAxisLabelRotation, }, + minInterval: + xAxisType === 'time' && timeGrainSqla + ? TIMEGRAIN_TO_TIMESTAMP[timeGrainSqla] + : 0, }, yAxis: [ { @@ -350,7 +376,7 @@ export default function transformProps( forecastValue.sort((a, b) => b.data[1] - a.data[1]); } - const rows: Array = [`${tooltipTimeFormatter(xValue)}`]; + const rows: Array = [`${tooltipFormatter(xValue)}`]; const forecastValues = extractForecastValuesFromTooltipParams(forecastValue); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/buildQuery.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/buildQuery.ts index 3c2d66175bc2..96c1d6e8e7b5 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/buildQuery.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/buildQuery.ts @@ -20,9 +20,9 @@ import { buildQueryContext, DTTM_ALIAS, ensureIsArray, - QueryFormData, normalizeOrderBy, PostProcessingPivot, + QueryFormData, } from '@superset-ui/core'; import { rollingWindowOperator, @@ -94,13 +94,13 @@ export default function buildQuery(formData: QueryFormData) { resampleOperator(formData, baseQueryObject), renameOperator(formData, { ...baseQueryObject, - ...{ is_timeseries }, + is_timeseries, }), contributionOperator(formData, baseQueryObject), flattenOperator(formData, baseQueryObject), // todo: move prophet before flatten prophetOperator(formData, baseQueryObject), - ].filter(op => op), + ].filter(Boolean), }, ]; }); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts index 85fa656f2ed0..87396c0015d7 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -29,7 +29,6 @@ import { isFormulaAnnotationLayer, isIntervalAnnotationLayer, isTimeseriesAnnotationLayer, - TimeGranularity, TimeseriesChartDataResponseResult, } from '@superset-ui/core'; import { EChartsCoreOption, SeriesOption } from 'echarts'; @@ -47,6 +46,7 @@ import { currentSeries, dedupSeries, extractSeries, + getAxisType, getColtypesMapping, getLegendProps, } from '../utils/series'; @@ -70,15 +70,7 @@ import { transformSeries, transformTimeseriesAnnotation, } from './transformers'; -import { TIMESERIES_CONSTANTS } from '../constants'; - -const TimeGrainToTimestamp = { - [TimeGranularity.HOUR]: 3600 * 1000, - [TimeGranularity.DAY]: 3600 * 1000 * 24, - [TimeGranularity.MONTH]: 3600 * 1000 * 24 * 31, - [TimeGranularity.QUARTER]: 3600 * 1000 * 24 * 31 * 3, - [TimeGranularity.YEAR]: 3600 * 1000 * 24 * 31 * 12, -}; +import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants'; export default function transformProps( chartProps: EchartsTimeseriesChartProps, @@ -157,18 +149,7 @@ export default function transformProps( Object.values(rawSeries).map(series => series.name as string), ); const xAxisDataType = dataTypes?.[xAxisCol]; - let xAxisType: 'time' | 'value' | 'category'; - switch (xAxisDataType) { - case GenericDataType.TEMPORAL: - xAxisType = 'time'; - break; - case GenericDataType.NUMERIC: - xAxisType = 'value'; - break; - default: - xAxisType = 'category'; - break; - } + const xAxisType = getAxisType(xAxisDataType); const series: SeriesOption[] = []; const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat); @@ -342,7 +323,7 @@ export default function transformProps( }, minInterval: xAxisType === 'time' && timeGrainSqla - ? TimeGrainToTimestamp[timeGrainSqla] + ? TIMEGRAIN_TO_TIMESTAMP[timeGrainSqla] : 0, }; let yAxis: any = { diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts index b2f3a28a6656..deef2f2e8c6f 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts @@ -17,6 +17,7 @@ * under the License. */ +import { TimeGranularity } from '@superset-ui/core'; import { LabelPositionEnum } from './types'; // eslint-disable-next-line import/prefer-default-export @@ -59,3 +60,11 @@ export enum OpacityEnum { SemiTransparent = 0.3, NonTransparent = 1, } + +export const TIMEGRAIN_TO_TIMESTAMP = { + [TimeGranularity.HOUR]: 3600 * 1000, + [TimeGranularity.DAY]: 3600 * 1000 * 24, + [TimeGranularity.MONTH]: 3600 * 1000 * 24 * 31, + [TimeGranularity.QUARTER]: 3600 * 1000 * 24 * 31 * 3, + [TimeGranularity.YEAR]: 3600 * 1000 * 24 * 31 * 12, +}; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts index 4da3681b7e23..fa8a23138cfd 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts @@ -239,3 +239,15 @@ export const currentSeries = { name: '', legend: '', }; + +export function getAxisType( + dataType?: GenericDataType, +): 'time' | 'value' | 'category' { + if (dataType === GenericDataType.TEMPORAL) { + return 'time'; + } + if (dataType === GenericDataType.NUMERIC) { + return 'value'; + } + return 'category'; +} diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/MixedTimeseries/buildQuery.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/MixedTimeseries/buildQuery.test.ts index 72f16482cb77..eb95a4f71df1 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/MixedTimeseries/buildQuery.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/MixedTimeseries/buildQuery.test.ts @@ -84,8 +84,8 @@ const formDataMixedChartWithAA = { }; test('should compile query object A', () => { - const query_a = buildQuery(formDataMixedChart).queries[0]; - expect(query_a).toEqual({ + const query = buildQuery(formDataMixedChart).queries[0]; + expect(query).toEqual({ time_range: '1980 : 2000', since: undefined, until: undefined, @@ -103,7 +103,7 @@ test('should compile query object A', () => { annotation_layers: [], row_limit: 10, row_offset: undefined, - series_columns: undefined, + series_columns: ['foo'], series_limit: undefined, series_limit_metric: undefined, timeseries_limit: 5, @@ -128,9 +128,6 @@ test('should compile query object A', () => { reset_index: false, }, }, - undefined, - undefined, - undefined, { operation: 'rename', options: { @@ -150,8 +147,8 @@ test('should compile query object A', () => { }); test('should compile query object B', () => { - const query_a = buildQuery(formDataMixedChart).queries[1]; - expect(query_a).toEqual({ + const query = buildQuery(formDataMixedChart).queries[1]; + expect(query).toEqual({ time_range: '1980 : 2000', since: undefined, until: undefined, @@ -169,7 +166,7 @@ test('should compile query object B', () => { annotation_layers: [], row_limit: 100, row_offset: undefined, - series_columns: undefined, + series_columns: [], series_limit: undefined, series_limit_metric: undefined, timeseries_limit: 0, @@ -194,10 +191,6 @@ test('should compile query object B', () => { reset_index: false, }, }, - undefined, - undefined, - undefined, - undefined, { operation: 'flatten', }, @@ -207,14 +200,14 @@ test('should compile query object B', () => { }); test('should compile AA in query A', () => { - const query_a = buildQuery(formDataMixedChartWithAA).queries[0]; + const query = buildQuery(formDataMixedChartWithAA).queries[0]; // time comparison - expect(query_a?.time_offsets).toEqual(['1 years ago']); + expect(query.time_offsets).toEqual(['1 years ago']); // cumsum expect( // prettier-ignore - query_a + query .post_processing ?.find(operator => operator?.operation === 'cum') ?.operation, @@ -223,7 +216,7 @@ test('should compile AA in query A', () => { // resample expect( // prettier-ignore - query_a + query .post_processing ?.find(operator => operator?.operation === 'resample'), ).toEqual({ @@ -237,14 +230,14 @@ test('should compile AA in query A', () => { }); test('should compile AA in query B', () => { - const query_b = buildQuery(formDataMixedChartWithAA).queries[1]; + const query = buildQuery(formDataMixedChartWithAA).queries[1]; // time comparison - expect(query_b?.time_offsets).toEqual(['3 years ago']); + expect(query.time_offsets).toEqual(['3 years ago']); // rolling total expect( // prettier-ignore - query_b + query .post_processing ?.find(operator => operator?.operation === 'rolling'), ).toEqual({ @@ -263,7 +256,7 @@ test('should compile AA in query B', () => { // resample expect( // prettier-ignore - query_b + query .post_processing ?.find(operator => operator?.operation === 'resample'), ).toEqual({ @@ -275,3 +268,99 @@ test('should compile AA in query B', () => { }, }); }); + +test('should compile query objects with x-axis', () => { + const { queries } = buildQuery({ + ...formDataMixedChart, + x_axis: 'my_index', + }); + expect(queries[0]).toEqual({ + time_range: '1980 : 2000', + since: undefined, + until: undefined, + granularity: 'ds', + filters: [], + extras: { + having: '', + having_druid: [], + time_grain_sqla: 'P1W', + where: "(foo in ('a', 'b'))", + }, + applied_time_extras: {}, + columns: ['my_index', 'foo'], + metrics: ['sum(sales)'], + annotation_layers: [], + row_limit: 10, + row_offset: undefined, + series_columns: ['foo'], + series_limit: undefined, + series_limit_metric: undefined, + timeseries_limit: 5, + url_params: {}, + custom_params: {}, + custom_form_data: {}, + is_timeseries: false, + time_offsets: [], + post_processing: [ + { + operation: 'pivot', + options: { + aggregates: { + 'sum(sales)': { + operator: 'mean', + }, + }, + columns: ['foo'], + drop_missing_columns: false, + flatten_columns: false, + index: ['my_index'], + reset_index: false, + }, + }, + { + operation: 'rename', + options: { + columns: { + 'sum(sales)': null, + }, + inplace: true, + level: 0, + }, + }, + { + operation: 'flatten', + }, + ], + orderby: [['count', false]], + }); + + // check the main props on the second query + expect(queries[1]).toEqual( + expect.objectContaining({ + is_timeseries: false, + columns: ['my_index'], + series_columns: [], + metrics: ['count'], + post_processing: [ + { + operation: 'pivot', + options: { + aggregates: { + count: { + operator: 'mean', + }, + }, + columns: [], + drop_missing_columns: false, + flatten_columns: false, + index: ['my_index'], + reset_index: false, + }, + }, + { + operation: 'flatten', + }, + ], + }), + ); +}); diff --git a/superset-frontend/src/explore/controlUtils/getSectionsToRender.ts b/superset-frontend/src/explore/controlUtils/getSectionsToRender.ts index 244114fc4091..c82833f4705b 100644 --- a/superset-frontend/src/explore/controlUtils/getSectionsToRender.ts +++ b/superset-frontend/src/explore/controlUtils/getSectionsToRender.ts @@ -24,6 +24,7 @@ import { import { ControlPanelConfig, expandControlConfig, + isControlPanelSectionConfig, } from '@superset-ui/chart-controls'; import * as SECTIONS from 'src/explore/controlPanels/sections'; @@ -60,8 +61,7 @@ const getMemoizedSectionsToRender = memoizeOne( : ['granularity_sqla', 'time_grain_sqla']; return [datasourceAndVizType] - .concat(controlPanelSections) - .filter(section => !!section) + .concat(controlPanelSections.filter(isControlPanelSectionConfig)) .map(section => { const { controlSetRows } = section; return {