768 lines
25 KiB
Markdown
768 lines
25 KiB
Markdown
# Workflow Routes Frontend Documentation
|
|
|
|
This document describes customer journeys for managing workflows through the frontend, focusing on how users interact with workflows and how the backend routes support these experiences. All UI components are dynamically generated from backend metadata—no hardcoding required.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Overview](#overview)
|
|
2. [Customer Journey 1: Discovering and Browsing Workflows](#customer-journey-1-discovering-and-browsing-workflows)
|
|
3. [Customer Journey 2: Viewing Workflow Details](#customer-journey-2-viewing-workflow-details)
|
|
4. [Customer Journey 3: Monitoring Workflow Execution](#customer-journey-3-monitoring-workflow-execution)
|
|
5. [Customer Journey 4: Editing Workflow Properties](#customer-journey-4-editing-workflow-properties)
|
|
6. [Customer Journey 5: Managing Workflow Messages](#customer-journey-5-managing-workflow-messages)
|
|
7. [Customer Journey 6: Cleaning Up Workflows](#customer-journey-6-cleaning-up-workflows)
|
|
8. [Backend Metadata System](#backend-metadata-system)
|
|
9. [Implementation Patterns](#implementation-patterns)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The workflow routes (`/api/workflows`) enable users to manage and monitor workflows that are already running or completed. These routes focus on **management and monitoring** rather than creation (workflows are created through the chat playground routes).
|
|
|
|
**Key Principles:**
|
|
- **User-Centric**: Documentation organized around what users want to accomplish
|
|
- **Backend-Driven**: All forms, tables, and UI components generated from backend metadata
|
|
- **No Hardcoding**: Field definitions, labels, validation rules, and options come from the backend
|
|
- **Real-Time Updates**: Support for polling and incremental data loading
|
|
- **Permission-Aware**: Backend enforces permissions; frontend handles gracefully
|
|
|
|
---
|
|
|
|
## Customer Journey 1: Discovering and Browsing Workflows
|
|
|
|
### User Goal
|
|
"I want to see all my workflows and find the one I'm looking for."
|
|
|
|
### User Story
|
|
As a user, I want to browse my workflows, search for specific workflows, filter by any field, sort them by different criteria, and quickly identify which ones are running, completed, or have errors.
|
|
|
|
### User Story Flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant User
|
|
participant Frontend
|
|
participant Backend
|
|
|
|
User->>Frontend: Navigate to /workflows
|
|
Frontend->>Backend: GET /api/attributes/ChatWorkflow
|
|
Backend-->>Frontend: Attribute definitions (fields, labels, types)
|
|
Frontend->>Backend: GET /api/workflows/?pagination=...
|
|
Backend-->>Frontend: Paginated workflows list
|
|
Frontend->>Frontend: Generate table columns from attributes
|
|
Frontend->>Frontend: Generate filter controls from attributes
|
|
Frontend->>Frontend: Render workflows table + search + filters
|
|
Frontend-->>User: Display workflow list with search/filter UI
|
|
|
|
alt User performs general search
|
|
User->>Frontend: Type in search box (e.g., "invoice")
|
|
Frontend->>Frontend: Update search query
|
|
Frontend->>Backend: GET /api/workflows/?pagination=...filters:{"search":"invoice"}
|
|
Backend-->>Frontend: Filtered workflows list
|
|
Frontend-->>User: Display matching workflows
|
|
end
|
|
|
|
alt User applies field filter
|
|
User->>Frontend: Select filter field (e.g., "Status")
|
|
Frontend->>Frontend: Show filter options from attribute metadata
|
|
User->>Frontend: Select filter value (e.g., "running")
|
|
Frontend->>Frontend: Update filter parameters
|
|
Frontend->>Backend: GET /api/workflows/?pagination=...filters:{"status":"running"}
|
|
Backend-->>Frontend: Filtered workflows list
|
|
Frontend-->>User: Display filtered workflows
|
|
end
|
|
|
|
alt User combines search + filter + sort
|
|
User->>Frontend: Apply search + filter + sort
|
|
Frontend->>Frontend: Combine all parameters
|
|
Frontend->>Backend: GET /api/workflows/?pagination=...filters:{"search":"invoice","status":"running"}...sort:desc
|
|
Backend-->>Frontend: Filtered, sorted workflows list
|
|
Frontend-->>User: Display results
|
|
end
|
|
|
|
User->>Frontend: Click column header (e.g., "Last Activity")
|
|
Frontend->>Frontend: Update sort parameters
|
|
Frontend->>Backend: GET /api/workflows/?pagination=...sort:desc
|
|
Backend-->>Frontend: Sorted workflows list
|
|
Frontend-->>User: Display sorted workflows
|
|
|
|
User->>Frontend: Click workflow row
|
|
Frontend->>Frontend: Navigate to /workflows/workflowId
|
|
Frontend-->>User: Show workflow detail page
|
|
```
|
|
|
|
|
|
## Customer Journey 2: Viewing Workflow Details
|
|
|
|
### User Goal
|
|
"I want to see everything about a specific workflow—its current state, what it's doing, and what it has accomplished."
|
|
|
|
### User Story
|
|
As a user, I want to open a workflow and see its complete information, including its configuration, current status, messages, and logs, all in one place.
|
|
|
|
### User Story Flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant User
|
|
participant Frontend
|
|
participant Backend
|
|
|
|
User->>Frontend: Click workflow from list
|
|
Frontend->>Backend: GET /api/workflows/workflowId
|
|
Backend-->>Frontend: Workflow object
|
|
|
|
Frontend->>Backend: GET /api/attributes/ChatWorkflow
|
|
Backend-->>Frontend: Field definitions
|
|
|
|
Frontend->>Backend: GET /api/workflows/workflowId/messages
|
|
Backend-->>Frontend: Messages list
|
|
|
|
Frontend->>Backend: GET /api/workflows/workflowId/logs
|
|
Backend-->>Frontend: Logs list
|
|
|
|
Frontend->>Frontend: Generate UI from metadata
|
|
Frontend->>Frontend: Render workflow info section
|
|
Frontend->>Frontend: Render messages section
|
|
Frontend->>Frontend: Render logs section
|
|
Frontend-->>User: Display complete workflow view
|
|
```
|
|
|
|
### Frontend Requirements
|
|
|
|
> **📋 Complete frontend requirements for this journey are documented in [Workflow Page Requirements](./workflow-page-requirements.md#customer-journey-2-viewing-workflow-details)**
|
|
|
|
The frontend must implement a workflow detail page that displays workflow information, messages, and logs. All UI components are generated from backend metadata—see the requirements document for complete details.
|
|
|
|
```typescript
|
|
// Fetch workflow and attributes
|
|
const [workflow, attributes] = await Promise.all([
|
|
fetch(`/api/workflows/${workflowId}`).then(r => r.json()),
|
|
fetch('/api/attributes/ChatWorkflow').then(r => r.json())
|
|
]);
|
|
|
|
// Separate editable vs read-only fields
|
|
const readOnlyFields = attributes.attributes.filter(attr =>
|
|
attr.visible && !attr.editable
|
|
);
|
|
const editableFields = attributes.attributes.filter(attr =>
|
|
attr.visible && attr.editable
|
|
);
|
|
|
|
// Render information section
|
|
<WorkflowInfoSection>
|
|
{readOnlyFields.map(attr => (
|
|
<InfoField
|
|
key={attr.name}
|
|
label={attr.label}
|
|
value={formatValue(workflow[attr.name], attr.type, attr.options)}
|
|
/>
|
|
))}
|
|
</WorkflowInfoSection>
|
|
|
|
// Show edit button if user has editable fields and permission
|
|
{editableFields.length > 0 && (
|
|
<Button onClick={() => navigate(`/workflows/${workflowId}/edit`)}>
|
|
Edit Workflow
|
|
</Button>
|
|
)}
|
|
```
|
|
|
|
---
|
|
|
|
## Customer Journey 3: Monitoring Workflow Execution
|
|
|
|
### User Goal
|
|
"I want to watch my workflow as it runs and see updates in real-time."
|
|
|
|
### User Story
|
|
As a user, I want to monitor a running workflow and see new messages and logs appear automatically without refreshing the page.
|
|
|
|
### User Story Flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant User
|
|
participant Frontend
|
|
participant Backend
|
|
|
|
User->>Frontend: Open workflow detail page
|
|
Frontend->>Backend: GET /api/workflows/id/status
|
|
Backend-->>Frontend: Current workflow status
|
|
Frontend->>Backend: GET /api/workflows/id/messages
|
|
Backend-->>Frontend: Initial messages
|
|
Frontend->>Backend: GET /api/workflows/id/logs
|
|
Backend-->>Frontend: Initial logs
|
|
Frontend-->>User: Display initial state
|
|
|
|
loop Every 5 seconds (if running)
|
|
Frontend->>Backend: GET /api/workflows/id/status
|
|
Backend-->>Frontend: Updated status
|
|
Frontend->>Frontend: Update status badge
|
|
Frontend-->>User: Show status update
|
|
end
|
|
|
|
loop Every 3 seconds (if running)
|
|
Frontend->>Backend: GET /api/workflows/id/messages?messageId=lastId
|
|
Backend-->>Frontend: New messages (if any)
|
|
alt New messages exist
|
|
Frontend->>Frontend: Append to messages list
|
|
Frontend-->>User: Show new messages
|
|
end
|
|
end
|
|
|
|
loop Every 3 seconds (if running)
|
|
Frontend->>Backend: GET /api/workflows/id/logs?logId=lastId
|
|
Backend-->>Frontend: New logs (if any)
|
|
alt New logs exist
|
|
Frontend->>Frontend: Prepend to logs list
|
|
Frontend-->>User: Show new logs
|
|
end
|
|
end
|
|
|
|
Backend->>Backend: Workflow completes
|
|
Frontend->>Backend: GET /api/workflows/id/status
|
|
Backend-->>Frontend: Status: "completed"
|
|
Frontend->>Frontend: Stop all polling
|
|
Frontend-->>User: Show completion message
|
|
```
|
|
|
|
## Customer Journey 4: Editing Workflow Properties
|
|
|
|
### User Goal
|
|
"I want to change workflow settings like its name, status, or mode."
|
|
|
|
### User Story
|
|
As a user, I want to edit a workflow's properties through a form that only shows fields I'm allowed to edit, with validation and clear error messages.
|
|
|
|
### User Story Flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant User
|
|
participant Frontend
|
|
participant Backend
|
|
|
|
User->>Frontend: Click "Edit" button
|
|
Frontend->>Frontend: Navigate to /workflows/id/edit
|
|
|
|
Frontend->>Backend: GET /api/workflows/workflowId
|
|
Backend-->>Frontend: Current workflow data
|
|
|
|
Frontend->>Backend: GET /api/attributes/ChatWorkflow
|
|
Backend-->>Frontend: Field definitions (editable fields)
|
|
|
|
Frontend->>Frontend: Filter editable fields
|
|
Frontend->>Frontend: Generate form from attributes
|
|
Frontend->>Frontend: Pre-populate form with workflow data
|
|
Frontend-->>User: Display edit form
|
|
|
|
User->>Frontend: Modify form fields
|
|
User->>Frontend: Click "Save"
|
|
|
|
Frontend->>Frontend: Validate form (required fields, types)
|
|
alt Validation fails
|
|
Frontend-->>User: Show validation errors
|
|
else Validation passes
|
|
Frontend->>Frontend: Optimistic update: Apply changes to UI immediately
|
|
Frontend->>Backend: PUT /api/workflows/workflowId + form data
|
|
alt Permission denied (403)
|
|
Backend-->>Frontend: 403 Forbidden
|
|
Frontend->>Frontend: Revert optimistic update
|
|
Frontend-->>User: Show permission error
|
|
else Validation error (400)
|
|
Backend-->>Frontend: 400 Bad Request + error details
|
|
Frontend->>Frontend: Revert optimistic update
|
|
Frontend-->>User: Show backend validation errors
|
|
else Success (200)
|
|
Backend-->>Frontend: Updated workflow
|
|
Frontend->>Frontend: Keep optimistic update (or refresh from response)
|
|
Frontend->>Frontend: Navigate to detail page
|
|
Frontend-->>User: Show updated workflow
|
|
end
|
|
end
|
|
```
|
|
## Customer Journey 5: Cleaning Up Workflows
|
|
|
|
### User Goal
|
|
"I want to delete workflows I no longer need."
|
|
|
|
### User Story
|
|
As a user, I want to delete workflows that are completed or no longer useful, with a clear confirmation to prevent accidental deletion.
|
|
|
|
### User Story Flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant User
|
|
participant Frontend
|
|
participant Backend
|
|
|
|
User->>Frontend: Click "Delete" button
|
|
Frontend->>Backend: GET /api/workflows/workflowId
|
|
Backend-->>Frontend: Workflow data (for name)
|
|
Frontend->>Frontend: Show confirmation dialog<br/>"Delete 'Workflow Name'?"
|
|
User->>Frontend: Cancel deletion
|
|
Frontend-->>User: Dialog closed, no action
|
|
|
|
User->>Frontend: Click "Delete" button again
|
|
Frontend->>Backend: GET /api/workflows/workflowId
|
|
Backend-->>Frontend: Workflow data
|
|
Frontend->>Frontend: Show confirmation dialog
|
|
User->>Frontend: Confirm deletion
|
|
Frontend->>Frontend: Optimistic update: Remove workflow from UI immediately
|
|
Frontend->>Backend: DELETE /api/workflows/workflowId
|
|
|
|
alt Permission denied (403)
|
|
Backend-->>Frontend: 403 Forbidden
|
|
Frontend->>Frontend: Revert optimistic update: Restore workflow in UI
|
|
Frontend-->>User: Show permission error
|
|
else Not found (404)
|
|
Backend-->>Frontend: 404 Not Found
|
|
Frontend->>Frontend: Revert optimistic update: Restore workflow in UI
|
|
Frontend-->>User: Show not found error
|
|
else Success (200)
|
|
Backend-->>Frontend: Success response
|
|
Frontend->>Frontend: Keep optimistic update (workflow already removed)
|
|
Frontend->>Frontend: Show success message
|
|
Frontend->>Frontend: Navigate to /workflows (if on detail page)
|
|
Frontend-->>User: Display workflow list (without deleted workflow)
|
|
end
|
|
```
|
|
|
|
### Frontend Requirements
|
|
|
|
> **📋 Complete frontend requirements for this journey are documented in [Workflow Page Requirements](./workflow-page-requirements.md#customer-journey-6-cleaning-up-workflows)**
|
|
|
|
The frontend must implement workflow deletion with confirmation dialogs and proper error handling. See the requirements document for complete details.
|
|
|
|
### Backend Routes Used
|
|
|
|
| Route | Purpose | When Used |
|
|
|-------|---------|-----------|
|
|
| `DELETE /api/workflows/{workflowId}` | Delete workflow | User confirmation |
|
|
|
|
### User Navigation Flow
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> WorkflowDetailPage
|
|
|
|
WorkflowDetailPage --> ViewingDeleteButton: Hover delete button
|
|
ViewingDeleteButton --> ClickingDelete: Click delete
|
|
|
|
ClickingDelete --> FetchingWorkflowName: Fetch workflow name
|
|
FetchingWorkflowName --> ShowingConfirmation: Show dialog
|
|
|
|
ShowingConfirmation --> WorkflowDetailPage: Cancel
|
|
ShowingConfirmation --> ConfirmingDeletion: Confirm
|
|
|
|
ConfirmingDeletion --> DeletingWorkflow: API call
|
|
DeletingWorkflow --> WorkflowListPage: Success
|
|
DeletingWorkflow --> ShowingError: Error (403/404)
|
|
|
|
ShowingError --> WorkflowDetailPage: Dismiss error
|
|
|
|
note right of ShowingConfirmation
|
|
ConfirmationDialog Component:
|
|
- Workflow name
|
|
- Warning message
|
|
- Cancel/Confirm buttons
|
|
end note
|
|
|
|
note right of DeletingWorkflow
|
|
DELETE /api/workflows/{id}
|
|
- Backend deletes workflow
|
|
- Deletes all associated data
|
|
- Returns success response
|
|
end note
|
|
|
|
note right of WorkflowListPage
|
|
List automatically refreshes
|
|
Deleted workflow removed
|
|
Success message shown
|
|
end note
|
|
```
|
|
|
|
### Example User Flow
|
|
|
|
```
|
|
1. User is on workflow detail page
|
|
2. User clicks "Delete" button
|
|
3. Frontend shows confirmation dialog: "Are you sure you want to delete 'Workflow Name'? This action cannot be undone."
|
|
4. User confirms deletion
|
|
5. Frontend calls: DELETE /api/workflows/{workflowId}
|
|
6. Backend deletes workflow and all associated data
|
|
7. Frontend receives success response
|
|
8. Frontend shows success message: "Workflow deleted successfully"
|
|
9. Frontend redirects to /workflows (list page)
|
|
10. User sees updated workflow list (without deleted workflow)
|
|
```
|
|
|
|
### Implementation Pattern
|
|
|
|
```typescript
|
|
function WorkflowDetailPage({ workflowId }) {
|
|
const navigate = useNavigate();
|
|
|
|
const handleDelete = async () => {
|
|
// Fetch workflow name for confirmation
|
|
const workflow = await fetch(`/api/workflows/${workflowId}`).then(r => r.json());
|
|
|
|
if (!confirm(`Are you sure you want to delete "${workflow.name || 'this workflow'}"? This action cannot be undone.`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/workflows/${workflowId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (response.status === 403) {
|
|
showError('You do not have permission to delete this workflow');
|
|
return;
|
|
}
|
|
|
|
if (response.status === 404) {
|
|
showError('Workflow not found');
|
|
return;
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
showError(error.detail || 'Failed to delete workflow');
|
|
return;
|
|
}
|
|
|
|
showSuccess('Workflow deleted successfully');
|
|
navigate('/workflows');
|
|
} catch (error) {
|
|
showError('Failed to delete workflow');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
{/* Workflow content */}
|
|
<Button onClick={handleDelete} variant="danger">
|
|
Delete Workflow
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Backend Metadata System
|
|
|
|
### How Metadata Works
|
|
|
|
The backend serves all UI metadata through the `/api/attributes/{entityType}` endpoint. This enables completely dynamic frontend generation with no hardcoding.
|
|
|
|
#### Attribute Definition Structure
|
|
|
|
```typescript
|
|
interface AttributeDefinition {
|
|
name: string; // Field name (e.g., "status", "name")
|
|
type: string; // Frontend type: "text", "select", "integer", "timestamp", "checkbox"
|
|
label: string; // Display label (localized to user's language)
|
|
description: string; // Field description
|
|
required: boolean; // Whether field is required
|
|
readonly: boolean; // Whether field is read-only
|
|
editable: boolean; // Whether field can be edited (inverse of readonly)
|
|
visible: boolean; // Whether field should be displayed
|
|
order: number; // Display order (lower = earlier)
|
|
placeholder: string; // Placeholder text for inputs
|
|
options?: Option[]; // Options for select fields (with localized labels)
|
|
default?: any; // Default value
|
|
}
|
|
|
|
interface Option {
|
|
value: string; // Option value (e.g., "running")
|
|
label: { // Localized labels
|
|
[language: string]: string; // e.g., {"en": "Running", "fr": "En cours"}
|
|
};
|
|
}
|
|
```
|
|
|
|
#### Fetching Metadata
|
|
|
|
```typescript
|
|
// Fetch attribute definitions for ChatWorkflow
|
|
const response = await fetch('/api/attributes/ChatWorkflow');
|
|
const data = await response.json();
|
|
const attributes = data.attributes; // AttributeDefinition[]
|
|
```
|
|
|
|
#### Using Metadata for Forms
|
|
|
|
```typescript
|
|
// Generate form fields from attributes
|
|
const formFields = attributes
|
|
.filter(attr => attr.visible && attr.editable) // Only editable, visible fields
|
|
.sort((a, b) => a.order - b.order) // Sort by order
|
|
.map(attr => ({
|
|
name: attr.name,
|
|
label: attr.label,
|
|
type: attr.type,
|
|
required: attr.required,
|
|
options: attr.options,
|
|
placeholder: attr.placeholder,
|
|
default: attr.default
|
|
}));
|
|
```
|
|
|
|
#### Using Metadata for Tables
|
|
|
|
```typescript
|
|
// Generate table columns from attributes
|
|
const tableColumns = attributes
|
|
.filter(attr => attr.visible) // Only visible fields
|
|
.sort((a, b) => a.order - b.order) // Sort by order
|
|
.map(attr => ({
|
|
key: attr.name,
|
|
label: attr.label,
|
|
type: attr.type,
|
|
sortable: true, // Can be made configurable
|
|
render: (value) => formatValue(value, attr.type, attr.options)
|
|
}));
|
|
```
|
|
|
|
### Field Type Handling
|
|
|
|
Different field types require different UI components:
|
|
|
|
| Type | UI Component | Example Values |
|
|
|------|--------------|----------------|
|
|
| `text` | Text input | "My Workflow" |
|
|
| `textarea` | Textarea | Long descriptions |
|
|
| `integer` | Number input | 5, 10, 100 |
|
|
| `select` | Dropdown | Options from `options` array |
|
|
| `checkbox` | Checkbox | true/false |
|
|
| `timestamp` | Date/time picker | Unix timestamp |
|
|
|
|
### Localization
|
|
|
|
All labels and option labels support multiple languages. The backend serves labels in the user's language (determined by authentication context). The frontend should:
|
|
|
|
1. Use `label` directly (already localized)
|
|
2. For select options, use `option.label[userLanguage]` or fallback to `option.value`
|
|
|
|
---
|
|
|
|
## Implementation Patterns
|
|
|
|
### Pattern 1: Dynamic Table Component
|
|
|
|
```typescript
|
|
function DynamicTable({ entityType, data, pagination, onSort, onPageChange, onRowClick }) {
|
|
const [attributes, setAttributes] = useState([]);
|
|
|
|
useEffect(() => {
|
|
fetch(`/api/attributes/${entityType}`)
|
|
.then(r => r.json())
|
|
.then(data => setAttributes(data.attributes.filter(attr => attr.visible)));
|
|
}, [entityType]);
|
|
|
|
const columns = attributes
|
|
.sort((a, b) => a.order - b.order)
|
|
.map(attr => ({
|
|
key: attr.name,
|
|
label: attr.label,
|
|
type: attr.type,
|
|
render: (value) => formatValue(value, attr.type, attr.options)
|
|
}));
|
|
|
|
return (
|
|
<Table>
|
|
<thead>
|
|
<tr>
|
|
{columns.map(col => (
|
|
<th key={col.key} onClick={() => handleSort(col.key)}>
|
|
{col.label}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{data.map(row => (
|
|
<tr key={row.id} onClick={() => onRowClick?.(row)}>
|
|
{columns.map(col => (
|
|
<td key={col.key}>{col.render(row[col.key])}</td>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</Table>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Pattern 2: Dynamic Form Component
|
|
|
|
```typescript
|
|
function DynamicForm({ entityType, initialValues, onSubmit, onCancel }) {
|
|
const [attributes, setAttributes] = useState([]);
|
|
const [formData, setFormData] = useState(initialValues || {});
|
|
const [errors, setErrors] = useState({});
|
|
|
|
useEffect(() => {
|
|
fetch(`/api/attributes/${entityType}`)
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
const editable = data.attributes.filter(attr => attr.visible && attr.editable);
|
|
setAttributes(editable);
|
|
});
|
|
}, [entityType]);
|
|
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
|
|
// Validate
|
|
const newErrors = {};
|
|
attributes.forEach(attr => {
|
|
if (attr.required && !formData[attr.name]) {
|
|
newErrors[attr.name] = `${attr.label} is required`;
|
|
}
|
|
});
|
|
|
|
if (Object.keys(newErrors).length > 0) {
|
|
setErrors(newErrors);
|
|
return;
|
|
}
|
|
|
|
onSubmit(formData);
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit}>
|
|
{attributes
|
|
.sort((a, b) => a.order - b.order)
|
|
.map(attr => (
|
|
<FormField
|
|
key={attr.name}
|
|
field={attr}
|
|
value={formData[attr.name] ?? attr.default}
|
|
onChange={(value) => setFormData(prev => ({ ...prev, [attr.name]: value }))}
|
|
error={errors[attr.name]}
|
|
/>
|
|
))}
|
|
<button type="submit">Save</button>
|
|
{onCancel && <button type="button" onClick={onCancel}>Cancel</button>}
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Pattern 3: Polling Hook
|
|
|
|
```typescript
|
|
function usePolling(url, interval, condition = true) {
|
|
const [data, setData] = useState(null);
|
|
|
|
useEffect(() => {
|
|
if (!condition) return;
|
|
|
|
const fetchData = async () => {
|
|
try {
|
|
const response = await fetch(url);
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
setData(result);
|
|
}
|
|
} catch (error) {
|
|
console.error('Polling error:', error);
|
|
}
|
|
};
|
|
|
|
fetchData(); // Initial fetch
|
|
const intervalId = setInterval(fetchData, interval);
|
|
|
|
return () => clearInterval(intervalId);
|
|
}, [url, interval, condition]);
|
|
|
|
return data;
|
|
}
|
|
|
|
// Usage
|
|
const workflow = usePolling(
|
|
`/api/workflows/${workflowId}/status`,
|
|
5000,
|
|
workflow?.status === 'running'
|
|
);
|
|
```
|
|
|
|
### Pattern 4: Incremental Data Loading
|
|
|
|
```typescript
|
|
function useIncrementalData(baseUrl, lastIdKey) {
|
|
const [items, setItems] = useState([]);
|
|
const [lastId, setLastId] = useState(null);
|
|
|
|
const loadInitial = async () => {
|
|
const response = await fetch(baseUrl).then(r => r.json());
|
|
const newItems = response.items || response;
|
|
setItems(newItems);
|
|
if (newItems.length > 0) {
|
|
setLastId(newItems[newItems.length - 1][lastIdKey]);
|
|
}
|
|
};
|
|
|
|
const loadNew = async () => {
|
|
if (!lastId) return;
|
|
|
|
const url = `${baseUrl}?${lastIdKey}=${lastId}`;
|
|
const response = await fetch(url).then(r => r.json());
|
|
const newItems = response.items || response;
|
|
|
|
if (newItems.length > 0) {
|
|
setItems(prev => [...prev, ...newItems]);
|
|
setLastId(newItems[newItems.length - 1][lastIdKey]);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadInitial();
|
|
}, [baseUrl]);
|
|
|
|
return { items, loadNew };
|
|
}
|
|
|
|
// Usage for messages
|
|
const { items: messages, loadNew: loadNewMessages } = useIncrementalData(
|
|
`/api/workflows/${workflowId}/messages`,
|
|
'messageId'
|
|
);
|
|
|
|
// Poll for new messages
|
|
useEffect(() => {
|
|
if (workflow?.status !== 'running') return;
|
|
|
|
const interval = setInterval(loadNewMessages, 3000);
|
|
return () => clearInterval(interval);
|
|
}, [workflow?.status, loadNewMessages]);
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
This documentation focuses on **customer journeys**—what users want to accomplish with workflows. Each journey describes:
|
|
|
|
1. **User Goal**: What the user is trying to achieve
|
|
2. **User Story**: The scenario from the user's perspective
|
|
3. **Frontend Requirements**: What pages/components are needed
|
|
4. **Backend Routes**: Which API endpoints support the journey
|
|
5. **Example Flows**: Step-by-step user interactions
|
|
6. **Implementation Patterns**: Code examples for common patterns
|
|
|
|
The key principle throughout: **Everything is dynamic and driven by backend metadata**. The frontend should never hardcode field definitions, validation rules, labels, or UI structure. All of this comes from the backend's attribute definition system.
|
|
|
|
By following these customer journeys and using the backend metadata system, you can build a frontend that:
|
|
- Adapts automatically to backend changes
|
|
- Supports multiple languages
|
|
- Enforces permissions correctly
|
|
- Provides real-time updates
|
|
- Requires minimal maintenance
|