import { useState } from 'react'; import { api } from '../lib/api'; import { useApi } from '../lib/useApi'; import { PageHeader } from '../components/PageHeader'; import { Empty } from '../components/Empty'; import { useToast } from '../components/Toast'; import { RefreshCw, Trash } from '../components/Icons'; // QA test cases and the GitHub issues filed against them. // // Status values match the client's TestCaseStatus enum. Only Failed<->Passed // are the reconciler's to move (a closed issue means fixed), so a Claimed case // is never rewritten by a sync — see docs/admin.md. const STATUS: Record = { 0: { label: 'Not yet tested', className: 'badge-neutral' }, 1: { label: 'Claimed', className: 'badge-neutral' }, 2: { label: 'Failed', className: 'badge-banned' }, 3: { label: 'Passed', className: 'badge-online' }, }; interface TestCaseRow { id: string; key: string; title: string; roomName: string; status: number; testPassId: number | null; issueUrl: string; issueNumber: number | null; updatedAt: string; } interface TestCaseList { githubConfigured: boolean; repository: string; cases: TestCaseRow[]; } export function TestCases() { const toast = useToast(); const [status, setStatus] = useState(''); const query = status === '' ? '' : `?status=${status}`; const { data, loading, error, refresh } = useApi(`/testcases${query}`); const [busy, setBusy] = useState(null); const [syncing, setSyncing] = useState(false); const fileIssue = async (row: TestCaseRow) => { setBusy(row.id); try { const res = await api<{ issue: { number: number; url: string }; created: boolean }>( `/testcases/${row.id}/issue`, { method: 'POST' }, ); toast.push( res.created ? `Filed #${res.issue.number} for ${row.key}` : `${row.key} is already linked to #${res.issue.number}`, 'success', ); refresh(); } catch (e) { toast.push((e as Error).message, 'error'); } finally { setBusy(null); } }; const unlink = async (row: TestCaseRow) => { setBusy(row.id); try { // Unlink only — the issue itself is deliberately left open. await api(`/testcases/${row.id}/issue`, { method: 'DELETE' }); toast.push(`Unlinked ${row.key}`, 'success'); refresh(); } catch (e) { toast.push((e as Error).message, 'error'); } finally { setBusy(null); } }; const sync = async () => { setSyncing(true); try { const res = await api<{ reconciled: number }>('/testcases/issues/sync', { method: 'POST' }); toast.push( res.reconciled === 0 ? 'Everything already in sync' : `Updated ${res.reconciled} case(s)`, 'success', ); refresh(); } catch (e) { toast.push((e as Error).message, 'error'); } finally { setSyncing(false); } }; return (
} /> {/* A server with no GitHub token is an ordinary deployment, so this is a note rather than an error. */} {data && !data.githubConfigured && (
GitHub issue linking is off. Set GitHub:Token and{' '} GitHub:Repository to enable it.
)} {error &&
{error}
}
{data && data.cases.length === 0 && } {data && data.cases.length > 0 && (
    {data.cases.map(row => { const badge = STATUS[row.status] ?? STATUS[0]; return (
  • {row.title} {row.key} {badge.label} {row.testPassId !== null && ( pass {row.testPassId} )}
    {row.roomName &&
    {row.roomName}
    }
    {row.issueNumber !== null ? ( <> #{row.issueNumber} ) : ( )}
  • ); })}
)}
); }