feat(web): show latest orchestrator event in task progress widget
Some checks failed
ci/woodpecker/push/web Pipeline failed
Some checks failed
ci/woodpecker/push/web Pipeline failed
This commit is contained in:
@@ -27,6 +27,13 @@ interface QueueStats {
|
||||
delayed: number;
|
||||
}
|
||||
|
||||
interface RecentOrchestratorEvent {
|
||||
type: string;
|
||||
timestamp: string;
|
||||
taskId?: string;
|
||||
agentId?: string;
|
||||
}
|
||||
|
||||
function getElapsedTime(spawnedAt: string, completedAt?: string): string {
|
||||
const start = new Date(spawnedAt).getTime();
|
||||
const end = completedAt ? new Date(completedAt).getTime() : Date.now();
|
||||
@@ -103,6 +110,7 @@ function getAgentTypeLabel(agentType: string): string {
|
||||
export function TaskProgressWidget({ id: _id, config: _config }: WidgetProps): React.JSX.Element {
|
||||
const [tasks, setTasks] = useState<AgentTask[]>([]);
|
||||
const [queueStats, setQueueStats] = useState<QueueStats | null>(null);
|
||||
const [recentEvents, setRecentEvents] = useState<RecentOrchestratorEvent[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isQueuePaused, setIsQueuePaused] = useState(false);
|
||||
@@ -135,6 +143,17 @@ export function TaskProgressWidget({ id: _id, config: _config }: WidgetProps): R
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchRecentEvents = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
const res = await fetch("/api/orchestrator/events/recent?limit=5");
|
||||
if (!res.ok) throw new Error(`HTTP ${String(res.status)}`);
|
||||
const data = (await res.json()) as { events: RecentOrchestratorEvent[] };
|
||||
setRecentEvents(data.events);
|
||||
} catch {
|
||||
// Optional enhancement path; do not fail widget if recent-events endpoint is unavailable.
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setQueueState = useCallback(
|
||||
async (action: "pause" | "resume"): Promise<void> => {
|
||||
setIsActionPending(true);
|
||||
@@ -157,10 +176,12 @@ export function TaskProgressWidget({ id: _id, config: _config }: WidgetProps): R
|
||||
useEffect(() => {
|
||||
void fetchTasks();
|
||||
void fetchQueueStats();
|
||||
void fetchRecentEvents();
|
||||
|
||||
const interval = setInterval(() => {
|
||||
void fetchTasks();
|
||||
void fetchQueueStats();
|
||||
void fetchRecentEvents();
|
||||
}, 15000);
|
||||
|
||||
const eventSource =
|
||||
@@ -169,6 +190,7 @@ export function TaskProgressWidget({ id: _id, config: _config }: WidgetProps): R
|
||||
eventSource.onmessage = (): void => {
|
||||
void fetchTasks();
|
||||
void fetchQueueStats();
|
||||
void fetchRecentEvents();
|
||||
};
|
||||
eventSource.onerror = (): void => {
|
||||
// Polling remains the resilience path.
|
||||
@@ -179,7 +201,9 @@ export function TaskProgressWidget({ id: _id, config: _config }: WidgetProps): R
|
||||
clearInterval(interval);
|
||||
eventSource?.close();
|
||||
};
|
||||
}, [fetchTasks, fetchQueueStats]);
|
||||
}, [fetchTasks, fetchQueueStats, fetchRecentEvents]);
|
||||
|
||||
const latestEvent = recentEvents.length > 0 ? recentEvents[recentEvents.length - 1] : null;
|
||||
|
||||
const stats = {
|
||||
total: tasks.length,
|
||||
@@ -226,6 +250,13 @@ export function TaskProgressWidget({ id: _id, config: _config }: WidgetProps): R
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{latestEvent && (
|
||||
<div className="rounded bg-gray-50 dark:bg-gray-800 px-2 py-1 text-xs text-gray-600 dark:text-gray-300">
|
||||
Latest: {latestEvent.type}
|
||||
{latestEvent.taskId ? ` · ${latestEvent.taskId}` : ""}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Summary stats */}
|
||||
<div className="grid grid-cols-4 gap-1 text-center text-xs">
|
||||
<div className="bg-gray-50 dark:bg-gray-800 rounded p-2">
|
||||
|
||||
@@ -242,4 +242,58 @@ describe("TaskProgressWidget", (): void => {
|
||||
expect(taskElements[1]?.textContent).toBe("COMPLETED-TASK");
|
||||
});
|
||||
});
|
||||
|
||||
it("should display latest orchestrator event when available", async (): Promise<void> => {
|
||||
mockFetch.mockImplementation((input: RequestInfo | URL) => {
|
||||
let url = "";
|
||||
if (typeof input === "string") {
|
||||
url = input;
|
||||
} else if (input instanceof URL) {
|
||||
url = input.toString();
|
||||
} else {
|
||||
url = input.url;
|
||||
}
|
||||
if (url.includes("/api/orchestrator/agents")) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve([]),
|
||||
} as unknown as Response);
|
||||
}
|
||||
if (url.includes("/api/orchestrator/queue/stats")) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
pending: 0,
|
||||
active: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
delayed: 0,
|
||||
}),
|
||||
} as unknown as Response);
|
||||
}
|
||||
if (url.includes("/api/orchestrator/events/recent")) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
events: [
|
||||
{
|
||||
type: "task.executing",
|
||||
timestamp: new Date().toISOString(),
|
||||
taskId: "TASK-123",
|
||||
},
|
||||
],
|
||||
}),
|
||||
} as unknown as Response);
|
||||
}
|
||||
return Promise.reject(new Error("Unknown endpoint"));
|
||||
});
|
||||
|
||||
render(<TaskProgressWidget id="task-progress-1" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Latest: task.executing · TASK-123/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -386,3 +386,4 @@
|
||||
| ORCH-OBS-007 | done | Add `OrchestratorEventsWidget` for live/recent orchestration visibility with Matrix signal hints | #411 | web | feature/mosaic-stack-finalization | ORCH-OBS-002 | | orch | 2026-02-17T16:55Z | 2026-02-17T17:03Z | 12K | 9K |
|
||||
| ORCH-OBS-008 | done | Integrate new widget into HUD/WidgetRegistry and extend widget regression coverage | #411 | web | feature/mosaic-stack-finalization | ORCH-OBS-007 | | orch | 2026-02-17T17:03Z | 2026-02-17T17:08Z | 10K | 7K |
|
||||
| ORCH-OBS-009 | done | Seed default/reset local HUD layout with orchestration widgets so visibility works out-of-box | #411 | web | feature/mosaic-stack-finalization | ORCH-OBS-008 | | orch | 2026-02-17T17:10Z | 2026-02-17T17:14Z | 8K | 6K |
|
||||
| ORCH-OBS-010 | done | Enrich `TaskProgressWidget` with latest recent-event context from `/api/orchestrator/events/recent` | #411 | web | feature/mosaic-stack-finalization | ORCH-OBS-009 | | orch | 2026-02-17T17:15Z | 2026-02-17T17:20Z | 8K | 6K |
|
||||
|
||||
Reference in New Issue
Block a user