120 lines
3.8 KiB
TypeScript
120 lines
3.8 KiB
TypeScript
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
|
|
import { useGateways } from "@/app/di";
|
|
import { onAgentRosterChanged } from "@/features/agents/agentRosterEvents";
|
|
import type { Agent, BackgroundCompletion, GatewayError, ScheduledProjectTask } from "@/domain";
|
|
import type { ScheduledTaskDefinitionInput } from "@/ports";
|
|
|
|
function message(error: unknown): string {
|
|
return error && typeof error === "object" && "message" in error
|
|
? String((error as GatewayError).message)
|
|
: String(error);
|
|
}
|
|
|
|
export function useScheduledTasks(projectId: string) {
|
|
const { scheduledTask, workState, agent, system } = useGateways();
|
|
const [tasks, setTasks] = useState<ScheduledProjectTask[]>([]);
|
|
const [agents, setAgents] = useState<Agent[]>([]);
|
|
const [runs, setRuns] = useState<BackgroundCompletion[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [busyId, setBusyId] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const refresh = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const [definitions, roster, state] = await Promise.all([
|
|
scheduledTask.list(projectId),
|
|
agent.listAgents(projectId).catch(() => []),
|
|
workState.getProjectWorkState(projectId).catch(() => null),
|
|
]);
|
|
setTasks(definitions);
|
|
setAgents(roster);
|
|
setRuns(state?.agents.flatMap((item) => item.backgroundTasks ?? []) ?? []);
|
|
setError(null);
|
|
} catch (cause) {
|
|
setError(message(cause));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [agent, projectId, scheduledTask, workState]);
|
|
|
|
useEffect(() => {
|
|
void refresh();
|
|
}, [refresh]);
|
|
|
|
useEffect(
|
|
() =>
|
|
onAgentRosterChanged((change) => {
|
|
if (change.projectId !== projectId) return;
|
|
void agent.listAgents(projectId).then(setAgents).catch(() => {});
|
|
}),
|
|
[agent, projectId],
|
|
);
|
|
|
|
useEffect(() => {
|
|
let unsubscribe: (() => void) | undefined;
|
|
let cancelled = false;
|
|
void system.onDomainEvent((event) => {
|
|
if (event.type === "backgroundTaskChanged" && event.projectId === projectId) {
|
|
void refresh();
|
|
}
|
|
}).then((stop) => {
|
|
if (cancelled) stop();
|
|
else unsubscribe = stop;
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
unsubscribe?.();
|
|
};
|
|
}, [projectId, refresh, system]);
|
|
|
|
const runsById = useMemo(
|
|
() => new Map(runs.map((run) => [run.taskId, run])),
|
|
[runs],
|
|
);
|
|
const agentNames = useMemo(
|
|
() => new Map(agents.map((item) => [item.id, item.name])),
|
|
[agents],
|
|
);
|
|
|
|
async function mutate(id: string, action: () => Promise<unknown>): Promise<boolean> {
|
|
setBusyId(id);
|
|
setError(null);
|
|
try {
|
|
await action();
|
|
await refresh();
|
|
return true;
|
|
} catch (cause) {
|
|
setError(message(cause));
|
|
return false;
|
|
} finally {
|
|
setBusyId(null);
|
|
}
|
|
}
|
|
|
|
return {
|
|
tasks,
|
|
loading,
|
|
busyId,
|
|
error,
|
|
runsById,
|
|
agentNames,
|
|
refresh,
|
|
create: (input: ScheduledTaskDefinitionInput) =>
|
|
mutate("new", () => scheduledTask.create(projectId, input)),
|
|
update: (task: ScheduledProjectTask, input: ScheduledTaskDefinitionInput) =>
|
|
mutate(task.id, () => scheduledTask.update(projectId, task.id, task.auditRevision, input)),
|
|
remove: (task: ScheduledProjectTask) =>
|
|
mutate(task.id, () => scheduledTask.delete(projectId, task.id)),
|
|
runNow: (task: ScheduledProjectTask) =>
|
|
mutate(task.id, () => scheduledTask.runNow(projectId, task.id)),
|
|
setEnabled: (task: ScheduledProjectTask, enabled: boolean) =>
|
|
mutate(task.id, () => scheduledTask.setEnabled(projectId, task.id, enabled)),
|
|
cancelRun: (task: ScheduledProjectTask) =>
|
|
task.lastTaskId
|
|
? mutate(task.id, () => workState.cancelBackgroundTask(task.lastTaskId!))
|
|
: Promise.resolve(undefined),
|
|
};
|
|
}
|