Frontend Automate Orchestration

This commit is contained in:
sriram
2026-08-31 15:00:56 +05:30
parent 80daf30a42
commit 097a4a5bfe
2 changed files with 99 additions and 7 deletions

View File

@@ -61,6 +61,12 @@ export function AdminPage() {
// sent something while you were looking at a different screen. A failed poll
// is swallowed: a transient network blip must not replace the panel with an
// error, and the next tick retries.
//
// Expect a steady zero on a deployment running UPLOAD_AUTORUN=true: uploads
// go straight into the pipeline and never wait for review, so there is
// nothing to be notified about. The badge renders only above zero, so that
// reads as "nothing to do" rather than as a broken counter, and the poll
// stays because the setting can be turned off without a redeploy.
useEffect(() => {
let alive = true;
const check = async () => {

View File

@@ -81,6 +81,7 @@ function fmtSecs(from, to) {
export function OrchestrationPanel() {
const [inbox, setInbox] = useState(null);
const [recent, setRecent] = useState([]);
const [selected, setSelected] = useState(() => new Set());
const [batch, setBatch] = useState(null);
const [busy, setBusy] = useState('');
@@ -104,9 +105,34 @@ export function OrchestrationPanel() {
}
};
// Runs that started without anyone here pressing anything.
//
// UPLOAD_AUTORUN sends a colleague's upload straight into the pipeline, so
// the inbox it used to wait in is empty and the run has an id this screen was
// never told. Without this list an auto-started batch is invisible: its
// eleven stages are recorded and served, and nothing renders them. Slim
// responses, so twenty of these do not carry twenty product manifests.
const loadRecent = async () => {
try {
const { batches } = await api.listCatalogBatches(20);
setRecent(batches || []);
} catch {
/* the inbox call surfaces auth/network trouble; one banner is enough */
}
};
const watch = (run) => {
setBatch(run);
batchIdRef.current = TERMINAL.has(run.status) ? null : run.batch_id;
};
useEffect(() => {
loadInbox();
const timer = setInterval(loadInbox, INBOX_POLL_MS);
loadRecent();
const timer = setInterval(() => {
loadInbox();
loadRecent();
}, INBOX_POLL_MS);
return () => clearInterval(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -210,10 +236,12 @@ export function OrchestrationPanel() {
<Workflow className="h-5 w-5 text-amber-500" /> Dagster Orchestration
</h2>
<p className="text-xs text-slate-500 leading-relaxed max-w-3xl">
Spreadsheets sent in by a colleague through the upload API. Tick the files you
want and press <strong>Start selected</strong> to send them through the 11-stage
pipeline as one batch, run by Dagster instead of this server&apos;s own worker.
Every stage each file passes through is recorded below and kept after the run
Spreadsheets sent in by a colleague through the upload API. Those uploads now
start on their own &mdash; pick one under <strong>Recent runs</strong> to follow
its stages. Anything still waiting for a decision appears below, where you can
tick files and press <strong>Start selected</strong> to send them through the
11-stage pipeline as one batch, run by Dagster instead of this server&apos;s own
worker. Every stage each file passes through is recorded and kept after the run
finishes.
</p>
</div>
@@ -286,13 +314,71 @@ export function OrchestrationPanel() {
)}
</div>
{/* ---- runs that started without anyone pressing anything -------- */}
{recent.length > 0 && (
<div className="rounded-2xl border border-ink-900/10 bg-paper-50 p-5 shadow-xs">
<h3 className="font-display text-sm font-bold text-ink-950 mb-1">Recent runs</h3>
<p className="text-[11px] text-slate-500 mb-3">
Uploads start on arrival, so this is where a colleague&apos;s files show up.
Pick one to see its eleven stages.
</p>
<ul className="space-y-1.5">
{recent.map((run) => {
const Icon = FILE_ICON[run.status] || Clock;
const live = !TERMINAL.has(run.status);
return (
<li key={run.batch_id}>
<button
type="button"
onClick={() => watch(run)}
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg border text-left transition cursor-pointer hover:border-amber-500/40 ${
batch?.batch_id === run.batch_id
? 'border-amber-500/40 bg-amber-500/5'
: 'border-ink-900/10 bg-white'
}`}
>
<Icon
className={`h-4 w-4 shrink-0 ${live ? 'text-amber-600' : 'text-slate-400'} ${
run.status === 'running' ? 'animate-spin' : ''
}`}
/>
<span className="font-mono text-[11px] text-slate-500 shrink-0">
{run.batch_id.slice(0, 8)}
</span>
<span className="text-xs text-ink-950 truncate flex-1">
{run.files_total} file(s)
{run.submitted_by ? ` from ${run.submitted_by}` : ''}
</span>
<span
className={`shrink-0 px-2 py-0.5 rounded-md border text-[10px] font-bold ${
FILE_TONE[run.status] || FILE_TONE.queued
}`}
>
{run.status}
</span>
<span className="shrink-0 text-[10px] text-slate-400 font-mono w-16 text-right">
{ago(run.created_at)}
</span>
</button>
</li>
);
})}
</ul>
</div>
)}
{/* ---- the drops waiting to be started --------------------------- */}
{inbox && submissions.length === 0 && !batch && (
<div className="rounded-2xl border border-ink-900/10 bg-paper-50 p-10 text-center">
<Workflow className="h-8 w-8 text-slate-300 mx-auto mb-3" />
<p className="text-xs text-slate-500">
Nothing waiting. Files sent to <code className="font-mono">/api/uploads/catalog</code>{' '}
appear here to be orchestrated.
Nothing waiting for review &mdash; files sent to{' '}
<code className="font-mono">/api/uploads/catalog</code> start on their own and
appear under Recent runs.
</p>
<p className="text-[11px] text-slate-400 mt-2">
Set <code className="font-mono">UPLOAD_AUTORUN=false</code> to hold them here for
a decision instead.
</p>
</div>
)}