Files
kandev/apps/web/hooks/domains/settings/use-automation-runs.ts
T

128 lines
5.1 KiB
TypeScript

"use client";
import { useEffect, useCallback } from "react";
import { toast } from "sonner";
import {
listAutomationRuns,
deleteAutomationRun,
deleteAllAutomationRuns,
} from "@/lib/api/domains/automation-api";
import { useAppStore, useAppStoreApi } from "@/components/state-provider";
import type { AutomationRun } from "@/lib/types/automation";
const EMPTY_RUNS: AutomationRun[] = [];
export function useAutomationRuns(automationId: string | null, workspaceId: string) {
const runs = useAppStore((state) =>
automationId ? (state.automationRuns.byAutomationId[automationId] ?? EMPTY_RUNS) : EMPTY_RUNS,
);
const loading = useAppStore((state) =>
automationId ? (state.automationRuns.loading[automationId] ?? false) : false,
);
const setRuns = useAppStore((state) => state.setAutomationRuns);
const setRunsLoading = useAppStore((state) => state.setAutomationRunsLoading);
const removeRun = useAppStore((state) => state.removeAutomationRun);
const clearRuns = useAppStore((state) => state.clearAutomationRuns);
const restoreRun = useAppStore((state) => state.restoreAutomationRun);
const storeApi = useAppStoreApi();
useEffect(() => {
if (!automationId || loading) return;
setRunsLoading(automationId, true);
listAutomationRuns(automationId)
.then((result) => {
setRuns(automationId, result ?? []);
})
.catch(() => {
setRuns(automationId, []);
})
.finally(() => {
setRunsLoading(automationId, false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [automationId]);
const refresh = useCallback(() => {
if (!automationId) return;
setRunsLoading(automationId, true);
listAutomationRuns(automationId)
.then((result) => {
setRuns(automationId, result ?? []);
})
.catch(() => {})
.finally(() => {
setRunsLoading(automationId, false);
});
}, [automationId, setRuns, setRunsLoading]);
const deleteRun = useCallback(
(runId: string) => {
if (!automationId) return;
// Snapshot the run being removed so we can restore it precisely (not
// the whole list, which could clobber unrelated concurrent changes)
// if both the delete and the recovery refresh below fail.
const deletedRun = storeApi
.getState()
.automationRuns.byAutomationId[automationId]?.find((r) => r.id === runId);
removeRun(automationId, runId); // optimistic
deleteAutomationRun(runId, workspaceId)
.then(() => {
// Re-apply the removal: an in-flight refresh() / initial load can
// resolve between the optimistic removeRun above and this success
// callback and overwrite the store with the pre-delete list,
// resurrecting the row. Removing it again here is a no-op unless
// that happened.
removeRun(automationId, runId);
})
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : "Failed to delete run";
toast.error(msg);
// revert on failure
listAutomationRuns(automationId)
.then((result) => setRuns(automationId, result ?? []))
.catch(() => {
// The recovery refresh also failed — the store would
// otherwise stay permanently missing this row even though
// the delete never succeeded server-side. Fall back to
// re-inserting just the run we know we removed.
if (deletedRun) {
restoreRun(automationId, deletedRun);
}
toast.error("Could not refresh runs — restored from local cache");
});
});
},
[automationId, removeRun, restoreRun, setRuns, storeApi, workspaceId],
);
const deleteAllRuns = useCallback(() => {
if (!automationId) return;
// Snapshot the full list so we can restore it if both the delete-all
// and the recovery refresh below fail.
const previousRuns = storeApi.getState().automationRuns.byAutomationId[automationId] ?? [];
clearRuns(automationId); // optimistic
deleteAllAutomationRuns(automationId, workspaceId)
.then(() => {
// See deleteRun: guard against an in-flight refresh() resurrecting
// rows between the optimistic clear and this success callback.
clearRuns(automationId);
})
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : "Failed to delete runs";
toast.error(msg);
// revert on failure
listAutomationRuns(automationId)
.then((result) => setRuns(automationId, result ?? []))
.catch(() => {
// The recovery refresh also failed — without this the store
// would stay permanently empty even though delete-all never
// succeeded server-side. Fall back to the pre-clear snapshot.
setRuns(automationId, previousRuns);
toast.error("Could not refresh runs — restored from local cache");
});
});
}, [automationId, clearRuns, setRuns, storeApi, workspaceId]);
return { runs, loading, refresh, deleteRun, deleteAllRuns };
}