-
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 flow run button to deployment details page (#17051)
- Loading branch information
1 parent
26f354b
commit b1908af
Showing
7 changed files
with
271 additions
and
2 deletions.
There are no files selected for viewing
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
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 { RunFlowButton } from "./run-flow-button"; |
31 changes: 31 additions & 0 deletions
31
ui-v2/src/components/deployments/run-flow-button/run-flow-button.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,31 @@ | ||
import { | ||
reactQueryDecorator, | ||
routerDecorator, | ||
toastDecorator, | ||
} from "@/storybook/utils"; | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
|
||
import { createFakeAutomation, createFakeDeployment } from "@/mocks"; | ||
import { buildApiUrl } from "@tests/utils/handlers"; | ||
import { http, HttpResponse } from "msw"; | ||
import { RunFlowButton } from "./run-flow-button"; | ||
|
||
const meta = { | ||
title: "Components/Deployments/RunFlowButton", | ||
component: RunFlowButton, | ||
decorators: [toastDecorator, routerDecorator, reactQueryDecorator], | ||
args: { deployment: createFakeDeployment() }, | ||
parameters: { | ||
msw: { | ||
handlers: [ | ||
http.post(buildApiUrl("/deployments/:id/create_flow_run"), () => { | ||
return HttpResponse.json(createFakeAutomation()); | ||
}), | ||
], | ||
}, | ||
}, | ||
} satisfies Meta<typeof RunFlowButton>; | ||
|
||
export default meta; | ||
|
||
export const story: StoryObj = { name: "RunFlowButton" }; |
95 changes: 95 additions & 0 deletions
95
ui-v2/src/components/deployments/run-flow-button/run-flow-button.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,95 @@ | ||
import { Toaster } from "@/components/ui/toaster"; | ||
import { createFakeDeployment, createFakeFlowRun } from "@/mocks"; | ||
import { QueryClient } from "@tanstack/react-query"; | ||
import { | ||
RouterProvider, | ||
createMemoryHistory, | ||
createRootRoute, | ||
createRouter, | ||
} from "@tanstack/react-router"; | ||
import { render, screen, within } from "@testing-library/react"; | ||
import userEvent from "@testing-library/user-event"; | ||
import { buildApiUrl, createWrapper, server } from "@tests/utils"; | ||
import { http, HttpResponse } from "msw"; | ||
import { describe, expect, it } from "vitest"; | ||
import { RunFlowButton, type RunFlowButtonProps } from "./run-flow-button"; | ||
|
||
describe("RunFlowButton", () => { | ||
// Wraps component in test with a Tanstack router provider | ||
const RunFlowButtonRouter = (props: RunFlowButtonProps) => { | ||
const rootRoute = createRootRoute({ | ||
component: () => ( | ||
<> | ||
<Toaster /> | ||
<RunFlowButton {...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("calls quick run option", async () => { | ||
// ------------ Setup | ||
const MOCK_DEPLOYMENT = createFakeDeployment(); | ||
const MOCK_FLOW_RUN_RESPONSE = createFakeFlowRun(); | ||
server.use( | ||
http.post(buildApiUrl("/deployments/:id/create_flow_run"), () => { | ||
return HttpResponse.json(MOCK_FLOW_RUN_RESPONSE); | ||
}), | ||
); | ||
const user = userEvent.setup(); | ||
render(<RunFlowButtonRouter deployment={MOCK_DEPLOYMENT} />, { | ||
wrapper: createWrapper(), | ||
}); | ||
|
||
// ------------ Act | ||
await user.click(screen.getByRole("button", { name: "Run", hidden: true })); | ||
await user.click(screen.getByRole("menuitem", { name: "Quick run" })); | ||
|
||
// ------------ Assert | ||
const list = screen.getByRole("list"); | ||
expect(within(list).getByRole("status")).toBeVisible(); | ||
expect( | ||
screen.getByRole("button", { | ||
name: /view run/i, | ||
}), | ||
).toBeVisible(); | ||
}); | ||
|
||
it("custom run option is a link with deployment parameters", async () => { | ||
// ------------ Setup | ||
const MOCK_DEPLOYMENT = createFakeDeployment({ | ||
id: "0", | ||
parameters: { | ||
// @ts-expect-error Need to update schema type | ||
paramKey: "paramValue", | ||
}, | ||
}); | ||
const user = userEvent.setup(); | ||
render(<RunFlowButtonRouter deployment={MOCK_DEPLOYMENT} />, { | ||
wrapper: createWrapper(), | ||
}); | ||
|
||
// ------------ Act | ||
|
||
await user.click(screen.getByRole("button", { name: "Run" })); | ||
|
||
// ------------ Assert | ||
expect(screen.getByRole("menuitem", { name: "Custom run" })).toBeVisible(); | ||
|
||
// Validates URL has search parameters with deployment parameters | ||
expect(screen.getByRole("link", { name: "Custom run" })).toHaveAttribute( | ||
"href", | ||
"/deployments/deployment/0/run?parameters=%7B%22paramKey%22%3A%22paramValue%22%7D", | ||
); | ||
}); | ||
}); |
89 changes: 89 additions & 0 deletions
89
ui-v2/src/components/deployments/run-flow-button/run-flow-button.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,89 @@ | ||
import { Deployment } from "@/api/deployments"; | ||
import { useDeploymentCreateFlowRun } from "@/api/flow-runs"; | ||
import { Button } from "@/components/ui/button"; | ||
import { | ||
DropdownMenu, | ||
DropdownMenuContent, | ||
DropdownMenuGroup, | ||
DropdownMenuItem, | ||
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); | ||
}, | ||
}, | ||
); | ||
}; | ||
|
||
return ( | ||
<DropdownMenu> | ||
<DropdownMenuTrigger asChild> | ||
<Button loading={isPending}> | ||
Run <Icon className="ml-1 h-4 w-4" id="Play" /> | ||
</Button> | ||
</DropdownMenuTrigger> | ||
<DropdownMenuContent> | ||
<DropdownMenuGroup> | ||
<DropdownMenuItem onClick={() => handleClickQuickRun(deployment.id)}> | ||
Quick run | ||
</DropdownMenuItem> | ||
<Link | ||
to="/deployments/deployment/$id/run" | ||
params={{ id: deployment.id }} | ||
search={{ parameters: deployment.parameters }} | ||
> | ||
<DropdownMenuItem>Custom run</DropdownMenuItem> | ||
</Link> | ||
</DropdownMenuGroup> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
); | ||
}; |
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
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,23 @@ | ||
import { createFileRoute } from "@tanstack/react-router"; | ||
import { zodValidator } from "@tanstack/zod-adapter"; | ||
import { z } from "zod"; | ||
|
||
// nb: Revisit search params to determine if we're decoding the parameters correctly. Or if there are stricter typings | ||
// We'll know stricter types as we write more of the webapp | ||
|
||
/** | ||
* Schema for validating URL search parameters for the create automation page. | ||
* @property actions used designate how to pre-populate the fields | ||
*/ | ||
const searchParams = z | ||
.object({ parameters: z.record(z.unknown()).optional() }) | ||
.optional(); | ||
|
||
export const Route = createFileRoute("/deployments/deployment_/$id/run")({ | ||
validateSearch: zodValidator(searchParams), | ||
component: RouteComponent, | ||
}); | ||
|
||
function RouteComponent() { | ||
return "🚧🚧 Pardon our dust! 🚧🚧"; | ||
} |