mirror of
https://github.com/block/buzz.git
synced 2026-11-07 18:01:06 +00:00
**Category:** new-feature **User Impact:** Workflow authors can build filtered, runtime-aware automations, understand them at a glance, and get a clear warning before turning on workflows likely to run often. **Problem:** Workflow setup exposed raw configuration without enough help composing message templates, filtering triggers, or understanding saved behavior; activation could also make a broadly triggered workflow live without explaining its likely frequency. **Solution:** Batch 3 adds local, deterministic template variables, trigger filters, and semantic summaries, then refines cards and activation around configured behavior and a risk-aware warning boundary. Scheduling remains the already-shipped implementation, advanced expressions remain lossless, and network-backed identity/message enrichment stays in Batch 4. | Message inputs | Trigger filters | | --- | --- | | Caret-aware, keyboard-accessible suggestions expose trigger-local values and safe prior-step outputs in `send_message.text`. | Structured conditions and validated manual IDs block invalid submission while preserving advanced expressions. | |  |  | | Workflow cards | Risk-aware activation | | --- | --- | | Semantic labels, channel-first hierarchy, configured reaction/action visuals, real step stacks, and compact status controls make behavior scannable. | Broad message and frequent schedule triggers explain the risk before **Turn on**; narrowly scoped triggers proceed without unnecessary ceremony. | |  |  | ## Changes <details> <summary>File changes</summary> **desktop/src/features/workflows/ui/WorkflowActionsMenu.tsx** Separates direct card status controls from secondary actions while retaining modal status actions. **desktop/src/features/workflows/ui/WorkflowCard.tsx** Adds semantic behavior, channel-first hierarchy, configured reaction/action visuals, real subsequent-step stacks, status controls, and reduced-motion-aware trigger feedback. **desktop/src/features/workflows/ui/WorkflowDialog.tsx** Warns before activating broadly triggered workflows while allowing narrowly scoped workflows to proceed directly. **desktop/src/features/workflows/ui/WorkflowFormBuilder.tsx** Connects structured trigger filters and template-aware step inputs while preserving schedules, trigger transitions, and selected YAML authority. **desktop/src/features/workflows/ui/WorkflowStepCard.tsx** Replaces generic labels with deterministic configured-step descriptions. **desktop/src/features/workflows/ui/WorkflowTemplateTextarea.tsx** Adds caret-aware variable suggestions with keyboard navigation and focus restoration. **desktop/src/features/workflows/ui/WorkflowTriggerConditions.tsx** Adds structured local filters, validated author/message IDs, and a lossless advanced-expression fallback. **desktop/src/features/workflows/ui/workflowActivationWarning.ts** and **workflowActivationWarning.test.mjs** Classify broad message and frequent schedule triggers for contextual activation warnings. **desktop/src/features/workflows/ui/workflowConditionExpression.ts** and **workflowConditionExpression.test.mjs** Model and cover parsing, serialization, validation, and advanced-expression preservation. **desktop/src/features/workflows/ui/workflowDefinition.ts** and **workflowDefinition.test.mjs** Preserve trigger/step configuration and derive deterministic card metadata across YAML round trips. **desktop/src/features/workflows/ui/workflowStepDescription.ts** and **workflowStepDescription.test.mjs** Generate and cover local step summaries. **desktop/src/features/workflows/ui/workflowTemplateVariables.ts** and **workflowTemplateVariables.test.mjs** Define and cover trigger-specific, order-bounded variables and caret insertion. **desktop/src/features/workflows/ui/workflowTriggerDescription.ts** and **workflowTriggerDescription.test.mjs** Generate and cover semantic trigger summaries without network lookups. **desktop/tests/e2e/workflow-local-controls.spec.ts** and snapshot Cover filters, IDs, advanced expressions, autocomplete, activation choices, summaries, and YAML authority. **desktop/tests/e2e/workflow-reaction-picker.spec.ts** Covers configured reaction emoji in workflow nodes and summaries. **desktop/tests/e2e/workflows.spec.ts** Covers risk-aware activation warnings, direct safe creation, duplication, and card status controls. </details> ## Reproduction steps 1. Create a message-posted workflow in **Workflows**, add a Send message step, and type `{{trig`; verify keyboard-selectable variables insert at the caret. 2. Configure message-text and manual ID filters; verify malformed IDs block submission and advanced expressions survive Form/YAML transitions. 3. Create a broad message workflow; verify **Back** persists nothing, **Keep off** saves it disabled, and **Turn on** enables it. Confirm a narrowly triggered webhook skips the warning. 4. Inspect the saved card; verify its channel, semantic behavior, configured actions/reaction, real step stack, and status are understandable without opening YAML. ## Validation Validated at exact clean head `f99503819889b95ee3c61657c5c3850aae35481e` on base `24ec6a468ec9d0d425ee58fbfc4d416412c446ad`. - Focused workflow regressions passed 59/60 locally; the only local miss was a 438-pixel macOS snapshot drift, while the checked-in Linux baseline comes from the failing CI artifact. Repository pre-push gates and E2E build/typecheck passed. - A broader 36-test smoke invocation had 31 passes and five unrelated pre-existing expectation/snapshot failures, so it is not claimed as fully green. Adversarial fixes are recorded in [round one](https://github.com/block/buzz/pull/6470#issuecomment-5366366670) and [round two](https://github.com/block/buzz/pull/6470#issuecomment-5366407535). --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Mongo <5398c5fd039b963ce132b3e078e7c4af097dd997517bb5e14c2682fe68c25197@buzz.block.builderlab.xyz>
532 lines
17 KiB
TypeScript
532 lines
17 KiB
TypeScript
import { ChevronRight, Trash2 } from "lucide-react";
|
|
import { type ReactNode, useState } from "react";
|
|
|
|
import { cn } from "@/shared/lib/cn";
|
|
import { Button } from "@/shared/ui/button";
|
|
import { Checkbox } from "@/shared/ui/checkbox";
|
|
import { Input } from "@/shared/ui/input";
|
|
import { Textarea } from "@/shared/ui/textarea";
|
|
import { WorkflowTemplateTextarea } from "./WorkflowTemplateTextarea";
|
|
import { WorkflowDurationField } from "./WorkflowDurationField";
|
|
import { WorkflowMessageTextCondition } from "./WorkflowMessageTextConditionEditor";
|
|
import { FieldLabel, FormSelect } from "./workflowFormPrimitives";
|
|
import { WorkflowWebhookHeadersEditor } from "./WorkflowWebhookHeadersEditor";
|
|
import {
|
|
isThreadReplyEligibleTrigger,
|
|
supportsMessageTextCondition,
|
|
type StepFormState,
|
|
type TriggerType,
|
|
} from "./workflowFormTypes";
|
|
|
|
const DEFAULT_STEP_TIMEOUT_SECONDS = 5 * 60;
|
|
|
|
type StepSetting = "run-controls" | "details";
|
|
|
|
function StepSettingAccordion({
|
|
children,
|
|
disabled,
|
|
expanded,
|
|
label,
|
|
onToggle,
|
|
summary,
|
|
}: {
|
|
children: ReactNode;
|
|
disabled?: boolean;
|
|
expanded: boolean;
|
|
label: string;
|
|
onToggle: () => void;
|
|
summary: string;
|
|
}) {
|
|
return (
|
|
<div>
|
|
<button
|
|
aria-expanded={expanded}
|
|
className="flex min-h-12 w-full items-center gap-3 py-3 text-left transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
disabled={disabled}
|
|
onClick={onToggle}
|
|
type="button"
|
|
>
|
|
<span className="min-w-0 flex-1 truncate text-base font-medium">
|
|
{label}
|
|
</span>
|
|
<span className="max-w-40 truncate text-sm text-muted-foreground">
|
|
{summary}
|
|
</span>
|
|
<ChevronRight
|
|
className={cn(
|
|
"h-4 w-4 shrink-0 text-muted-foreground/70 transition-transform duration-150 motion-reduce:transition-none",
|
|
expanded && "rotate-90",
|
|
)}
|
|
/>
|
|
</button>
|
|
|
|
{expanded ? (
|
|
<div className="animate-in pb-4 pt-1 fade-in slide-in-from-top-1 duration-150 motion-reduce:animate-none">
|
|
{children}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function runControlsSummary(step: StepFormState): string {
|
|
const hasCondition = Boolean(step.condition?.trim());
|
|
const timeout = step.timeoutSecs?.trim();
|
|
if (hasCondition && timeout) return `Conditional · ${timeout}`;
|
|
if (hasCondition) return "Conditional";
|
|
if (timeout) return timeout;
|
|
return "Default";
|
|
}
|
|
|
|
function BackendSupportHint({ action }: { action: StepFormState["action"] }) {
|
|
switch (action) {
|
|
case "send_dm":
|
|
return (
|
|
<p className="rounded-md border border-amber-500/30 bg-amber-500/10 px-2 py-1 text-xs text-amber-700">
|
|
Backend note: `send_dm` is not executed yet, so runs fail at this
|
|
step.
|
|
</p>
|
|
);
|
|
case "set_channel_topic":
|
|
return (
|
|
<p className="rounded-md border border-amber-500/30 bg-amber-500/10 px-2 py-1 text-xs text-amber-700">
|
|
Backend note: `set_channel_topic` is not executed yet, so runs fail at
|
|
this step.
|
|
</p>
|
|
);
|
|
case "request_approval":
|
|
return (
|
|
<p className="rounded-md border border-amber-500/30 bg-amber-500/10 px-2 py-1 text-xs text-amber-700">
|
|
Backend note: approval gates still stop runs with WF-08; approval
|
|
records are not persisted yet.
|
|
</p>
|
|
);
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function StepConfigFields({
|
|
step,
|
|
prefix,
|
|
disabled,
|
|
previousSteps,
|
|
triggerType,
|
|
workflowChannelId,
|
|
onUpdate,
|
|
}: {
|
|
step: StepFormState;
|
|
prefix: string;
|
|
disabled?: boolean;
|
|
previousSteps: StepFormState[];
|
|
triggerType: TriggerType;
|
|
workflowChannelId?: string | null;
|
|
onUpdate: (step: StepFormState) => void;
|
|
}) {
|
|
switch (step.action) {
|
|
case "delay":
|
|
return (
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-duration`}>Duration</FieldLabel>
|
|
<Input
|
|
autoCapitalize="off"
|
|
disabled={disabled}
|
|
id={`${prefix}-duration`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, duration: event.target.value })
|
|
}
|
|
placeholder="e.g. 5s, 1m, 1h"
|
|
value={step.duration ?? ""}
|
|
/>
|
|
</div>
|
|
);
|
|
case "send_message":
|
|
return (
|
|
<div className="space-y-2">
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-text`}>Message text</FieldLabel>
|
|
<WorkflowTemplateTextarea
|
|
autoCapitalize="off"
|
|
className="min-h-[60px] resize-y text-xs"
|
|
disabled={disabled}
|
|
id={`${prefix}-text`}
|
|
onValueChange={(text) => onUpdate({ ...step, text })}
|
|
placeholder="e.g. Deployment started by {{trigger.author}}"
|
|
previousSteps={previousSteps}
|
|
triggerType={triggerType}
|
|
value={step.text ?? ""}
|
|
/>
|
|
</div>
|
|
{workflowChannelId ? (
|
|
<p className="text-xs text-muted-foreground">
|
|
Messages post to the workflow channel selected above.
|
|
</p>
|
|
) : (
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-channel`}>
|
|
Channel override (optional)
|
|
</FieldLabel>
|
|
<Input
|
|
autoCapitalize="off"
|
|
disabled={disabled}
|
|
id={`${prefix}-channel`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, channel: event.target.value })
|
|
}
|
|
placeholder="Channel UUID"
|
|
value={step.channel ?? ""}
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
Defaults to the channel that triggered the workflow. Webhook and
|
|
manual triggers require a channel.
|
|
</p>
|
|
{triggerType === "webhook" && !(step.channel ?? "").trim() ? (
|
|
<p className="text-xs text-amber-700">
|
|
This step will fail for webhook-triggered runs until a channel
|
|
override is set.
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
)}
|
|
{isThreadReplyEligibleTrigger(triggerType) ? (
|
|
<div className="flex items-center gap-2">
|
|
<Checkbox
|
|
checked={step.replyInThread === true}
|
|
disabled={disabled}
|
|
id={`${prefix}-reply-in-thread`}
|
|
onCheckedChange={(checked) =>
|
|
onUpdate({ ...step, replyInThread: checked === true })
|
|
}
|
|
/>
|
|
<label className="text-xs" htmlFor={`${prefix}-reply-in-thread`}>
|
|
Reply to triggering message in thread
|
|
</label>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
case "send_dm":
|
|
return (
|
|
<div className="space-y-2">
|
|
<BackendSupportHint action={step.action} />
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-to`}>To (pubkey)</FieldLabel>
|
|
<Input
|
|
autoCapitalize="off"
|
|
disabled={disabled}
|
|
id={`${prefix}-to`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, to: event.target.value })
|
|
}
|
|
placeholder="e.g. {{trigger.author}} or hex pubkey"
|
|
value={step.to ?? ""}
|
|
/>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-text`}>Message text</FieldLabel>
|
|
<Textarea
|
|
autoCapitalize="off"
|
|
className="min-h-[60px] resize-y text-xs"
|
|
disabled={disabled}
|
|
id={`${prefix}-text`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, text: event.target.value })
|
|
}
|
|
placeholder="DM content"
|
|
value={step.text ?? ""}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
case "call_webhook":
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-url`}>Endpoint URL</FieldLabel>
|
|
<Input
|
|
autoCapitalize="off"
|
|
disabled={disabled}
|
|
id={`${prefix}-url`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, url: event.target.value })
|
|
}
|
|
placeholder="https://..."
|
|
value={step.url ?? ""}
|
|
/>
|
|
{step.url && !step.url.startsWith("https://") ? (
|
|
<p className="text-xs text-destructive">
|
|
URL must start with https://
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-method`}>HTTP method</FieldLabel>
|
|
<FormSelect
|
|
disabled={disabled}
|
|
id={`${prefix}-method`}
|
|
onChange={(value) => onUpdate({ ...step, method: value })}
|
|
value={step.method ?? "POST"}
|
|
>
|
|
<option value="POST">POST</option>
|
|
<option value="GET">GET</option>
|
|
<option value="PUT">PUT</option>
|
|
<option value="PATCH">PATCH</option>
|
|
<option value="DELETE">DELETE</option>
|
|
</FormSelect>
|
|
</div>
|
|
<WorkflowWebhookHeadersEditor
|
|
disabled={disabled}
|
|
headers={step.headers ?? []}
|
|
onChange={(headers) => onUpdate({ ...step, headers })}
|
|
stepId={step.id || prefix}
|
|
/>
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-body`}>
|
|
Request body (optional)
|
|
</FieldLabel>
|
|
<Textarea
|
|
autoCapitalize="off"
|
|
className="min-h-[60px] resize-y font-mono text-xs"
|
|
disabled={disabled}
|
|
id={`${prefix}-body`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, body: event.target.value })
|
|
}
|
|
placeholder='{"key": "{{trigger.text}}"}'
|
|
value={step.body ?? ""}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
case "request_approval":
|
|
return (
|
|
<div className="space-y-2">
|
|
<BackendSupportHint action={step.action} />
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-from`}>From (approver)</FieldLabel>
|
|
<Input
|
|
autoCapitalize="off"
|
|
disabled={disabled}
|
|
id={`${prefix}-from`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, from: event.target.value })
|
|
}
|
|
placeholder="Pubkey or role"
|
|
value={step.from ?? ""}
|
|
/>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-message`}>Message</FieldLabel>
|
|
<Input
|
|
autoCapitalize="off"
|
|
disabled={disabled}
|
|
id={`${prefix}-message`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, message: event.target.value })
|
|
}
|
|
placeholder="Approval request message"
|
|
value={step.message ?? ""}
|
|
/>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-timeout`}>
|
|
Timeout (optional)
|
|
</FieldLabel>
|
|
<Input
|
|
autoCapitalize="off"
|
|
disabled={disabled}
|
|
id={`${prefix}-timeout`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, timeout: event.target.value })
|
|
}
|
|
placeholder="e.g. 24h"
|
|
value={step.timeout ?? ""}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
case "add_reaction":
|
|
return (
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-emoji`}>Emoji</FieldLabel>
|
|
<Input
|
|
autoCapitalize="off"
|
|
disabled={disabled}
|
|
id={`${prefix}-emoji`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, emoji: event.target.value })
|
|
}
|
|
placeholder="e.g. thumbsup"
|
|
value={step.emoji ?? ""}
|
|
/>
|
|
</div>
|
|
);
|
|
case "set_channel_topic":
|
|
return (
|
|
<div className="space-y-2">
|
|
<BackendSupportHint action={step.action} />
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-topic`}>Topic</FieldLabel>
|
|
<Input
|
|
autoCapitalize="off"
|
|
disabled={disabled}
|
|
id={`${prefix}-topic`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, topic: event.target.value })
|
|
}
|
|
placeholder="New channel topic"
|
|
value={step.topic ?? ""}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function WorkflowStepCard({
|
|
bare = false,
|
|
showHeader = true,
|
|
index,
|
|
disabled,
|
|
onRemove,
|
|
onUpdate,
|
|
step,
|
|
previousSteps = [],
|
|
triggerType,
|
|
workflowChannelId,
|
|
}: {
|
|
bare?: boolean;
|
|
showHeader?: boolean;
|
|
index: number;
|
|
disabled?: boolean;
|
|
onRemove: () => void;
|
|
onUpdate: (step: StepFormState) => void;
|
|
step: StepFormState;
|
|
previousSteps?: StepFormState[];
|
|
triggerType: TriggerType;
|
|
workflowChannelId?: string | null;
|
|
}) {
|
|
const prefix = `wf-step-${index}`;
|
|
const [expandedSetting, setExpandedSetting] = useState<StepSetting | null>(
|
|
null,
|
|
);
|
|
|
|
const toggleSetting = (setting: StepSetting) => {
|
|
setExpandedSetting((current) => (current === setting ? null : setting));
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"space-y-0",
|
|
!bare && "rounded-lg border border-border/70 bg-muted/10 p-3",
|
|
)}
|
|
>
|
|
{showHeader ? (
|
|
<div className="mb-3 flex items-center justify-between gap-2">
|
|
<span className="text-xs font-medium text-muted-foreground">
|
|
Step {index + 1}
|
|
</span>
|
|
<Button
|
|
aria-label="Remove step"
|
|
className="h-7 w-7"
|
|
disabled={disabled}
|
|
onClick={onRemove}
|
|
size="icon"
|
|
type="button"
|
|
variant="ghost"
|
|
>
|
|
<Trash2 className="h-4 w-4 text-muted-foreground" />
|
|
</Button>
|
|
</div>
|
|
) : null}
|
|
|
|
<section className="space-y-4 pb-5">
|
|
<StepConfigFields
|
|
disabled={disabled}
|
|
onUpdate={onUpdate}
|
|
prefix={prefix}
|
|
previousSteps={previousSteps}
|
|
step={step}
|
|
triggerType={triggerType}
|
|
workflowChannelId={workflowChannelId}
|
|
/>
|
|
</section>
|
|
|
|
<section className="divide-y divide-border/50 border-t border-border/50">
|
|
<StepSettingAccordion
|
|
disabled={disabled}
|
|
expanded={expandedSetting === "run-controls"}
|
|
label="Run controls"
|
|
onToggle={() => toggleSetting("run-controls")}
|
|
summary={runControlsSummary(step)}
|
|
>
|
|
<div className="space-y-4">
|
|
{supportsMessageTextCondition(triggerType) ? (
|
|
<WorkflowMessageTextCondition
|
|
allowAdvanced={false}
|
|
disabled={disabled}
|
|
onChange={(condition) => onUpdate({ ...step, condition })}
|
|
value={step.condition ?? ""}
|
|
/>
|
|
) : null}
|
|
<WorkflowDurationField
|
|
disabled={disabled}
|
|
fallbackSeconds={DEFAULT_STEP_TIMEOUT_SECONDS}
|
|
id={`${prefix}-timeout-secs`}
|
|
label="Timeout"
|
|
onChange={(timeoutSecs) => onUpdate({ ...step, timeoutSecs })}
|
|
placeholder="5m"
|
|
value={step.timeoutSecs ?? ""}
|
|
/>
|
|
</div>
|
|
</StepSettingAccordion>
|
|
|
|
<StepSettingAccordion
|
|
disabled={disabled}
|
|
expanded={expandedSetting === "details"}
|
|
label="Step details"
|
|
onToggle={() => toggleSetting("details")}
|
|
summary={step.name?.trim() || step.id}
|
|
>
|
|
<div className="space-y-4">
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-name`}>
|
|
Name (optional)
|
|
</FieldLabel>
|
|
<Input
|
|
autoCapitalize="off"
|
|
disabled={disabled}
|
|
id={`${prefix}-name`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, name: event.target.value })
|
|
}
|
|
placeholder="e.g. Notify deployment channel"
|
|
value={step.name ?? ""}
|
|
/>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<FieldLabel htmlFor={`${prefix}-id`}>Step ID</FieldLabel>
|
|
<Input
|
|
autoCapitalize="off"
|
|
disabled={disabled}
|
|
id={`${prefix}-id`}
|
|
onChange={(event) =>
|
|
onUpdate({ ...step, id: event.target.value })
|
|
}
|
|
placeholder="unique_step_id"
|
|
value={step.id}
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
Used in configuration and run history.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</StepSettingAccordion>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|