gateway/docs/frontend-documentation/workflow-routes-frontend.md

53 KiB

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
  2. Customer Journey 1: Discovering and Browsing Workflows
  3. Customer Journey 2: Viewing Workflow Details
  4. Customer Journey 3: Monitoring Workflow Execution
  5. Customer Journey 4: Editing Workflow Properties
  6. Customer Journey 5: Managing Workflow Messages
  7. Customer Journey 6: Cleaning Up Workflows
  8. Backend Metadata System
  9. 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

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

// 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

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

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

// 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

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

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

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

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

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

// 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

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

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

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

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

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

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

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

// 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

// 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

// 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

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

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

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

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