Skip to content

Commit

Permalink
[UI v2] feat: Adds pause and resume deployment options for automation…
Browse files Browse the repository at this point in the history
… wizard (#16789)
  • Loading branch information
devinvillarosa authored Jan 21, 2025
1 parent 0f0c0a9 commit 2f92d0f
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 25 deletions.
2 changes: 2 additions & 0 deletions ui-v2/src/api/deployments/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { components } from "@/api/prefect";
import { getQueryService } from "@/api/service";
import {
keepPreviousData,
queryOptions,
useMutation,
useQueryClient,
Expand Down Expand Up @@ -80,6 +81,7 @@ export const buildPaginateDeploymentsQuery = (
});
return res.data;
},
placeholderData: keepPreviousData,
});

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export { useListDeploymentsWithFlows } from "./use-list-deployments-with-flows";
export {
useListDeploymentsWithFlows,
type DeploymentWithFlow,
} from "./use-list-deployments-with-flows";
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import {
Deployment,
DeploymentsPaginationFilter,
buildPaginateDeploymentsQuery,
} from "@/api/deployments";
import { Flow, buildListFlowsQuery } from "@/api/flows";
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";

export type DeploymentWithFlow = Deployment & {
flow: Flow | undefined;
};

/**
* A hook that is used to get a pagination list of Deployments, with flow data joined
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useWatch } from "react-hook-form";
import { ActionTypeSelect } from "./action-type-select";
import { AutomationsSelectStateFields } from "./automations-select-state-fields";
import { ChangeFlowRunStateFields } from "./change-flow-run-fields";
import { DeploymentsSelectStateFields } from "./deployments-select-state-fields";

type ActionStepProps = {
index: number;
Expand Down Expand Up @@ -49,9 +50,11 @@ const ActionTypeAdditionalFields = ({
case "change-flow-run-state":
return <ChangeFlowRunStateFields index={index} />;
case "run-deployment":
return <DeploymentsSelectStateFields action="Run" index={index} />;
case "pause-deployment":
return <DeploymentsSelectStateFields action="Pause" index={index} />;
case "resume-deployment":
return <div>TODO Deployment</div>;
return <DeploymentsSelectStateFields action="Resume" index={index} />;
case "pause-work-queue":
case "resume-work-queue":
return <div>TODO Work Queue</div>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import type { Meta, StoryObj } from "@storybook/react";

import { AutomationWizardSchema } from "@/components/automations/automations-wizard/automation-schema";
import { Form } from "@/components/ui/form";
import { createFakeAutomation } from "@/mocks";
import {
createFakeAutomation,
createFakeDeployment,
createFakeFlow,
} from "@/mocks";
import { reactQueryDecorator } from "@/storybook/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import { fn } from "@storybook/test";
Expand All @@ -12,6 +16,16 @@ import { useForm } from "react-hook-form";
import { ActionsStep } from "./actions-step";

const MOCK_AUTOMATIONS_DATA = Array.from({ length: 5 }, createFakeAutomation);
const MOCK_DEPLOYMENTS_WITH_FLOW_A = Array.from({ length: 2 }, () =>
createFakeDeployment({ flow_id: "a" }),
);
const MOCK_DEPLOYMENTS_WITH_FLOW_B = Array.from({ length: 3 }, () =>
createFakeDeployment({ flow_id: "b" }),
);
const MOCK_FLOWS_DATA = [
createFakeFlow({ id: "a" }),
createFakeFlow({ id: "b" }),
];

const meta = {
title: "Components/Automations/Wizard/ActionsStep",
Expand All @@ -24,6 +38,21 @@ const meta = {
http.post(buildApiUrl("/automations/filter"), () => {
return HttpResponse.json(MOCK_AUTOMATIONS_DATA);
}),
http.post(buildApiUrl("/deployments/paginate"), () => {
return HttpResponse.json({
count: 5,
limit: 100,
page: 1,
pages: 1,
results: [
...MOCK_DEPLOYMENTS_WITH_FLOW_A,
...MOCK_DEPLOYMENTS_WITH_FLOW_B,
],
});
}),
http.post(buildApiUrl("/flows/filter"), () => {
return HttpResponse.json(MOCK_FLOWS_DATA);
}),
],
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { createFakeAutomation } from "@/mocks";
import { ActionsStep } from "./actions-step";

import { Automation } from "@/api/automations";
import type { Automation } from "@/api/automations";
import type { Deployment } from "@/api/deployments";
import type { Flow } from "@/api/flows";
import {
AutomationWizardSchema,
type AutomationWizardSchema as TAutomationWizardSchema,
} from "@/components/automations/automations-wizard/automation-schema";
import { Form } from "@/components/ui/form";
import {
createFakeAutomation,
createFakeDeployment,
createFakeFlow,
} from "@/mocks";
import { zodResolver } from "@hookform/resolvers/zod";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
Expand All @@ -15,6 +19,7 @@ import { mockPointerEvents } from "@tests/utils/browser";
import { http, HttpResponse } from "msw";
import { useForm } from "react-hook-form";
import { beforeAll, describe, expect, it } from "vitest";
import { ActionsStep } from "./actions-step";

const ActionStepFormContainer = () => {
const form = useForm<TAutomationWizardSchema>({
Expand Down Expand Up @@ -240,4 +245,103 @@ describe("ActionsStep", () => {
expect(screen.getAllByText("my automation 1")).toBeTruthy();
});
});

describe("action type -- deployments", () => {
const mockPaginateDeploymentsAPI = (deployments: Array<Deployment>) => {
server.use(
http.post(buildApiUrl("/deployments/paginate"), () => {
return HttpResponse.json({
results: deployments,
count: deployments.length,
page: 1,
pages: 1,
limit: 10,
});
}),
);
};
const mockListFlowsAPI = (flows: Array<Flow>) => {
server.use(
http.post(buildApiUrl("/flows/filter"), () => {
return HttpResponse.json(flows);
}),
);
};

it("able to configure pause a deployment action type", async () => {
const DEPLOYMENTS_DATA = [
createFakeDeployment({ name: "my deployment 0", flow_id: "a" }),
createFakeDeployment({ name: "my deployment 1", flow_id: "a" }),
];
const FLOWS_DATA = [createFakeFlow({ id: "a" })];

mockPaginateDeploymentsAPI(DEPLOYMENTS_DATA);
mockListFlowsAPI(FLOWS_DATA);

const user = userEvent.setup();

// ------------ Setup
render(<ActionStepFormContainer />, {
wrapper: createWrapper(),
});

// ------------ Act
await user.click(
screen.getByRole("combobox", { name: /select action/i }),
);
await user.click(
screen.getByRole("option", { name: "Pause a deployment" }),
);

expect(screen.getAllByText("Infer Deployment")).toBeTruthy();
await user.click(
screen.getByRole("combobox", { name: /select deployment to pause/i }),
);

await user.click(screen.getByRole("option", { name: "my deployment 0" }));
// ------------ Assert
expect(screen.getAllByText("Pause a deployment")).toBeTruthy();
expect(screen.getAllByText("my deployment 0")).toBeTruthy();
});

it("able to configure resume a deployment action type", async () => {
const DEPLOYMENTS_DATA = [
createFakeDeployment({ name: "my deployment 0", flow_id: "a" }),
createFakeDeployment({ name: "my deployment 1", flow_id: "a" }),
];
const FLOWS_DATA = [createFakeFlow({ id: "a" })];

mockPaginateDeploymentsAPI(DEPLOYMENTS_DATA);
mockListFlowsAPI(FLOWS_DATA);
const user = userEvent.setup();

// ------------ Setup
render(<ActionStepFormContainer />, {
wrapper: createWrapper(),
});

// ------------ Act
await user.click(
screen.getByRole("combobox", { name: /select action/i }),
);
await user.click(
screen.getByRole("option", { name: "Resume a deployment" }),
);

expect(screen.getAllByText("Infer Deployment")).toBeTruthy();
await user.click(
screen.getByRole("combobox", {
name: /select deployment to resume/i,
}),
);

await user.click(screen.getByRole("option", { name: "my deployment 1" }));

// ------------ Assert
expect(screen.getAllByText("Pause a deployment")).toBeTruthy();
expect(screen.getAllByText("my deployment 1")).toBeTruthy();
});

it.todo("able to configure run a deployment action type", async () => {});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,16 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Skeleton } from "@/components/ui/skeleton";
import { useQuery } from "@tanstack/react-query";
import { useDeferredValue, useMemo, useState } from "react";
import { useFormContext } from "react-hook-form";
import { LoadingSelectState } from "./loading-select-state";

const INFER_AUTOMATION = {
const INFER_OPTION = {
value: UNASSIGNED,
name: "Infer Automation" as const,
name: "Infer Automation",
} as const;

const NUM_SKELETONS = 4;

type AutomationsSelectStateFieldsProps = {
action: "Pause" | "Resume";
index: number;
Expand All @@ -41,8 +39,8 @@ const getButtonLabel = (
data: Array<Automation> | undefined,
fieldValue: string | null,
) => {
if (fieldValue === INFER_AUTOMATION.value) {
return INFER_AUTOMATION.name;
if (fieldValue === INFER_OPTION.value) {
return INFER_OPTION.name;
}
const automation = data?.find((automation) => automation.id === fieldValue);
if (automation) {
Expand Down Expand Up @@ -70,7 +68,7 @@ export const AutomationsSelectStateFields = ({
);
}, [data, deferredSearch]);

const isInferredOptionFiltered = INFER_AUTOMATION.name
const isInferredOptionFiltered = INFER_OPTION.name
.toLowerCase()
.includes(deferredSearch.toLowerCase());

Expand Down Expand Up @@ -101,14 +99,14 @@ export const AutomationsSelectStateFields = ({
<ComboboxCommandGroup>
{isInferredOptionFiltered && (
<ComboboxCommandItem
selected={field.value === INFER_AUTOMATION.value}
selected={field.value === INFER_OPTION.value}
onSelect={(value) => {
field.onChange(value);
setSearch("");
}}
value={INFER_AUTOMATION.value}
value={INFER_OPTION.value}
>
{INFER_AUTOMATION.name}
{INFER_OPTION.name}
</ComboboxCommandItem>
)}
{isSuccess ? (
Expand All @@ -126,7 +124,7 @@ export const AutomationsSelectStateFields = ({
</ComboboxCommandItem>
))
) : (
<AutomationLoadingState length={NUM_SKELETONS} />
<LoadingSelectState />
)}
</ComboboxCommandGroup>
</ComboboxCommandList>
Expand All @@ -139,8 +137,3 @@ export const AutomationsSelectStateFields = ({
/>
);
};

const AutomationLoadingState = ({ length }: { length: number }) =>
Array.from({ length }, (_, index) => (
<Skeleton key={index} className="mt-2 p-4 h-2 w-full" />
));
Loading

0 comments on commit 2f92d0f

Please sign in to comment.