- format long import statements with consistent line wrapping - apply consistent indentation across client and server modules - remove generated tsconfig.tsbuildinfo file
119 lines
2.8 KiB
TypeScript
119 lines
2.8 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Activity } from 'lucide-react';
|
|
import { runs } from '../../api';
|
|
import type { ScenarioRun, ScenarioRunStep, ScenarioRunStatus } from '../../api';
|
|
import {
|
|
AutoRefreshIndicator,
|
|
Badge,
|
|
Breadcrumbs,
|
|
Table,
|
|
Timestamp,
|
|
UuidBadge,
|
|
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 {
|
|
data: items = [],
|
|
isLoading,
|
|
error,
|
|
refetch,
|
|
} = useQuery<AllRunRow[]>({
|
|
queryKey: ['runs'],
|
|
queryFn: async () => {
|
|
const res = await runs.listAll();
|
|
return res.data as AllRunRow[];
|
|
},
|
|
refetchInterval: 10_000,
|
|
staleTime: 0,
|
|
});
|
|
|
|
const columns: TableColumn<AllRunRow>[] = [
|
|
{ key: 'id', header: t('runs.col_id'), render: (r) => <UuidBadge id={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}>
|
|
<Breadcrumbs items={[{ label: t('runs.title'), icon: <Activity size={14} /> }]} />
|
|
<AutoRefreshIndicator
|
|
active
|
|
pulseKey={items.length}
|
|
error={!!error}
|
|
onClick={() => refetch()}
|
|
/>
|
|
</div>
|
|
|
|
<Table
|
|
columns={columns}
|
|
data={items}
|
|
rowKey={(r) => r.id}
|
|
loading={isLoading}
|
|
emptyMessage={t('runs.empty')}
|
|
pageSize={20}
|
|
pageSizeOptions={[10, 20, 50]}
|
|
onRowClick={(r) => navigate(`/scenarios/${r.scenarioId}/runs/${r.id}`)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|