Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useFormContext, useWatch } from "react-hook-form";
import type { AutomationWizardSchema } from "@/components/automations/automations-wizard/automation-schema";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { PostureSelect } from "./posture-select";

export const CustomTriggerFields = () => {
const form = useFormContext<AutomationWizardSchema>();
const posture = useWatch<AutomationWizardSchema>({ name: "trigger.posture" });
Comment on lines +14 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Missing trigger-field component tests 📘 Rule violation ⛯ Reliability

New React component files were added without corresponding co-located *.test.tsx files in the same
directory. This violates the requirement that each component file has an adjacent test file,
reducing test discoverability and enforcement.
Agent Prompt
## Issue description
New React component files were added under `ui-v2/` without co-located `*.test.tsx` files, violating the rule that each `component.tsx` must have an adjacent `component.test.tsx`.

## Issue Context
This PR adds multiple new trigger-field components in `ui-v2/src/components/automations/automations-wizard/trigger-step/`.

## Fix Focus Areas
- ui-v2/src/components/automations/automations-wizard/trigger-step/custom-trigger-fields.tsx[14-93]
- ui-v2/src/components/automations/automations-wizard/trigger-step/deployment-status-trigger-fields.tsx[25-112]
- ui-v2/src/components/automations/automations-wizard/trigger-step/work-pool-status-trigger-fields.tsx[26-109]
- ui-v2/src/components/automations/automations-wizard/trigger-step/work-queue-status-trigger-fields.tsx[26-109]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


return (
<div className="space-y-4">
<div className="flex items-end gap-4">
<PostureSelect />
</div>

<FormField
control={form.control}
name="trigger.expect"
render={({ field }) => {
const events = field.value ?? [];
const textValue = events.join("\n");
return (
<FormItem>
<FormLabel>Expected Events (one per line)</FormLabel>
<FormControl>
<Textarea
placeholder="prefect.flow-run.Completed"
value={textValue}
onChange={(e) => {
const lines = e.target.value.split("\n");
field.onChange(lines.length > 0 ? lines : undefined);
}}
Comment on lines +34 to +40
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. Custom events can't clear 🐞 Bug ✓ Correctness

CustomTriggerFields uses split("\n") and then checks lines.length > 0, which is always true, so
clearing the textarea stores [""] instead of clearing the field. This prevents users from removing
expected events and can silently create a non-matching empty-string event entry.
Agent Prompt
### Issue description
Clearing the custom expected-events textarea currently saves `[""]` and never clears the form field because `split("\n")` always returns at least one element.

### Issue Context
This can create an empty-string event in the trigger configuration and prevents users from clearing the field.

### Fix Focus Areas
- ui-v2/src/components/automations/automations-wizard/trigger-step/custom-trigger-fields.tsx[34-40]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

rows={4}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>

<div className="flex gap-4">
<FormField
control={form.control}
name="trigger.threshold"
render={({ field }) => (
<FormItem className="w-32">
<FormLabel>Threshold</FormLabel>
<FormControl>
<Input
type="number"
min={1}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

{posture === "Proactive" && (
<FormField
control={form.control}
name="trigger.within"
render={({ field }) => (
<FormItem className="w-32">
<FormLabel>Within (seconds)</FormLabel>
<FormControl>
<Input
type="number"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { useFormContext, useWatch } from "react-hook-form";
import type { AutomationWizardSchema } from "@/components/automations/automations-wizard/automation-schema";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { PostureSelect } from "./posture-select";

const DEPLOYMENT_STATUSES = [
{ value: "prefect.deployment.ready", label: "Ready" },
{ value: "prefect.deployment.not-ready", label: "Not Ready" },
];

export const DeploymentStatusTriggerFields = () => {
const form = useFormContext<AutomationWizardSchema>();
const posture = useWatch<AutomationWizardSchema>({ name: "trigger.posture" });

// Determine which field to use based on posture
const statusFieldName =
posture === "Proactive" ? "trigger.after" : "trigger.expect";

return (
<div className="space-y-4">
<div className="flex items-end gap-4">
<PostureSelect />
<FormField
control={form.control}
name={statusFieldName}
render={({ field }) => {
const selectedStatus = field.value?.[0];
return (
<FormItem className="flex-1">
<FormLabel>Status</FormLabel>
<FormControl>
<Select
value={selectedStatus ?? ""}
onValueChange={(value) => field.onChange([value])}
>
<SelectTrigger>
<SelectValue placeholder="Select status" />
</SelectTrigger>
<SelectContent>
{DEPLOYMENT_STATUSES.map((status) => (
<SelectItem key={status.value} value={status.value}>
{status.label}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>

<div className="flex gap-4">
<FormField
control={form.control}
name="trigger.threshold"
render={({ field }) => (
<FormItem className="w-32">
<FormLabel>Threshold</FormLabel>
<FormControl>
<Input
type="number"
min={1}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

{posture === "Proactive" && (
<FormField
control={form.control}
name="trigger.within"
render={({ field }) => (
<FormItem className="w-32">
<FormLabel>Within (seconds)</FormLabel>
<FormControl>
<Input
type="number"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
export { CustomTriggerFields } from "./custom-trigger-fields";
export { DeploymentStatusTriggerFields } from "./deployment-status-trigger-fields";
export { FlowRunStateTriggerFields } from "./flow-run-state-trigger-fields";
export { PostureSelect } from "./posture-select";
export { StateMultiSelect } from "./state-multi-select";
export { TriggerStep } from "./trigger-step";
export { getDefaultTriggerForTemplate } from "./trigger-step-utils";
export { WorkPoolStatusTriggerFields } from "./work-pool-status-trigger-fields";
export { WorkQueueStatusTriggerFields } from "./work-queue-status-trigger-fields";
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@ describe("TriggerStep", () => {
expect(screen.getByLabelText("Trigger Template")).toBeVisible();
});

it("shows placeholder text when deployment-status template is selected", async () => {
it("can select deployment-status template and shows trigger fields", async () => {
const user = userEvent.setup();

render(<TriggerStepFormContainer />);

await user.click(screen.getByLabelText("Trigger Template"));
await user.click(screen.getByRole("option", { name: "Deployment status" }));

expect(
screen.getByText("Deployment status trigger fields coming soon"),
).toBeVisible();
// Should show the DeploymentStatusTriggerFields component
expect(screen.getByLabelText("select posture")).toBeVisible();
expect(screen.getByLabelText("Threshold")).toBeVisible();
});

it("can select flow-run-state template and shows trigger fields", async () => {
Expand All @@ -68,40 +68,45 @@ describe("TriggerStep", () => {
expect(screen.getByLabelText("Threshold")).toBeVisible();
});

it("can select work-pool-status template", async () => {
it("can select work-pool-status template and shows trigger fields", async () => {
const user = userEvent.setup();

render(<TriggerStepFormContainer />);

await user.click(screen.getByLabelText("Trigger Template"));
await user.click(screen.getByRole("option", { name: "Work pool status" }));

expect(
screen.getByText("Work pool status trigger fields coming soon"),
).toBeVisible();
// Should show the WorkPoolStatusTriggerFields component
expect(screen.getByLabelText("select posture")).toBeVisible();
expect(screen.getByLabelText("Threshold")).toBeVisible();
});

it("can select work-queue-status template", async () => {
it("can select work-queue-status template and shows trigger fields", async () => {
const user = userEvent.setup();

render(<TriggerStepFormContainer />);

await user.click(screen.getByLabelText("Trigger Template"));
await user.click(screen.getByRole("option", { name: "Work queue status" }));

expect(
screen.getByText("Work queue status trigger fields coming soon"),
).toBeVisible();
// Should show the WorkQueueStatusTriggerFields component
expect(screen.getByLabelText("select posture")).toBeVisible();
expect(screen.getByLabelText("Threshold")).toBeVisible();
});

it("can select custom template", async () => {
it("can select custom template and shows trigger fields", async () => {
const user = userEvent.setup();

render(<TriggerStepFormContainer />);

await user.click(screen.getByLabelText("Trigger Template"));
await user.click(screen.getByRole("option", { name: "Custom" }));

expect(screen.getByText("Custom trigger fields coming soon")).toBeVisible();
// Should show the CustomTriggerFields component
expect(screen.getByLabelText("select posture")).toBeVisible();
expect(screen.getByLabelText("Threshold")).toBeVisible();
expect(
screen.getByLabelText("Expected Events (one per line)"),
).toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ import {
AutomationsTriggerTemplateSelect,
type TriggerTemplate,
} from "@/components/automations/automations-wizard/automations-trigger-template-select";
import { CustomTriggerFields } from "./custom-trigger-fields";
import { DeploymentStatusTriggerFields } from "./deployment-status-trigger-fields";
import { FlowRunStateTriggerFields } from "./flow-run-state-trigger-fields";
import { getDefaultTriggerForTemplate } from "./trigger-step-utils";
import { WorkPoolStatusTriggerFields } from "./work-pool-status-trigger-fields";
import { WorkQueueStatusTriggerFields } from "./work-queue-status-trigger-fields";

export const TriggerStep = () => {
const form = useFormContext<AutomationWizardSchema>();
Expand Down Expand Up @@ -37,29 +41,13 @@ const TriggerTemplateFields = ({ template }: TriggerTemplateFieldsProps) => {
case "flow-run-state":
return <FlowRunStateTriggerFields />;
case "deployment-status":
return (
<div className="text-muted-foreground">
Deployment status trigger fields coming soon
</div>
);
return <DeploymentStatusTriggerFields />;
case "work-pool-status":
return (
<div className="text-muted-foreground">
Work pool status trigger fields coming soon
</div>
);
return <WorkPoolStatusTriggerFields />;
case "work-queue-status":
return (
<div className="text-muted-foreground">
Work queue status trigger fields coming soon
</div>
);
return <WorkQueueStatusTriggerFields />;
case "custom":
return (
<div className="text-muted-foreground">
Custom trigger fields coming soon
</div>
);
return <CustomTriggerFields />;
default:
return null;
}
Expand Down
Loading
Loading