mirror of
https://github.com/apache/superset.git
synced 2026-05-12 11:25:56 +00:00
test(plugin-chart-echarts): regression guards for temporal x-axis labels on timeseries charts (#39208)
This commit is contained in:
@@ -20,12 +20,14 @@ import {
|
||||
AnnotationStyle,
|
||||
AnnotationType,
|
||||
AnnotationSourceType,
|
||||
AxisType,
|
||||
DataRecord,
|
||||
FormulaAnnotationLayer,
|
||||
IntervalAnnotationLayer,
|
||||
VizType,
|
||||
ChartDataResponseResult,
|
||||
} from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/common';
|
||||
import {
|
||||
LegendOrientation,
|
||||
LegendType,
|
||||
@@ -496,3 +498,133 @@ test('should add a formula annotation when X-axis column has dataset-level label
|
||||
expect(Array.isArray(formulaSeries?.data)).toBe(true);
|
||||
expect((formulaSeries!.data as unknown[]).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('numeric x coltype never gets silently coerced to the Time axis', () => {
|
||||
// Regression guard for echarts-timeseries-epoch-x-axis-labels investigation.
|
||||
// Mixed Timeseries must follow the reported coltype: Numeric values stay
|
||||
// off the Time axis and are not silently reinterpreted as Date instances.
|
||||
// A future change that coerces Numeric → Time would bring back the "NaN"
|
||||
// label symptom we were investigating. We also assert that whichever
|
||||
// formatter is picked, it produces a string and does not emit "NaN".
|
||||
const ts1 = 1745784000000;
|
||||
const ts2 = 1745870400000;
|
||||
const epochRows = [
|
||||
{ __timestamp: ts1, metric: 10 },
|
||||
{ __timestamp: ts2, metric: 20 },
|
||||
];
|
||||
const epochQueryData = createTestQueryData(epochRows, {
|
||||
colnames: ['__timestamp', 'metric'],
|
||||
coltypes: [GenericDataType.Numeric, GenericDataType.Numeric],
|
||||
label_map: { __timestamp: ['__timestamp'], metric: ['metric'] },
|
||||
});
|
||||
|
||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
||||
EchartsMixedTimeseriesFormData,
|
||||
EchartsMixedTimeseriesProps
|
||||
>({
|
||||
...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
|
||||
defaultQueriesData: [epochQueryData, epochQueryData],
|
||||
formData: {
|
||||
...formData,
|
||||
x_axis: '__timestamp',
|
||||
metrics: ['metric'],
|
||||
metricsB: ['metric'],
|
||||
groupby: [],
|
||||
groupbyB: [],
|
||||
},
|
||||
queriesData: [epochQueryData, epochQueryData],
|
||||
});
|
||||
|
||||
const { echartOptions } = transformProps(chartProps);
|
||||
const xAxis = echartOptions.xAxis as {
|
||||
type: string;
|
||||
axisLabel: { formatter: (v: number) => string };
|
||||
};
|
||||
|
||||
expect(xAxis.type).not.toBe(AxisType.Time);
|
||||
const label = xAxis.axisLabel.formatter(ts1);
|
||||
expect(typeof label).toBe('string');
|
||||
expect(label).not.toMatch(/NaN/);
|
||||
});
|
||||
|
||||
test('xAxisForceCategorical forces Category axis regardless of Numeric coltype', () => {
|
||||
const ts1 = 1745784000000;
|
||||
const ts2 = 1745870400000;
|
||||
const epochRows = [
|
||||
{ __timestamp: ts1, metric: 10 },
|
||||
{ __timestamp: ts2, metric: 20 },
|
||||
];
|
||||
const epochQueryData = createTestQueryData(epochRows, {
|
||||
colnames: ['__timestamp', 'metric'],
|
||||
coltypes: [GenericDataType.Numeric, GenericDataType.Numeric],
|
||||
label_map: { __timestamp: ['__timestamp'], metric: ['metric'] },
|
||||
});
|
||||
|
||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
||||
EchartsMixedTimeseriesFormData,
|
||||
EchartsMixedTimeseriesProps
|
||||
>({
|
||||
...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
|
||||
defaultQueriesData: [epochQueryData, epochQueryData],
|
||||
formData: {
|
||||
...formData,
|
||||
x_axis: '__timestamp',
|
||||
metrics: ['metric'],
|
||||
metricsB: ['metric'],
|
||||
groupby: [],
|
||||
groupbyB: [],
|
||||
xAxisForceCategorical: true,
|
||||
},
|
||||
queriesData: [epochQueryData, epochQueryData],
|
||||
});
|
||||
|
||||
const { echartOptions } = transformProps(chartProps);
|
||||
const xAxis = echartOptions.xAxis as { type: string };
|
||||
|
||||
expect(xAxis.type).toBe(AxisType.Category);
|
||||
});
|
||||
|
||||
test('temporal x coltype wires the time formatter and Time axis', () => {
|
||||
// Regression guard: the happy path for mixed-timeseries charts. Ensures
|
||||
// Temporal coltype still routes through the TimeFormatter so the time axis
|
||||
// rendering path is exercised by the test suite.
|
||||
const ts1 = 1745784000000;
|
||||
const ts2 = 1745870400000;
|
||||
const temporalRows = [
|
||||
{ __timestamp: ts1, metric: 10 },
|
||||
{ __timestamp: ts2, metric: 20 },
|
||||
];
|
||||
const temporalQueryData = createTestQueryData(temporalRows, {
|
||||
colnames: ['__timestamp', 'metric'],
|
||||
coltypes: [GenericDataType.Temporal, GenericDataType.Numeric],
|
||||
label_map: { __timestamp: ['__timestamp'], metric: ['metric'] },
|
||||
});
|
||||
|
||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
||||
EchartsMixedTimeseriesFormData,
|
||||
EchartsMixedTimeseriesProps
|
||||
>({
|
||||
...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
|
||||
defaultQueriesData: [temporalQueryData, temporalQueryData],
|
||||
formData: {
|
||||
...formData,
|
||||
x_axis: '__timestamp',
|
||||
metrics: ['metric'],
|
||||
metricsB: ['metric'],
|
||||
groupby: [],
|
||||
groupbyB: [],
|
||||
},
|
||||
queriesData: [temporalQueryData, temporalQueryData],
|
||||
});
|
||||
|
||||
const { echartOptions } = transformProps(chartProps);
|
||||
const xAxis = echartOptions.xAxis as {
|
||||
type: string;
|
||||
axisLabel: { formatter: (v: Date) => string };
|
||||
};
|
||||
|
||||
expect(xAxis.type).toBe(AxisType.Time);
|
||||
const label = xAxis.axisLabel.formatter(new Date(ts1));
|
||||
expect(typeof label).toBe('string');
|
||||
expect(label).not.toMatch(/NaN/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user