Skip to content

Commit

Permalink
[UI v2] feat: Completes action items in deployments data table (#17282)
Browse files Browse the repository at this point in the history
  • Loading branch information
devinvillarosa authored Feb 26, 2025
1 parent 764b79f commit 6836cce
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 162 deletions.
42 changes: 20 additions & 22 deletions ui-v2/src/components/deployments/data-table/cells.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DropdownMenuItem } from "@/components/ui/dropdown-menu";

import type { DeploymentWithFlow } from "@/api/deployments";
import { buildFilterFlowRunsQuery } from "@/api/flow-runs";
import { useQuickRun } from "@/components/deployments/use-quick-run";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
Expand All @@ -14,29 +15,21 @@ import { Icon } from "@/components/ui/icons";
import useDebounce from "@/hooks/use-debounce";
import { useToast } from "@/hooks/use-toast";
import { useQuery } from "@tanstack/react-query";
import { Link } from "@tanstack/react-router";
import type { CellContext } from "@tanstack/react-table";
import { subSeconds } from "date-fns";
import { secondsInWeek } from "date-fns/constants";
import { useCallback, useState } from "react";

type ActionsCellProps = CellContext<DeploymentWithFlow, unknown> & {
onQuickRun: (deployment: DeploymentWithFlow) => void;
onCustomRun: (deployment: DeploymentWithFlow) => void;
onEdit: (deployment: DeploymentWithFlow) => void;
onDelete: (deployment: DeploymentWithFlow) => void;
onDuplicate: (deployment: DeploymentWithFlow) => void;
};

export const ActionsCell = ({
row,
onQuickRun,
onCustomRun,
onEdit,
onDelete,
onDuplicate,
}: ActionsCellProps) => {
export const ActionsCell = ({ row, onDelete }: ActionsCellProps) => {
const id = row.original.id;
const { toast } = useToast();
const { onQuickRun, isPending } = useQuickRun();

if (!id) return null;

return (
Expand All @@ -50,30 +43,35 @@ export const ActionsCell = ({
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem onClick={() => onQuickRun(row.original)}>
<DropdownMenuItem disabled={isPending} onClick={() => onQuickRun(id)}>
Quick Run
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onCustomRun(row.original)}>
Custom Run
<DropdownMenuItem>
<Link to="/deployments/deployment/$id/run" params={{ id }}>
Custom Run
</Link>
</DropdownMenuItem>

<DropdownMenuItem
onClick={() => {
void navigator.clipboard.writeText(id);
toast({
title: "ID copied",
});
toast({ title: "ID copied" });
}}
>
Copy ID
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onEdit(row.original)}>
Edit
<DropdownMenuItem>
<Link to="/deployments/deployment/$id/edit" params={{ id }}>
Edit
</Link>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onDelete(row.original)}>
Delete
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onDuplicate(row.original)}>
Duplicate
<DropdownMenuItem>
<Link to="/deployments/deployment/$id/duplicate" params={{ id }}>
Duplicate
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,6 @@ export const Default: StoryObj<StoryArgs> = {
args: {
numberOfDeployments: 10,
onPaginationChange: fn(),
onQuickRun: fn(),
onCustomRun: fn(),
onEdit: fn(),
onDuplicate: fn(),
},
render: (
args: Omit<
Expand Down
92 changes: 39 additions & 53 deletions ui-v2/src/components/deployments/data-table/data-table.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { DeploymentWithFlow } from "@/api/deployments";
import { Toaster } from "@/components/ui/toaster";
import { createFakeFlowRunWithDeploymentAndFlow } from "@/mocks/create-fake-flow-run";
import {
createFakeFlowRun,
createFakeFlowRunWithDeploymentAndFlow,
} from "@/mocks/create-fake-flow-run";
import { QueryClient } from "@tanstack/react-query";
import {
RouterProvider,
Expand Down Expand Up @@ -60,16 +63,17 @@ describe("DeploymentsDataTable", () => {
onPaginationChange: vi.fn(),
onSortChange: vi.fn(),
onColumnFiltersChange: vi.fn(),
onQuickRun: vi.fn(),
onCustomRun: vi.fn(),
onEdit: vi.fn(),
onDuplicate: vi.fn(),
};

// Wraps component in test with a Tanstack router provider
const DeploymentsDataTableRouter = (props: DeploymentsDataTableProps) => {
const rootRoute = createRootRoute({
component: () => <DeploymentsDataTable {...props} />,
component: () => (
<>
<Toaster />
<DeploymentsDataTable {...props} />
</>
),
});

const router = createRouter({
Expand Down Expand Up @@ -152,59 +156,48 @@ describe("DeploymentsDataTable", () => {
});

it("calls onQuickRun when quick run action is clicked", async () => {
const onQuickRun = vi.fn();
render(
<DeploymentsDataTableRouter {...defaultProps} onQuickRun={onQuickRun} />,
{ wrapper: createWrapper() },
server.use(
http.post(buildApiUrl("/deployments/:id/create_flow_run"), () => {
return HttpResponse.json(createFakeFlowRun({ name: "new-flow-run" }));
}),
);

render(<DeploymentsDataTableRouter {...defaultProps} />, {
wrapper: createWrapper(),
});

await userEvent.click(screen.getByRole("button", { name: "Open menu" }));
const quickRunButton = screen.getByRole("menuitem", { name: "Quick Run" });
await userEvent.click(quickRunButton);

expect(onQuickRun).toHaveBeenCalledWith(mockDeployment);
expect(screen.getByText("new-flow-run")).toBeVisible();
expect(screen.getByRole("button", { name: "View run" })).toBeVisible();
});

it("calls onCustomRun when custom run action is clicked", async () => {
const onCustomRun = vi.fn();
render(
<DeploymentsDataTableRouter
{...defaultProps}
onCustomRun={onCustomRun}
/>,
{ wrapper: createWrapper() },
);

await userEvent.click(screen.getByRole("button", { name: "Open menu" }));
const customRunButton = screen.getByRole("menuitem", {
name: "Custom Run",
it("has an action menu item that links to create a custom run", async () => {
render(<DeploymentsDataTableRouter {...defaultProps} />, {
wrapper: createWrapper(),
});
await userEvent.click(customRunButton);

expect(onCustomRun).toHaveBeenCalledWith(mockDeployment);
await userEvent.click(screen.getByRole("button", { name: "Open menu" }));
expect(screen.getByRole("menuitem", { name: "Custom Run" })).toBeVisible();
expect(screen.getByRole("link", { name: /custom run/i })).toBeVisible();
});

it("calls onEdit when edit action is clicked", async () => {
const onEdit = vi.fn();
render(<DeploymentsDataTableRouter {...defaultProps} onEdit={onEdit} />, {
it("has an action menu item that links to edit deployment", async () => {
render(<DeploymentsDataTableRouter {...defaultProps} />, {
wrapper: createWrapper(),
});

await userEvent.click(screen.getByRole("button", { name: "Open menu" }));
const editButton = screen.getByRole("menuitem", { name: "Edit" });
await userEvent.click(editButton);

expect(onEdit).toHaveBeenCalledWith(mockDeployment);
expect(screen.getByRole("menuitem", { name: "Edit" })).toBeVisible();
expect(screen.getByRole("link", { name: /edit/i })).toBeVisible();
});

it("handles deletion", async () => {
render(
<>
<Toaster />
<DeploymentsDataTableRouter {...defaultProps} />
</>,
{ wrapper: createWrapper() },
);
render(<DeploymentsDataTableRouter {...defaultProps} />, {
wrapper: createWrapper(),
});

await userEvent.click(screen.getByRole("button", { name: "Open menu" }));
const deleteButton = screen.getByRole("menuitem", { name: "Delete" });
Expand All @@ -218,21 +211,14 @@ describe("DeploymentsDataTable", () => {
expect(screen.getByText("Deployment deleted")).toBeInTheDocument();
});

it("calls onDuplicate when duplicate action is clicked", async () => {
const onDuplicate = vi.fn();
render(
<DeploymentsDataTableRouter
{...defaultProps}
onDuplicate={onDuplicate}
/>,
{ wrapper: createWrapper() },
);
it("has an action menu item that links to duplicate deployment", async () => {
render(<DeploymentsDataTableRouter {...defaultProps} />, {
wrapper: createWrapper(),
});

await userEvent.click(screen.getByRole("button", { name: "Open menu" }));
const duplicateButton = screen.getByRole("menuitem", { name: "Duplicate" });
await userEvent.click(duplicateButton);

expect(onDuplicate).toHaveBeenCalledWith(mockDeployment);
expect(screen.getByRole("menuitem", { name: "Duplicate" })).toBeVisible();
expect(screen.getByRole("link", { name: /duplicate/i })).toBeVisible();
});

it("calls onPaginationChange when pagination buttons are clicked", async () => {
Expand Down
31 changes: 1 addition & 30 deletions ui-v2/src/components/deployments/data-table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,14 @@ export type DeploymentsDataTableProps = {
onPaginationChange: (pagination: PaginationState) => void;
onSortChange: (sort: components["schemas"]["DeploymentSort"]) => void;
onColumnFiltersChange: (columnFilters: ColumnFiltersState) => void;
onQuickRun: (deployment: DeploymentWithFlow) => void;
onCustomRun: (deployment: DeploymentWithFlow) => void;
onEdit: (deployment: DeploymentWithFlow) => void;
onDuplicate: (deployment: DeploymentWithFlow) => void;
};

const columnHelper = createColumnHelper<DeploymentWithFlow>();

const createColumns = ({
onQuickRun,
onCustomRun,
onEdit,
onDelete,
onDuplicate,
}: {
onQuickRun: (deployment: DeploymentWithFlow) => void;
onCustomRun: (deployment: DeploymentWithFlow) => void;
onEdit: (deployment: DeploymentWithFlow) => void;
onDelete: (deployment: DeploymentWithFlow) => void;
onDuplicate: (deployment: DeploymentWithFlow) => void;
}) => [
columnHelper.display({
id: "name",
Expand Down Expand Up @@ -132,16 +120,7 @@ const createColumns = ({
}),
columnHelper.display({
id: "actions",
cell: (props) => (
<ActionsCell
{...props}
onQuickRun={onQuickRun}
onCustomRun={onCustomRun}
onEdit={onEdit}
onDelete={onDelete}
onDuplicate={onDuplicate}
/>
),
cell: (props) => <ActionsCell {...props} onDelete={onDelete} />,
}),
];

Expand All @@ -155,10 +134,6 @@ export const DeploymentsDataTable = ({
onPaginationChange,
onSortChange,
onColumnFiltersChange,
onQuickRun,
onCustomRun,
onEdit,
onDuplicate,
}: DeploymentsDataTableProps) => {
const [deleteConfirmationDialogState, confirmDelete] =
useDeleteDeploymentConfirmationDialog();
Expand Down Expand Up @@ -209,16 +184,12 @@ export const DeploymentsDataTable = ({
const table = useReactTable({
data: deployments,
columns: createColumns({
onQuickRun,
onCustomRun,
onEdit,
onDelete: (deployment) => {
const name = deployment.flow?.name
? `${deployment.flow?.name}/${deployment.name}`
: deployment.name;
confirmDelete({ ...deployment, name });
},
onDuplicate,
}),
getCoreRowModel: getCoreRowModel(),
pageCount,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Deployment } from "@/api/deployments";
import { useDeploymentCreateFlowRun } from "@/api/flow-runs";
import { useQuickRun } from "@/components/deployments/use-quick-run";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
Expand All @@ -9,59 +9,14 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Icon } from "@/components/ui/icons";
import { useToast } from "@/hooks/use-toast";
import { Link } from "@tanstack/react-router";

const DEPLOYMENT_QUICK_RUN_PAYLOAD = {
state: {
type: "SCHEDULED",
message: "Run from the Prefect UI with defaults",
state_details: {
deferred: false,
untrackable_result: false,
pause_reschedule: false,
},
},
} as const;

export type RunFlowButtonProps = {
deployment: Deployment;
};

export const RunFlowButton = ({ deployment }: RunFlowButtonProps) => {
const { toast } = useToast();
const { createDeploymentFlowRun, isPending } = useDeploymentCreateFlowRun();

const handleClickQuickRun = (id: string) => {
createDeploymentFlowRun(
{
id,
...DEPLOYMENT_QUICK_RUN_PAYLOAD,
},
{
onSuccess: (res) => {
toast({
action: (
<Link to="/runs/flow-run/$id" params={{ id: res.id }}>
<Button size="sm">View run</Button>
</Link>
),
description: (
<p>
<span className="font-bold">{res.name}</span> scheduled to start{" "}
<span className="font-bold">now</span>
</p>
),
});
},
onError: (error) => {
const message =
error.message || "Unknown error while creating flow run.";
console.error(message);
},
},
);
};
const { onQuickRun, isPending } = useQuickRun();

return (
<DropdownMenu>
Expand All @@ -72,7 +27,7 @@ export const RunFlowButton = ({ deployment }: RunFlowButtonProps) => {
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuItem onClick={() => handleClickQuickRun(deployment.id)}>
<DropdownMenuItem onClick={() => onQuickRun(deployment.id)}>
Quick run
</DropdownMenuItem>
<Link
Expand Down
Loading

0 comments on commit 6836cce

Please sign in to comment.