Portal Community

Config Schema

{
  "nodeType": "Widget",
  "config": {
    "widgetId": "data-approval-widget",
    "actorExpression": "$output.getApprovers.userIds",
    "title": "Review Line Items",
    "widgetProps": {
      "items": "$output.fetchInvoice.lineItems",
      "totalAmount": "$output.fetchInvoice.total",
      "currency": "$output.fetchInvoice.currency",
      "readOnly": false,
      "showBudgetComparison": true
    },
    "interactionEvents": ["approve", "reject", "requestAmendment"],
    "submitPort": "approved",
    "cancelPort": "cancelled",
    "timeoutSeconds": 172800
  }
}

Config Fields

FieldTypeDescription
widgetIdstringRegistered widget identifier
actorExpressionexpressionUser ID or array of IDs allowed to interact
widgetPropsobjectProps passed to the widget component (expressions evaluated)
interactionEventsstring[]Expected interaction event names from the widget
submitPortstringPort to fire on primary interaction (default: "main")

Executor

[NodeCapability(CapabilityType.Widget)]
public class WidgetExecutor : BaseNodeExecutor
{
    public override async Task<NodeExecutionResult> ExecuteAsync(NodeExecutionContext ctx)
    {
        var config = ctx.GetConfig<WidgetConfig>();

        // Evaluate widgetProps expressions
        var props = config.WidgetProps.ToDictionary(
            kv => kv.Key,
            kv => ctx.EvaluateExpression<object>(kv.Value));

        // Suspend via HIL
        await _hilService.SuspendAsync(new WidgetHILRequest
        {
            ExecutionId = ctx.ExecutionId,
            WidgetId = config.WidgetId,
            Props = props,
            ActorId = ctx.EvaluateExpression<string>(config.ActorExpression),
            TimeoutSeconds = config.TimeoutSeconds
        });

        return NodeExecutionResult.Suspended();
    }
}

Interaction Data as Output

When the widget fires an interaction, the interaction data becomes the node output:

// data-approval-widget fired "approve" interaction with:
// { approvedItems: [...], rejectedItems: [], totalApproved: 2450.00, notes: "Approved" }

$output.reviewWidget.approvedItems      // array of approved line items
$output.reviewWidget.totalApproved      // 2450.00
$output.reviewWidget._interactionEvent  // "approve"
$output.reviewWidget._interactedBy     // "user-alice"