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" });

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 +37 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.

⚠️ Potential issue | 🟡 Minor

Empty textarea incorrectly stores [""] instead of clearing the field.

"".split("\n") returns [""] (length 1), so the condition lines.length > 0 is always true. An empty textarea will store [""] rather than undefined or [].

Proposed fix
 onChange={(e) => {
-	const lines = e.target.value.split("\n");
-	field.onChange(lines.length > 0 ? lines : undefined);
+	const lines = e.target.value.split("\n").filter(Boolean);
+	field.onChange(lines.length > 0 ? lines : undefined);
 }}
🤖 Prompt for AI Agents
In
`@ui-v2/src/components/automations/automations-wizard/trigger-step/custom-trigger-fields.tsx`
around lines 37 - 40, The onChange handler for the textarea currently does const
lines = e.target.value.split("\n") and sets field.onChange(lines.length > 0 ?
lines : undefined), but "" splits to [""] so an empty textarea stores [""];
change the logic in the onChange callback (the handler that calls
field.onChange) to detect empty input or strip empty lines before setting the
field: e.g. check if e.target.value === "" (set undefined) or use
lines.filter(Boolean) and then call field.onChange(filtered.length ? filtered :
undefined) so blank textarea clears the field instead of storing [""].

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";
Comment on lines +29 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cd ui-v2 && find . -type f -name "*.tsx" -o -name "*.ts" | head -20

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 972


🏁 Script executed:

rg -n "trigger\.(expect|after)" --type=ts --type=tsx -C 3

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 113


🏁 Script executed:

cat -n src/components/automations/automations-wizard/trigger-step/deployment-status-trigger-fields.tsx

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 213


🏁 Script executed:

# Search for form submission and schema validation logic
rg -n "onSubmit\|schema\|validation" src/components/automations/automations-wizard/trigger-step/ --type=ts --type=tsx -C 2

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 113


🏁 Script executed:

# Look for where the form data is processed/submitted
rg -n "trigger\." src/components/automations/automations-wizard/ --type=ts --type=tsx | grep -i "submit\|reset\|clear\|handle" | head -20

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 113


🏁 Script executed:

find . -name "deployment-status-trigger-fields.tsx" -o -name "deployment-status-trigger-fields.ts"

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 189


🏁 Script executed:

rg -n "trigger\.(expect|after)" -C 3

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 16318


🏁 Script executed:

find . -path "*/automations-wizard/trigger-step/*" -type f | head -20

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 1411


🏁 Script executed:

find . -name "*schema*" -o -name "*validation*" | grep -i automation | head -20

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 424


🏁 Script executed:

cat -n ui-v2/src/components/automations/automations-wizard/trigger-step/deployment-status-trigger-fields.tsx

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 3768


🏁 Script executed:

cat -n ui-v2/src/components/automations/automations-wizard/automation-schema.ts

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 6118


🏁 Script executed:

# Look at how form submission is handled
rg -n "onSubmit\|handleSubmit" ui-v2/src/components/automations/automations-wizard/trigger-step/ -C 3

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 68


🏁 Script executed:

# Check flow-run-state-trigger-fields for comparison - similar dynamic field pattern
cat -n ui-v2/src/components/automations/automations-wizard/trigger-step/flow-run-state-trigger-fields.tsx

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 3796


🏁 Script executed:

# Check the trigger step parent component to see how form is submitted
cat -n ui-v2/src/components/automations/automations-wizard/trigger-step/trigger-step.tsx

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 2335


🏁 Script executed:

# Search for any form submission or validation logic that might handle cleanup
rg -n "FormProvider\|useForm\|handleSubmit" ui-v2/src/components/automations/automations-wizard/ -A 5 -B 2

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 68


🏁 Script executed:

# Check if there are any tests for this component to understand expected behavior
cat -n ui-v2/src/components/automations/automations-wizard/trigger-step/deployment-status-trigger-fields.test.tsx 2>/dev/null || echo "No test file found"

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 104


🏁 Script executed:

# Look at how the form data is sent to the backend
rg -n "JSON\|stringify\|submit\|mutation" ui-v2/src/components/automations/automations-wizard/ --type=ts -C 2

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 68


🏁 Script executed:

# Look for the main automation wizard form or submission logic
rg -n "useForm\|FormProvider" ui-v2/src/components/automations/automations-wizard/ --type=ts -C 5

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 68


🏁 Script executed:

# Check for any form reset or cleanup logic when template changes
cat -n ui-v2/src/components/automations/automations-wizard/trigger-step/trigger-step-utils.ts

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 1582


🏁 Script executed:

# Look at test files to understand expected behavior
cat -n ui-v2/src/components/automations/automations-wizard/trigger-step/trigger-step.test.tsx 2>/dev/null | head -100 || echo "Checking for test files..."

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 4041


🏁 Script executed:

# Find the main automation wizard component
find ui-v2 -name "*automation*wizard*.tsx" -type f | grep -v node_modules

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 241


🏁 Script executed:

cat -n ui-v2/src/components/automations/automations-wizard/automation-wizard.tsx

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 3754


🏁 Script executed:

# Check if react-hook-form has any built-in handling for dynamic field names
rg -n "field.value\|trigger.after\|trigger.expect" ui-v2/src/components/automations/automations-wizard/ -C 2

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 68


🏁 Script executed:

# Look for any transform or cleanup logic in form submission
rg -n "formData\|transform\|filter\|omit" ui-v2/src/components/automations/automations-wizard/ --type=ts -C 3

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 68


🏁 Script executed:

# Check if there's any handling in the API call for automations
rg -n "trigger" ui-v2/src/api/automations/ --type=ts -C 3

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 1548


🏁 Script executed:

# Search for form submission transformation or API handling
rg -n "axios\|fetch\|mutation\|useMutation" ui-v2/src/components/automations/automations-wizard/ --type=ts -C 2

Repository: qodo-benchmark/prefect-combined-coderabbit

Length of output: 68


Dynamic field switching may leave stale data when posture changes.

When posture toggles between Reactive and Proactive, the component switches between trigger.expect and trigger.after for the status selection. The previously used field retains its value in form state, potentially resulting in both fields being populated during form submission.

Consider clearing the alternate field when posture changes, or implement cleanup logic in the form submission to ensure only the relevant field is sent based on the current posture.

🤖 Prompt for AI Agents
In
`@ui-v2/src/components/automations/automations-wizard/trigger-step/deployment-status-trigger-fields.tsx`
around lines 29 - 31, The component currently switches statusFieldName based on
posture (posture === "Proactive" ? "trigger.after" : "trigger.expect") but
leaves the alternate field populated; add logic to clear the unused field when
posture changes—locate the place where statusFieldName is computed in
deployment-status-trigger-fields (or the component render) and add an effect or
change handler that calls the form API (e.g., resetField/setValue) to clear the
opposite key ("trigger.expect" or "trigger.after") whenever posture changes;
alternatively, add cleanup in the form submission handler to drop the unused
field based on posture so only the relevant trigger field is submitted.


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