feat(FN-3397): document add-step chooser flow in workflow-steps docs
Docs(FN-3397): Documented the add-step chooser flow in the workflow steps reference, adding 9 lines to `docs/workflow-steps.md`. Fusion-Task-Id: FN-3397
This commit is contained in:
@@ -228,6 +228,29 @@
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* Create chooser */
|
||||
.wfm-create-chooser {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-md);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.wfm-create-chooser-hint {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.wfm-create-custom-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
/* Edit/create form */
|
||||
.wfm-form {
|
||||
padding: var(--space-md);
|
||||
@@ -490,6 +513,10 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wfm-create-custom-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wfm-footer {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
||||
const [activeTab, setActiveTab] = useState<TabId>("my-steps");
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [showCreateChooser, setShowCreateChooser] = useState(false);
|
||||
const [form, setForm] = useState<StepFormData>(EMPTY_FORM);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [refining, setRefining] = useState(false);
|
||||
@@ -192,6 +193,14 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
||||
}, [isOpen, loadSteps, loadTemplates, loadScripts, loadModels]);
|
||||
|
||||
const handleCreate = useCallback(() => {
|
||||
setShowCreateChooser(true);
|
||||
setIsCreating(false);
|
||||
setEditingId(null);
|
||||
setForm(EMPTY_FORM);
|
||||
}, []);
|
||||
|
||||
const handleCreateCustom = useCallback(() => {
|
||||
setShowCreateChooser(false);
|
||||
setIsCreating(true);
|
||||
setEditingId(null);
|
||||
setForm(EMPTY_FORM);
|
||||
@@ -217,6 +226,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
||||
const handleCancel = useCallback(() => {
|
||||
setEditingId(null);
|
||||
setIsCreating(false);
|
||||
setShowCreateChooser(false);
|
||||
setForm(EMPTY_FORM);
|
||||
}, []);
|
||||
|
||||
@@ -359,6 +369,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
||||
await loadSteps();
|
||||
// Switch to "My Workflow Steps" tab to show the newly added step
|
||||
setActiveTab("my-steps");
|
||||
setShowCreateChooser(false);
|
||||
} catch (err) {
|
||||
const msg = getErrorMessage(err);
|
||||
if (msg?.includes("already exists")) {
|
||||
@@ -428,7 +439,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
||||
)}
|
||||
|
||||
{/* My Workflow Steps Tab */}
|
||||
{activeTab === "my-steps" && !isEditing && (
|
||||
{activeTab === "my-steps" && !isEditing && !showCreateChooser && (
|
||||
<>
|
||||
{steps.length === 0 && (
|
||||
<div className="wfm-empty" data-testid="empty-state">
|
||||
@@ -515,7 +526,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
||||
)}
|
||||
|
||||
{/* Templates Tab */}
|
||||
{activeTab === "templates" && !isEditing && (
|
||||
{activeTab === "templates" && !isEditing && !showCreateChooser && (
|
||||
<>
|
||||
{templatesLoading ? (
|
||||
<div className="wfm-loading">
|
||||
@@ -588,6 +599,84 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Create chooser */}
|
||||
{showCreateChooser && !isEditing && (
|
||||
<div className="wfm-create-chooser" data-testid="workflow-step-create-chooser">
|
||||
<h3 className="wfm-form-title">How would you like to create this workflow step?</h3>
|
||||
<p className="wfm-create-chooser-hint">
|
||||
Start from a built-in template or create a fully custom workflow step.
|
||||
</p>
|
||||
|
||||
<button
|
||||
className="btn wfm-create-custom-btn"
|
||||
onClick={handleCreateCustom}
|
||||
data-testid="create-custom-step"
|
||||
>
|
||||
<Plus size={14} />
|
||||
Custom workflow step
|
||||
</button>
|
||||
|
||||
{templatesLoading ? (
|
||||
<div className="wfm-loading" data-testid="create-chooser-template-loading">
|
||||
<Loader2 size={24} className="spin wfm-spinner" />
|
||||
Loading built-in templates...
|
||||
</div>
|
||||
) : templates.length === 0 ? (
|
||||
<div className="wfm-empty" data-testid="create-chooser-no-templates">
|
||||
No built-in templates are available right now. You can still create a custom step.
|
||||
</div>
|
||||
) : (
|
||||
<div className="wfm-template-list" data-testid="create-chooser-templates">
|
||||
{templates.map((template) => {
|
||||
const IconComponent = getTemplateIcon(template.icon);
|
||||
const categoryClassName = getCategoryClassName(template.category);
|
||||
const isAdding = addingTemplateId === template.id;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={template.id}
|
||||
className="wfm-template-card"
|
||||
data-testid={`chooser-template-${template.id}`}
|
||||
>
|
||||
<div className="wfm-template-inner">
|
||||
<div className="wfm-template-icon">
|
||||
<IconComponent size={20} />
|
||||
</div>
|
||||
|
||||
<div className="wfm-template-content">
|
||||
<div className="wfm-template-title-row">
|
||||
<span className="wfm-template-name">{template.name}</span>
|
||||
<span className={categoryClassName}>{template.category}</span>
|
||||
</div>
|
||||
<div className="wfm-template-desc">{template.description}</div>
|
||||
<button
|
||||
className="btn btn-primary wfm-template-add-btn"
|
||||
onClick={() => handleAddTemplate(template)}
|
||||
disabled={isAdding}
|
||||
data-testid={`chooser-add-template-${template.id}`}
|
||||
>
|
||||
{isAdding ? (
|
||||
<>
|
||||
<Loader2 size={12} className="spin" />
|
||||
Adding...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus size={12} />
|
||||
Add template
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Edit / Create form */}
|
||||
{isEditing && (
|
||||
<div className="wfm-form" data-testid="workflow-step-form">
|
||||
@@ -813,7 +902,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
{!isEditing && (
|
||||
{!isEditing && !showCreateChooser && (
|
||||
<div className="wfm-footer">
|
||||
<button
|
||||
className="btn btn-primary wfm-footer-add-btn"
|
||||
|
||||
@@ -199,7 +199,7 @@ describe("WorkflowStepManager", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("opens create form when Add button is clicked", async () => {
|
||||
it("shows create chooser first when Add button is clicked", async () => {
|
||||
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
|
||||
|
||||
render(<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
@@ -210,9 +210,26 @@ describe("WorkflowStepManager", () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
|
||||
expect(screen.getByTestId("workflow-step-form")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("workflow-step-name")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("workflow-step-description")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("workflow-step-create-chooser")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("create-custom-step")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("workflow-step-form")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("creates a step from chooser template action", async () => {
|
||||
vi.mocked(fetchWorkflowSteps).mockResolvedValue([]);
|
||||
|
||||
render(<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
|
||||
await screen.findByTestId("add-workflow-step");
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
|
||||
const chooserTemplate = await screen.findByTestId("chooser-template-browser-verification");
|
||||
fireEvent.click(within(chooserTemplate).getByTestId("chooser-add-template-browser-verification"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createWorkflowStepFromTemplate).toHaveBeenCalledWith("browser-verification", undefined);
|
||||
expect(addToast).toHaveBeenCalledWith("Added Browser Verification workflow step", "success");
|
||||
});
|
||||
});
|
||||
|
||||
it("submits new workflow step", async () => {
|
||||
@@ -227,6 +244,7 @@ describe("WorkflowStepManager", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
const nameInput = screen.getByTestId("workflow-step-name");
|
||||
const descInput = screen.getByTestId("workflow-step-description");
|
||||
@@ -399,6 +417,7 @@ describe("WorkflowStepManager", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
expect(screen.getByTestId("workflow-step-mode-selector")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("mode-prompt")).toBeInTheDocument();
|
||||
@@ -415,6 +434,7 @@ describe("WorkflowStepManager", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
// Default is prompt mode — should show prompt field
|
||||
expect(screen.getByTestId("workflow-step-prompt")).toBeInTheDocument();
|
||||
@@ -461,6 +481,7 @@ describe("WorkflowStepManager", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
const nameInput = screen.getByTestId("workflow-step-name");
|
||||
const descInput = screen.getByTestId("workflow-step-description");
|
||||
@@ -488,6 +509,7 @@ describe("WorkflowStepManager", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
const nameInput = screen.getByTestId("workflow-step-name");
|
||||
const descInput = screen.getByTestId("workflow-step-description");
|
||||
@@ -557,6 +579,7 @@ describe("WorkflowStepManager", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
const defaultOnCheckbox = screen.getByTestId("workflow-step-default-on") as HTMLInputElement;
|
||||
expect(defaultOnCheckbox).toBeInTheDocument();
|
||||
@@ -575,6 +598,7 @@ describe("WorkflowStepManager", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
const nameInput = screen.getByTestId("workflow-step-name");
|
||||
const descInput = screen.getByTestId("workflow-step-description");
|
||||
@@ -847,6 +871,7 @@ describe("WorkflowStepManager theme class structure", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
expect(container.querySelector(".wfm-form")).toBeInTheDocument();
|
||||
expect(container.querySelector(".wfm-form-title")).toBeInTheDocument();
|
||||
@@ -866,6 +891,7 @@ describe("WorkflowStepManager theme class structure", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
expect(container.querySelector(".wfm-mode-selector")).toBeInTheDocument();
|
||||
expect(container.querySelectorAll(".wfm-mode-btn")).toHaveLength(4); // 2 mode + 2 phase
|
||||
@@ -883,6 +909,7 @@ describe("WorkflowStepManager theme class structure", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
expect(container.querySelector(".wfm-prompt-textarea")).toBeInTheDocument();
|
||||
expect(container.querySelector(".wfm-prompt-header")).toBeInTheDocument();
|
||||
@@ -901,6 +928,7 @@ describe("WorkflowStepManager theme class structure", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
expect(container.querySelector(".wfm-checkbox-label")).toBeInTheDocument();
|
||||
});
|
||||
@@ -917,6 +945,7 @@ describe("WorkflowStepManager theme class structure", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
expect(container.querySelector(".wfm-form-actions")).toBeInTheDocument();
|
||||
});
|
||||
@@ -949,6 +978,7 @@ describe("WorkflowStepManager theme class structure", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
fireEvent.click(screen.getByTestId("mode-script"));
|
||||
|
||||
expect(container.querySelector(".wfm-no-scripts")).toBeInTheDocument();
|
||||
@@ -966,6 +996,7 @@ describe("WorkflowStepManager model override", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
// Model override field should be visible in prompt mode
|
||||
expect(screen.getByTestId("workflow-step-model-field")).toBeInTheDocument();
|
||||
@@ -982,6 +1013,7 @@ describe("WorkflowStepManager model override", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
fireEvent.click(screen.getByTestId("mode-script"));
|
||||
|
||||
// Model override field should NOT be visible in script mode
|
||||
@@ -998,6 +1030,7 @@ describe("WorkflowStepManager model override", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
// Select a model via the mock dropdown
|
||||
const selectBtn = screen.getByTestId("dropdown-select-Model override for this workflow step");
|
||||
@@ -1030,6 +1063,7 @@ describe("WorkflowStepManager model override", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
const nameInput = screen.getByTestId("workflow-step-name");
|
||||
const descInput = screen.getByTestId("workflow-step-description");
|
||||
@@ -1066,6 +1100,7 @@ describe("WorkflowStepManager model override", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
const nameInput = screen.getByTestId("workflow-step-name");
|
||||
const descInput = screen.getByTestId("workflow-step-description");
|
||||
@@ -1222,6 +1257,7 @@ describe("WorkflowStepManager model override", () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||
fireEvent.click(screen.getByTestId("create-custom-step"));
|
||||
|
||||
// No model selected — clear button should NOT be visible
|
||||
expect(screen.queryByTestId("clear-model-override")).not.toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user