Skip to content

Commit 33bc37f

Browse files
feat(schedule-details): add static metrics chart series (SLICE-9.1 / PR09d)
Render visx markers for successful runs, missed executions, and the next execution time from static fixture data, replacing the empty state when series data is present. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 2d1cd11 commit 33bc37f

9 files changed

Lines changed: 286 additions & 12 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { type ScheduleMetricsChartSeriesData } from '../schedule-detail-metrics-chart-series.types';
2+
3+
export const SCHEDULE_METRICS_CHART_FIXTURE_NOW_MS = new Date(
4+
'2024-06-15T12:00:00.000Z'
5+
).getTime();
6+
7+
export const scheduleMetricsChartFixture: ScheduleMetricsChartSeriesData = {
8+
successfulRuns: [
9+
{ scheduledTimeMs: SCHEDULE_METRICS_CHART_FIXTURE_NOW_MS - 6 * 60 * 60 * 1000 },
10+
{ scheduledTimeMs: SCHEDULE_METRICS_CHART_FIXTURE_NOW_MS - 4 * 60 * 60 * 1000 },
11+
{ scheduledTimeMs: SCHEDULE_METRICS_CHART_FIXTURE_NOW_MS - 1 * 60 * 60 * 1000 },
12+
],
13+
missedExecutions: [
14+
{ scheduledTimeMs: SCHEDULE_METRICS_CHART_FIXTURE_NOW_MS - 2 * 60 * 60 * 1000 },
15+
],
16+
nextExecutionTimeMs:
17+
SCHEDULE_METRICS_CHART_FIXTURE_NOW_MS + 2 * 60 * 60 * 1000,
18+
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
3+
import { render, screen } from '@/test-utils/rtl';
4+
5+
import {
6+
CHART_EMPTY_STATE_MESSAGE,
7+
CHART_REGION_ARIA_LABEL,
8+
CHART_SVG_TEST_ID,
9+
} from '../schedule-detail-metrics-chart.constants';
10+
import ScheduleDetailMetricsChart from '../schedule-detail-metrics-chart';
11+
12+
jest.mock('@visx/responsive', () => ({
13+
ParentSize: ({
14+
children,
15+
}: {
16+
children: (args: { width: number; height: number }) => React.ReactNode;
17+
}) => <>{children({ width: 800, height: 280 })}</>,
18+
}));
19+
20+
jest.mock('../__fixtures__/schedule-detail-metrics-chart-fixture', () => ({
21+
SCHEDULE_METRICS_CHART_FIXTURE_NOW_MS: new Date(
22+
'2024-06-15T12:00:00.000Z'
23+
).getTime(),
24+
scheduleMetricsChartFixture: {
25+
successfulRuns: [],
26+
missedExecutions: [],
27+
nextExecutionTimeMs: null,
28+
},
29+
}));
30+
31+
describe(`${ScheduleDetailMetricsChart.name} empty state`, () => {
32+
it('renders empty state when there is no chart data', () => {
33+
render(
34+
<ScheduleDetailMetricsChart
35+
params={{
36+
domain: 'test-domain',
37+
cluster: 'test-cluster',
38+
scheduleId: 'my-schedule',
39+
scheduleTab: 'runs',
40+
}}
41+
/>
42+
);
43+
44+
expect(
45+
screen.getByRole('region', { name: CHART_REGION_ARIA_LABEL })
46+
).toBeInTheDocument();
47+
expect(screen.getByRole('status')).toHaveTextContent(
48+
CHART_EMPTY_STATE_MESSAGE
49+
);
50+
expect(
51+
screen.queryByTestId(CHART_SVG_TEST_ID)
52+
).not.toBeInTheDocument();
53+
});
54+
});

src/views/schedule-page/schedule-detail-metrics-chart/__tests__/schedule-detail-metrics-chart.test.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import { render, screen, within } from '@/test-utils/rtl';
55
import {
66
CHART_NOW_LINE_TEST_ID,
77
CHART_REGION_ARIA_LABEL,
8+
CHART_SERIES_TEST_IDS,
89
CHART_SVG_TEST_ID,
910
CHART_TOOLBAR_ARIA_LABEL,
1011
CHART_TOOLBAR_BUTTON_LABELS,
1112
} from '../schedule-detail-metrics-chart.constants';
13+
import { SCHEDULE_METRICS_CHART_FIXTURE_NOW_MS } from '../__fixtures__/schedule-detail-metrics-chart-fixture';
1214
import ScheduleDetailMetricsChart from '../schedule-detail-metrics-chart';
1315

1416
jest.mock('@visx/responsive', () => ({
@@ -19,24 +21,35 @@ jest.mock('@visx/responsive', () => ({
1921
}) => <>{children({ width: 800, height: 280 })}</>,
2022
}));
2123

22-
const mockNow = new Date('2024-06-15T12:00:00Z').getTime();
23-
2424
describe(ScheduleDetailMetricsChart.name, () => {
2525
beforeEach(() => {
26-
jest.useFakeTimers({ now: mockNow });
26+
jest.useFakeTimers({ now: SCHEDULE_METRICS_CHART_FIXTURE_NOW_MS });
2727
});
2828

2929
afterEach(() => {
3030
jest.useRealTimers();
3131
});
3232

33-
it('renders chart region with svg frame', () => {
33+
it('renders chart region with svg frame and static fixture series markers', () => {
3434
setup();
3535

3636
expect(
3737
screen.getByRole('region', { name: CHART_REGION_ARIA_LABEL })
3838
).toBeInTheDocument();
3939
expect(screen.getByTestId(CHART_SVG_TEST_ID)).toBeInTheDocument();
40+
expect(
41+
screen.getByTestId(CHART_SERIES_TEST_IDS.svg)
42+
).toBeInTheDocument();
43+
expect(
44+
screen.getAllByTestId(CHART_SERIES_TEST_IDS.successfulRunMarker)
45+
).toHaveLength(3);
46+
expect(
47+
screen.getByTestId(CHART_SERIES_TEST_IDS.missedExecutionMarker)
48+
).toBeInTheDocument();
49+
expect(
50+
screen.getByTestId(CHART_SERIES_TEST_IDS.nextExecutionMarker)
51+
).toBeInTheDocument();
52+
expect(screen.queryByRole('status')).not.toBeInTheDocument();
4053
});
4154

4255
it('renders x and y axes in the chart svg', () => {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { type ScheduleMetricsChartSeriesData } from '../schedule-detail-metrics-chart-series.types';
2+
3+
export default function hasScheduleMetricsChartData(
4+
data: ScheduleMetricsChartSeriesData
5+
) {
6+
return (
7+
data.successfulRuns.length > 0 ||
8+
data.missedExecutions.length > 0 ||
9+
data.nextExecutionTimeMs != null
10+
);
11+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react';
2+
3+
import { Group } from '@visx/group';
4+
import { Circle, Line } from '@visx/shape';
5+
6+
import {
7+
CHART_SERIES_MARKER_RADIUS_PX,
8+
CHART_SERIES_MISSED_MARKER_RADIUS_PX,
9+
CHART_SERIES_MISSED_STROKE_WIDTH_PX,
10+
CHART_SERIES_MISSED_Y_RATIO,
11+
CHART_SERIES_NEXT_EXECUTION_STROKE_WIDTH_PX,
12+
CHART_SERIES_SUCCESS_Y_RATIO,
13+
CHART_SERIES_TEST_IDS,
14+
} from './schedule-detail-metrics-chart.constants';
15+
import { type ScheduleMetricsChartSeriesProps } from './schedule-detail-metrics-chart-series.types';
16+
17+
export default function ScheduleDetailMetricsChartSeries({
18+
width,
19+
height,
20+
xScale,
21+
data,
22+
successfulRunColor,
23+
missedExecutionColor,
24+
nextExecutionColor,
25+
}: ScheduleMetricsChartSeriesProps) {
26+
const successfulRunY = height * CHART_SERIES_SUCCESS_Y_RATIO;
27+
const missedExecutionY = height * CHART_SERIES_MISSED_Y_RATIO;
28+
29+
return (
30+
<Group data-testid={CHART_SERIES_TEST_IDS.svg}>
31+
{data.successfulRuns.map(({ scheduledTimeMs }) => (
32+
<Circle
33+
key={`successful-${scheduledTimeMs}`}
34+
cx={xScale(scheduledTimeMs)}
35+
cy={successfulRunY}
36+
r={CHART_SERIES_MARKER_RADIUS_PX}
37+
fill={successfulRunColor}
38+
data-testid={CHART_SERIES_TEST_IDS.successfulRunMarker}
39+
/>
40+
))}
41+
{data.missedExecutions.map(({ scheduledTimeMs }) => {
42+
const x = xScale(scheduledTimeMs);
43+
44+
return (
45+
<Group key={`missed-${scheduledTimeMs}`}>
46+
<Line
47+
from={{ x, y: successfulRunY }}
48+
to={{ x, y: missedExecutionY }}
49+
stroke={missedExecutionColor}
50+
strokeWidth={CHART_SERIES_MISSED_STROKE_WIDTH_PX}
51+
strokeDasharray="4 3"
52+
pointerEvents="none"
53+
/>
54+
<Circle
55+
cx={x}
56+
cy={missedExecutionY}
57+
r={CHART_SERIES_MISSED_MARKER_RADIUS_PX}
58+
fill="transparent"
59+
stroke={missedExecutionColor}
60+
strokeWidth={CHART_SERIES_MISSED_STROKE_WIDTH_PX}
61+
data-testid={CHART_SERIES_TEST_IDS.missedExecutionMarker}
62+
/>
63+
</Group>
64+
);
65+
})}
66+
{data.nextExecutionTimeMs != null && (
67+
<Line
68+
from={{ x: xScale(data.nextExecutionTimeMs), y: 0 }}
69+
to={{ x: xScale(data.nextExecutionTimeMs), y: height }}
70+
stroke={nextExecutionColor}
71+
strokeWidth={CHART_SERIES_NEXT_EXECUTION_STROKE_WIDTH_PX}
72+
strokeDasharray="6 4"
73+
pointerEvents="none"
74+
data-testid={CHART_SERIES_TEST_IDS.nextExecutionMarker}
75+
/>
76+
)}
77+
<rect width={width} height={height} fill="transparent" pointerEvents="none" />
78+
</Group>
79+
);
80+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type ScaleLinear } from 'd3-scale';
2+
3+
export type ScheduleMetricsChartExecutionPoint = {
4+
scheduledTimeMs: number;
5+
};
6+
7+
export type ScheduleMetricsChartSeriesData = {
8+
successfulRuns: ScheduleMetricsChartExecutionPoint[];
9+
missedExecutions: ScheduleMetricsChartExecutionPoint[];
10+
nextExecutionTimeMs: number | null;
11+
};
12+
13+
export type ScheduleMetricsChartXScale = ScaleLinear<number, number, never>;
14+
15+
export type ScheduleMetricsChartSeriesProps = {
16+
width: number;
17+
height: number;
18+
xScale: ScheduleMetricsChartXScale;
19+
data: ScheduleMetricsChartSeriesData;
20+
successfulRunColor: string;
21+
missedExecutionColor: string;
22+
nextExecutionColor: string;
23+
};

src/views/schedule-page/schedule-detail-metrics-chart/schedule-detail-metrics-chart.constants.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export const CHART_TOOLBAR_BUTTON_LABELS = {
1818
now: 'Now',
1919
} as const;
2020

21+
export const CHART_EMPTY_STATE_MESSAGE = 'No chart data available yet';
22+
2123
export const CHART_REGION_ARIA_LABEL = 'Schedule metrics chart';
2224

2325
export const CHART_TOOLBAR_ARIA_LABEL = 'Chart controls';
@@ -37,3 +39,17 @@ export const CHART_FUTURE_GUTTER_MS = 30 * 60_000;
3739

3840
/** Horizontal inset applied to the chart drawable area (px). */
3941
export const CHART_SIDE_PADDING_PX = 24;
42+
43+
export const CHART_SERIES_TEST_IDS = {
44+
svg: 'schedule-metrics-chart-series-svg',
45+
successfulRunMarker: 'schedule-metrics-chart-successful-run-marker',
46+
missedExecutionMarker: 'schedule-metrics-chart-missed-execution-marker',
47+
nextExecutionMarker: 'schedule-metrics-chart-next-execution-marker',
48+
} as const;
49+
50+
export const CHART_SERIES_MARKER_RADIUS_PX = 5;
51+
export const CHART_SERIES_MISSED_MARKER_RADIUS_PX = 6;
52+
export const CHART_SERIES_NEXT_EXECUTION_STROKE_WIDTH_PX = 2;
53+
export const CHART_SERIES_MISSED_STROKE_WIDTH_PX = 2;
54+
export const CHART_SERIES_SUCCESS_Y_RATIO = 0.45;
55+
export const CHART_SERIES_MISSED_Y_RATIO = 0.65;

src/views/schedule-page/schedule-detail-metrics-chart/schedule-detail-metrics-chart.styles.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ export const styled = {
3333
width: '100%',
3434
height: '100%',
3535
})),
36+
EmptyState: createStyled('div', ({ $theme }: { $theme: Theme }) => ({
37+
display: 'flex',
38+
alignItems: 'center',
39+
justifyContent: 'center',
40+
width: '100%',
41+
height: '100%',
42+
...$theme.typography.ParagraphSmall,
43+
color: $theme.colors.contentSecondary,
44+
})),
3645
};
3746

3847
const toolbarButtonRootOverrides = {

0 commit comments

Comments
 (0)