test(plugin-chart-echarts): regression guards for temporal x-axis labels on timeseries charts (#39208)

This commit is contained in:
jesperct
2026-05-05 12:37:35 -03:00
committed by GitHub
parent 8173cfe9e3
commit 5b5f23d127
4 changed files with 313 additions and 0 deletions

View File

@@ -20,6 +20,7 @@ import {
AnnotationSourceType,
AnnotationStyle,
AnnotationType,
AxisType,
ComparisonType,
DataRecord,
EventAnnotationLayer,
@@ -1472,6 +1473,118 @@ test('x-axis formatter deduplicates consecutive identical labels for coarse time
expect(label4).toBe('');
});
test('numeric x coltype routes through the number formatter (not the time formatter)', () => {
// Regression guard for echarts-timeseries-epoch-x-axis-labels investigation.
// When the query reports a Numeric x-axis coltype (including epoch-ms-like
// values), Timeseries transformProps must pick the Value axis and run the
// label through getNumberFormatter, not the time formatter. If this ever
// changes, epoch-ms values that arrive as Numeric would suddenly be treated
// as Date instances and could render "NaN" — the symptom that prompted this
// investigation.
const ts1 = 1745784000000;
const ts2 = 1745870400000;
const chartProps = createTestChartProps({
formData: {
metrics: ['metric'],
granularity_sqla: 'ds',
x_axis: '__timestamp',
},
queriesData: [
createTestQueryData(
[
{ __timestamp: ts1, metric: 10 },
{ __timestamp: ts2, metric: 20 },
],
{
colnames: ['__timestamp', 'metric'],
coltypes: [GenericDataType.Numeric, GenericDataType.Numeric],
},
),
],
});
const { echartOptions } = transformProps(chartProps);
const xAxis = echartOptions.xAxis as {
type: string;
axisLabel: { formatter: (v: number) => string };
};
expect(xAxis.type).toBe(AxisType.Value);
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 chartProps = createTestChartProps({
formData: {
metrics: ['metric'],
granularity_sqla: 'ds',
x_axis: '__timestamp',
xAxisForceCategorical: true,
},
queriesData: [
createTestQueryData(
[
{ __timestamp: ts1, metric: 10 },
{ __timestamp: ts2, metric: 20 },
],
{
colnames: ['__timestamp', 'metric'],
coltypes: [GenericDataType.Numeric, GenericDataType.Numeric],
},
),
],
});
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 time-series charts. Ensures that
// Temporal coltype keeps routing through the TimeFormatter so a refactor
// does not accidentally drop Date handling (the feared regression that
// sparked this investigation).
const ts1 = 1745784000000;
const ts2 = 1745870400000;
const chartProps = createTestChartProps({
formData: {
metrics: ['metric'],
granularity_sqla: 'ds',
x_axis: '__timestamp',
},
queriesData: [
createTestQueryData(
[
{ __timestamp: ts1, metric: 10 },
{ __timestamp: ts2, metric: 20 },
],
{
colnames: ['__timestamp', 'metric'],
coltypes: [GenericDataType.Temporal, GenericDataType.Numeric],
},
),
],
});
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/);
expect(label).not.toBe(String(ts1));
});
test('should assign distinct dash patterns for multiple time offsets consistently', () => {
const queriesDataWithMultipleOffsets = [
createTestQueryData([