Веб: подхват refresh при пакете wiki_sync, ошибка вложения сразу (M-12, M-15)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c8af45ced8
commit
8a3ae3342e
@ -43,12 +43,15 @@ export function RefreshProvider({ children, pollMs = POLL_MS }: { children: Reac
|
||||
picking.current = true;
|
||||
(async () => {
|
||||
const r = await api.get<{ batches: Batch[] }>("/batches", { limit: 20 });
|
||||
const b = r.batches.find((x) => x.author === "viewer" && (x.status === "queued" || x.status === "running"));
|
||||
if (!b) return;
|
||||
const full = await api.get<Batch>(`/batches/${b.id}`); // T10.6: в списке нет команд — тип смотрим в пакете (R-2)
|
||||
if (full.commands?.[0]?.type !== "refresh" || activeRef.current || startingRef.current) return;
|
||||
failures.current = 0;
|
||||
setActive((cur) => cur ?? { id: b.id, agentOffline: false });
|
||||
const candidates = r.batches.filter((x) => x.author === "viewer" && (x.status === "queued" || x.status === "running"));
|
||||
for (const b of candidates) { // M-12: пакет wiki_sync не заслоняет более старый refresh
|
||||
const full = await api.get<Batch>(`/batches/${b.id}`); // T10.6: в списке нет команд — тип смотрим в пакете (R-2)
|
||||
if (activeRef.current || startingRef.current) return;
|
||||
if (full.commands?.[0]?.type !== "refresh") continue;
|
||||
failures.current = 0;
|
||||
setActive((cur) => cur ?? { id: b.id, agentOffline: false });
|
||||
return;
|
||||
}
|
||||
})().catch(() => { /* не критично: подхватим при следующей загрузке */ }).finally(() => { picking.current = false; });
|
||||
}, []);
|
||||
|
||||
|
||||
@ -128,6 +128,16 @@ describe("RefreshProvider", () => {
|
||||
expect(h.count("GET /batches/7")).toBe(1); // только проверка типа, опроса нет
|
||||
});
|
||||
|
||||
it("M-12: пакет wiki_sync viewer не заслоняет более старый refresh", async () => {
|
||||
hub({
|
||||
"GET /batches": () => json(200, { batches: [{ id: 9, author: "viewer", status: "queued" }, { id: 7, author: "viewer", status: "running" }] }),
|
||||
"GET /batches/:id": (url) => batch("running", { type: url.pathname.endsWith("/9") ? "wiki_sync" : "refresh" }),
|
||||
});
|
||||
const { result } = refreshHook();
|
||||
await waitFor(() => expect(result.current.event).toMatchObject({ kind: "batch", id: 7 }));
|
||||
expect(result.current.busy).toBe(true);
|
||||
});
|
||||
|
||||
it("401 на запуске пакета с текущим токеном — токен сброшен", async () => {
|
||||
hub({ "GET /agent": () => json(401, { error: "unauthorized" }) });
|
||||
const { result } = refreshHook();
|
||||
|
||||
@ -21,19 +21,21 @@ const decode = (s: string) => { try { return decodeURIComponent(s); } catch { re
|
||||
export function PageView({ project, code, hrefOf }: { project: string; code: string; hrefOf(code: string): string }) {
|
||||
const page = useWikiPage(project, code);
|
||||
const dl = useWikiDownload(project, code);
|
||||
const atts = page.data?.attachments;
|
||||
const failed = useMemo(() => new Map((atts ?? []).filter((a) => a.state === "failed" && a.error).map((a) => [String(a.id), a.error!])), [atts]);
|
||||
|
||||
const components = useMemo<Components>(() => ({
|
||||
a({ href, children }: ComponentProps<"a">) {
|
||||
if (!href) return <>{children}</>;
|
||||
if (/^page:/i.test(href)) return <Link to={hrefOf(decode(href.slice(5)))}>{children}</Link>;
|
||||
if (/^attachment:/i.test(href)) return <AttachmentLink id={decode(href.slice(11))} dl={dl}>{children}</AttachmentLink>;
|
||||
if (/^attachment:/i.test(href)) return <AttachmentLink id={decode(href.slice(11))} dl={dl} failed={failed}>{children}</AttachmentLink>;
|
||||
if (/^https?:/i.test(href)) return <a href={href} target="_blank" rel="noopener noreferrer">{children}</a>;
|
||||
return <a href={href}>{children}</a>;
|
||||
},
|
||||
// картинки по внешним адресам не загружаются (трекинг, чужие ресурсы): виден только текст
|
||||
img: ({ alt }: ComponentProps<"img">) => <span className="wiki-img">[изображение{alt ? `: ${alt}` : ""}]</span>,
|
||||
table: ({ children }: ComponentProps<"table">) => <div className="wiki-table"><table>{children}</table></div>,
|
||||
}), [hrefOf, dl]);
|
||||
}), [hrefOf, dl, failed]);
|
||||
|
||||
if (page.isPending) return <p className="wiki-hint">Загрузка страницы…</p>;
|
||||
if (page.isError) return <Notice tone="error">{page.error.message}</Notice>;
|
||||
@ -57,8 +59,12 @@ export function PageView({ project, code, hrefOf }: { project: string; code: str
|
||||
}
|
||||
|
||||
// ссылка attachment:<id>: запрос на скачивание, ожидание, сохранение файла; ошибка — текстом рядом
|
||||
function AttachmentLink({ id, dl, children }: { id: string; dl: ReturnType<typeof useWikiDownload>; children?: ReactNode }) {
|
||||
const { busy, error } = dl.status(id);
|
||||
// failed — ошибки вложений в состоянии failed из ответа страницы: показываются сразу, до клика
|
||||
function AttachmentLink({ id, dl, failed, children }: {
|
||||
id: string; dl: ReturnType<typeof useWikiDownload>; failed: Map<string, string>; children?: ReactNode;
|
||||
}) {
|
||||
const st = dl.status(id);
|
||||
const busy = st.busy, error = st.error ?? (busy ? null : failed.get(id) ?? null);
|
||||
return (
|
||||
<>
|
||||
<button type="button" className="wiki-att" disabled={busy} onClick={() => dl.start(id)}>
|
||||
|
||||
@ -41,6 +41,14 @@ const open = (route: string) => renderWithProviders(<WikiPage project="AWPCA" />
|
||||
describe("страница «Вики»", () => {
|
||||
beforeEach(() => setToken("tok"));
|
||||
|
||||
it("M-15: ошибка вложения в состоянии failed видна сразу рядом со ссылкой", async () => {
|
||||
hub((_m, url) => url.pathname === "/wiki/pages/DOC-3"
|
||||
? json(200, { ...page("DOC-3"), attachments: [{ id: 7, name: "a.txt", state: "failed", size: null, error: "Eva: 404" }] })
|
||||
: undefined);
|
||||
open("/wiki?project=AWPCA&page=DOC-3");
|
||||
expect(await screen.findByText(/Eva: 404/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("ссылка page: открывает страницу", async () => {
|
||||
hub();
|
||||
const { router } = open("/wiki?project=AWPCA&page=DOC-3");
|
||||
|
||||
@ -7,7 +7,7 @@ export interface WikiStatus {
|
||||
root: string; root_title: string | null; synced_at: string | null; pages: number; error: string | null; batch: WikiBatch | null;
|
||||
}
|
||||
export interface WikiNode { code: string; title: string | null; depth: number; parent_code: string | null; size: number | null }
|
||||
export interface WikiAttachment { id: number; name: string | null; state: string; size: number | null }
|
||||
export interface WikiAttachment { id: number; name: string | null; state: string; size: number | null; error?: string | null }
|
||||
export interface WikiPageData {
|
||||
code: string; title: string | null; path: string; url: string | null; synced_at: string | null; markdown: string; attachments: WikiAttachment[];
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user