feat(runs): add runs pages, polling, autorefresh indicator, and scheduler fix
- add global runs page and per-scenario runs/run-detail pages - run detail page polls every 1s until pass/fail, cleans up on unmount - runs list pages poll every 10s with AutoRefreshIndicator (pulse on data load) - fix scheduler: set run to pass after empty step loop to prevent stuck in_progress - fix table header colors and link cell color for readability - fix play button to navigate to the new run after creation - fix package.json import paths in server for Docker build context
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { runs } from '../../api';
|
||||
import type { ScenarioRun, ScenarioRunStep, ScenarioRunStatus } from '../../api';
|
||||
import { AutoRefreshIndicator, Badge, Table, Timestamp, type TableColumn } from '../../ui';
|
||||
import type { BadgeVariant } from '../../ui';
|
||||
import styles from '../Page.module.css';
|
||||
|
||||
const STATUS_VARIANT: Record<ScenarioRunStatus, BadgeVariant> = {
|
||||
pending: 'neutral',
|
||||
in_progress: 'info',
|
||||
pass: 'success',
|
||||
fail: 'error',
|
||||
};
|
||||
|
||||
const STATUS_LABEL: Record<ScenarioRunStatus, string> = {
|
||||
pending: 'Pending',
|
||||
in_progress: 'Running',
|
||||
pass: 'Pass',
|
||||
fail: 'Fail',
|
||||
};
|
||||
|
||||
type AllRunRow = ScenarioRun & {
|
||||
stepRuns: ScenarioRunStep[];
|
||||
scenario: { id: number; name: string };
|
||||
};
|
||||
|
||||
export function AllRunsPage() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [items, setItems] = useState<AllRunRow[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [pulseKey, setPulseKey] = useState(0);
|
||||
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
|
||||
const load = () => {
|
||||
runs
|
||||
.listAll()
|
||||
.then((res) => {
|
||||
setItems(res.data as AllRunRow[]);
|
||||
setPulseKey((k) => k + 1);
|
||||
})
|
||||
.catch((err: Error) => setError(err.message))
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
load();
|
||||
pollRef.current = setInterval(load, 10_000);
|
||||
return () => {
|
||||
if (pollRef.current) clearInterval(pollRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const columns: TableColumn<AllRunRow>[] = [
|
||||
{ key: 'id', header: t('runs.col_id'), render: (r) => r.id, width: 60 },
|
||||
{
|
||||
key: 'scenario',
|
||||
header: t('runs.col_scenario'),
|
||||
render: (r) => (
|
||||
<span
|
||||
className={styles.linkCell}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/scenarios/${r.scenario.id}`);
|
||||
}}
|
||||
>
|
||||
{r.scenario.name}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
header: t('runs.col_status'),
|
||||
width: 110,
|
||||
render: (r) => (
|
||||
<Badge variant={STATUS_VARIANT[r.status]}>{STATUS_LABEL[r.status]}</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'steps',
|
||||
header: t('runs.col_steps'),
|
||||
width: 80,
|
||||
render: (r) => r.stepRuns.length,
|
||||
},
|
||||
{
|
||||
key: 'created',
|
||||
header: t('runs.col_created'),
|
||||
width: 160,
|
||||
render: (r) => <Timestamp value={r.createdAt} />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.pageToolbar}>
|
||||
<h1 className={styles.pageTitle}>{t('runs.title')}</h1>
|
||||
<AutoRefreshIndicator active pulseKey={pulseKey} />
|
||||
</div>
|
||||
|
||||
{error && <p className={styles.error}>{error}</p>}
|
||||
<Table
|
||||
columns={columns}
|
||||
data={items}
|
||||
rowKey={(r) => r.id}
|
||||
loading={loading}
|
||||
emptyMessage={t('runs.empty')}
|
||||
pageSize={20}
|
||||
pageSizeOptions={[10, 20, 50]}
|
||||
onRowClick={(r) => navigate(`/scenarios/${r.scenarioId}/runs/${r.id}`)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user