diff --git a/ui-v2/src/api/flow-runs/flow-runs.test.ts b/ui-v2/src/api/flow-runs/flow-runs.test.ts index f8eba299f23a..216bf5200fae 100644 --- a/ui-v2/src/api/flow-runs/flow-runs.test.ts +++ b/ui-v2/src/api/flow-runs/flow-runs.test.ts @@ -8,6 +8,7 @@ import { describe, expect, it } from "vitest"; import { buildListFlowRunsQuery, queryKeyFactory, + useDeleteFlowRun, useDeploymentCreateFlowRun, } from "."; @@ -83,6 +84,56 @@ describe("flow runs api", () => { expect(refetchInterval).toBe(customRefetchInterval); }); }); + describe("useDeleteFlowRun", () => { + it("invalidates cache and fetches updated value", async () => { + const FILTER = { + sort: "ID_DESC", + offset: 0, + } as const; + const queryClient = new QueryClient(); + const EXISTING_CACHE = [ + createFakeFlowRun({ id: "0" }), + createFakeFlowRun({ id: "1" }), + ]; + const MOCK_ID_TO_DELETE = "1"; + + // ------------ Mock API requests after queries are invalidated + const mockData = EXISTING_CACHE.filter( + (data) => data.id !== MOCK_ID_TO_DELETE, + ); + mockFetchFlowRunsAPI(mockData); + + // ------------ Initialize cache + queryClient.setQueryData(queryKeyFactory.list(FILTER), EXISTING_CACHE); + + // ------------ Initialize hooks to test + const { result: useDeleteFlowRunResult } = renderHook(useDeleteFlowRun, { + wrapper: createWrapper({ queryClient }), + }); + + const { result: useListFlowRunsResult } = renderHook( + () => useSuspenseQuery(buildListFlowRunsQuery(FILTER)), + { wrapper: createWrapper({ queryClient }) }, + ); + + // ------------ Invoke mutation + act(() => + useDeleteFlowRunResult.current.deleteFlowRun(MOCK_ID_TO_DELETE), + ); + + // ------------ Assert + await waitFor(() => + expect(useDeleteFlowRunResult.current.isSuccess).toBe(true), + ); + + expect(useListFlowRunsResult.current.data).toHaveLength(1); + + const newFlowRun = useListFlowRunsResult.current.data?.find( + (flowRun) => flowRun.id === MOCK_ID_TO_DELETE, + ); + expect(newFlowRun).toBeUndefined(); + }); + }); describe("useDeploymentCreateFlowRun", () => { it("invalidates cache and fetches updated value", async () => { const FILTER = { diff --git a/ui-v2/src/api/flow-runs/index.ts b/ui-v2/src/api/flow-runs/index.ts index c2e6eef9c8c3..32b9c5e6745a 100644 --- a/ui-v2/src/api/flow-runs/index.ts +++ b/ui-v2/src/api/flow-runs/index.ts @@ -79,6 +79,47 @@ export const buildListFlowRunsQuery = ( // ----- ✍🏼 Mutations 🗄️ // ---------------------------- +/** + * Hook for deleting a flow run + * + * @returns Mutation object for deleting a flow run with loading/error states and trigger function + * + * @example + * ```ts + * const { deleteFlowRun, isLoading } = useDeleteFlowRun(); + * + * deleteflowRun(id, { + * onSuccess: () => { + * // Handle successful deletion + * console.log('Flow run deleted successfully'); + * }, + * onError: (error) => { + * // Handle error + * console.error('Failed to delete flow run:', error); + * } + * }); + * ``` + */ +export const useDeleteFlowRun = () => { + const queryClient = useQueryClient(); + const { mutate: deleteFlowRun, ...rest } = useMutation({ + mutationFn: async (id: string) => + getQueryService().DELETE("/flow_runs/{id}", { + params: { path: { id } }, + }), + onSuccess: () => { + // After a successful creation, invalidate only list queries to refetch + return queryClient.invalidateQueries({ + queryKey: queryKeyFactory.lists(), + }); + }, + }); + return { + deleteFlowRun, + ...rest, + }; +}; + type MutateCreateFlowRun = { id: string; } & components["schemas"]["DeploymentFlowRunCreate"]; diff --git a/ui-v2/tests/utils/handlers.ts b/ui-v2/tests/utils/handlers.ts index 28ce31cf6ffd..203d0d5ac74a 100644 --- a/ui-v2/tests/utils/handlers.ts +++ b/ui-v2/tests/utils/handlers.ts @@ -75,6 +75,10 @@ const flowRunHandlers = [ { id: "2", name: "Flow 2", tags: [] }, ]); }), + + http.delete(buildApiUrl("/flow_runs/:id"), () => { + return HttpResponse.json({ status: 204 }); + }), ]; const globalConcurrencyLimitsHandlers = [