apply the same render(h(...)) simplification to JobqGraph
mara, follow-up on PR#3365: "follow up with the same for jobq". Drops
mountJobqGraph the same way mountJobqRollup was dropped - builds.js
now calls render(h(JobqGraph, {...}), root) directly, split into
mountRebuildQueue() (the one-time replaceChildren() + first render,
called once at page init) and renderRebuildQueue() (the render-only
path the rebuild_queue_changed handler and the cancel flow reuse,
bumping a module-level jobqGraphToken instead of holding a mount
handle). The split matters here specifically: repeating
replaceChildren() on every refresh would wipe Preact's own tracked
children out from under its diffing instead of letting it update them
minimally - JobqRollup's simpler version didn't need this since it
only ever renders into its own dedicated section once per app
lifetime's worth of state, but the rebuild queue refreshes on every
`rebuild_queue_changed` tick.
Re-verified: npm run build (whole workspace) + swarm-ui typecheck
clean (JobqGraph is also used via JSX on swarm-ui's /jobs page,
untouched by this), comment-block + issue-ref lints clean, headless-
chromium screenshot of builds.html's R3BU1LD QU3U3 tab against a
mocked /api/jobq/graph payload - tree, filter checkboxes, and cancel
buttons all render identically to before.
This commit is contained in:
parent
5c9d89dc64
commit
075d1f63e3
2 changed files with 35 additions and 38 deletions
|
|
@ -14,9 +14,10 @@ import { $, fmtAgeSecs, openStream, openBuildLogStream, initServerWarnings } fro
|
|||
import { el } from '@hive/shared/dom.js';
|
||||
import { bindAsyncForms } from '@hive/shared/forms.js';
|
||||
import { themedConfirm } from '@hive/shared/modal.js';
|
||||
import { h, render } from 'preact';
|
||||
import { fmtAgo, fmtDuration, truncate } from './util.js';
|
||||
import '@hive/shared/hive-tab-strip.js';
|
||||
import { mountJobqGraph } from '@hive/shared/jobq-graph.js';
|
||||
import { JobqGraph } from '@hive/shared/jobq-graph.js';
|
||||
|
||||
// ─── derived state ───────────────────────────────────────────────────────────
|
||||
let metaInputsState = [];
|
||||
|
|
@ -26,7 +27,7 @@ let metaUpdateRunning = false;
|
|||
// the tree itself; this flat array exists only for the two things it
|
||||
// doesn't render: the count-pill and the live-log panel.
|
||||
let jobqNodes = [];
|
||||
let jobqGraphHandle = null;
|
||||
let jobqGraphToken = 0;
|
||||
|
||||
function syncFromSnapshot(s) {
|
||||
metaInputsState = (s.meta_inputs || []).slice();
|
||||
|
|
@ -127,27 +128,40 @@ function renderMetaInputs(s) {
|
|||
}
|
||||
|
||||
// ─── rebuild queue ────────────────────────────────────────────────────────────
|
||||
// R3BU1LD QU3U3 is `JobqGraph` (Preact, mounted imperatively — see
|
||||
// mountJobqGraph's own doc comment in @hive/shared/jobq-graph.js for why
|
||||
// this file has no JSX pipeline) directly — no hand-rolled tree/roll-up
|
||||
// rendering here anymore. The component owns fetching GET /api/jobq/graph
|
||||
// and its own refetch (`.refresh()` on the mount handle); this page just
|
||||
// reads its `onUpdate` callback to keep `jobqNodes` (the flat array) in
|
||||
// sync for the two things the generic view doesn't render itself: the
|
||||
// count-pill and the live-log panel below.
|
||||
// R3BU1LD QU3U3 is `JobqGraph` (Preact, rendered via plain `render(h(...))`
|
||||
// — no JSX pragma needed for either call, no mount wrapper: `render` is
|
||||
// already the re-render/diff entry point, per mara on review) directly —
|
||||
// no hand-rolled tree/roll-up rendering here anymore. The component owns
|
||||
// fetching GET /api/jobq/graph and its own refetch (bump `jobqGraphToken`
|
||||
// and render again); this page just reads its `onUpdate` callback to keep
|
||||
// `jobqNodes` (the flat array) in sync for the two things the generic
|
||||
// view doesn't render itself: the count-pill and the live-log panel below.
|
||||
//
|
||||
// Cancel is the one action this page *does* wire up: the `cancellable`
|
||||
// prop turns on the component's own per-node cancel button, which calls
|
||||
// `onCancel(id)` rather than posting anything — the endpoint
|
||||
// (`/api/rebuild-queue/{id}/cancel`) is this page's domain concept, not
|
||||
// the generic component's.
|
||||
// Split from `renderRebuildQueue` below: `replaceChildren()` only
|
||||
// belongs on the *first* render, to clear the static "loading…"
|
||||
// placeholder `#rebuild-queue-section` starts with in `builds.html` —
|
||||
// Preact doesn't know that placeholder is there, so a later render
|
||||
// call must NOT repeat `replaceChildren()` (it would wipe Preact's own
|
||||
// tracked children out from under its diffing rather than let it
|
||||
// update them minimally).
|
||||
function mountRebuildQueue() {
|
||||
const root = $('rebuild-queue-section');
|
||||
if (!root) return;
|
||||
root.replaceChildren();
|
||||
jobqGraphHandle = mountJobqGraph(root, {
|
||||
renderRebuildQueue();
|
||||
}
|
||||
function renderRebuildQueue() {
|
||||
const root = $('rebuild-queue-section');
|
||||
if (!root) return;
|
||||
render(h(JobqGraph, {
|
||||
endpoint: '/api/jobq/graph',
|
||||
cancellable: true,
|
||||
refreshToken: jobqGraphToken,
|
||||
onUpdate: (nodes) => {
|
||||
jobqNodes = nodes || [];
|
||||
renderRebuildLiveLog();
|
||||
|
|
@ -165,12 +179,12 @@ function mountRebuildQueue() {
|
|||
if (!r.ok) throw new Error('http ' + r.status);
|
||||
// No manual refresh: cancel flips node state, which fires
|
||||
// rebuild_queue_changed over SSE — the existing handler below
|
||||
// already calls jobqGraphHandle.refresh() on that tick.
|
||||
// already bumps jobqGraphToken and re-renders on that tick.
|
||||
} catch (err) {
|
||||
console.error('cancel failed', err);
|
||||
}
|
||||
},
|
||||
});
|
||||
}), root);
|
||||
}
|
||||
|
||||
// ─── running-rebuild live log ─────────────────────────────────────────────────
|
||||
|
|
@ -471,7 +485,8 @@ const SSE_HANDLERS = {
|
|||
// queue snapshot on the wire (tabs.js/SW4RM still consumes it for the
|
||||
// swarm badges, untouched by this), this page just treats it as a
|
||||
// refetch trigger. `JobqGraph` owns the actual fetch.
|
||||
if (jobqGraphHandle) jobqGraphHandle.refresh();
|
||||
jobqGraphToken += 1;
|
||||
renderRebuildQueue();
|
||||
// Auto-refresh build log list when the queue changes and BUILD L0GS is active.
|
||||
if (buildTabs && buildTabs.active() === 'buildlogs') {
|
||||
if (buildRefreshTimer) clearTimeout(buildRefreshTimer);
|
||||
|
|
|
|||
|
|
@ -17,11 +17,10 @@
|
|||
// needing the raw list (a count badge, a live-log panel) without its
|
||||
// own parallel fetch.
|
||||
//
|
||||
// Two ways to use this: JSX (swarm-ui, or any dashboard page that
|
||||
// renders it directly) — `<JobqGraph endpoint="..." cancellable
|
||||
// onCancel={...} onUpdate={...} />`. Or imperative mount
|
||||
// (dashboard/src/builds.js, plain `.js` — mounting needs no JSX pragma)
|
||||
// — `mountJobqGraph(container, props)` returns `{ refresh(), update(props) }`.
|
||||
// JSX (swarm-ui) — `<JobqGraph endpoint="..." cancellable
|
||||
// onCancel={...} onUpdate={...} />`. Or plain `render(h(JobqGraph,
|
||||
// props), container)` (dashboard/src/builds.js, no JSX pragma needed)
|
||||
// — call again with a bumped `refreshToken` to refetch.
|
||||
//
|
||||
// Styles live in `@hive/shared/jobq-graph.css`, `@import`ed from a
|
||||
// page/component CSS file rather than imported here — dashboard bundles
|
||||
|
|
@ -29,7 +28,6 @@
|
|||
// loader is `text` (for unrelated shadow-DOM components' CSS-as-string
|
||||
// needs), and that loader is global per call, not per-module.
|
||||
|
||||
import { h, render } from 'preact';
|
||||
import { useState, useEffect, useCallback } from 'preact/hooks';
|
||||
|
||||
// Mirrors `hive_jobq_wire::StateSchema` verbatim (variant names, no
|
||||
|
|
@ -233,8 +231,8 @@ export interface JobqGraphProps {
|
|||
}
|
||||
|
||||
// `refreshToken` is not read anywhere in the body — its only job is to
|
||||
// change identity so the effect below re-runs, giving a host (or
|
||||
// `mountJobqGraph`) an explicit "refetch now" lever without an
|
||||
// change identity so the effect below re-runs, giving a host an
|
||||
// explicit "refetch now" lever (bump it and re-render) without an
|
||||
// imperative ref into this component.
|
||||
export function JobqGraph({ endpoint, cancellable = false, onUpdate, onCancel, refreshToken = 0 }: JobqGraphProps) {
|
||||
const [selectedStates, setSelectedStates] = useState<Set<NodeState>>(
|
||||
|
|
@ -294,19 +292,3 @@ export function JobqGraph({ endpoint, cancellable = false, onUpdate, onCancel, r
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Imperative mount helper for a call site that isn't itself a JSX file
|
||||
// (dashboard/src/builds.js — plain `.js`, no per-file JSX pragma to
|
||||
// write `<JobqGraph .../>` inline). Returns a handle: `.refresh()`
|
||||
// (re-fetch with the current props) and `.update(props)` (merge new
|
||||
// props — e.g. a different `endpoint` — and re-render).
|
||||
export function mountJobqGraph(container: Element, initialProps: JobqGraphProps) {
|
||||
let props = initialProps;
|
||||
let token = 0;
|
||||
const draw = () => render(h(JobqGraph, { ...props, refreshToken: token }), container);
|
||||
draw();
|
||||
return {
|
||||
refresh() { token += 1; draw(); },
|
||||
update(next: Partial<JobqGraphProps>) { props = { ...props, ...next }; draw(); },
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue