-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[UI v2] feat: Adds Deployment Action Menu component (#17015)
- Loading branch information
1 parent
57635cf
commit a84945e
Showing
8 changed files
with
284 additions
and
21 deletions.
There are no files selected for viewing
19 changes: 19 additions & 0 deletions
19
ui-v2/src/components/deployments/deployment-action-menu/deployment-action-menu.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { routerDecorator, toastDecorator } from "@/storybook/utils"; | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
import { fn } from "@storybook/test"; | ||
|
||
import { DeploymentActionMenu } from "./deployment-action-menu"; | ||
|
||
const meta = { | ||
title: "Components/Deployments/DeploymentActionMenu", | ||
component: DeploymentActionMenu, | ||
decorators: [toastDecorator, routerDecorator], | ||
args: { | ||
id: "my-id", | ||
onDelete: fn(), | ||
}, | ||
} satisfies Meta<typeof DeploymentActionMenu>; | ||
|
||
export default meta; | ||
|
||
export const story: StoryObj = { name: "DeploymentActionMenu" }; |
106 changes: 106 additions & 0 deletions
106
ui-v2/src/components/deployments/deployment-action-menu/deployment-action-menu.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { Toaster } from "@/components/ui/toaster"; | ||
|
||
import { render, screen } from "@testing-library/react"; | ||
import userEvent from "@testing-library/user-event"; | ||
import { describe, expect, it, vi } from "vitest"; | ||
|
||
import { QueryClient } from "@tanstack/react-query"; | ||
import { | ||
RouterProvider, | ||
createMemoryHistory, | ||
createRootRoute, | ||
createRouter, | ||
} from "@tanstack/react-router"; | ||
import { | ||
DeploymentActionMenu, | ||
type DeploymentActionMenuProps, | ||
} from "./deployment-action-menu"; | ||
|
||
describe("DeploymentActionMenu", () => { | ||
// Wraps component in test with a Tanstack router provider | ||
const DeploymentActionMenuRouter = (props: DeploymentActionMenuProps) => { | ||
const rootRoute = createRootRoute({ | ||
component: () => <DeploymentActionMenu {...props} />, | ||
}); | ||
|
||
const router = createRouter({ | ||
routeTree: rootRoute, | ||
history: createMemoryHistory({ | ||
initialEntries: ["/"], | ||
}), | ||
context: { queryClient: new QueryClient() }, | ||
}); | ||
// @ts-expect-error - Type error from using a test router | ||
return <RouterProvider router={router} />; | ||
}; | ||
|
||
it("copies the id", async () => { | ||
// ------------ Setup | ||
const user = userEvent.setup(); | ||
render( | ||
<> | ||
<Toaster /> | ||
<DeploymentActionMenuRouter id="my-id" onDelete={vi.fn()} /> | ||
</>, | ||
); | ||
|
||
// ------------ Act | ||
await user.click( | ||
screen.getByRole("button", { name: /open menu/i, hidden: true }), | ||
); | ||
await user.click(screen.getByRole("menuitem", { name: "Copy ID" })); | ||
|
||
// ------------ Assert | ||
expect(screen.getByText("ID copied")).toBeVisible(); | ||
}); | ||
|
||
it("calls delete option ", async () => { | ||
// ------------ Setup | ||
const user = userEvent.setup(); | ||
const mockOnDeleteFn = vi.fn(); | ||
|
||
render(<DeploymentActionMenuRouter id="my-id" onDelete={mockOnDeleteFn} />); | ||
|
||
// ------------ Act | ||
|
||
await user.click( | ||
screen.getByRole("button", { name: /open menu/i, hidden: true }), | ||
); | ||
await user.click(screen.getByRole("menuitem", { name: /delete/i })); | ||
|
||
// ------------ Assert | ||
expect(mockOnDeleteFn).toHaveBeenCalledOnce(); | ||
}); | ||
|
||
it("edit option is visible", async () => { | ||
const user = userEvent.setup(); | ||
|
||
// ------------ Setup | ||
render(<DeploymentActionMenuRouter id="my-id" onDelete={vi.fn()} />); | ||
|
||
// ------------ Act | ||
|
||
await user.click( | ||
screen.getByRole("button", { name: /open menu/i, hidden: true }), | ||
); | ||
|
||
// ------------ Assert | ||
expect(screen.getByRole("menuitem", { name: /edit/i })).toBeVisible(); | ||
}); | ||
|
||
it("duplicate option is visible", async () => { | ||
const user = userEvent.setup(); | ||
|
||
// ------------ Setup | ||
render(<DeploymentActionMenuRouter id="my-id" onDelete={vi.fn()} />); | ||
|
||
// ------------ Act | ||
|
||
await user.click( | ||
screen.getByRole("button", { name: /open menu/i, hidden: true }), | ||
); | ||
|
||
// ------------ Assert | ||
expect(screen.getByRole("menuitem", { name: /duplicate/i })).toBeVisible(); | ||
}); | ||
}); |
52 changes: 52 additions & 0 deletions
52
ui-v2/src/components/deployments/deployment-action-menu/deployment-action-menu.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { Button } from "@/components/ui/button"; | ||
import { | ||
DropdownMenu, | ||
DropdownMenuContent, | ||
DropdownMenuItem, | ||
DropdownMenuLabel, | ||
DropdownMenuTrigger, | ||
} from "@/components/ui/dropdown-menu"; | ||
import { Icon } from "@/components/ui/icons"; | ||
import { useToast } from "@/hooks/use-toast"; | ||
import { Link } from "@tanstack/react-router"; | ||
|
||
export type DeploymentActionMenuProps = { | ||
id: string; | ||
onDelete: () => void; | ||
}; | ||
|
||
export const DeploymentActionMenu = ({ | ||
id, | ||
onDelete, | ||
}: DeploymentActionMenuProps) => { | ||
const { toast } = useToast(); | ||
|
||
const handleCopyId = (_id: string) => { | ||
void navigator.clipboard.writeText(_id); | ||
toast({ title: "ID copied" }); | ||
}; | ||
|
||
return ( | ||
<DropdownMenu> | ||
<DropdownMenuTrigger asChild> | ||
<Button variant="outline" className="h-8 w-8 p-0"> | ||
<span className="sr-only">Open menu</span> | ||
<Icon id="MoreVertical" className="h-4 w-4" /> | ||
</Button> | ||
</DropdownMenuTrigger> | ||
<DropdownMenuContent align="end"> | ||
<DropdownMenuLabel>Actions</DropdownMenuLabel> | ||
<DropdownMenuItem onClick={() => handleCopyId(id)}> | ||
Copy ID | ||
</DropdownMenuItem> | ||
<Link to="/deployments/deployment/$id/edit" params={{ id }}> | ||
<DropdownMenuItem>Edit</DropdownMenuItem> | ||
</Link> | ||
<DropdownMenuItem onClick={onDelete}>Delete</DropdownMenuItem> | ||
<Link to="/deployments/deployment/$id/duplicate" params={{ id }}> | ||
<DropdownMenuItem>Duplicate</DropdownMenuItem> | ||
</Link> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
); | ||
}; |
1 change: 1 addition & 0 deletions
1
ui-v2/src/components/deployments/deployment-action-menu/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { DeploymentActionMenu } from "./deployment-action-menu"; |
53 changes: 33 additions & 20 deletions
53
ui-v2/src/components/deployments/deployment-details-page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,54 @@ | ||
import { buildDeploymentDetailsQuery } from "@/api/deployments"; | ||
import { DeleteConfirmationDialog } from "@/components/ui/delete-confirmation-dialog"; | ||
import { useSuspenseQuery } from "@tanstack/react-query"; | ||
|
||
import { DeploymentActionMenu } from "./deployment-action-menu"; | ||
import { DeploymentDetailsHeader } from "./deployment-details-header"; | ||
import { DeploymentDetailsTabs } from "./deployment-details-tabs"; | ||
import { DeploymentFlowLink } from "./deployment-flow-link"; | ||
import { DeploymentMetadata } from "./deployment-metadata"; | ||
import { useDeleteDeploymentConfirmationDialog } from "./use-delete-deployment-confirmation-dialog"; | ||
|
||
type DeploymentDetailsPageProps = { | ||
id: string; | ||
}; | ||
|
||
export const DeploymentDetailsPage = ({ id }: DeploymentDetailsPageProps) => { | ||
const { data } = useSuspenseQuery(buildDeploymentDetailsQuery(id)); | ||
const [deleteConfirmationDialogState, confirmDelete] = | ||
useDeleteDeploymentConfirmationDialog(); | ||
|
||
return ( | ||
<div className="flex flex-col gap-4"> | ||
<div className="flex align-middle justify-between"> | ||
<div className="flex flex-col gap-2"> | ||
<DeploymentDetailsHeader deployment={data} /> | ||
<DeploymentFlowLink flowId={data.flow_id} /> | ||
<> | ||
<div className="flex flex-col gap-4"> | ||
<div className="flex align-middle justify-between"> | ||
<div className="flex flex-col gap-2"> | ||
<DeploymentDetailsHeader deployment={data} /> | ||
<DeploymentFlowLink flowId={data.flow_id} /> | ||
</div> | ||
<div className="flex align-middle gap-2"> | ||
<div className="border border-red-400">{"<RunButton />"}</div> | ||
<DeploymentActionMenu | ||
id={id} | ||
onDelete={() => confirmDelete(data, { shouldNavigate: true })} | ||
/> | ||
</div> | ||
</div> | ||
<div className="flex align-middle gap-2"> | ||
<div className="border border-red-400">{"<RunButton />"}</div> | ||
<div className="border border-red-400">{"<Actions />"}</div> | ||
<div className="grid gap-4" style={{ gridTemplateColumns: "3fr 1fr" }}> | ||
<div className="flex flex-col gap-5"> | ||
<DeploymentDetailsTabs /> | ||
</div> | ||
<div className="flex flex-col gap-3"> | ||
<div className="border border-red-400"> | ||
{"<SchedulesSection />"} | ||
</div> | ||
<div className="border border-red-400">{"<TriggerSection />"}</div> | ||
<hr /> | ||
<DeploymentMetadata deployment={data} /> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="grid gap-4" style={{ gridTemplateColumns: "3fr 1fr" }}> | ||
<div className="flex flex-col gap-5"> | ||
<DeploymentDetailsTabs /> | ||
</div> | ||
<div className="flex flex-col gap-3"> | ||
<div className="border border-red-400">{"<SchedulesSection />"}</div> | ||
<div className="border border-red-400">{"<TriggerSection />"}</div> | ||
<hr /> | ||
<DeploymentMetadata deployment={data} /> | ||
</div> | ||
</div> | ||
</div> | ||
<DeleteConfirmationDialog {...deleteConfirmationDialogState} /> | ||
</> | ||
); | ||
}; |
Oops, something went wrong.