This is an automated email from the ASF dual-hosted git repository.
msyavuz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 080f629ea21 fix(echarts): formula annotations not rendering with
dataset-level columns label (#37522)
080f629ea21 is described below
commit 080f629ea219028e850f1865aa513451513d048b
Author: Jamile Celento <[email protected]>
AuthorDate: Fri Feb 13 06:37:19 2026 -0300
fix(echarts): formula annotations not rendering with dataset-level columns
label (#37522)
---
.../src/MixedTimeseries/transformProps.ts | 2 +-
.../src/Timeseries/transformProps.ts | 3 +-
.../test/MixedTimeseries/transformProps.test.ts | 273 +++++---
.../test/Timeseries/transformProps.test.ts | 710 ++++++++++++---------
.../plugins/plugin-chart-echarts/test/helpers.ts | 110 ++++
5 files changed, 726 insertions(+), 372 deletions(-)
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 907ed4803d4..26094b3a106 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
@@ -360,7 +360,7 @@ export default function transformProps(
series.push(
transformFormulaAnnotation(
layer,
- data1,
+ rebasedDataA as TimeseriesDataRecord[],
xAxisLabel,
xAxisType,
colorScale,
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 7acd63132b9..095e757d707 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
@@ -39,6 +39,7 @@ import {
isTimeseriesAnnotationLayer,
resolveAutoCurrency,
TimeseriesChartDataResponseResult,
+ TimeseriesDataRecord,
NumberFormats,
} from '@superset-ui/core';
import { GenericDataType } from '@apache-superset/core/api/core';
@@ -463,7 +464,7 @@ export default function transformProps(
series.push(
transformFormulaAnnotation(
layer,
- data,
+ rebasedData as TimeseriesDataRecord[],
xAxisLabel,
xAxisType,
colorScale,
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/test/MixedTimeseries/transformProps.test.ts
b/superset-frontend/plugins/plugin-chart-echarts/test/MixedTimeseries/transformProps.test.ts
index 3562a3a7668..76562999515 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/test/MixedTimeseries/transformProps.test.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/test/MixedTimeseries/transformProps.test.ts
@@ -16,8 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { ChartProps, VizType } from '@superset-ui/core';
-import { supersetTheme } from '@apache-superset/core/ui';
+import {
+ AnnotationStyle,
+ AnnotationType,
+ DataRecord,
+ FormulaAnnotationLayer,
+ VizType,
+ ChartDataResponseResult,
+} from '@superset-ui/core';
import {
LegendOrientation,
LegendType,
@@ -28,6 +34,48 @@ import {
EchartsMixedTimeseriesFormData,
EchartsMixedTimeseriesProps,
} from '../../src/MixedTimeseries/types';
+import { DEFAULT_FORM_DATA } from '../../src/MixedTimeseries/types';
+import { createEchartsTimeseriesTestChartProps } from '../helpers';
+import type { SeriesOption } from 'echarts';
+
+/**
+ * Creates a partial ChartDataResponseResult for testing.
+ * Only includes the fields needed for tests, with sensible defaults for
required fields.
+ */
+function createTestQueryData(
+ data: unknown[],
+ overrides?: Partial<ChartDataResponseResult> & {
+ label_map?: Record<string, string[]>;
+ },
+): ChartDataResponseResult {
+ return {
+ annotation_data: null,
+ cache_key: null,
+ cache_timeout: null,
+ cached_dttm: null,
+ queried_dttm: null,
+ data: data as DataRecord[],
+ colnames: [],
+ coltypes: [],
+ error: null,
+ is_cached: false,
+ query: '',
+ rowcount: data.length,
+ sql_rowcount: data.length,
+ stacktrace: null,
+ status: 'success',
+ from_dttm: null,
+ to_dttm: null,
+ label_map: {},
+ ...overrides,
+ } as ChartDataResponseResult & { label_map?: Record<string, string[]> };
+}
+
+/** Defaults for createEchartsTimeseriesTestChartProps in Mixed Timeseries
tests. */
+const MIXED_TIMESERIES_CHART_PROPS_DEFAULTS = {
+ defaultFormData: DEFAULT_FORM_DATA,
+ defaultVizType: 'mixed_timeseries' as const,
+};
const formData: EchartsMixedTimeseriesFormData = {
annotationLayers: [],
@@ -85,49 +133,28 @@ const formData: EchartsMixedTimeseriesFormData = {
legendSort: null,
};
-const queriesData = [
- {
- data: [
- { boy: 1, girl: 2, ds: 599616000000 },
- { boy: 3, girl: 4, ds: 599916000000 },
- ],
- label_map: {
- ds: ['ds'],
- boy: ['boy'],
- girl: ['girl'],
- },
- },
- {
- data: [
- { boy: 1, girl: 2, ds: 599616000000 },
- { boy: 3, girl: 4, ds: 599916000000 },
- ],
- label_map: {
- ds: ['ds'],
- boy: ['boy'],
- girl: ['girl'],
- },
- },
+const defaultQueryRows = [
+ { boy: 1, girl: 2, ds: 599616000000 },
+ { boy: 3, girl: 4, ds: 599916000000 },
];
+const defaultLabelMap = { ds: ['ds'], boy: ['boy'], girl: ['girl'] };
-const chartPropsConfig = {
- formData,
- width: 800,
- height: 600,
- queriesData,
- theme: supersetTheme,
-};
+const queriesData: ChartDataResponseResult[] = [
+ createTestQueryData(defaultQueryRows, { label_map: defaultLabelMap }),
+ createTestQueryData(defaultQueryRows, { label_map: defaultLabelMap }),
+];
test('should transform chart props for viz with showQueryIdentifiers=false',
() => {
- const chartPropsConfigWithoutIdentifiers = {
- ...chartPropsConfig,
- formData: {
- ...formData,
- showQueryIdentifiers: false,
- },
- };
- const chartProps = new ChartProps(chartPropsConfigWithoutIdentifiers);
- const transformed = transformProps(chartProps as
EchartsMixedTimeseriesProps);
+ const chartProps = createEchartsTimeseriesTestChartProps<
+ EchartsMixedTimeseriesFormData,
+ EchartsMixedTimeseriesProps
+ >({
+ ...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
+ defaultQueriesData: queriesData,
+ formData: { ...formData, showQueryIdentifiers: false },
+ queriesData,
+ });
+ const transformed = transformProps(chartProps);
// Check that series IDs don't include query identifiers
const seriesIds = (transformed.echartOptions.series as any[]).map(
@@ -160,15 +187,16 @@ test('should transform chart props for viz with
showQueryIdentifiers=false', ()
});
test('should transform chart props for viz with showQueryIdentifiers=true', ()
=> {
- const chartPropsConfigWithIdentifiers = {
- ...chartPropsConfig,
- formData: {
- ...formData,
- showQueryIdentifiers: true,
- },
- };
- const chartProps = new ChartProps(chartPropsConfigWithIdentifiers);
- const transformed = transformProps(chartProps as
EchartsMixedTimeseriesProps);
+ const chartProps = createEchartsTimeseriesTestChartProps<
+ EchartsMixedTimeseriesFormData,
+ EchartsMixedTimeseriesProps
+ >({
+ ...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
+ defaultQueriesData: queriesData,
+ formData: { ...formData, showQueryIdentifiers: true },
+ queriesData,
+ });
+ const transformed = transformProps(chartProps);
// Check that series IDs include query identifiers
const seriesIds = (transformed.echartOptions.series as any[]).map(
@@ -202,22 +230,25 @@ test('should transform chart props for viz with
showQueryIdentifiers=true', () =
describe('legend sorting', () => {
const getChartProps = (overrides = {}) =>
- new ChartProps({
- ...chartPropsConfig,
+ createEchartsTimeseriesTestChartProps<
+ EchartsMixedTimeseriesFormData,
+ EchartsMixedTimeseriesProps
+ >({
+ ...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
+ defaultQueriesData: queriesData,
formData: {
...formData,
...overrides,
showQueryIdentifiers: true,
},
+ queriesData,
});
test('sort legend by data', () => {
const chartProps = getChartProps({
legendSort: null,
});
- const transformed = transformProps(
- chartProps as EchartsMixedTimeseriesProps,
- );
+ const transformed = transformProps(chartProps);
expect((transformed.echartOptions.legend as any).data).toEqual([
'sum__num (Query A), girl',
@@ -231,9 +262,7 @@ describe('legend sorting', () => {
const chartProps = getChartProps({
legendSort: 'asc',
});
- const transformed = transformProps(
- chartProps as EchartsMixedTimeseriesProps,
- );
+ const transformed = transformProps(chartProps);
expect((transformed.echartOptions.legend as any).data).toEqual([
'sum__num (Query A), boy',
@@ -247,9 +276,7 @@ describe('legend sorting', () => {
const chartProps = getChartProps({
legendSort: 'desc',
});
- const transformed = transformProps(
- chartProps as EchartsMixedTimeseriesProps,
- );
+ const transformed = transformProps(chartProps);
expect((transformed.echartOptions.legend as any).data).toEqual([
'sum__num (Query B), girl',
@@ -261,64 +288,148 @@ describe('legend sorting', () => {
});
test('legend margin: top orientation sets grid.top correctly', () => {
- const chartPropsConfigWithoutIdentifiers = {
- ...chartPropsConfig,
+ const chartProps = createEchartsTimeseriesTestChartProps<
+ EchartsMixedTimeseriesFormData,
+ EchartsMixedTimeseriesProps
+ >({
+ ...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
+ defaultQueriesData: queriesData,
formData: {
...formData,
legendMargin: 250,
showLegend: true,
},
- };
- const chartProps = new ChartProps(chartPropsConfigWithoutIdentifiers);
- const transformed = transformProps(chartProps as
EchartsMixedTimeseriesProps);
+ queriesData,
+ });
+ const transformed = transformProps(chartProps);
expect((transformed.echartOptions.grid as any).top).toEqual(270);
});
test('legend margin: bottom orientation sets grid.bottom correctly', () => {
- const chartPropsConfigWithoutIdentifiers = {
- ...chartPropsConfig,
+ const chartProps = createEchartsTimeseriesTestChartProps<
+ EchartsMixedTimeseriesFormData,
+ EchartsMixedTimeseriesProps
+ >({
+ ...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
+ defaultQueriesData: queriesData,
formData: {
...formData,
legendMargin: 250,
showLegend: true,
legendOrientation: LegendOrientation.Bottom,
},
- };
- const chartProps = new ChartProps(chartPropsConfigWithoutIdentifiers);
- const transformed = transformProps(chartProps as
EchartsMixedTimeseriesProps);
+ queriesData,
+ });
+ const transformed = transformProps(chartProps);
expect((transformed.echartOptions.grid as any).bottom).toEqual(270);
});
test('legend margin: left orientation sets grid.left correctly', () => {
- const chartPropsConfigWithoutIdentifiers = {
- ...chartPropsConfig,
+ const chartProps = createEchartsTimeseriesTestChartProps<
+ EchartsMixedTimeseriesFormData,
+ EchartsMixedTimeseriesProps
+ >({
+ ...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
+ defaultQueriesData: queriesData,
formData: {
...formData,
legendMargin: 250,
showLegend: true,
legendOrientation: LegendOrientation.Left,
},
- };
- const chartProps = new ChartProps(chartPropsConfigWithoutIdentifiers);
- const transformed = transformProps(chartProps as
EchartsMixedTimeseriesProps);
+ queriesData,
+ });
+ const transformed = transformProps(chartProps);
expect((transformed.echartOptions.grid as any).left).toEqual(270);
});
test('legend margin: right orientation sets grid.right correctly', () => {
- const chartPropsConfigWithoutIdentifiers = {
- ...chartPropsConfig,
+ const chartProps = createEchartsTimeseriesTestChartProps<
+ EchartsMixedTimeseriesFormData,
+ EchartsMixedTimeseriesProps
+ >({
+ ...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
+ defaultQueriesData: queriesData,
formData: {
...formData,
legendMargin: 270,
showLegend: true,
legendOrientation: LegendOrientation.Right,
},
- };
- const chartProps = new ChartProps(chartPropsConfigWithoutIdentifiers);
- const transformed = transformProps(chartProps as
EchartsMixedTimeseriesProps);
+ queriesData,
+ });
+ const transformed = transformProps(chartProps);
expect((transformed.echartOptions.grid as any).right).toEqual(270);
});
+
+test('should add a formula annotation when X-axis column has dataset-level
label', () => {
+ const formula: FormulaAnnotationLayer = {
+ name: 'My Formula',
+ annotationType: AnnotationType.Formula,
+ value: 'x*2',
+ style: AnnotationStyle.Solid,
+ show: true,
+ showLabel: true,
+ };
+ const timeColumnName = 'ds';
+ const timeColumnLabel = 'Time Label';
+ const testData = [
+ {
+ [timeColumnLabel]: 599616000000,
+ boy: 1,
+ girl: 2,
+ },
+ {
+ [timeColumnLabel]: 599916000000,
+ boy: 3,
+ girl: 4,
+ },
+ ];
+ const chartProps = createEchartsTimeseriesTestChartProps<
+ EchartsMixedTimeseriesFormData,
+ EchartsMixedTimeseriesProps
+ >({
+ ...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
+ defaultQueriesData: [],
+ formData: {
+ ...formData,
+ x_axis: timeColumnName,
+ annotationLayers: [formula],
+ },
+ queriesData: [
+ createTestQueryData(testData, {
+ label_map: {
+ [timeColumnName]: [timeColumnLabel],
+ boy: ['boy'],
+ girl: ['girl'],
+ },
+ }),
+ createTestQueryData(testData, {
+ label_map: {
+ [timeColumnName]: [timeColumnLabel],
+ boy: ['boy'],
+ girl: ['girl'],
+ },
+ }),
+ ],
+ datasource: {
+ verboseMap: {
+ [timeColumnName]: timeColumnLabel,
+ },
+ columnFormats: {},
+ currencyFormats: {},
+ },
+ });
+ const result = transformProps(chartProps);
+ const formulaSeries = (
+ result.echartOptions.series as SeriesOption[] | undefined
+ )?.find((s: SeriesOption) => s.name === 'My Formula');
+ expect(formulaSeries).toBeDefined();
+ expect(formulaSeries?.data).toBeDefined();
+ expect(Array.isArray(formulaSeries?.data)).toBe(true);
+ expect((formulaSeries?.data as unknown[]).length).toBeGreaterThan(0);
+});
diff --git
a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts
b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts
index 562f3fd5190..89303f7f1bb 100644
---
a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts
+++
b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts
@@ -20,23 +20,61 @@ import {
AnnotationSourceType,
AnnotationStyle,
AnnotationType,
- ChartProps,
ComparisonType,
+ DataRecord,
EventAnnotationLayer,
FormulaAnnotationLayer,
IntervalAnnotationLayer,
SqlaFormData,
TimeseriesAnnotationLayer,
+ ChartDataResponseResult,
} from '@superset-ui/core';
-import { supersetTheme } from '@apache-superset/core/ui';
import { EchartsTimeseriesChartProps } from '../../src/types';
+import type { SeriesOption } from 'echarts';
import transformProps from '../../src/Timeseries/transformProps';
import {
EchartsTimeseriesSeriesType,
OrientationType,
+ EchartsTimeseriesFormData,
} from '../../src/Timeseries/types';
+import { StackControlsValue } from '../../src/constants';
+import { DEFAULT_FORM_DATA } from '../../src/Timeseries/constants';
+import { createEchartsTimeseriesTestChartProps } from '../helpers';
import { BASE_TIMESTAMP, createTestData } from './helpers';
+/**
+ * Creates a partial ChartDataResponseResult for testing.
+ * Only includes the fields needed for tests, with sensible defaults for
required fields.
+ */
+function createTestQueryData(
+ data: unknown[],
+ overrides?: Partial<ChartDataResponseResult> & {
+ label_map?: Record<string, string[]>;
+ },
+): ChartDataResponseResult {
+ return {
+ annotation_data: null,
+ cache_key: null,
+ cache_timeout: null,
+ cached_dttm: null,
+ queried_dttm: null,
+ data: data as DataRecord[],
+ colnames: [],
+ coltypes: [],
+ error: null,
+ is_cached: false,
+ query: '',
+ rowcount: data.length,
+ sql_rowcount: data.length,
+ stacktrace: null,
+ status: 'success',
+ from_dttm: null,
+ to_dttm: null,
+ label_map: {},
+ ...overrides,
+ } as ChartDataResponseResult & { label_map?: Record<string, string[]> };
+}
+
type YAxisFormatter = (value: number, index: number) => string;
function getYAxisFormatter(
@@ -51,6 +89,37 @@ function getYAxisFormatter(
return yAxis.axisLabel!.formatter!;
}
+/**
+ * Creates a properly typed EchartsTimeseriesChartProps for testing.
+ * Uses shared createEchartsTimeseriesTestChartProps with Timeseries defaults.
+ */
+function createTestChartProps(config: {
+ formData?: Partial<EchartsTimeseriesFormData>;
+ queriesData?: ChartDataResponseResult[];
+ annotationData?: Record<string, unknown>;
+ datasource?: {
+ verboseMap?: Record<string, string>;
+ columnFormats?: Record<string, string>;
+ currencyFormats?: Record<
+ string,
+ { symbol: string; symbolPosition: string }
+ >;
+ currencyCodeColumn?: string;
+ };
+ width?: number;
+ height?: number;
+}): EchartsTimeseriesChartProps {
+ return createEchartsTimeseriesTestChartProps<
+ EchartsTimeseriesFormData,
+ EchartsTimeseriesChartProps
+ >({
+ defaultFormData: DEFAULT_FORM_DATA,
+ defaultVizType: 'my_viz',
+ defaultQueriesData: queriesData,
+ ...config,
+ });
+}
+
const formData: SqlaFormData = {
colorScheme: 'bnbColors',
datasource: '3__table',
@@ -59,29 +128,21 @@ const formData: SqlaFormData = {
groupby: ['foo', 'bar'],
viz_type: 'my_viz',
};
-const queriesData = [
- {
- data: createTestData(
+const queriesData: ChartDataResponseResult[] = [
+ createTestQueryData(
+ createTestData(
[
{ 'San Francisco': 1, 'New York': 2 },
{ 'San Francisco': 3, 'New York': 4 },
],
{ intervalMs: 300000000 },
),
- },
+ ),
];
-const chartPropsConfig = {
- formData,
- width: 800,
- height: 600,
- queriesData,
- theme: supersetTheme,
-};
-
describe('EchartsTimeseries transformProps', () => {
test('should transform chart props for viz', () => {
- const chartProps = new ChartProps(chartPropsConfig);
- expect(transformProps(chartProps as EchartsTimeseriesChartProps)).toEqual(
+ const chartProps = createTestChartProps({});
+ expect(transformProps(chartProps)).toEqual(
expect.objectContaining({
width: 800,
height: 600,
@@ -111,14 +172,13 @@ describe('EchartsTimeseries transformProps', () => {
});
test('should transform chart props for horizontal viz', () => {
- const chartProps = new ChartProps({
- ...chartPropsConfig,
+ const chartProps = createTestChartProps({
formData: {
...formData,
- orientation: 'horizontal',
+ orientation: OrientationType.Horizontal,
},
});
- expect(transformProps(chartProps as EchartsTimeseriesChartProps)).toEqual(
+ expect(transformProps(chartProps)).toEqual(
expect.objectContaining({
width: 800,
height: 600,
@@ -156,14 +216,13 @@ describe('EchartsTimeseries transformProps', () => {
show: true,
showLabel: true,
};
- const chartProps = new ChartProps({
- ...chartPropsConfig,
+ const chartProps = createTestChartProps({
formData: {
...formData,
annotationLayers: [formula],
},
});
- expect(transformProps(chartProps as EchartsTimeseriesChartProps)).toEqual(
+ expect(transformProps(chartProps)).toEqual(
expect.objectContaining({
width: 800,
height: 600,
@@ -199,6 +258,137 @@ describe('EchartsTimeseries transformProps', () => {
);
});
+ test('should add a formula annotation when X-axis column has dataset-level
label', () => {
+ const formula: FormulaAnnotationLayer = {
+ name: 'My Formula',
+ annotationType: AnnotationType.Formula,
+ value: 'x*2',
+ style: AnnotationStyle.Solid,
+ show: true,
+ showLabel: true,
+ };
+ const timeColumnName = 'ds';
+ const timeColumnLabel = 'Time Label';
+ const testData = [
+ {
+ [timeColumnLabel]: new Date(BASE_TIMESTAMP).toISOString(),
+ 'San Francisco': 1,
+ 'New York': 2,
+ },
+ {
+ [timeColumnLabel]: new Date(BASE_TIMESTAMP + 300000000).toISOString(),
+ 'San Francisco': 3,
+ 'New York': 4,
+ },
+ ];
+ const chartProps = createTestChartProps({
+ formData: {
+ ...formData,
+ x_axis: timeColumnName,
+ granularity_sqla: timeColumnName,
+ annotationLayers: [formula],
+ },
+ queriesData: [createTestQueryData(testData)],
+ datasource: {
+ verboseMap: {
+ [timeColumnName]: timeColumnLabel,
+ },
+ columnFormats: {},
+ currencyFormats: {},
+ },
+ });
+ const result = transformProps(chartProps);
+ const formulaSeries = (
+ result.echartOptions.series as SeriesOption[] | undefined
+ )?.find((s: SeriesOption) => s.name === 'My Formula');
+ expect(formulaSeries).toBeDefined();
+ expect(formulaSeries?.data).toBeDefined();
+ expect(Array.isArray(formulaSeries?.data)).toBe(true);
+ expect((formulaSeries?.data as unknown[]).length).toBeGreaterThan(0);
+ const firstDataPoint = (formulaSeries?.data as [number, number][])[0];
+ expect(firstDataPoint).toBeDefined();
+ expect(firstDataPoint[1]).toBe(firstDataPoint[0] * 2);
+ });
+
+ test('should add a formula annotation when X-axis column has dataset-level
label and verboseMap is empty (backward compatibility)', () => {
+ const formula: FormulaAnnotationLayer = {
+ name: 'My Formula',
+ annotationType: AnnotationType.Formula,
+ value: 'x+1',
+ style: AnnotationStyle.Solid,
+ show: true,
+ showLabel: true,
+ };
+ const chartProps = createTestChartProps({
+ formData: {
+ ...formData,
+ annotationLayers: [formula],
+ },
+ datasource: {
+ verboseMap: {},
+ columnFormats: {},
+ currencyFormats: {},
+ },
+ });
+ const result = transformProps(chartProps);
+ const formulaSeries = (
+ result.echartOptions.series as SeriesOption[] | undefined
+ )?.find((s: SeriesOption) => s.name === 'My Formula');
+ expect(formulaSeries).toBeDefined();
+ expect(formulaSeries?.data).toBeDefined();
+ expect(Array.isArray(formulaSeries?.data)).toBe(true);
+ });
+
+ test('should add a formula annotation when X-axis column has dataset-level
label in horizontal orientation', () => {
+ const formula: FormulaAnnotationLayer = {
+ name: 'My Formula',
+ annotationType: AnnotationType.Formula,
+ value: 'x*2',
+ style: AnnotationStyle.Solid,
+ show: true,
+ showLabel: true,
+ };
+ const timeColumnName = 'ds';
+ const timeColumnLabel = 'Time Label';
+ const testData = [
+ {
+ [timeColumnLabel]: new Date(BASE_TIMESTAMP).toISOString(),
+ 'San Francisco': 1,
+ 'New York': 2,
+ },
+ {
+ [timeColumnLabel]: new Date(BASE_TIMESTAMP + 300000000).toISOString(),
+ 'San Francisco': 3,
+ 'New York': 4,
+ },
+ ];
+ const chartProps = createTestChartProps({
+ formData: {
+ ...formData,
+ x_axis: timeColumnName,
+ granularity_sqla: timeColumnName,
+ orientation: OrientationType.Horizontal,
+ annotationLayers: [formula],
+ },
+ queriesData: [createTestQueryData(testData)],
+ datasource: {
+ verboseMap: {
+ [timeColumnName]: timeColumnLabel,
+ },
+ columnFormats: {},
+ currencyFormats: {},
+ },
+ });
+ const result = transformProps(chartProps);
+ const formulaSeries = (
+ result.echartOptions.series as SeriesOption[] | undefined
+ )?.find((s: SeriesOption) => s.name === 'My Formula');
+ expect(formulaSeries).toBeDefined();
+ const firstDataPoint = (formulaSeries?.data as [number, number][])[0];
+ expect(firstDataPoint).toBeDefined();
+ expect(firstDataPoint[0]).toBe(firstDataPoint[1] * 2);
+ });
+
test('should add an interval, event and timeseries annotation to viz', () =>
{
const event: EventAnnotationLayer = {
annotationType: AnnotationType.Event,
@@ -270,8 +460,7 @@ describe('EchartsTimeseries transformProps', () => {
],
},
};
- const chartProps = new ChartProps({
- ...chartPropsConfig,
+ const chartProps = createTestChartProps({
formData: {
...formData,
annotationLayers: [event, interval, timeseries],
@@ -279,12 +468,12 @@ describe('EchartsTimeseries transformProps', () => {
annotationData,
queriesData: [
{
- ...queriesData[0],
+ ...(queriesData[0] as ChartDataResponseResult),
annotation_data: annotationData,
},
],
});
- expect(transformProps(chartProps as EchartsTimeseriesChartProps)).toEqual(
+ expect(transformProps(chartProps)).toEqual(
expect.objectContaining({
echartOptions: expect.objectContaining({
legend: expect.objectContaining({
@@ -310,9 +499,9 @@ describe('EchartsTimeseries transformProps', () => {
});
test('Should add a baseline series for stream graph', () => {
- const streamQueriesData = [
- {
- data: createTestData(
+ const streamQueriesDataTyped: ChartDataResponseResult[] = [
+ createTestQueryData(
+ createTestData(
[
{
'San Francisco': 120,
@@ -366,21 +555,18 @@ describe('EchartsTimeseries transformProps', () => {
],
{ intervalMs: 1 },
),
- },
+ ),
];
- const streamFormData = { ...formData, stack: 'Stream' };
- const props = {
- ...chartPropsConfig,
- formData: streamFormData,
- queriesData: streamQueriesData,
+ const streamFormData: Partial<EchartsTimeseriesFormData> = {
+ ...formData,
+ stack: StackControlsValue.Stream,
};
-
- const chartProps = new ChartProps(props);
+ const chartProps = createTestChartProps({
+ formData: streamFormData,
+ queriesData: streamQueriesDataTyped,
+ });
expect(
- (
- transformProps(chartProps as EchartsTimeseriesChartProps).echartOptions
- .series as any[]
- )[0],
+ (transformProps(chartProps).echartOptions.series as any[])[0],
).toEqual({
areaStyle: {
opacity: 0,
@@ -437,9 +623,9 @@ describe('Does transformProps transform series correctly',
() => {
onlyTotal: false,
percentageThreshold: 50,
};
- const queriesData = [
- {
- data: createTestData(
+ const queriesData: ChartDataResponseResult[] = [
+ createTestQueryData(
+ createTestData(
[
{
'San Francisco': 1,
@@ -464,21 +650,15 @@ describe('Does transformProps transform series
correctly', () => {
],
{ intervalMs: 300000000 },
),
- },
+ ),
];
- const chartPropsConfig = {
- formData,
- width: 800,
- height: 600,
- queriesData,
- theme: supersetTheme,
- };
const totalStackedValues = queriesData[0].data.reduce(
(totals, currentStack) => {
const total = Object.keys(currentStack).reduce((stackSum, key) => {
if (key === '__timestamp') return stackSum;
- return stackSum + currentStack[key as keyof typeof currentStack];
+ const val = currentStack[key as keyof typeof currentStack];
+ return stackSum + (typeof val === 'number' ? val : 0);
}, 0);
totals.push(total);
return totals;
@@ -487,11 +667,10 @@ describe('Does transformProps transform series
correctly', () => {
);
test('should show labels when showValue is true', () => {
- const chartProps = new ChartProps(chartPropsConfig);
+ const chartProps = createTestChartProps({ formData, queriesData });
- const transformedSeries = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- ).echartOptions.series as seriesType[];
+ const transformedSeries = transformProps(chartProps).echartOptions
+ .series as seriesType[];
transformedSeries.forEach(series => {
expect(series.label.show).toBe(true);
@@ -499,16 +678,13 @@ describe('Does transformProps transform series
correctly', () => {
});
test('should not show labels when showValue is false', () => {
- const updatedChartPropsConfig = {
- ...chartPropsConfig,
+ const chartProps = createTestChartProps({
formData: { ...formData, showValue: false },
- };
-
- const chartProps = new ChartProps(updatedChartPropsConfig);
+ queriesData,
+ });
- const transformedSeries = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- ).echartOptions.series as seriesType[];
+ const transformedSeries = transformProps(chartProps).echartOptions
+ .series as seriesType[];
transformedSeries.forEach(series => {
expect(series.label.show).toBe(false);
@@ -516,16 +692,13 @@ describe('Does transformProps transform series
correctly', () => {
});
test('should show only totals when onlyTotal is true', () => {
- const updatedChartPropsConfig = {
- ...chartPropsConfig,
+ const chartProps = createTestChartProps({
formData: { ...formData, onlyTotal: true },
- };
-
- const chartProps = new ChartProps(updatedChartPropsConfig);
+ queriesData,
+ });
- const transformedSeries = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- ).echartOptions.series as seriesType[];
+ const transformedSeries = transformProps(chartProps).echartOptions
+ .series as seriesType[];
const showValueIndexes: number[] = [];
@@ -561,11 +734,10 @@ describe('Does transformProps transform series
correctly', () => {
});
test('should show labels on values >= percentageThreshold if onlyTotal is
false', () => {
- const chartProps = new ChartProps(chartPropsConfig);
+ const chartProps = createTestChartProps({ formData, queriesData });
- const transformedSeries = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- ).echartOptions.series as seriesType[];
+ const transformedSeries = transformProps(chartProps).echartOptions
+ .series as seriesType[];
const expectedThresholds = totalStackedValues.map(
total => ((formData.percentageThreshold || 0) / 100) * total,
@@ -587,16 +759,13 @@ describe('Does transformProps transform series
correctly', () => {
});
test('should not apply percentage threshold when showValue is true and stack
is false', () => {
- const updatedChartPropsConfig = {
- ...chartPropsConfig,
+ const chartProps = createTestChartProps({
formData: { ...formData, stack: false },
- };
-
- const chartProps = new ChartProps(updatedChartPropsConfig);
+ queriesData,
+ });
- const transformedSeries = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- ).echartOptions.series as seriesType[];
+ const transformedSeries = transformProps(chartProps).echartOptions
+ .series as seriesType[];
transformedSeries.forEach((series, seriesIndex) => {
expect(series.label.show).toBe(true);
@@ -613,28 +782,23 @@ describe('Does transformProps transform series
correctly', () => {
});
test('should remove time shift labels from label_map', () => {
- const updatedChartPropsConfig = {
- ...chartPropsConfig,
+ const chartProps = createTestChartProps({
formData: {
...formData,
timeCompare: ['1 year ago'],
},
queriesData: [
- {
- ...queriesData[0],
+ createTestQueryData(queriesData[0].data as DataRecord[], {
label_map: {
'1 year ago, foo1, bar1': ['1 year ago', 'foo1', 'bar1'],
'1 year ago, foo2, bar2': ['1 year ago', 'foo2', 'bar2'],
'foo1, bar1': ['foo1', 'bar1'],
'foo2, bar2': ['foo2', 'bar2'],
},
- },
+ }),
],
- };
- const chartProps = new ChartProps(updatedChartPropsConfig);
- const transformedProps = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- );
+ });
+ const transformedProps = transformProps(chartProps);
expect(transformedProps.labelMap).toEqual({
'1 year ago, foo1, bar1': ['foo1', 'bar1'],
'1 year ago, foo2, bar2': ['foo2', 'bar2'],
@@ -645,9 +809,9 @@ describe('Does transformProps transform series correctly',
() => {
});
describe('legend sorting', () => {
- const legendSortData = [
- {
- data: createTestData(
+ const legendSortData: ChartDataResponseResult[] = [
+ createTestQueryData(
+ createTestData(
[
{
Milton: 40,
@@ -676,13 +840,12 @@ describe('legend sorting', () => {
],
{ intervalMs: 300000000 },
),
- },
+ ),
];
- const getChartProps = (formData: Partial<SqlaFormData>) =>
- new ChartProps({
- ...chartPropsConfig,
- formData: { ...formData },
+ const getChartProps = (formDataOverrides: Partial<SqlaFormData>) =>
+ createTestChartProps({
+ formData: { ...formData, ...formDataOverrides },
queriesData: legendSortData,
});
@@ -692,9 +855,7 @@ describe('legend sorting', () => {
sortSeriesType: 'min',
sortSeriesAscending: true,
});
- const transformed = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- );
+ const transformed = transformProps(chartProps);
expect((transformed.echartOptions.legend as any).data).toEqual([
'San Francisco',
@@ -710,9 +871,7 @@ describe('legend sorting', () => {
sortSeriesType: 'min',
sortSeriesAscending: true,
});
- const transformed = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- );
+ const transformed = transformProps(chartProps);
expect((transformed.echartOptions.legend as any).data).toEqual([
'Boston',
@@ -728,9 +887,7 @@ describe('legend sorting', () => {
sortSeriesType: 'min',
sortSeriesAscending: true,
});
- const transformed = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- );
+ const transformed = transformProps(chartProps);
expect((transformed.echartOptions.legend as any).data).toEqual([
'San Francisco',
@@ -749,25 +906,15 @@ const timeCompareFormData: SqlaFormData = {
viz_type: 'my_viz',
};
-const timeCompareChartPropsConfig = {
- formData: timeCompareFormData,
- width: 800,
- height: 600,
- theme: supersetTheme,
-};
-
test('should apply dashed line style to time comparison series with single
metric', () => {
const queriesDataWithTimeCompare = [
- {
- data: [
- { sum__num: 100, '1 week ago': 80, __timestamp: 599616000000 },
- { sum__num: 150, '1 week ago': 120, __timestamp: 599916000000 },
- ],
- },
+ createTestQueryData([
+ { sum__num: 100, '1 week ago': 80, __timestamp: 599616000000 },
+ { sum__num: 150, '1 week ago': 120, __timestamp: 599916000000 },
+ ]),
];
- const chartProps = new ChartProps({
- ...timeCompareChartPropsConfig,
+ const chartProps = createTestChartProps({
formData: {
...timeCompareFormData,
time_compare: ['1 week ago'],
@@ -776,44 +923,51 @@ test('should apply dashed line style to time comparison
series with single metri
queriesData: queriesDataWithTimeCompare,
});
- const transformed = transformProps(
- chartProps as unknown as EchartsTimeseriesChartProps,
- );
- const series = transformed.echartOptions.series as any[];
+ const transformed = transformProps(chartProps);
+ const series = (transformed.echartOptions.series as SeriesOption[]) || [];
- const mainSeries = series.find(s => s.name === 'sum__num');
- const comparisonSeries = series.find(s => s.name === '1 week ago');
+ const mainSeries = series.find(s => s.name === 'sum__num') as
+ | (SeriesOption & { lineStyle?: { type?: number[] | string } })
+ | undefined;
+ const comparisonSeries = series.find(s => s.name === '1 week ago') as
+ | (SeriesOption & { lineStyle?: { type?: number[] | string } })
+ | undefined;
expect(mainSeries).toBeDefined();
expect(comparisonSeries).toBeDefined();
// Main series should not have a dash pattern array
- expect(Array.isArray(mainSeries.lineStyle?.type)).toBe(false);
+ expect(Array.isArray(mainSeries?.lineStyle?.type)).toBe(false);
// Comparison series should have a visible dash pattern array [dash, gap]
- expect(Array.isArray(comparisonSeries.lineStyle?.type)).toBe(true);
- expect(comparisonSeries.lineStyle?.type[0]).toBeGreaterThanOrEqual(4);
- expect(comparisonSeries.lineStyle?.type[1]).toBeGreaterThanOrEqual(3);
+ expect(Array.isArray(comparisonSeries?.lineStyle?.type)).toBe(true);
+ expect(
+ Array.isArray(comparisonSeries?.lineStyle?.type)
+ ? comparisonSeries.lineStyle.type[0]
+ : undefined,
+ ).toBeGreaterThanOrEqual(4);
+ expect(
+ Array.isArray(comparisonSeries?.lineStyle?.type)
+ ? comparisonSeries.lineStyle.type[1]
+ : undefined,
+ ).toBeGreaterThanOrEqual(3);
});
test('should apply dashed line style to time comparison series with
metric__offset pattern', () => {
const queriesDataWithTimeCompare = [
- {
- data: [
- {
- sum__num: 100,
- 'sum__num__1 week ago': 80,
- __timestamp: 599616000000,
- },
- {
- sum__num: 150,
- 'sum__num__1 week ago': 120,
- __timestamp: 599916000000,
- },
- ],
- },
+ createTestQueryData([
+ {
+ sum__num: 100,
+ 'sum__num__1 week ago': 80,
+ __timestamp: 599616000000,
+ },
+ {
+ sum__num: 150,
+ 'sum__num__1 week ago': 120,
+ __timestamp: 599916000000,
+ },
+ ]),
];
- const chartProps = new ChartProps({
- ...timeCompareChartPropsConfig,
+ const chartProps = createTestChartProps({
formData: {
...timeCompareFormData,
time_compare: ['1 week ago'],
@@ -822,37 +976,46 @@ test('should apply dashed line style to time comparison
series with metric__offs
queriesData: queriesDataWithTimeCompare,
});
- const transformed = transformProps(
- chartProps as unknown as EchartsTimeseriesChartProps,
- );
- const series = transformed.echartOptions.series as any[];
+ const transformed = transformProps(chartProps);
+ const series = (transformed.echartOptions.series as SeriesOption[]) || [];
- const mainSeries = series.find(s => s.name === 'sum__num');
- const comparisonSeries = series.find(s => s.name === 'sum__num__1 week ago');
+ const mainSeries = series.find(s => s.name === 'sum__num') as
+ | (SeriesOption & { lineStyle?: { type?: number[] | string } })
+ | undefined;
+ const comparisonSeries = series.find(
+ s => s.name === 'sum__num__1 week ago',
+ ) as
+ | (SeriesOption & { lineStyle?: { type?: number[] | string } })
+ | undefined;
expect(mainSeries).toBeDefined();
expect(comparisonSeries).toBeDefined();
// Main series should not have a dash pattern array
- expect(Array.isArray(mainSeries.lineStyle?.type)).toBe(false);
+ expect(Array.isArray(mainSeries?.lineStyle?.type)).toBe(false);
// Comparison series should have a visible dash pattern array [dash, gap]
- expect(Array.isArray(comparisonSeries.lineStyle?.type)).toBe(true);
- expect(comparisonSeries.lineStyle?.type[0]).toBeGreaterThanOrEqual(4);
- expect(comparisonSeries.lineStyle?.type[1]).toBeGreaterThanOrEqual(3);
+ expect(Array.isArray(comparisonSeries?.lineStyle?.type)).toBe(true);
+ expect(
+ Array.isArray(comparisonSeries?.lineStyle?.type)
+ ? comparisonSeries.lineStyle.type[0]
+ : undefined,
+ ).toBeGreaterThanOrEqual(4);
+ expect(
+ Array.isArray(comparisonSeries?.lineStyle?.type)
+ ? comparisonSeries.lineStyle.type[1]
+ : undefined,
+ ).toBeGreaterThanOrEqual(3);
});
test('should apply connectNulls to time comparison series', () => {
const queriesDataWithNulls = [
- {
- data: [
- { sum__num: 100, '1 week ago': null, __timestamp: 599616000000 },
- { sum__num: 150, '1 week ago': 120, __timestamp: 599916000000 },
- { sum__num: 200, '1 week ago': null, __timestamp: 600216000000 },
- ],
- },
+ createTestQueryData([
+ { sum__num: 100, '1 week ago': null, __timestamp: 599616000000 },
+ { sum__num: 150, '1 week ago': 120, __timestamp: 599916000000 },
+ { sum__num: 200, '1 week ago': null, __timestamp: 600216000000 },
+ ]),
];
- const chartProps = new ChartProps({
- ...timeCompareChartPropsConfig,
+ const chartProps = createTestChartProps({
formData: {
...timeCompareFormData,
time_compare: ['1 week ago'],
@@ -861,29 +1024,26 @@ test('should apply connectNulls to time comparison
series', () => {
queriesData: queriesDataWithNulls,
});
- const transformed = transformProps(
- chartProps as unknown as EchartsTimeseriesChartProps,
- );
- const series = transformed.echartOptions.series as any[];
+ const transformed = transformProps(chartProps);
+ const series = (transformed.echartOptions.series as SeriesOption[]) || [];
- const comparisonSeries = series.find(s => s.name === '1 week ago');
+ const comparisonSeries = series.find(s => s.name === '1 week ago') as
+ | (SeriesOption & { connectNulls?: boolean })
+ | undefined;
expect(comparisonSeries).toBeDefined();
- expect(comparisonSeries.connectNulls).toBe(true);
+ expect(comparisonSeries?.connectNulls).toBe(true);
});
test('should not apply dashed line style for non-Values comparison types', ()
=> {
const queriesDataWithTimeCompare = [
- {
- data: [
- { sum__num: 100, '1 week ago': 80, __timestamp: 599616000000 },
- { sum__num: 150, '1 week ago': 120, __timestamp: 599916000000 },
- ],
- },
+ createTestQueryData([
+ { sum__num: 100, '1 week ago': 80, __timestamp: 599616000000 },
+ { sum__num: 150, '1 week ago': 120, __timestamp: 599916000000 },
+ ]),
];
- const chartProps = new ChartProps({
- ...timeCompareChartPropsConfig,
+ const chartProps = createTestChartProps({
formData: {
...timeCompareFormData,
time_compare: ['1 week ago'],
@@ -892,22 +1052,24 @@ test('should not apply dashed line style for non-Values
comparison types', () =>
queriesData: queriesDataWithTimeCompare,
});
- const transformed = transformProps(
- chartProps as unknown as EchartsTimeseriesChartProps,
- );
- const series = transformed.echartOptions.series as any[];
+ const transformed = transformProps(chartProps);
+ const series = (transformed.echartOptions.series as SeriesOption[]) || [];
- const comparisonSeries = series.find(s => s.name === '1 week ago');
+ const comparisonSeries = series.find(s => s.name === '1 week ago') as
+ | (SeriesOption & {
+ lineStyle?: { type?: number[] | string };
+ connectNulls?: boolean;
+ })
+ | undefined;
expect(comparisonSeries).toBeDefined();
// Non-Values comparison types don't get dashed styling (isDerivedSeries
returns false)
- expect(Array.isArray(comparisonSeries.lineStyle?.type)).toBe(false);
- expect(comparisonSeries.connectNulls).toBeFalsy();
+ expect(Array.isArray(comparisonSeries?.lineStyle?.type)).toBe(false);
+ expect(comparisonSeries?.connectNulls).toBeFalsy();
});
test('EchartsTimeseries AUTO mode should detect single currency and format
with $ for USD', () => {
- const chartProps = new ChartProps<SqlaFormData>({
- ...chartPropsConfig,
+ const chartProps = createTestChartProps({
formData: {
...formData,
metrics: ['sum__num'],
@@ -920,8 +1082,8 @@ test('EchartsTimeseries AUTO mode should detect single
currency and format with
verboseMap: {},
},
queriesData: [
- {
- data: [
+ createTestQueryData(
+ [
{
'San Francisco': 1000,
__timestamp: 599616000000,
@@ -933,19 +1095,19 @@ test('EchartsTimeseries AUTO mode should detect single
currency and format with
currency_code: 'USD',
},
],
- },
+ { detected_currency: 'USD' },
+ ),
],
});
- const transformed = transformProps(chartProps as
EchartsTimeseriesChartProps);
+ const transformed = transformProps(chartProps);
const formatter = getYAxisFormatter(transformed);
expect(formatter(1000, 0)).toContain('$');
});
test('EchartsTimeseries AUTO mode should use neutral formatting for mixed
currencies', () => {
- const chartProps = new ChartProps<SqlaFormData>({
- ...chartPropsConfig,
+ const chartProps = createTestChartProps({
formData: {
...formData,
metrics: ['sum__num'],
@@ -958,24 +1120,22 @@ test('EchartsTimeseries AUTO mode should use neutral
formatting for mixed curren
verboseMap: {},
},
queriesData: [
- {
- data: [
- {
- 'San Francisco': 1000,
- __timestamp: 599616000000,
- currency_code: 'USD',
- },
- {
- 'San Francisco': 2000,
- __timestamp: 599916000000,
- currency_code: 'EUR',
- },
- ],
- },
+ createTestQueryData([
+ {
+ 'San Francisco': 1000,
+ __timestamp: 599616000000,
+ currency_code: 'USD',
+ },
+ {
+ 'San Francisco': 2000,
+ __timestamp: 599916000000,
+ currency_code: 'EUR',
+ },
+ ]),
],
});
- const transformed = transformProps(chartProps as
EchartsTimeseriesChartProps);
+ const transformed = transformProps(chartProps);
// With mixed currencies, Y-axis should use neutral formatting
const formatter = getYAxisFormatter(transformed);
@@ -985,8 +1145,7 @@ test('EchartsTimeseries AUTO mode should use neutral
formatting for mixed curren
});
test('EchartsTimeseries should preserve static currency format with £ for
GBP', () => {
- const chartProps = new ChartProps<SqlaFormData>({
- ...chartPropsConfig,
+ const chartProps = createTestChartProps({
formData: {
...formData,
metrics: ['sum__num'],
@@ -999,24 +1158,22 @@ test('EchartsTimeseries should preserve static currency
format with £ for GBP',
verboseMap: {},
},
queriesData: [
- {
- data: [
- {
- 'San Francisco': 1000,
- __timestamp: 599616000000,
- currency_code: 'USD',
- },
- {
- 'San Francisco': 2000,
- __timestamp: 599916000000,
- currency_code: 'EUR',
- },
- ],
- },
+ createTestQueryData([
+ {
+ 'San Francisco': 1000,
+ __timestamp: 599616000000,
+ currency_code: 'USD',
+ },
+ {
+ 'San Francisco': 2000,
+ __timestamp: 599916000000,
+ currency_code: 'EUR',
+ },
+ ]),
],
});
- const transformed = transformProps(chartProps as
EchartsTimeseriesChartProps);
+ const transformed = transformProps(chartProps);
// Static mode should always show £
const formatter = getYAxisFormatter(transformed);
@@ -1037,26 +1194,21 @@ const baseFormDataHorizontalBar: SqlaFormData = {
};
test('should set yAxis max to actual data max for horizontal bar charts', ()
=> {
- const queriesData = [
- {
- data: createTestData(
+ const queriesData: ChartDataResponseResult[] = [
+ createTestQueryData(
+ createTestData(
[{ 'Series A': 15000 }, { 'Series A': 20000 }, { 'Series A': 18000 }],
{ intervalMs: 300000000 },
),
- },
+ ),
];
- const chartProps = new ChartProps({
+ const chartProps = createTestChartProps({
formData: baseFormDataHorizontalBar,
- width: 800,
- height: 600,
queriesData,
- theme: supersetTheme,
});
- const transformedProps = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- );
+ const transformedProps = transformProps(chartProps);
// In horizontal orientation, axes are swapped, so yAxis becomes xAxis
const xAxisRaw = transformedProps.echartOptions.xAxis as any;
@@ -1064,26 +1216,21 @@ test('should set yAxis max to actual data max for
horizontal bar charts', () =>
});
test('should set yAxis min and max for diverging horizontal bar charts', () =>
{
- const queriesData = [
- {
- data: createTestData(
+ const queriesData: ChartDataResponseResult[] = [
+ createTestQueryData(
+ createTestData(
[{ 'Series A': -21000 }, { 'Series A': 20000 }, { 'Series A': 18000 }],
{ intervalMs: 300000000 },
),
- },
+ ),
];
- const chartProps = new ChartProps({
+ const chartProps = createTestChartProps({
formData: baseFormDataHorizontalBar,
- width: 800,
- height: 600,
queriesData,
- theme: supersetTheme,
});
- const transformedProps = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- );
+ const transformedProps = transformProps(chartProps);
// In horizontal orientation, axes are swapped, so yAxis becomes xAxis
const xAxisRaw = transformedProps.echartOptions.xAxis as any;
@@ -1092,29 +1239,24 @@ test('should set yAxis min and max for diverging
horizontal bar charts', () => {
});
test('should not override explicit yAxisBounds for horizontal bar charts', ()
=> {
- const queriesData = [
- {
- data: createTestData(
+ const queriesData: ChartDataResponseResult[] = [
+ createTestQueryData(
+ createTestData(
[{ 'Series A': 15000 }, { 'Series A': 20000 }, { 'Series A': 18000 }],
{ intervalMs: 300000000 },
),
- },
+ ),
];
- const chartProps = new ChartProps({
+ const chartProps = createTestChartProps({
formData: {
...baseFormDataHorizontalBar,
yAxisBounds: [0, 25000], // Explicit bounds
},
- width: 800,
- height: 600,
queriesData,
- theme: supersetTheme,
});
- const transformedProps = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- );
+ const transformedProps = transformProps(chartProps);
// In horizontal orientation, axes are swapped, so yAxis becomes xAxis
const xAxisRaw = transformedProps.echartOptions.xAxis as any;
@@ -1123,29 +1265,24 @@ test('should not override explicit yAxisBounds for
horizontal bar charts', () =>
});
test('should not apply axis bounds calculation when truncateYAxis is false for
horizontal bar charts', () => {
- const queriesData = [
- {
- data: createTestData(
+ const queriesData: ChartDataResponseResult[] = [
+ createTestQueryData(
+ createTestData(
[{ 'Series A': 15000 }, { 'Series A': 20000 }, { 'Series A': 18000 }],
{ intervalMs: 300000000 },
),
- },
+ ),
];
- const chartProps = new ChartProps({
+ const chartProps = createTestChartProps({
formData: {
...baseFormDataHorizontalBar,
truncateYAxis: false,
},
- width: 800,
- height: 600,
queriesData,
- theme: supersetTheme,
});
- const transformedProps = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- );
+ const transformedProps = transformProps(chartProps);
// In horizontal orientation, axes are swapped, so yAxis becomes xAxis
const xAxis = transformedProps.echartOptions.xAxis as any;
@@ -1154,29 +1291,24 @@ test('should not apply axis bounds calculation when
truncateYAxis is false for h
});
test('should not apply axis bounds calculation when seriesType is not Bar for
horizontal charts', () => {
- const queriesData = [
- {
- data: createTestData(
+ const queriesData: ChartDataResponseResult[] = [
+ createTestQueryData(
+ createTestData(
[{ 'Series A': 15000 }, { 'Series A': 20000 }, { 'Series A': 18000 }],
{ intervalMs: 300000000 },
),
- },
+ ),
];
- const chartProps = new ChartProps({
+ const chartProps = createTestChartProps({
formData: {
...baseFormDataHorizontalBar,
seriesType: EchartsTimeseriesSeriesType.Line,
},
- width: 800,
- height: 600,
queriesData,
- theme: supersetTheme,
});
- const transformedProps = transformProps(
- chartProps as EchartsTimeseriesChartProps,
- );
+ const transformedProps = transformProps(chartProps);
// In horizontal orientation, axes are swapped, so yAxis becomes xAxis
const xAxisRaw = transformedProps.echartOptions.xAxis as any;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/helpers.ts
b/superset-frontend/plugins/plugin-chart-echarts/test/helpers.ts
new file mode 100644
index 00000000000..2fe9dc8ae03
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-echarts/test/helpers.ts
@@ -0,0 +1,110 @@
+/**
+ * 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 { ChartProps, ChartDataResponseResult } from '@superset-ui/core';
+import { supersetTheme } from '@apache-superset/core/ui';
+
+/**
+ * Datasource shape used by Echarts Timeseries and Mixed Timeseries chart
props.
+ */
+export interface EchartsTimeseriesTestDatasource {
+ verboseMap?: Record<string, string>;
+ columnFormats?: Record<string, string>;
+ currencyFormats?: Record<string, { symbol: string; symbolPosition: string }>;
+ currencyCodeColumn?: string;
+}
+
+const DEFAULT_DATASOURCE: EchartsTimeseriesTestDatasource = {
+ verboseMap: {},
+ columnFormats: {},
+ currencyFormats: {},
+};
+
+/**
+ * Form data shape that at minimum has datasource and viz_type (used for
merging).
+ */
+export interface EchartsTimeseriesTestFormDataBase {
+ datasource?: string;
+ viz_type?: string;
+ [key: string]: unknown;
+}
+
+/**
+ * Config for creating Echarts Timeseries-style chart props in tests.
+ * Shared by Timeseries and Mixed Timeseries transformProps tests.
+ */
+export interface CreateEchartsTimeseriesTestChartPropsConfig<TFormData> {
+ defaultFormData: TFormData;
+ defaultVizType: string;
+ defaultQueriesData?: ChartDataResponseResult[];
+ formData?: Partial<TFormData>;
+ queriesData?: ChartDataResponseResult[];
+ datasource?: EchartsTimeseriesTestDatasource;
+ annotationData?: Record<string, unknown>;
+ width?: number;
+ height?: number;
+}
+
+/**
+ * Creates chart props for Echarts Timeseries-style plugins in tests.
+ * Merges partial formData with defaultFormData and builds a ChartProps-like
object.
+ * Use this to avoid duplicating createTestChartProps in Timeseries and Mixed
Timeseries tests.
+ *
+ * @param config - defaultFormData, defaultVizType, defaultQueriesData, and
optional overrides
+ * @returns Chart props object typed as TProps (e.g.
EchartsTimeseriesChartProps)
+ */
+export function createEchartsTimeseriesTestChartProps<
+ TFormData extends EchartsTimeseriesTestFormDataBase,
+ TProps,
+>(config: CreateEchartsTimeseriesTestChartPropsConfig<TFormData>): TProps {
+ const {
+ defaultFormData,
+ defaultVizType,
+ defaultQueriesData = [],
+ formData: partialFormData = {},
+ queriesData: customQueriesData,
+ datasource: customDatasource,
+ annotationData,
+ width = 800,
+ height = 600,
+ } = config;
+
+ const partial = partialFormData as
Partial<EchartsTimeseriesTestFormDataBase>;
+ const fullFormData = {
+ ...defaultFormData,
+ ...partialFormData,
+ datasource: partial.datasource ?? '3__table',
+ viz_type: partial.viz_type ?? defaultVizType,
+ } as TFormData;
+
+ const chartProps = new ChartProps({
+ formData: fullFormData,
+ width,
+ height,
+ queriesData: customQueriesData ?? defaultQueriesData,
+ theme: supersetTheme,
+ datasource: customDatasource ?? { ...DEFAULT_DATASOURCE },
+ ...(annotationData !== undefined && { annotationData }),
+ });
+
+ return {
+ ...chartProps,
+ formData: fullFormData,
+ queriesData: customQueriesData ?? defaultQueriesData,
+ } as TProps;
+}