From 4a69fe80defaeea0c75fb0b15ec4f9624cc75936 Mon Sep 17 00:00:00 2001 From: Devin Villarosa Date: Thu, 20 Feb 2025 10:29:06 -0800 Subject: [PATCH] [UI v2] feat: adds query to join flow runs with parent flow --- .../index.ts | 1 + .../use-paginate-flow-runs-with-flows.test.ts | 85 +++++++++++++++++++ .../use-paginate-flow-runs-with-flows.ts | 82 ++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/index.ts create mode 100644 ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/use-paginate-flow-runs-with-flows.test.ts create mode 100644 ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/use-paginate-flow-runs-with-flows.ts diff --git a/ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/index.ts b/ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/index.ts new file mode 100644 index 000000000000..70b66effcc6b --- /dev/null +++ b/ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/index.ts @@ -0,0 +1 @@ +export { usePaginateFlowRunswithFlows } from "./use-paginate-flow-runs-with-flows"; diff --git a/ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/use-paginate-flow-runs-with-flows.test.ts b/ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/use-paginate-flow-runs-with-flows.test.ts new file mode 100644 index 000000000000..ad5d0cf006bd --- /dev/null +++ b/ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/use-paginate-flow-runs-with-flows.test.ts @@ -0,0 +1,85 @@ +import { createFakeFlow, createFakeFlowRun } from "@/mocks"; + +import type { FlowRun } from "@/api/flow-runs"; +import type { Flow } from "@/api/flows"; + +import { QueryClient } from "@tanstack/react-query"; +import { renderHook, waitFor } from "@testing-library/react"; +import { buildApiUrl, createWrapper, server } from "@tests/utils"; +import { http, HttpResponse } from "msw"; +import { describe, expect, it } from "vitest"; +import { usePaginateFlowRunswithFlows } from "./use-paginate-flow-runs-with-flows"; + +describe("usePaginateFlowRunswithFlows", () => { + const mockPaginateFlowRunsAPI = (flowRuns: Array) => { + server.use( + http.post(buildApiUrl("/flow_runs/paginate"), () => { + return HttpResponse.json({ + limit: 10, + page: 1, + pages: 1, + results: flowRuns, + count: flowRuns.length, + }); + }), + ); + }; + + const mockFilterFlowsAPI = (flows: Array) => { + server.use( + http.post(buildApiUrl("/flows/filter"), () => { + return HttpResponse.json(flows); + }), + ); + }; + + it("returns maps of the different sources used in an automation", async () => { + // SETUP + const queryClient = new QueryClient(); + const MOCK_FLOW_RUN_0 = createFakeFlowRun({ + id: "0", + flow_id: "flow-id-0", + }); + const MOCK_FLOW_RUN_1 = createFakeFlowRun({ + id: "0", + flow_id: "flow-id-0", + }); + const MOCK_FLOW_RUN_2 = createFakeFlowRun({ + id: "0", + flow_id: "flow-id-1", + }); + const MOCK_FLOW_0 = createFakeFlow({ id: "flow-id-0" }); + const MOCK_FLOW_1 = createFakeFlow({ id: "flow-id-1" }); + + const mockFlowRuns = [MOCK_FLOW_RUN_0, MOCK_FLOW_RUN_1, MOCK_FLOW_RUN_2]; + const mockFlows = [MOCK_FLOW_0, MOCK_FLOW_1]; + mockPaginateFlowRunsAPI(mockFlowRuns); + mockFilterFlowsAPI(mockFlows); + + // TEST + const { result } = renderHook( + () => usePaginateFlowRunswithFlows({ page: 1, sort: "NAME_ASC" }), + { wrapper: createWrapper({ queryClient }) }, + ); + + await waitFor(() => expect(result.current.status).toEqual("success")); + + // ASSERT + const EXPECTED = [ + { + ...MOCK_FLOW_RUN_0, + flow: MOCK_FLOW_0, + }, + { + ...MOCK_FLOW_RUN_1, + flow: MOCK_FLOW_0, + }, + { + ...MOCK_FLOW_RUN_2, + flow: MOCK_FLOW_1, + }, + ]; + + expect(result.current.data?.results).toEqual(EXPECTED); + }); +}); diff --git a/ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/use-paginate-flow-runs-with-flows.ts b/ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/use-paginate-flow-runs-with-flows.ts new file mode 100644 index 000000000000..55b435c87e1d --- /dev/null +++ b/ui-v2/src/api/flow-runs/use-paginate-flow-runs-with-flows/use-paginate-flow-runs-with-flows.ts @@ -0,0 +1,82 @@ +import { + type FlowRunsPaginateFilter, + buildPaginateFlowRunsQuery, +} from "@/api/flow-runs"; +import { Flow, buildListFlowsQuery } from "@/api/flows"; +import { useQuery } from "@tanstack/react-query"; +import { useMemo } from "react"; + +/** + * + * @param filter + * @returns a simplified query object that joins a flow run's pagination data with it's parent flow + */ +export const usePaginateFlowRunswithFlows = ( + filter: FlowRunsPaginateFilter, +) => { + const { data: paginateFlowRunsData, error: paginateFlowRunsError } = useQuery( + buildPaginateFlowRunsQuery(filter), + ); + + const flowIds = useMemo(() => { + if (!paginateFlowRunsData) { + return new Set(); + } + const flowIds = paginateFlowRunsData.results.map( + (flowRun) => flowRun.flow_id, + ); + return new Set(flowIds); + }, [paginateFlowRunsData]); + + const { data: flows, error: flowsError } = useQuery( + buildListFlowsQuery( + { + flows: { id: { any_: Array.from(flowIds) }, operator: "and_" }, + offset: 0, + sort: "CREATED_DESC", + }, + { enabled: flowIds.size > 0 }, + ), + ); + + const flowMap = useMemo(() => { + if (!flows) { + return new Map(); + } + return new Map(flows.map((flow) => [flow.id, flow])); + }, [flows]); + + if (paginateFlowRunsData && flowMap.size > 0) { + return { + status: "success" as const, + error: null, + data: { + ...paginateFlowRunsData, + results: paginateFlowRunsData.results.map((flowRun) => { + const flow = flowMap.get(flowRun.flow_id); + if (!flow) { + throw new Error("Expecting parent flow to be found"); + } + return { + ...flowRun, + flow, + }; + }), + }, + }; + } + + if (paginateFlowRunsError || flowsError) { + return { + status: "error" as const, + error: paginateFlowRunsError || flowsError, + data: undefined, + }; + } + + return { + status: "pending" as const, + error: null, + data: undefined, + }; +};