mirror of
https://github.com/block/buzz.git
synced 2026-11-04 16:31:38 +00:00
Improve initial workflow step creation
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
@@ -35,6 +35,7 @@ import { ChannelCombobox } from "./ChannelCombobox";
|
||||
import {
|
||||
WorkflowFormBuilder,
|
||||
type WorkflowEditorMode,
|
||||
type WorkflowFormBuilderHandle,
|
||||
} from "./WorkflowFormBuilder";
|
||||
import { WorkflowWebhookSecretDialog } from "./WorkflowWebhookSecretDialog";
|
||||
import type { WorkflowEditorPane } from "./workflowEditorPane";
|
||||
@@ -155,6 +156,8 @@ function WorkflowNameEditor({
|
||||
<div className="flex h-6 items-center gap-1.5">
|
||||
<Input
|
||||
aria-label="Workflow name"
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
className="h-6 w-72 px-2 font-mono text-sm"
|
||||
disabled={disabled}
|
||||
onChange={(event) => setDraft(event.target.value)}
|
||||
@@ -217,6 +220,7 @@ export function WorkflowDialog({
|
||||
pane,
|
||||
workflow,
|
||||
}: WorkflowDialogProps) {
|
||||
const formBuilderRef = React.useRef<WorkflowFormBuilderHandle>(null);
|
||||
const channelId =
|
||||
mode === "edit" && workflow?.channelId ? workflow.channelId : "";
|
||||
|
||||
@@ -255,6 +259,14 @@ export function WorkflowDialog({
|
||||
|
||||
const selectedChannel =
|
||||
channels.find((c) => c.id === selectedChannelId) ?? null;
|
||||
const parsedDefinition = yamlDefinition.trim()
|
||||
? yamlToFormState(yamlDefinition)
|
||||
: null;
|
||||
const isAddingFirstStep =
|
||||
mode === "create" &&
|
||||
editorMode === "form" &&
|
||||
(parsedDefinition === null ||
|
||||
(parsedDefinition.ok && parsedDefinition.state.steps.length === 0));
|
||||
|
||||
const workflowChannelId = workflow?.channelId ?? null;
|
||||
const resetCreate = createMutation.reset;
|
||||
@@ -489,6 +501,7 @@ export function WorkflowDialog({
|
||||
}}
|
||||
onSelectedNodeChange={onEditorPaneChange}
|
||||
parseError={editorParseError}
|
||||
ref={formBuilderRef}
|
||||
scopeField={
|
||||
showChannelSelector ? (
|
||||
<div className="space-y-1">
|
||||
@@ -563,19 +576,32 @@ export function WorkflowDialog({
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
disabled={
|
||||
!selectedChannelId ||
|
||||
!yamlDefinition.trim() ||
|
||||
mutation.isPending
|
||||
}
|
||||
onClick={handleSubmit}
|
||||
type="button"
|
||||
>
|
||||
{mutation.isPending
|
||||
? PENDING_LABELS[mode]
|
||||
: SUBMIT_LABELS[mode]}
|
||||
</Button>
|
||||
{isAddingFirstStep ? (
|
||||
<Button
|
||||
aria-label="Add first step"
|
||||
data-testid="workflow-dialog-primary-action"
|
||||
disabled={!selectedChannelId || mutation.isPending}
|
||||
onClick={() => formBuilderRef.current?.addFirstStep()}
|
||||
type="button"
|
||||
>
|
||||
Add step
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
data-testid="workflow-dialog-primary-action"
|
||||
disabled={
|
||||
!selectedChannelId ||
|
||||
!yamlDefinition.trim() ||
|
||||
mutation.isPending
|
||||
}
|
||||
onClick={handleSubmit}
|
||||
type="button"
|
||||
>
|
||||
{mutation.isPending
|
||||
? PENDING_LABELS[mode]
|
||||
: SUBMIT_LABELS[mode]}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -158,6 +158,10 @@ type WorkflowFormBuilderProps = {
|
||||
workflowChannelId?: string | null;
|
||||
};
|
||||
|
||||
export type WorkflowFormBuilderHandle = {
|
||||
addFirstStep: () => void;
|
||||
};
|
||||
|
||||
export type WorkflowEditorMode = "form" | "yaml";
|
||||
|
||||
function nodePosition(node: Exclude<WorkflowEditorPane, null>): number {
|
||||
@@ -362,19 +366,25 @@ function WorkflowNode({
|
||||
);
|
||||
}
|
||||
|
||||
export function WorkflowFormBuilder({
|
||||
channels,
|
||||
disabled,
|
||||
mode,
|
||||
nameLeadingContainer,
|
||||
onChange,
|
||||
onSelectedNodeChange,
|
||||
parseError,
|
||||
scopeField,
|
||||
selectedNode: selectedRouteNode,
|
||||
yaml,
|
||||
workflowChannelId,
|
||||
}: WorkflowFormBuilderProps) {
|
||||
export const WorkflowFormBuilder = React.forwardRef<
|
||||
WorkflowFormBuilderHandle,
|
||||
WorkflowFormBuilderProps
|
||||
>(function WorkflowFormBuilder(
|
||||
{
|
||||
channels,
|
||||
disabled,
|
||||
mode,
|
||||
nameLeadingContainer,
|
||||
onChange,
|
||||
onSelectedNodeChange,
|
||||
parseError,
|
||||
scopeField,
|
||||
selectedNode: selectedRouteNode,
|
||||
yaml,
|
||||
workflowChannelId,
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
// Parse once on mount instead of calling yamlToFormState three times
|
||||
const initialParseRef = React.useRef(yaml ? yamlToFormState(yaml) : null);
|
||||
const [formState, setFormState] = React.useState<WorkflowFormState>(
|
||||
@@ -470,6 +480,14 @@ export function WorkflowFormBuilder({
|
||||
[formState, selectNode, updateFormState],
|
||||
);
|
||||
|
||||
React.useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
addFirstStep: () => insertStep(0, "send_message"),
|
||||
}),
|
||||
[insertStep],
|
||||
);
|
||||
|
||||
const removeStep = React.useCallback(
|
||||
(index: number) => {
|
||||
updateFormState({
|
||||
@@ -838,4 +856,4 @@ export function WorkflowFormBuilder({
|
||||
: null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -28,8 +28,8 @@ export type ActionType = (typeof ACTION_TYPES)[number];
|
||||
// Keep every recognized schema action above so existing YAML remains editable,
|
||||
// but only offer actions that can complete successfully in form selectors.
|
||||
export const SELECTABLE_ACTION_TYPES = [
|
||||
"delay",
|
||||
"send_message",
|
||||
"delay",
|
||||
"call_webhook",
|
||||
] as const satisfies readonly ActionType[];
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ async function createWorkflow(
|
||||
name: string,
|
||||
) {
|
||||
await page.getByRole("button", { name: "Create Workflow" }).click();
|
||||
const dialog = page.getByRole("dialog");
|
||||
const dialog = page.getByRole("dialog", { name: "Create workflow" });
|
||||
await expect(dialog).toBeVisible();
|
||||
await dialog.getByRole("combobox", { name: "Channel" }).click();
|
||||
await dialog
|
||||
@@ -32,10 +32,15 @@ async function createWorkflow(
|
||||
.getByRole("button")
|
||||
.first()
|
||||
.click();
|
||||
await dialog.getByLabel("Workflow name").fill(name);
|
||||
await dialog.getByRole("button", { name: "Add step" }).click();
|
||||
await page.getByRole("menuitem", { name: "Delay" }).click();
|
||||
await dialog.getByRole("button", { name: "Create workflow" }).click();
|
||||
await dialog.getByRole("button", { name: "Edit workflow name" }).click();
|
||||
await dialog.getByRole("textbox", { name: "Workflow name" }).fill(name);
|
||||
await dialog.getByRole("button", { name: "Save workflow name" }).click();
|
||||
const primaryAction = dialog.getByTestId("workflow-dialog-primary-action");
|
||||
await expect(primaryAction).toHaveText("Add step");
|
||||
await primaryAction.click();
|
||||
await expect(primaryAction).toHaveText("Create workflow");
|
||||
await dialog.getByLabel("Message text").fill("Test workflow message");
|
||||
await primaryAction.click();
|
||||
await expect(dialog).not.toBeVisible();
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,8 @@ async function expectUnsupportedActionWarnings(
|
||||
menu: import("@playwright/test").Locator,
|
||||
) {
|
||||
await expect(menu.getByRole("menuitem")).toHaveText([
|
||||
"Delay",
|
||||
"Send message",
|
||||
"Delay",
|
||||
"Call webhook",
|
||||
"Send DM",
|
||||
"Request approval",
|
||||
@@ -100,15 +100,19 @@ async function createWorkflow(
|
||||
},
|
||||
) {
|
||||
await page.getByRole("button", { name: "Create Workflow" }).click();
|
||||
const dialog = page.getByRole("dialog");
|
||||
const dialog = page.getByRole("dialog", { name: "Create workflow" });
|
||||
await expect(dialog).toBeVisible();
|
||||
await expect(dialog.getByText("Choose a channel")).toBeVisible();
|
||||
await expect(
|
||||
dialog.getByRole("button", { name: "Create workflow" }),
|
||||
).toBeDisabled();
|
||||
const primaryAction = dialog.getByTestId("workflow-dialog-primary-action");
|
||||
await expect(primaryAction).toHaveText("Add step");
|
||||
await expect(primaryAction).toBeDisabled();
|
||||
await selectFirstChannel(dialog);
|
||||
await expect(primaryAction).toHaveText("Add step");
|
||||
await expect(primaryAction).toBeEnabled();
|
||||
|
||||
await dialog.getByLabel("Workflow name").fill(name);
|
||||
await dialog.getByRole("button", { name: "Edit workflow name" }).click();
|
||||
await dialog.getByRole("textbox", { name: "Workflow name" }).fill(name);
|
||||
await dialog.getByRole("button", { name: "Save workflow name" }).click();
|
||||
if (options?.description) {
|
||||
await dialog.getByLabel("Description (optional)").fill(options.description);
|
||||
}
|
||||
@@ -131,8 +135,13 @@ async function createWorkflow(
|
||||
await dialog.getByLabel("Custom expression").fill(options.triggerCondition);
|
||||
}
|
||||
|
||||
await dialog.getByRole("button", { name: "Add step" }).click();
|
||||
await page.getByRole("menuitem", { name: "Delay" }).click();
|
||||
await primaryAction.click();
|
||||
await expect(primaryAction).toHaveText("Create workflow");
|
||||
await expect(dialog.getByLabel("Action")).toHaveAttribute(
|
||||
"data-value",
|
||||
"send_message",
|
||||
);
|
||||
await dialog.getByLabel("Message text").fill("Test workflow message");
|
||||
if (options?.stepName) {
|
||||
await dialog.getByLabel("Name (optional)").fill(options.stepName);
|
||||
}
|
||||
@@ -140,7 +149,7 @@ async function createWorkflow(
|
||||
await dialog.getByLabel("Timeout (seconds)").fill(options.stepTimeoutSecs);
|
||||
}
|
||||
|
||||
await dialog.getByRole("button", { name: "Create workflow" }).click();
|
||||
await primaryAction.click();
|
||||
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Create workflow" }),
|
||||
@@ -172,10 +181,10 @@ test("disables autocapitalization in the workflow form", async ({ page }) => {
|
||||
await page.getByRole("button", { name: "Create Workflow" }).click();
|
||||
const dialog = page.getByRole("dialog");
|
||||
|
||||
await expect(dialog.getByLabel("Workflow name")).toHaveAttribute(
|
||||
"autocapitalize",
|
||||
"off",
|
||||
);
|
||||
await dialog.getByRole("button", { name: "Edit workflow name" }).click();
|
||||
await expect(
|
||||
dialog.getByRole("textbox", { name: "Workflow name" }),
|
||||
).toHaveAttribute("autocapitalize", "off");
|
||||
|
||||
await dialog.getByRole("button", { name: "Add step" }).click();
|
||||
await page.getByRole("menuitem", { name: "Delay" }).click();
|
||||
|
||||
Reference in New Issue
Block a user