Skip to content

Commit

Permalink
[UI v2] feat: Updates loader for concurrency limit route to include a…
Browse files Browse the repository at this point in the history
…ctive task runs logic
  • Loading branch information
devinvillarosa committed Dec 27, 2024
1 parent cf2f269 commit 66ac5e7
Show file tree
Hide file tree
Showing 6 changed files with 408 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
export const TaskRunConcurrencyLimitActiveTaskRuns = () => {
return <div>TODO: Active Task Runs View</div>;
import type { components } from "@/api/prefect";
import { Typography } from "@/components/ui/typography";

type Props = {
data: Array<{
taskRun: components["schemas"]["TaskRun"];
flowRun?: components["schemas"]["FlowRun"];
flow?: components["schemas"]["Flow"];
}>;
};

export const TaskRunConcurrencyLimitActiveTaskRuns = ({ data }: Props) => {
return (
<div>
<Typography>TODO: Active Task Runs</Typography>
{JSON.stringify(data)}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { TaskRunConcurrencyLimitHeader } from "@/components/concurrency/task-run-concurrency-limits/task-run-concurrency-limit-header";
import { useGetTaskRunConcurrencyLimit } from "@/hooks/task-run-concurrency-limits";
import { buildConcurrenyLimitDetailsActiveRunsQuery } from "@/hooks/task-run-concurrency-limits";
import { useState } from "react";

import { TaskRunConcurrencyLimitActiveTaskRuns } from "@/components/concurrency/task-run-concurrency-limits/task-run-concurrency-limit-active-task-runs";
import { TaskRunConcurrencyLimitDetails } from "@/components/concurrency/task-run-concurrency-limits/task-run-concurrency-limit-details";
import { useSuspenseQuery } from "@tanstack/react-query";
import {
type Dialogs,
TaskRunConcurrencyLimitDialog,
Expand All @@ -16,7 +17,9 @@ type Props = {

export const TaskRunConcurrencyLimitPage = ({ id }: Props) => {
const [openDialog, setOpenDialog] = useState<Dialogs>(null);
const { data } = useGetTaskRunConcurrencyLimit(id);
const { data } = useSuspenseQuery(
buildConcurrenyLimitDetailsActiveRunsQuery(id),
);

const handleOpenDeleteDialog = () => setOpenDialog("delete");
const handleOpenResetDialog = () => setOpenDialog("reset");
Expand All @@ -29,23 +32,27 @@ export const TaskRunConcurrencyLimitPage = ({ id }: Props) => {
}
};

const { activeTaskRuns, taskRunConcurrencyLimit } = data;

return (
<>
<div className="flex flex-col gap-4">
<TaskRunConcurrencyLimitHeader
data={data}
data={taskRunConcurrencyLimit}
onDelete={handleOpenDeleteDialog}
onReset={handleOpenResetDialog}
/>
<div className="grid gap-4" style={{ gridTemplateColumns: "3fr 1fr" }}>
<TaskRunConcurrencyLimitTabNavigation
activetaskRunsView={<TaskRunConcurrencyLimitActiveTaskRuns />}
activetaskRuns={
<TaskRunConcurrencyLimitActiveTaskRuns data={activeTaskRuns} />
}
/>
<TaskRunConcurrencyLimitDetails data={data} />
<TaskRunConcurrencyLimitDetails data={taskRunConcurrencyLimit} />
</div>
</div>
<TaskRunConcurrencyLimitDialog
data={data}
data={taskRunConcurrencyLimit}
openDialog={openDialog}
onOpenChange={handleOpenChange}
onCloseDialog={handleCloseDialog}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ const TAB_OPTIONS: Record<TabOptions, TabOptionValues> = {
} as const;

type Props = {
activetaskRunsView: React.ReactNode;
activetaskRuns: React.ReactNode;
};

// TODO: Move Tabs for navigation to a generic styled component
export const TaskRunConcurrencyLimitTabNavigation = ({
activetaskRunsView,
activetaskRuns,
}: Props) => {
const { tab } = routeApi.useSearch();
const navigate = routeApi.useNavigate();
Expand All @@ -48,7 +48,7 @@ export const TaskRunConcurrencyLimitTabNavigation = ({
</TabsTrigger>
</TabsList>
<TabsContent value={TAB_OPTIONS["active-task-runs"].tabSearchValue}>
{activetaskRunsView}
{activetaskRuns}
</TabsContent>
</Tabs>
);
Expand Down
254 changes: 252 additions & 2 deletions ui-v2/src/hooks/task-run-concurrency-limits.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { QueryClient } from "@tanstack/react-query";
import { components } from "@/api/prefect";
import { QueryClient, useSuspenseQuery } from "@tanstack/react-query";
import { act, renderHook, waitFor } from "@testing-library/react";
import { createWrapper, server } from "@tests/utils";
import { http, HttpResponse } from "msw";
import { describe, expect, it } from "vitest";
import {
type TaskRunConcurrencyLimit,
buildConcurrenyLimitDetailsActiveRunsQuery,
queryKeyFactory,
useCreateTaskRunConcurrencyLimit,
useDeleteTaskRunConcurrencyLimit,
Expand All @@ -21,7 +23,7 @@ describe("task run concurrency limits hooks", () => {
updated: "2021-01-01T00:00:00Z",
tag: "my tag 0",
concurrency_limit: 1,
active_slots: [] as Array<string>,
active_slots: ["task_0"],
},
];

Expand Down Expand Up @@ -246,3 +248,251 @@ describe("task run concurrency limits hooks", () => {
expect(limit?.active_slots).toHaveLength(0);
});
});

describe("buildConcurrenyLimitDetailsActiveRunsQuery()", () => {
const seedData = () => ({
id: "0",
created: "2021-01-01T00:00:00Z",
updated: "2021-01-01T00:00:00Z",
tag: "my tag 0",
concurrency_limit: 1,
active_slots: ["task_0"],
});

const MOCK_TASK_RUNS: Array<components["schemas"]["TaskRun"]> = [
{
id: "task_0",
created: "2024-12-18T19:07:44.837299Z",
updated: "2024-12-18T19:07:45.019000Z",
name: "hello-task-a0c",
flow_run_id: "3f1093f1-c349-47f1-becf-111999ac9351",
task_key: "say_hello-6b199e75",
dynamic_key: "a0c05e3b-4129-4e6d-bcf9-8b6d289aa4c0",
cache_key: null,
cache_expiration: null,
task_version: null,
empirical_policy: {
max_retries: 0,
retry_delay_seconds: 0,
retries: 0,
retry_delay: 0,
retry_jitter_factor: null,
},
tags: ["tag1", "test1", "onboarding", "test", "test2"],
labels: {},
state_id: "050b3aa3-c085-44da-bada-a6a239ca617b",
task_inputs: {
name: [],
},
state_type: "RUNNING",
state_name: "Running",
run_count: 1,
flow_run_run_count: 1,
expected_start_time: "2024-12-18T19:07:44.786401Z",
next_scheduled_start_time: null,
start_time: "2024-12-18T19:07:44.791572Z",
end_time: null,
total_run_time: 0,
estimated_run_time: 0.901001,
estimated_start_time_delta: 0.005171,
state: {
id: "050b3aa3-c085-44da-bada-a6a239ca617b",
type: "RUNNING",
name: "Running",
timestamp: "2024-12-18T19:07:44.791572Z",
message: "",
data: null,
state_details: {
flow_run_id: "3f1093f1-c349-47f1-becf-111999ac9351",
task_run_id: "652d0799-8e73-4b2d-8428-e60b070d0a97",
child_flow_run_id: null,
scheduled_time: null,
cache_key: null,
cache_expiration: null,
deferred: false,
untrackable_result: false,
pause_timeout: null,
pause_reschedule: false,
pause_key: null,
run_input_keyset: null,
refresh_cache: null,
retriable: null,
transition_id: null,
task_parameters_id: null,
},
},
},
];

const MOCK_FLOW_RUNS: Array<components["schemas"]["FlowRun"]> = [
{
id: "3f1093f1-c349-47f1-becf-111999ac9351",
created: "2024-12-18T19:07:44.726912Z",
updated: "2024-12-18T19:07:49.473000Z",
name: "authentic-cockatoo",
flow_id: "5d8df4c9-d846-45b6-a5bc-55f0cb264bd2",
state_id: "2298e945-6e3f-4def-ba97-1276937d3ef3",
deployment_id: null,
deployment_version: null,
work_queue_id: null,
work_queue_name: null,
flow_version: "e22328439175aef5dfcba6aa44a4c392",
parameters: {},
idempotency_key: null,
context: {},
empirical_policy: {
max_retries: 0,
retry_delay_seconds: 0.0,
retries: 0,
retry_delay: 0,
pause_keys: [],
resuming: false,
retry_type: null,
},
tags: [],
labels: { "prefect.flow.id": "5d8df4c9-d846-45b6-a5bc-55f0cb264bd2" },
parent_task_run_id: null,
state_type: "COMPLETED",
state_name: "Completed",
run_count: 1,
expected_start_time: "2024-12-18T19:07:44.726851Z",
next_scheduled_start_time: null,
start_time: "2024-12-18T19:07:44.750254Z",
end_time: "2024-12-18T19:07:49.458237Z",
total_run_time: 4.707983,
estimated_run_time: 4.707983,
estimated_start_time_delta: 0.023403,
auto_scheduled: false,
infrastructure_document_id: null,
infrastructure_pid: null,
created_by: null,
state: {
id: "2298e945-6e3f-4def-ba97-1276937d3ef3",
type: "COMPLETED",
name: "Completed",
timestamp: "2024-12-18T19:07:49.458237Z",
message: null,
data: null,
state_details: {
flow_run_id: "3f1093f1-c349-47f1-becf-111999ac9351",
task_run_id: null,
child_flow_run_id: null,
scheduled_time: null,
cache_key: null,
cache_expiration: null,
deferred: null,
untrackable_result: true,
pause_timeout: null,
pause_reschedule: false,
pause_key: null,
run_input_keyset: null,
refresh_cache: null,
retriable: null,
transition_id: "c0ae12f9-d2a0-459a-8a2d-947fe7cebf46",
task_parameters_id: null,
},
},
job_variables: {},
},
];
const MOCK_FLOW: components["schemas"]["Flow"] = {
id: "5d8df4c9-d846-45b6-a5bc-55f0cb264bd2",
created: "2024-12-10T21:50:18.315610Z",
updated: "2024-12-10T21:50:18.315612Z",
name: "hello-universe",
tags: [],
labels: {},
};

const mockFetchDetailsAPI = (data: TaskRunConcurrencyLimit) => {
server.use(
http.get("http://localhost:4200/api/concurrency_limits/:id", () => {
return HttpResponse.json(data);
}),
);
};

const mockTaskRunsFiltersAPI = (
data: Array<components["schemas"]["TaskRun"]>,
) => {
server.use(
http.post("http://localhost:4200/api/task_runs/filter", () => {
return HttpResponse.json(data);
}),
);
};
const mockFlowRunsFiltersAPI = (
data: Array<components["schemas"]["FlowRun"]>,
) => {
server.use(
http.post("http://localhost:4200/api/flow_runs/filter", () => {
return HttpResponse.json(data);
}),
);
};

const mockGetFlowAPI = (data: components["schemas"]["Flow"]) => {
server.use(
http.get("http://localhost:4200/api/flows/:id", () => {
return HttpResponse.json(data);
}),
);
};

/**
* Data Management:
* - Asserts fetch API to get details on the task run concurrency limit
* - Other APIs will not be fired because there are no active task runs
*/
it("will fetch only necessary data when there is no active task runs", async () => {
const MOCK_DATA = { ...seedData(), active_slots: [] };
const MOCK_ID = MOCK_DATA.id;
mockFetchDetailsAPI(MOCK_DATA);

const { result } = renderHook(
() =>
useSuspenseQuery(buildConcurrenyLimitDetailsActiveRunsQuery(MOCK_ID)),
{ wrapper: createWrapper() },
);

// ------------ Assert
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toMatchObject({
taskRunConcurrencyLimit: MOCK_DATA,
activeTaskRuns: [],
});
});

/**
* Data Management:
* - Asserts waterfall of APIs will fire to get details on the task run concurrency limit
* - Other APIs will fire to get details on each task run, flow run associated with the task run, and overall flow details
*/
it("will fetch a necessary data for task runs", async () => {
const MOCK_DATA = seedData();
const MOCK_ID = MOCK_DATA.id;
mockFetchDetailsAPI(MOCK_DATA);
mockTaskRunsFiltersAPI(MOCK_TASK_RUNS);
mockFlowRunsFiltersAPI(MOCK_FLOW_RUNS);
mockGetFlowAPI(MOCK_FLOW);

const { result } = renderHook(
() =>
useSuspenseQuery(buildConcurrenyLimitDetailsActiveRunsQuery(MOCK_ID)),
{ wrapper: createWrapper() },
);

// ------------ Assert
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toMatchObject({
taskRunConcurrencyLimit: MOCK_DATA,
activeTaskRuns: [
{
flow: MOCK_FLOW,
flowRun: MOCK_FLOW_RUNS[0],
taskRun: MOCK_TASK_RUNS[0],
},
],
});
});
});
Loading

0 comments on commit 66ac5e7

Please sign in to comment.