From 5cdbf19fd194b4385e86dac2b407649016aca06a Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Thu, 13 Aug 2026 22:41:31 -0700 Subject: [PATCH] Clarify workflow step conditions Signed-off-by: Taylor Ho --- .../workflows/ui/WorkflowConditionBuilder.tsx | 4 +-- .../workflows/ui/WorkflowStepCard.tsx | 18 ++++++++++ .../ui/workflowConditionExpression.ts | 2 +- desktop/tests/e2e/workflows.spec.ts | 36 +++++++++++++++++-- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/desktop/src/features/workflows/ui/WorkflowConditionBuilder.tsx b/desktop/src/features/workflows/ui/WorkflowConditionBuilder.tsx index 558767ceb..dbf0f9db6 100644 --- a/desktop/src/features/workflows/ui/WorkflowConditionBuilder.tsx +++ b/desktop/src/features/workflows/ui/WorkflowConditionBuilder.tsx @@ -179,9 +179,7 @@ export function WorkflowConditionBuilder({ return (
- - Run when - + Condition
{fieldOptions.map((field) => { const isMatchAll = field.value === ""; diff --git a/desktop/src/features/workflows/ui/WorkflowStepCard.tsx b/desktop/src/features/workflows/ui/WorkflowStepCard.tsx index cae2f79cd..ac2ecc5dc 100644 --- a/desktop/src/features/workflows/ui/WorkflowStepCard.tsx +++ b/desktop/src/features/workflows/ui/WorkflowStepCard.tsx @@ -6,6 +6,7 @@ import { Button } from "@/shared/ui/button"; import { Input } from "@/shared/ui/input"; import { Textarea } from "@/shared/ui/textarea"; import { ChannelCombobox } from "./ChannelCombobox"; +import { WorkflowConditionBuilder } from "./WorkflowConditionBuilder"; import { WorkflowEmojiField } from "./WorkflowEmojiField"; import { WorkflowTemplateTextarea } from "./WorkflowTemplateTextarea"; import { FieldLabel, FormSelect } from "./workflowFormPrimitives"; @@ -378,6 +379,23 @@ export function WorkflowStepCard({ /> +
+ +

+ Checked after the workflow starts. If it does not match, Buzz skips + only this step and continues the run. +

+ onUpdate({ ...step, condition })} + triggerType={triggerType} + value={step.condition ?? ""} + /> +
+
diff --git a/desktop/src/features/workflows/ui/workflowConditionExpression.ts b/desktop/src/features/workflows/ui/workflowConditionExpression.ts index c63b8b5ed..27564809e 100644 --- a/desktop/src/features/workflows/ui/workflowConditionExpression.ts +++ b/desktop/src/features/workflows/ui/workflowConditionExpression.ts @@ -63,7 +63,7 @@ const FIELDS_BY_TRIGGER: Record = { reaction_added: [ { label: "Reaction emoji", value: "trigger_emoji" }, AUTHOR_FIELD, - { label: "Reacted-to message ID", value: "trigger_message_id" }, + { label: "Message ID", value: "trigger_message_id" }, ], webhook: [{ label: "Webhook field…", value: "webhook_field" }], schedule: [{ label: "Scheduled timestamp", value: "trigger_timestamp" }], diff --git a/desktop/tests/e2e/workflows.spec.ts b/desktop/tests/e2e/workflows.spec.ts index b88c8618e..7b05c98bd 100644 --- a/desktop/tests/e2e/workflows.spec.ts +++ b/desktop/tests/e2e/workflows.spec.ts @@ -277,7 +277,6 @@ test("builds a valid trigger condition from plain-language choices", async ({ const inspector = dialog.getByTestId("workflow-node-inspector"); const conditionFields = inspector.getByRole("group", { name: "Condition" }); - await expect(inspector.getByText("Run when", { exact: true })).toBeVisible(); const conditionOptions = conditionFields.getByRole("button"); await expect(conditionOptions).toHaveCount(4); const allMessages = conditionFields.getByRole("button", { @@ -330,6 +329,30 @@ test("builds a valid trigger condition from plain-language choices", async ({ await expect(inspector.getByLabel("Custom expression")).not.toBeVisible(); }); +test("keeps a step condition separate from its action configuration", async ({ + page, +}) => { + await navigateToWorkflows(page); + + await page.getByRole("button", { name: "Create Workflow" }).click(); + const dialog = page.getByRole("dialog"); + await dialog.getByRole("button", { name: "Add step" }).click(); + await page.getByRole("menuitem", { name: "Send Message" }).click(); + + const inspector = dialog.getByTestId("workflow-node-inspector"); + await expect(inspector.getByLabel("Message text")).toBeVisible(); + await expect( + inspector.getByRole("heading", { name: "Step condition" }), + ).toBeVisible(); + await inspector.getByRole("button", { name: "Message text" }).click(); + await inspector.getByLabel("Text to match").fill("deploy"); + + await dialog.getByRole("tab", { name: "YAML" }).click(); + await expect(dialog.getByLabel("Workflow YAML")).toHaveValue( + /if: str_contains\(trigger_text, "deploy"\)/, + ); +}); + test("chooses a trigger author condition from live user search", async ({ page, }) => { @@ -505,7 +528,7 @@ test("chooses a reaction emoji condition with the app emoji picker", async ({ inspector = dialog.getByTestId("workflow-node-inspector"); await expect( - inspector.getByRole("button", { name: "Reacted-to message ID" }), + inspector.getByRole("button", { name: "Message ID" }), ).toBeVisible(); await expect( inspector.getByRole("button", { name: "Channel ID" }), @@ -633,8 +656,15 @@ test("opens node configuration in a contextual inspector", async ({ page }) => { "send_message", ); await expect(inspector.getByLabel("Message text")).toBeVisible(); + await expect( + inspector.getByRole("heading", { name: "Step condition" }), + ).toBeVisible(); + await expect( + inspector.getByRole("button", { name: "Always run this step" }), + ).toHaveAttribute("aria-pressed", "true"); + await expect(inspector.getByText(/skips only this step/)).toBeVisible(); await expect(inspector.getByRole("group", { name: "Condition" })).toHaveCount( - 0, + 1, ); await expect( inspector.getByRole("heading", { name: "Step timeout" }),