mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-05 04:27:09 +00:00
replay ui fix (#56)
* refactor: rename inspect to replay across the codebase Renames inspect-ui/ to replay-ui/, internal/inspect/ to internal/replay/, the CLI subcommand from `sanderling inspect` to `sanderling replay`, and updates all references in docs, Makefile, README, and Go comments. * feat(replay-ui): show spec filename with full path on hover RunList and RunDetail now render the basename of spec_path (e.g. login.spec.ts) with the full path available as a title tooltip.
This commit is contained in:
71 files changed
+104
-92
No files matched your search
@@ -0,0 +1,102 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { listRuns } from "../api";
|
||||
import type { RunSummary } from "../types";
|
||||
import { useSse } from "../hooks/useSse";
|
||||
|
||||
function formatDuration(milliseconds?: number): string {
|
||||
if (!milliseconds) {
|
||||
return "-";
|
||||
}
|
||||
if (milliseconds < 1000) {
|
||||
return `${milliseconds}ms`;
|
||||
}
|
||||
const seconds = milliseconds / 1000;
|
||||
if (seconds < 60) {
|
||||
return `${seconds.toFixed(1)}s`;
|
||||
}
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = Math.round(seconds - minutes * 60);
|
||||
return `${minutes}m${remainingSeconds}s`;
|
||||
}
|
||||
|
||||
function formatStartedAt(value: string): string {
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return value;
|
||||
}
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
function basename(specPath: string): string {
|
||||
const index = specPath.lastIndexOf("/");
|
||||
return index >= 0 ? specPath.slice(index + 1) : specPath;
|
||||
}
|
||||
|
||||
export default function RunList() {
|
||||
const [runs, setRuns] = useState<RunSummary[] | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
listRuns()
|
||||
.then(setRuns)
|
||||
.catch((failure: unknown) => {
|
||||
setError(failure instanceof Error ? failure.message : String(failure));
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
useSse("runs.changed", refresh);
|
||||
|
||||
if (error) {
|
||||
return <div className="status-block status-error">failed to load runs: {error}</div>;
|
||||
}
|
||||
if (!runs) {
|
||||
return <div className="status-block">loading runs...</div>;
|
||||
}
|
||||
if (runs.length === 0) {
|
||||
return <div className="status-block">no runs yet. run `sanderling test` to produce some.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="run-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>started</th>
|
||||
<th>spec</th>
|
||||
<th>seed</th>
|
||||
<th>platform</th>
|
||||
<th>duration</th>
|
||||
<th>steps</th>
|
||||
<th>violations</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{runs.map((run) => (
|
||||
<tr key={run.id}>
|
||||
<td>
|
||||
<Link to={`/runs/${run.id}`}>{formatStartedAt(run.started_at)}</Link>
|
||||
</td>
|
||||
<td title={run.spec_path}>{basename(run.spec_path)}</td>
|
||||
<td>{run.seed}</td>
|
||||
<td>{run.platform}</td>
|
||||
<td>{formatDuration(run.duration_millis)}</td>
|
||||
<td>{run.step_count}</td>
|
||||
<td>
|
||||
{run.violation_count > 0 ? (
|
||||
<span className="chip chip-violation">{run.violation_count}</span>
|
||||
) : (
|
||||
"0"
|
||||
)}
|
||||
</td>
|
||||
<td>{run.in_progress ? <span className="chip chip-progress">in progress</span> : null}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user