Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: load a repo's rules into the review try-out

A button beside the repo field reads the repo's rules from its default branch into the classification rows, names the file and the commit it read, asks before replacing rows the operator wrote, and shows the route's error without touching the rows.
This commit is contained in:
iris 2026-10-12 08:18:19 +02:00 • committed by mara
commit cf4c4cde87

View file

@ -6,6 +6,9 @@
// `202 {id}`, and polls `GET .../try-out/{id}` until the run is `done` or
// `failed`. A run takes a minute or more, so the page says it is running.
//
// **load the repo's rules** fills the rows from the rules on the repo's default
// branch (`GET /api/review-classifier/rules`), to run as they are or to edit.
//
// The inputs stay as typed after a run, so the operator can edit and run
// again. The page keeps nothing you typed and no result, and posts nothing to
// the forge; the only thing the browser remembers is the result table's
@ -14,22 +17,27 @@ import { useEffect, useState } from "preact/hooks";
import { ApiErrorPanel } from "@hive/shared/api-error-panel.js";
import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
import { Button } from "../../ui/button/Button.js";
import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js";
import { Panel } from "../../ui/panel/Panel.js";
import { TextField } from "../../ui/text-field/TextField.js";
import { ClassificationEditor } from "./ClassificationEditor.js";
import { TryOutResult } from "./TryOutResult.js";
import {
draftFromRule,
draftProblem,
duplicateName,
hasTypedText,
prNumber,
repoProblem,
toBody,
type ClassificationDraft,
type RulesAnswer,
type TryOutResult as Result,
} from "./TryOutTypes.js";
import "./ReviewTryOutPage.css";
const TRY_OUT = "/api/review-classifier/try-out";
const RULES = "/api/review-classifier/rules";
const POLL_MS = 2000;
// More classifications than this are not offered a row. It is the try-out
// endpoint's own cap, which answers a longer list with a 400 that the page
@ -61,6 +69,14 @@ export function ReviewTryOutPage() {
// Whole seconds since the run started, so a long wait shows it is alive.
const [elapsed, setElapsed] = useState(0);
const [loadingRules, setLoadingRules] = useState(false);
const [rulesError, setRulesError] = useState<ProblemDetails | null>(null);
// Where the rows came from, until the next load replaces it.
const [loaded, setLoaded] = useState<(RulesAnswer & { repo: string }) | null>(
null,
);
const [confirmLoad, setConfirmLoad] = useState(false);
const running = starting || runId !== null;
useEffect(() => {
@ -151,6 +167,35 @@ export function ReviewTryOutPage() {
}
}
// Reads the repo's rules from its default branch into the rows. A failed
// load leaves the rows as they were.
async function loadRules() {
const target = repo.trim();
setConfirmLoad(false);
setLoadingRules(true);
setRulesError(null);
try {
const res = await fetch(`${RULES}?repo=${encodeURIComponent(target)}`);
if (!res.ok) {
setRulesError(await readApiError(res));
return;
}
const answer = (await res.json()) as RulesAnswer;
setDrafts(answer.classifications.map((c) => draftFromRule(c, nextKey++)));
setLoaded({ ...answer, repo: target });
} catch (err: unknown) {
setRulesError({ detail: String(err) });
} finally {
setLoadingRules(false);
}
}
// Typed rows are the operator's work, so replacing them is asked first.
function askLoadRules() {
if (hasTypedText(drafts)) setConfirmLoad(true);
else void loadRules();
}
function changeDraft(next: ClassificationDraft) {
setDrafts((ds) => ds.map((d) => (d.key === next.key ? next : d)));
}
@ -180,7 +225,28 @@ export function ReviewTryOutPage() {
pattern="[0-9]+"
onInput={setPr}
/>
<Button
onClick={askLoadRules}
disabled={loadingRules || running || repoProblem(repo) !== null}
>
{loadingRules ? "loading…" : "load the repo's rules"}
</Button>
</div>
{rulesError ? (
<ApiErrorPanel
context="the repo's rules could not be loaded"
problem={rulesError}
/>
) : null}
{loaded ? (
<p class="ui-tryout-loaded">
Loaded {loaded.classifications.length} from{" "}
<code>{loaded.path}</code> on <code>{loaded.branch}</code> of{" "}
{loaded.repo}, read at commit{" "}
<code>{loaded.commit.slice(0, 12)}</code>. Edit them below, then
run.
</p>
) : null}
{drafts.map((d) => (
<ClassificationEditor
key={d.key}
@ -211,6 +277,18 @@ export function ReviewTryOutPage() {
{problem !== null ? <p class="ui-tryout-hint">{problem}</p> : null}
</form>
</Panel>
<ConfirmDialog
open={confirmLoad}
label="replace the classifications"
confirmLabel="replace them"
onCancel={() => setConfirmLoad(false)}
onConfirm={() => void loadRules()}
>
<p>
Loading the rules of {repo.trim()} replaces the classifications you
have written below.
</p>
</ConfirmDialog>
{running || result || error ? (
<Panel title="result" icon="📋">
{running ? (