Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
279 lines
8.6 KiB
Markdown
279 lines
8.6 KiB
Markdown
# Inline Forms in Workflows
|
|
|
|
This guide demonstrates how to use inline forms in your workflows. Inline forms allow you to define form schemas directly within your workflow code, without needing to pre-register them separately.
|
|
|
|
## Use Cases
|
|
|
|
Inline forms are ideal for:
|
|
|
|
- Ad-hoc forms that are only used in one workflow
|
|
- Dynamic forms with schema determined at runtime
|
|
- Prototyping and development where you want to iterate quickly
|
|
- Forms with a short lifecycle or single-use forms
|
|
|
|
## Implementation Overview
|
|
|
|
The system supports inline forms with the following components:
|
|
|
|
1. A database schema with an `is_temporary` flag on form definitions
|
|
2. A `createTaskWithInlineForm` method that creates temporary form definitions on-the-fly
|
|
3. A `createInlineTaskAndWaitForResult` composite action that creates a task and waits for its completion
|
|
4. A cleanup job that removes temporary forms periodically
|
|
|
|
When a task is created using an inline form, the system dynamically generates temporary, tenant-specific entries in the `workflow_form_definitions`, `workflow_form_schemas`, and `workflow_task_definitions` tables. The task instance in `workflow_tasks` then links to these temporary definitions. This allows the Task Inbox to discover and render the form using its standard mechanisms, as if it were a pre-registered form, while still allowing for ad-hoc form creation within workflows.
|
|
|
|
## Example Usage
|
|
|
|
### Basic Example: Creating a Task with Inline Form
|
|
|
|
```typescript
|
|
// In your workflow definition
|
|
async function myWorkflow(context) {
|
|
// Create a task with an inline form definition
|
|
const createTaskResult = await context.actions.create_task_with_inline_form({
|
|
title: "Approve Service Request",
|
|
description: "Please review and approve this service request",
|
|
priority: "high",
|
|
dueDate: new Date(Date.now() + 86400000).toISOString(), // 1 day from now
|
|
assignTo: {
|
|
roles: ["approver"],
|
|
},
|
|
contextData: {
|
|
requestId: "REQ-12345",
|
|
serviceName: "Server Provisioning",
|
|
requestedBy: "john.doe@example.com"
|
|
},
|
|
form: {
|
|
jsonSchema: {
|
|
type: "object",
|
|
required: ["approved", "comments"],
|
|
properties: {
|
|
requestInfo: {
|
|
type: "string",
|
|
title: "Request Information",
|
|
default: "Service: ${contextData.serviceName}\nRequested by: ${contextData.requestedBy}",
|
|
readOnly: true
|
|
},
|
|
approved: {
|
|
type: "boolean",
|
|
title: "Approve Request",
|
|
default: false
|
|
},
|
|
comments: {
|
|
type: "string",
|
|
title: "Comments"
|
|
}
|
|
}
|
|
},
|
|
uiSchema: {
|
|
requestInfo: {
|
|
"ui:widget": "textarea",
|
|
"ui:options": {
|
|
rows: 3
|
|
}
|
|
},
|
|
comments: {
|
|
"ui:widget": "textarea",
|
|
"ui:options": {
|
|
rows: 5
|
|
}
|
|
}
|
|
}
|
|
},
|
|
formCategory: "approvals"
|
|
});
|
|
|
|
if (createTaskResult.success) {
|
|
console.log(`Task created with ID: ${createTaskResult.taskId}`);
|
|
|
|
// Continue with workflow logic...
|
|
// Note that the task is asynchronous - the workflow continues while the task is pending
|
|
}
|
|
}
|
|
```
|
|
|
|
### Advanced Example: Creating a Task and Waiting for Result
|
|
|
|
```typescript
|
|
// In your workflow definition
|
|
async function customerServiceWorkflow(context) {
|
|
try {
|
|
// Create a task with inline form and wait for its completion
|
|
const taskResult = await context.actions.createInlineTaskAndWaitForResult({
|
|
title: `Mapping Error for Product: ${context.state.productName}`,
|
|
description: 'Please resolve this mapping issue',
|
|
priority: 'high',
|
|
contextData: {
|
|
serviceId: context.state.serviceId,
|
|
errorDetails: context.state.errorMessage
|
|
},
|
|
form: {
|
|
jsonSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
errorDetails: {
|
|
type: 'string',
|
|
title: 'Error Details',
|
|
readOnly: true,
|
|
default: '${contextData.errorMessage}'
|
|
},
|
|
resolution: {
|
|
type: 'string',
|
|
title: 'Resolution Notes'
|
|
},
|
|
resolved: {
|
|
type: 'boolean',
|
|
title: 'Mark as Resolved',
|
|
default: false
|
|
}
|
|
},
|
|
required: ['resolution', 'resolved']
|
|
},
|
|
uiSchema: {
|
|
errorDetails: {
|
|
'ui:widget': 'textarea',
|
|
'ui:readonly': true
|
|
},
|
|
resolution: {
|
|
'ui:widget': 'textarea'
|
|
}
|
|
}
|
|
},
|
|
waitForEventTimeoutMilliseconds: 3600000 // Optional: 1 hour timeout
|
|
});
|
|
|
|
if (taskResult.success) {
|
|
// Process the result data
|
|
const resolutionData = taskResult.resolutionData;
|
|
|
|
if (resolutionData.resolved) {
|
|
console.log(`Issue resolved: ${resolutionData.resolution}`);
|
|
await context.actions.update_ticket_status({
|
|
ticketId: context.state.ticketId,
|
|
status: 'resolved',
|
|
resolution: resolutionData.resolution
|
|
});
|
|
} else {
|
|
console.log('Issue marked as unresolved');
|
|
await context.actions.escalate_ticket({
|
|
ticketId: context.state.ticketId,
|
|
notes: resolutionData.resolution
|
|
});
|
|
}
|
|
} else {
|
|
// Handle error or timeout
|
|
console.error(`Task failed: ${taskResult.error}`);
|
|
await context.actions.log_error({
|
|
message: `Task failed: ${taskResult.error}`,
|
|
details: taskResult.details
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in workflow execution:', error);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Dynamic Form Generation
|
|
|
|
One of the key benefits of inline forms is the ability to generate form schemas dynamically based on runtime data:
|
|
|
|
```typescript
|
|
async function dynamicFormWorkflow(context) {
|
|
// Get data that will influence the form
|
|
const serviceData = await context.actions.get_service_data({
|
|
serviceId: context.state.serviceId
|
|
});
|
|
|
|
// Dynamically build form schema based on service fields
|
|
const formProperties = {
|
|
serviceId: {
|
|
type: 'string',
|
|
title: 'Service ID',
|
|
default: serviceData.id,
|
|
readOnly: true
|
|
},
|
|
serviceName: {
|
|
type: 'string',
|
|
title: 'Service Name',
|
|
default: serviceData.name,
|
|
readOnly: true
|
|
}
|
|
};
|
|
|
|
// Add fields based on service configuration
|
|
if (serviceData.hasScheduling) {
|
|
formProperties.scheduleDate = {
|
|
type: 'string',
|
|
format: 'date',
|
|
title: 'Schedule Date'
|
|
};
|
|
|
|
formProperties.scheduleTimeSlot = {
|
|
type: 'string',
|
|
title: 'Time Slot',
|
|
enum: serviceData.availableTimeSlots
|
|
};
|
|
}
|
|
|
|
if (serviceData.requiresApproval) {
|
|
formProperties.approvalNote = {
|
|
type: 'string',
|
|
title: 'Approval Notes'
|
|
};
|
|
}
|
|
|
|
// Create form with dynamically generated schema
|
|
const taskResult = await context.actions.createInlineTaskAndWaitForResult({
|
|
title: `Configure Service: ${serviceData.name}`,
|
|
assignTo: {
|
|
roles: ['service_manager']
|
|
},
|
|
form: {
|
|
jsonSchema: {
|
|
type: 'object',
|
|
required: serviceData.hasScheduling ? ['scheduleDate', 'scheduleTimeSlot'] : [],
|
|
properties: formProperties
|
|
}
|
|
}
|
|
});
|
|
|
|
// Process the result...
|
|
}
|
|
```
|
|
|
|
## Form Cleanup
|
|
|
|
The system automatically cleans up temporary forms using a scheduled job that runs daily. You can also manually trigger cleanup if needed:
|
|
|
|
```typescript
|
|
// Manually trigger cleanup in a workflow if needed
|
|
await context.actions.cleanup_temporary_forms({
|
|
tenant: context.tenant
|
|
});
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Use inline forms for single-use cases**: If a form will be reused across multiple workflows, consider registering it normally instead.
|
|
|
|
2. **Keep form schemas modular**: Focus on what the current task needs rather than creating large, complex forms.
|
|
|
|
3. **Provide rich context data**: Include all relevant information in the `contextData` property to make forms more useful.
|
|
|
|
4. **Set realistic timeouts**: When using `createInlineTaskAndWaitForResult`, set timeouts appropriate to your business process.
|
|
|
|
5. **Handle timeouts gracefully**: Always check the `success` flag and handle errors appropriately.
|
|
|
|
6. **Use proper form categories**: Setting a meaningful `formCategory` helps with organization and filtering in the UI.
|
|
|
|
## Limitations
|
|
|
|
1. Inline forms are marked as temporary and will be cleaned up periodically.
|
|
|
|
2. They are only accessible within the workflow execution context and associated tasks.
|
|
|
|
3. They don't appear in the form registry for general use outside their specific tasks.
|
|
|
|
4. Changes to inline forms require workflow code updates; they can't be edited separately.
|