1677 lines
53 KiB
Markdown
1677 lines
53 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
|
|
```
|
|
|
|
|
|
### Frontend Requirements
|
|
|
|
#### Page: Workflow List (`/workflows`)
|
|
|
|
**What the user sees:**
|
|
- A table or card grid showing all workflows
|
|
- Each workflow shows: name, status, last activity, progress indicators
|
|
- General search box (searches across all fields)
|
|
- Field-specific filter controls (one filter per visible field)
|
|
- Sorting controls (by name, status, last activity, etc.)
|
|
- Pagination controls (if many workflows)
|
|
- Active filter indicators (showing which filters are applied)
|
|
|
|
**What the frontend needs to do:**
|
|
|
|
1. **Fetch Workflow List**
|
|
```
|
|
GET /api/workflows/?pagination={"page":1,"pageSize":20,"sort":[],"filters":null}
|
|
```
|
|
- If user wants all workflows: omit `pagination` parameter
|
|
- If user wants paginated view: include `pagination` JSON
|
|
- Include `filters` in pagination object for search and field filtering
|
|
- Filters structure: `{"search":"query","fieldName":"value",...}`
|
|
|
|
2. **Fetch Field Definitions**
|
|
```
|
|
GET /api/attributes/ChatWorkflow
|
|
```
|
|
- Use this to generate table columns dynamically
|
|
- Determine which fields are visible, sortable, etc.
|
|
|
|
3. **Render Dynamic Table**
|
|
- Generate columns from attribute definitions (filter `visible: true`)
|
|
- Use `label` for column headers (localized)
|
|
- Format values based on `type`:
|
|
- `status` → Show as badge with color coding
|
|
- `lastActivity` → Format as relative time ("2 hours ago")
|
|
- `name` → Display as link to detail page
|
|
- Support sorting: when user clicks column header, update `sort` in pagination and refetch
|
|
|
|
4. **Handle Pagination**
|
|
- Display pagination controls using `pagination` metadata from response
|
|
- Show "Page X of Y" and "Showing 1-20 of 45 workflows"
|
|
- Handle page changes: update `page` in pagination params and refetch
|
|
|
|
5. **Handle General Search**
|
|
- Display search input box (searches across all text fields)
|
|
- When user types: update `filters.search` in pagination params
|
|
- Debounce search input (wait 300-500ms after user stops typing)
|
|
- Reset to page 1 when search changes
|
|
- Refetch workflows with search filter applied
|
|
|
|
6. **Handle Field Filtering**
|
|
- Generate filter controls dynamically from attribute definitions
|
|
- For each visible field, show appropriate filter UI:
|
|
- `text` fields → Text input filter
|
|
- `select` fields → Dropdown with options from metadata
|
|
- `integer` fields → Number range filter (min/max)
|
|
- `timestamp` fields → Date range filter
|
|
- `checkbox` fields → Boolean toggle filter
|
|
- When user applies filter: update `filters[fieldName]` in pagination params
|
|
- Support multiple filters simultaneously
|
|
- Show active filter indicators (chips/badges)
|
|
- Allow removing individual filters
|
|
- Reset to page 1 when filters change
|
|
- Refetch workflows with filters applied
|
|
|
|
7. **Handle Combined Search + Filter + Sort**
|
|
- All parameters work together: search + filters + sort + pagination
|
|
- When any parameter changes, combine all and refetch
|
|
- Preserve all active filters when sorting or changing pages
|
|
|
|
8. **Handle Row Clicks**
|
|
- Navigate to workflow detail page: `/workflows/{workflowId}`
|
|
|
|
### Backend Routes Used
|
|
|
|
| Route | Purpose | When Used |
|
|
|-------|---------|-----------|
|
|
| `GET /api/workflows/` | Get all workflows | Initial page load, pagination changes, sort changes, filter changes, search changes |
|
|
| `GET /api/attributes/ChatWorkflow` | Get field definitions | Page load (once) - used to generate columns, filters, and search UI |
|
|
|
|
### Example User Flow
|
|
|
|
```
|
|
1. User navigates to /workflows
|
|
2. Frontend fetches: GET /api/attributes/ChatWorkflow
|
|
3. Frontend fetches: GET /api/workflows/?pagination={"page":1,"pageSize":20,"sort":[],"filters":null}
|
|
4. Frontend generates table columns from attributes
|
|
5. Frontend generates filter controls from attributes (one per visible field)
|
|
6. Frontend renders workflows table with search box and filter controls
|
|
7. User types "invoice" in search box
|
|
8. Frontend debounces (waits 400ms), then updates filters: {"search":"invoice"}
|
|
9. Frontend refetches: GET /api/workflows/?pagination={"page":1,"pageSize":20,"sort":[],"filters":{"search":"invoice"}}
|
|
10. Frontend displays matching workflows
|
|
11. User selects "Status" filter dropdown, chooses "running"
|
|
12. Frontend updates filters: {"search":"invoice","status":"running"}
|
|
13. Frontend refetches with combined filters
|
|
14. Frontend displays filtered results
|
|
15. User clicks "Last Activity" column header to sort
|
|
16. Frontend updates sort while preserving filters: {"search":"invoice","status":"running"} + sort
|
|
17. Frontend refetches with filters + sort
|
|
18. User clicks on a workflow row
|
|
19. Frontend navigates to /workflows/{workflowId}
|
|
```
|
|
|
|
### Dynamic UI Generation
|
|
|
|
```typescript
|
|
// Fetch attribute definitions
|
|
const attributes = await fetch('/api/attributes/ChatWorkflow').then(r => r.json());
|
|
|
|
// Filter visible, sortable columns
|
|
const tableColumns = attributes.attributes
|
|
.filter(attr => attr.visible)
|
|
.sort((a, b) => a.order - b.order)
|
|
.map(attr => ({
|
|
key: attr.name,
|
|
label: attr.label, // Localized label
|
|
type: attr.type,
|
|
sortable: true, // Can be made configurable
|
|
render: (value) => formatValue(value, attr.type, attr.options)
|
|
}));
|
|
|
|
// Generate filter controls from attributes
|
|
const filterControls = attributes.attributes
|
|
.filter(attr => attr.visible)
|
|
.map(attr => ({
|
|
field: attr.name,
|
|
label: attr.label,
|
|
type: attr.type,
|
|
options: attr.options, // For select fields
|
|
component: getFilterComponent(attr.type) // Returns appropriate filter UI component
|
|
}));
|
|
|
|
// Generate table from columns
|
|
<DynamicTable
|
|
columns={tableColumns}
|
|
data={workflows}
|
|
onSort={handleSort}
|
|
onRowClick={(workflow) => navigate(`/workflows/${workflow.id}`)}
|
|
/>
|
|
|
|
// Render search and filters
|
|
<SearchBox
|
|
value={searchQuery}
|
|
onChange={(query) => handleSearch(query)}
|
|
placeholder="Search workflows..."
|
|
/>
|
|
|
|
<FilterPanel>
|
|
{filterControls.map(filter => (
|
|
<FilterControl
|
|
key={filter.field}
|
|
filter={filter}
|
|
value={activeFilters[filter.field]}
|
|
onChange={(value) => handleFilterChange(filter.field, value)}
|
|
/>
|
|
))}
|
|
</FilterPanel>
|
|
```
|
|
|
|
---
|
|
|
|
## 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
|
|
|
|
#### Page: Workflow Detail (`/workflows/{workflowId}`)
|
|
|
|
**What the user sees:**
|
|
- Workflow header: name, status badge, key metrics
|
|
- Workflow information section: all workflow properties
|
|
- Messages section: conversation/execution history
|
|
- Logs section: execution logs with filtering
|
|
- Action buttons: Edit, Delete (if user has permission)
|
|
|
|
**What the frontend needs to do:**
|
|
|
|
1. **Fetch Workflow Data**
|
|
```
|
|
GET /api/workflows/{workflowId}
|
|
```
|
|
- Get complete workflow object
|
|
- Includes nested data (messages, logs, stats) if available
|
|
|
|
2. **Fetch Field Definitions**
|
|
```
|
|
GET /api/attributes/ChatWorkflow
|
|
```
|
|
- Use to render workflow information section
|
|
- Determine which fields are editable vs read-only
|
|
|
|
3. **Fetch Messages** (if not included in workflow object)
|
|
```
|
|
GET /api/workflows/{workflowId}/messages
|
|
```
|
|
- Get all messages for the workflow
|
|
- Support pagination if many messages
|
|
|
|
4. **Fetch Logs** (if not included in workflow object)
|
|
```
|
|
GET /api/workflows/{workflowId}/logs
|
|
```
|
|
- Get all logs for the workflow
|
|
- Support filtering by log type
|
|
|
|
5. **Render Workflow Information**
|
|
- Display read-only fields as formatted text
|
|
- Show editable fields with edit indicators (if user has permission)
|
|
- Format special fields:
|
|
- `status` → Status badge with color
|
|
- `lastActivity`, `startedAt` → Formatted timestamps
|
|
- `workflowMode` → Display mode label (not enum value)
|
|
- Progress fields → Progress bars
|
|
|
|
6. **Render Messages Section**
|
|
- Display messages in chronological order
|
|
- Show message role (user/assistant/system)
|
|
- Display attached documents/files
|
|
- Support threading if `parentMessageId` exists
|
|
|
|
7. **Render Logs Section**
|
|
- Display logs in reverse chronological order (newest first)
|
|
- Color-code by type (info/warning/error)
|
|
- Show progress indicators if `progress` field exists
|
|
- Filter by log type (if implemented)
|
|
|
|
### Backend Routes Used
|
|
|
|
| Route | Purpose | When Used |
|
|
|-------|---------|-----------|
|
|
| `GET /api/workflows/{workflowId}` | Get workflow details | Page load |
|
|
| `GET /api/workflows/{workflowId}/messages` | Get workflow messages | Page load, refresh |
|
|
| `GET /api/workflows/{workflowId}/logs` | Get workflow logs | Page load, refresh |
|
|
| `GET /api/attributes/ChatWorkflow` | Get field definitions | Page load (once) |
|
|
|
|
### User Navigation Flow
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> WorkflowListPage
|
|
|
|
WorkflowListPage --> WorkflowDetailPage: Click workflow
|
|
|
|
WorkflowDetailPage --> WorkflowListPage: Click back
|
|
WorkflowDetailPage --> WorkflowEditPage: Click Edit
|
|
WorkflowDetailPage --> WorkflowDetailPage: View messages
|
|
WorkflowDetailPage --> WorkflowDetailPage: View logs
|
|
WorkflowDetailPage --> WorkflowDetailPage: Delete workflow
|
|
|
|
WorkflowEditPage --> WorkflowDetailPage: Save or Cancel
|
|
|
|
note right of WorkflowDetailPage
|
|
Component Structure:
|
|
- WorkflowHeader (name, status, metrics)
|
|
- WorkflowInfoSection (all fields)
|
|
- MessageList (chronological)
|
|
- LogViewer (reverse chronological)
|
|
- ActionButtons (edit, delete)
|
|
end note
|
|
|
|
note right of WorkflowDetailPage: User can scroll through<br/>messages and logs<br/>without leaving page
|
|
```
|
|
|
|
### Example User Flow
|
|
|
|
```
|
|
1. User clicks workflow from list → navigates to /workflows/{workflowId}
|
|
2. Frontend fetches: GET /api/workflows/{workflowId}
|
|
3. Frontend fetches: GET /api/attributes/ChatWorkflow
|
|
4. Frontend fetches: GET /api/workflows/{workflowId}/messages
|
|
5. Frontend fetches: GET /api/workflows/{workflowId}/logs
|
|
6. Frontend renders:
|
|
- Workflow header with status badge
|
|
- Information section (read-only fields from attributes)
|
|
- Messages list (chronological)
|
|
- Logs viewer (reverse chronological)
|
|
7. User sees complete workflow state
|
|
```
|
|
|
|
### Dynamic UI Generation
|
|
|
|
```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
|
|
```
|
|
|
|
### Frontend Requirements
|
|
|
|
#### Component: Real-Time Workflow Monitor
|
|
|
|
**What the user sees:**
|
|
- Status indicator that updates automatically
|
|
- New messages appearing in the messages list
|
|
- New logs appearing in the logs viewer
|
|
- Progress indicators updating
|
|
- Activity indicators showing workflow is active
|
|
|
|
**What the frontend needs to do:**
|
|
|
|
1. **Initial Load**
|
|
- Fetch workflow status: `GET /api/workflows/{workflowId}/status`
|
|
- Fetch messages: `GET /api/workflows/{workflowId}/messages`
|
|
- Fetch logs: `GET /api/workflows/{workflowId}/logs`
|
|
|
|
2. **Poll for Status Updates**
|
|
```
|
|
GET /api/workflows/{workflowId}/status
|
|
```
|
|
- Poll every 2-5 seconds if workflow status is "running"
|
|
- Stop polling if workflow status is "completed", "stopped", or "error"
|
|
- Update status badge and workflow information
|
|
|
|
3. **Poll for New Messages** (Incremental Loading)
|
|
```
|
|
GET /api/workflows/{workflowId}/messages?messageId={lastMessageId}
|
|
```
|
|
- Store ID of last displayed message
|
|
- Poll every 2-3 seconds for messages after last message ID
|
|
- Append new messages to list (don't reload all)
|
|
- Only poll if workflow is "running"
|
|
|
|
4. **Poll for New Logs** (Incremental Loading)
|
|
```
|
|
GET /api/workflows/{workflowId}/logs?logId={lastLogId}
|
|
```
|
|
- Store ID of last displayed log
|
|
- Poll every 2-3 seconds for logs after last log ID
|
|
- Prepend new logs to list (newest first)
|
|
- Only poll if workflow is "running"
|
|
|
|
5. **Handle Workflow Completion**
|
|
- Stop all polling when status changes to "completed" or "stopped"
|
|
- Show completion message
|
|
- Update UI to indicate workflow is finished
|
|
|
|
### Backend Routes Used
|
|
|
|
| Route | Purpose | When Used |
|
|
|-------|---------|-----------|
|
|
| `GET /api/workflows/{workflowId}/status` | Get current status | Polling (every 2-5s) |
|
|
| `GET /api/workflows/{workflowId}/messages?messageId={id}` | Get new messages | Polling (every 2-3s) |
|
|
| `GET /api/workflows/{workflowId}/logs?logId={id}` | Get new logs | Polling (every 2-3s) |
|
|
|
|
### User Interaction Flow
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> ViewingWorkflow
|
|
|
|
ViewingWorkflow --> MonitoringActive: Workflow is running
|
|
|
|
MonitoringActive --> UpdatingStatus: Poll status (5s)
|
|
MonitoringActive --> UpdatingMessages: Poll messages (3s)
|
|
MonitoringActive --> UpdatingLogs: Poll logs (3s)
|
|
|
|
UpdatingStatus --> MonitoringActive: Continue if running
|
|
UpdatingStatus --> WorkflowCompleted: Status = completed
|
|
|
|
UpdatingMessages --> MonitoringActive: Continue if running
|
|
UpdatingMessages --> WorkflowCompleted: Status = completed
|
|
|
|
UpdatingLogs --> MonitoringActive: Continue if running
|
|
UpdatingLogs --> WorkflowCompleted: Status = completed
|
|
|
|
WorkflowCompleted --> ViewingWorkflow: Stop all polling
|
|
|
|
note right of MonitoringActive
|
|
User sees real-time updates:
|
|
- Status badge changes
|
|
- New messages appear
|
|
- New logs appear
|
|
- Progress bars update
|
|
end note
|
|
|
|
note right of WorkflowCompleted
|
|
All polling stops
|
|
Completion message shown
|
|
UI updates to final state
|
|
end note
|
|
```
|
|
|
|
### Example User Flow
|
|
|
|
```
|
|
1. User opens workflow detail page for running workflow
|
|
2. Frontend loads initial data (workflow, messages, logs)
|
|
3. Frontend starts polling:
|
|
- Status: every 5 seconds
|
|
- Messages: every 3 seconds (with lastMessageId)
|
|
- Logs: every 3 seconds (with lastLogId)
|
|
4. User watches as:
|
|
- Status updates (if changed)
|
|
- New messages appear at bottom of list
|
|
- New logs appear at top of list
|
|
- Progress bars update
|
|
5. Workflow completes → status changes to "completed"
|
|
6. Frontend stops all polling
|
|
7. Frontend shows "Workflow completed" message
|
|
```
|
|
|
|
### Implementation Pattern
|
|
|
|
```typescript
|
|
function useWorkflowMonitoring(workflowId: string) {
|
|
const [workflow, setWorkflow] = useState(null);
|
|
const [messages, setMessages] = useState([]);
|
|
const [logs, setLogs] = useState([]);
|
|
const [lastMessageId, setLastMessageId] = useState(null);
|
|
const [lastLogId, setLastLogId] = useState(null);
|
|
|
|
// Status polling
|
|
useEffect(() => {
|
|
if (!workflow || workflow.status === 'completed' || workflow.status === 'stopped') {
|
|
return; // Stop polling if completed
|
|
}
|
|
|
|
const interval = setInterval(async () => {
|
|
const updated = await fetch(`/api/workflows/${workflowId}/status`).then(r => r.json());
|
|
setWorkflow(updated);
|
|
}, 5000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [workflowId, workflow?.status]);
|
|
|
|
// Message polling (incremental)
|
|
useEffect(() => {
|
|
if (!workflow || workflow.status !== 'running') return;
|
|
|
|
const interval = setInterval(async () => {
|
|
const url = lastMessageId
|
|
? `/api/workflows/${workflowId}/messages?messageId=${lastMessageId}`
|
|
: `/api/workflows/${workflowId}/messages`;
|
|
|
|
const response = await fetch(url).then(r => r.json());
|
|
if (response.items.length > 0) {
|
|
setMessages(prev => [...prev, ...response.items]);
|
|
setLastMessageId(response.items[response.items.length - 1].id);
|
|
}
|
|
}, 3000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [workflowId, workflow?.status, lastMessageId]);
|
|
|
|
// Log polling (incremental)
|
|
useEffect(() => {
|
|
if (!workflow || workflow.status !== 'running') return;
|
|
|
|
const interval = setInterval(async () => {
|
|
const url = lastLogId
|
|
? `/api/workflows/${workflowId}/logs?logId=${lastLogId}`
|
|
: `/api/workflows/${workflowId}/logs`;
|
|
|
|
const response = await fetch(url).then(r => r.json());
|
|
if (response.items.length > 0) {
|
|
setLogs(prev => [...response.items, ...prev]); // Prepend (newest first)
|
|
setLastLogId(response.items[0].id);
|
|
}
|
|
}, 3000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [workflowId, workflow?.status, lastLogId]);
|
|
|
|
return { workflow, messages, logs };
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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->>Backend: PUT /api/workflows/workflowId + form data
|
|
alt Permission denied (403)
|
|
Backend-->>Frontend: 403 Forbidden
|
|
Frontend-->>User: Show permission error
|
|
else Validation error (400)
|
|
Backend-->>Frontend: 400 Bad Request + error details
|
|
Frontend-->>User: Show backend validation errors
|
|
else Success (200)
|
|
Backend-->>Frontend: Updated workflow
|
|
Frontend->>Frontend: Navigate to detail page
|
|
Frontend-->>User: Show updated workflow
|
|
end
|
|
end
|
|
```
|
|
|
|
### Frontend Requirements
|
|
|
|
#### Page: Workflow Edit (`/workflows/{workflowId}/edit`)
|
|
|
|
**What the user sees:**
|
|
- Form with editable workflow fields
|
|
- Only fields the user can edit (based on backend metadata)
|
|
- Required field indicators
|
|
- Validation errors (if any)
|
|
- Save and Cancel buttons
|
|
|
|
**What the frontend needs to do:**
|
|
|
|
1. **Fetch Workflow Data**
|
|
```
|
|
GET /api/workflows/{workflowId}
|
|
```
|
|
- Get current workflow values
|
|
- Pre-populate form
|
|
|
|
2. **Fetch Field Definitions**
|
|
```
|
|
GET /api/attributes/ChatWorkflow
|
|
```
|
|
- Filter to only editable fields (`editable: true`)
|
|
- Use to generate form fields dynamically
|
|
- Get validation rules (required, type, options)
|
|
|
|
3. **Generate Dynamic Form**
|
|
- Create form fields from attribute definitions
|
|
- Use appropriate input types based on `type`:
|
|
- `text` → Text input
|
|
- `select` → Dropdown with `options`
|
|
- `integer` → Number input
|
|
- `checkbox` → Checkbox
|
|
- Show required indicators for `required: true` fields
|
|
- Use `label` for field labels (localized)
|
|
- Use `placeholder` for placeholder text
|
|
|
|
4. **Client-Side Validation**
|
|
- Validate required fields before submit
|
|
- Validate types (e.g., integer fields must be numbers)
|
|
- Validate select fields (value must be in options)
|
|
|
|
5. **Submit Changes**
|
|
```
|
|
PUT /api/workflows/{workflowId}
|
|
```
|
|
- Send only changed fields (or all form data)
|
|
- Handle 403 (permission denied) gracefully
|
|
- Handle 400 (validation errors) and display errors
|
|
- On success: navigate back to detail page or show success message
|
|
|
|
### Backend Routes Used
|
|
|
|
| Route | Purpose | When Used |
|
|
|-------|---------|-----------|
|
|
| `GET /api/workflows/{workflowId}` | Get workflow to edit | Page load |
|
|
| `GET /api/attributes/ChatWorkflow` | Get editable field definitions | Page load |
|
|
| `PUT /api/workflows/{workflowId}` | Update workflow | Form submit |
|
|
|
|
### User Navigation Flow
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> WorkflowDetailPage
|
|
|
|
WorkflowDetailPage --> WorkflowEditPage: Click Edit button
|
|
|
|
WorkflowEditPage --> EditingForm: Form loaded
|
|
EditingForm --> EditingForm: Modify fields
|
|
EditingForm --> ValidatingForm: Click Save
|
|
|
|
ValidatingForm --> EditingForm: Validation errors
|
|
ValidatingForm --> SubmittingForm: All valid
|
|
|
|
SubmittingForm --> WorkflowDetailPage: Success
|
|
SubmittingForm --> EditingForm: Backend errors
|
|
|
|
WorkflowEditPage --> WorkflowDetailPage: Click Cancel
|
|
|
|
note right of EditingForm
|
|
Components:
|
|
- DynamicForm (generated from metadata)
|
|
- FormFields (text, select, number, etc.)
|
|
- ValidationErrors (inline)
|
|
end note
|
|
|
|
note right of ValidatingForm
|
|
Client-side validation:
|
|
- Required fields
|
|
- Type checking
|
|
- Format validation
|
|
end note
|
|
|
|
note right of SubmittingForm
|
|
Backend validation:
|
|
- Permission check
|
|
- Server-side rules
|
|
- Data integrity
|
|
end note
|
|
```
|
|
|
|
### Example User Flow
|
|
|
|
```
|
|
1. User clicks "Edit" button on workflow detail page
|
|
2. Frontend navigates to /workflows/{workflowId}/edit
|
|
3. Frontend fetches workflow and attributes
|
|
4. Frontend generates form from editable attributes
|
|
5. Frontend pre-populates form with workflow values
|
|
6. User changes workflow name and status
|
|
7. User clicks "Save"
|
|
8. Frontend validates form (required fields, types)
|
|
9. Frontend submits: PUT /api/workflows/{workflowId} with form data
|
|
10. Backend validates and updates workflow
|
|
11. Frontend navigates back to detail page
|
|
12. User sees updated workflow
|
|
```
|
|
|
|
### Dynamic Form Generation
|
|
|
|
```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())
|
|
]);
|
|
|
|
// Filter editable fields
|
|
const editableFields = attributes.attributes.filter(attr =>
|
|
attr.visible && attr.editable
|
|
);
|
|
|
|
// Generate form fields
|
|
<DynamicForm
|
|
fields={editableFields}
|
|
initialValues={workflow}
|
|
onSubmit={async (formData) => {
|
|
try {
|
|
const response = await fetch(`/api/workflows/${workflowId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(formData)
|
|
});
|
|
|
|
if (response.status === 403) {
|
|
showError('You do not have permission to edit this workflow');
|
|
return;
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
showError(error.detail);
|
|
return;
|
|
}
|
|
|
|
navigate(`/workflows/${workflowId}`);
|
|
} catch (error) {
|
|
showError('Failed to update workflow');
|
|
}
|
|
}}
|
|
/>
|
|
|
|
// DynamicForm component renders fields based on type
|
|
function DynamicForm({ fields, initialValues, onSubmit }) {
|
|
const [formData, setFormData] = useState(initialValues);
|
|
const [errors, setErrors] = useState({});
|
|
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
|
|
// Validate required fields
|
|
const newErrors = {};
|
|
fields.forEach(field => {
|
|
if (field.required && !formData[field.name]) {
|
|
newErrors[field.name] = `${field.label} is required`;
|
|
}
|
|
});
|
|
|
|
if (Object.keys(newErrors).length > 0) {
|
|
setErrors(newErrors);
|
|
return;
|
|
}
|
|
|
|
onSubmit(formData);
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit}>
|
|
{fields.map(field => (
|
|
<FormField
|
|
key={field.name}
|
|
field={field}
|
|
value={formData[field.name]}
|
|
onChange={(value) => setFormData(prev => ({ ...prev, [field.name]: value }))}
|
|
error={errors[field.name]}
|
|
/>
|
|
))}
|
|
<button type="submit">Save</button>
|
|
<button type="button" onClick={() => navigate(-1)}>Cancel</button>
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Customer Journey 5: Managing Workflow Messages
|
|
|
|
### User Goal
|
|
"I want to view, organize, and clean up messages in a workflow."
|
|
|
|
### User Story
|
|
As a user, I want to see all messages in a workflow, view message details including attached files, and delete messages if needed.
|
|
|
|
### User Story Flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant User
|
|
participant Frontend
|
|
participant Backend
|
|
|
|
User->>Frontend: Open workflow detail page
|
|
Frontend->>Backend: GET /api/workflows/workflowId/messages
|
|
Backend-->>Frontend: Messages list
|
|
Frontend->>Frontend: Sort by publishedAt
|
|
Frontend->>Frontend: Render messages chronologically
|
|
Frontend-->>User: Display messages with files
|
|
|
|
User->>Frontend: Click delete button on message
|
|
Frontend->>Frontend: Show confirmation dialog
|
|
User->>Frontend: Confirm deletion
|
|
Frontend->>Backend: DELETE /api/workflows/workflowId/messages/messageId
|
|
Backend-->>Frontend: Success response
|
|
Frontend->>Frontend: Remove message from UI
|
|
Frontend-->>User: Updated message list
|
|
|
|
User->>Frontend: Click delete button on file
|
|
Frontend->>Frontend: Show confirmation dialog
|
|
User->>Frontend: Confirm deletion
|
|
Frontend->>Backend: DELETE /api/workflows/workflowId/messages/messageId/files/fileId
|
|
Backend-->>Frontend: Success response
|
|
Frontend->>Frontend: Remove file from message
|
|
Frontend-->>User: Updated message display
|
|
```
|
|
|
|
### Frontend Requirements
|
|
|
|
#### Component: Message Management
|
|
|
|
**What the user sees:**
|
|
- List of all messages in chronological order
|
|
- Each message shows: role, content, timestamp, attached files
|
|
- Message actions: View details, Delete (if permitted)
|
|
- Pagination if many messages
|
|
|
|
**What the frontend needs to do:**
|
|
|
|
1. **Fetch Messages**
|
|
```
|
|
GET /api/workflows/{workflowId}/messages
|
|
```
|
|
- Get all messages (or paginated if many)
|
|
- Support pagination for large message lists
|
|
|
|
2. **Display Messages**
|
|
- Show messages in chronological order (`publishedAt`)
|
|
- Display message role (user/assistant/system) with styling
|
|
- Show message content or summary
|
|
- Display attached documents/files if `documents` array has items
|
|
- Format timestamps as readable dates
|
|
|
|
3. **Handle Message Deletion**
|
|
```
|
|
DELETE /api/workflows/{workflowId}/messages/{messageId}
|
|
```
|
|
- Show delete button/action for each message
|
|
- Show confirmation dialog before deletion
|
|
- After deletion: refresh message list or remove from UI
|
|
|
|
4. **Handle File Deletion** (within messages)
|
|
```
|
|
DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}
|
|
```
|
|
- Show delete button for each file attachment
|
|
- Show confirmation before deletion
|
|
- After deletion: update message display (remove file from list)
|
|
|
|
### Backend Routes Used
|
|
|
|
| Route | Purpose | When Used |
|
|
|-------|---------|-----------|
|
|
| `GET /api/workflows/{workflowId}/messages` | Get all messages | Initial load, refresh |
|
|
| `DELETE /api/workflows/{workflowId}/messages/{messageId}` | Delete message | User action |
|
|
| `DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}` | Delete file | User action |
|
|
|
|
### User Interaction Flow
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> ViewingMessages
|
|
|
|
ViewingMessages --> ViewingMessage: Click message
|
|
ViewingMessage --> ViewingFile: Click file
|
|
ViewingMessage --> DeletingMessage: Click delete
|
|
|
|
ViewingFile --> ViewingMessage: Close file view
|
|
|
|
DeletingMessage --> ConfirmDeleteMessage: Show dialog
|
|
ConfirmDeleteMessage --> ViewingMessages: Cancel
|
|
ConfirmDeleteMessage --> DeletingMessageAPI: Confirm
|
|
DeletingMessageAPI --> ViewingMessages: Success
|
|
|
|
ViewingMessage --> DeletingFile: Click delete file
|
|
DeletingFile --> ConfirmDeleteFile: Show dialog
|
|
ConfirmDeleteFile --> ViewingMessage: Cancel
|
|
ConfirmDeleteFile --> DeletingFileAPI: Confirm
|
|
DeletingFileAPI --> ViewingMessage: File removed
|
|
|
|
note right of ViewingMessages
|
|
Component: MessageList
|
|
- Chronological order
|
|
- Scrollable list
|
|
- Pagination support
|
|
end note
|
|
|
|
note right of ViewingMessage
|
|
Component: MessageCard
|
|
- Full message content
|
|
- Attached files
|
|
- Action buttons
|
|
end note
|
|
|
|
note right of DeletingMessageAPI
|
|
Updates UI immediately
|
|
Optimistic update pattern
|
|
end note
|
|
```
|
|
|
|
### Example User Flow
|
|
|
|
```
|
|
1. User opens workflow detail page
|
|
2. Frontend fetches: GET /api/workflows/{workflowId}/messages
|
|
3. Frontend displays messages in chronological list
|
|
4. User sees a message with attached files
|
|
5. User clicks on a file to view it
|
|
6. User decides to delete a message
|
|
7. User clicks delete button → confirmation dialog appears
|
|
8. User confirms deletion
|
|
9. Frontend calls: DELETE /api/workflows/{workflowId}/messages/{messageId}
|
|
10. Frontend removes message from UI (or refreshes list)
|
|
11. User sees updated message list
|
|
```
|
|
|
|
### Implementation Pattern
|
|
|
|
```typescript
|
|
function MessageList({ workflowId }) {
|
|
const [messages, setMessages] = useState([]);
|
|
|
|
useEffect(() => {
|
|
fetch(`/api/workflows/${workflowId}/messages`)
|
|
.then(r => r.json())
|
|
.then(data => setMessages(data.items || data));
|
|
}, [workflowId]);
|
|
|
|
const handleDeleteMessage = async (messageId) => {
|
|
if (!confirm('Are you sure you want to delete this message?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await fetch(`/api/workflows/${workflowId}/messages/${messageId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
// Remove from UI
|
|
setMessages(prev => prev.filter(msg => msg.id !== messageId));
|
|
} catch (error) {
|
|
showError('Failed to delete message');
|
|
}
|
|
};
|
|
|
|
const handleDeleteFile = async (messageId, fileId) => {
|
|
if (!confirm('Are you sure you want to delete this file?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await fetch(`/api/workflows/${workflowId}/messages/${messageId}/files/${fileId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
// Update message in UI (remove file from documents array)
|
|
setMessages(prev => prev.map(msg =>
|
|
msg.id === messageId
|
|
? { ...msg, documents: msg.documents.filter(doc => doc.fileId !== fileId) }
|
|
: msg
|
|
));
|
|
} catch (error) {
|
|
showError('Failed to delete file');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
{messages.map(message => (
|
|
<MessageCard key={message.id} message={message}>
|
|
<MessageContent message={message} />
|
|
{message.documents?.map(doc => (
|
|
<FileAttachment
|
|
key={doc.fileId}
|
|
file={doc}
|
|
onDelete={() => handleDeleteFile(message.id, doc.fileId)}
|
|
/>
|
|
))}
|
|
<Button onClick={() => handleDeleteMessage(message.id)}>
|
|
Delete Message
|
|
</Button>
|
|
</MessageCard>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Customer Journey 6: 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->>Backend: DELETE /api/workflows/workflowId
|
|
|
|
alt Permission denied (403)
|
|
Backend-->>Frontend: 403 Forbidden
|
|
Frontend-->>User: Show permission error
|
|
else Not found (404)
|
|
Backend-->>Frontend: 404 Not Found
|
|
Frontend-->>User: Show not found error
|
|
else Success (200)
|
|
Backend-->>Frontend: Success response
|
|
Frontend->>Frontend: Show success message
|
|
Frontend->>Frontend: Navigate to /workflows
|
|
Frontend-->>User: Display workflow list (without deleted workflow)
|
|
end
|
|
```
|
|
|
|
### Frontend Requirements
|
|
|
|
#### Action: Delete Workflow
|
|
|
|
**What the user sees:**
|
|
- Delete button/action on workflow detail page or list
|
|
- Confirmation dialog before deletion
|
|
- Success message after deletion
|
|
- Redirect to workflow list after deletion
|
|
|
|
**What the frontend needs to do:**
|
|
|
|
1. **Show Delete Action**
|
|
- Display delete button (only if user has permission—backend will enforce)
|
|
- Can be on detail page or in list row actions
|
|
|
|
2. **Handle Delete Confirmation**
|
|
- Show confirmation dialog when user clicks delete
|
|
- Display workflow name in confirmation
|
|
- Warn about permanent deletion
|
|
|
|
3. **Execute Deletion**
|
|
```
|
|
DELETE /api/workflows/{workflowId}
|
|
```
|
|
- Call delete endpoint
|
|
- Handle 403 (permission denied) gracefully
|
|
- Handle 404 (not found) gracefully
|
|
|
|
4. **Handle Success**
|
|
- Show success message
|
|
- If on detail page: redirect to workflow list
|
|
- If on list page: remove workflow from list (or refresh)
|
|
|
|
### 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
|