mirror of https://github.com/apache/superset.git
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
This commit is contained in:
parent
b2a7fadba9
commit
d5c5e58583
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
it('returns true for ColumnMeta', () => {
|
||||
expect(isColumnMeta(COLUMN_META)).toEqual(true);
|
||||
});
|
||||
test('isColumnMeta returns false for AdhocColumn', () => {
|
||||
expect(isColumnMeta(ADHOC_COLUMN)).toEqual(false);
|
||||
});
|
||||
|
||||
describe('isAdhocColumn', () => {
|
||||
it('returns true for AdhocColumn', () => {
|
||||
expect(isAdhocColumn(ADHOC_COLUMN)).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false for ColumnMeta', () => {
|
||||
expect(isAdhocColumn(COLUMN_META)).toEqual(false);
|
||||
});
|
||||
test('isColumnMeta returns true for ColumnMeta', () => {
|
||||
expect(isColumnMeta(COLUMN_META)).toEqual(true);
|
||||
});
|
||||
|
||||
describe('isSavedExpression', () => {
|
||||
it('returns false for AdhocColumn', () => {
|
||||
expect(isSavedExpression(ADHOC_COLUMN)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false for ColumnMeta without expression', () => {
|
||||
expect(isSavedExpression(COLUMN_META)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns true for ColumnMeta with expression', () => {
|
||||
expect(isSavedExpression(SAVED_EXPRESSION)).toEqual(true);
|
||||
});
|
||||
test('isAdhocColumn returns true for AdhocColumn', () => {
|
||||
expect(isAdhocColumn(ADHOC_COLUMN)).toEqual(true);
|
||||
});
|
||||
|
||||
test('isAdhocColumn returns false for ColumnMeta', () => {
|
||||
expect(isAdhocColumn(COLUMN_META)).toEqual(false);
|
||||
});
|
||||
|
||||
test('isSavedExpression returns false for AdhocColumn', () => {
|
||||
expect(isSavedExpression(ADHOC_COLUMN)).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,
|
||||
);
|
||||
});
|
||||
|
||||
test('isControlPanelSectionConfig returns true for null value', () => {
|
||||
expect(isControlPanelSectionConfig(null)).toEqual(false);
|
||||
});
|
||||
|
|
|
@ -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)];
|
||||
}),
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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'),
|
||||
],
|
||||
|
|
|
@ -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<string> = [`${tooltipTimeFormatter(xValue)}`];
|
||||
const rows: Array<string> = [`${tooltipFormatter(xValue)}`];
|
||||
const forecastValues =
|
||||
extractForecastValuesFromTooltipParams(forecastValue);
|
||||
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue