Portal Community

Performance Design Principles

Indexed Fields by Section

SectionIndexed FieldsIndex Type
Taskstitle, workflowName, status, dueAt, actorIdFull-text on title; B-tree on status, dueAt, actorId
HistoryworkflowName, status, triggeredAt, actorIdFull-text on workflowName; B-tree on status, triggeredAt
Notificationstitle, body, type, isRead, createdAt, userIdFull-text on title+body; B-tree on type, isRead, userId

Performance SLOs

Query TypeP50 TargetP99 Target
Global search (all sections)< 80ms< 300ms
Section-scoped search< 40ms< 150ms
Filter-only (no text query)< 20ms< 80ms

Pagination for Large Result Sets

When a search returns more than the limit per section (hasMore: true), the WorkDesk UI shows a "Show more from Tasks" link below the results. Clicking it fetches the next page of results for that section with section=tasks&page=2. This prevents overloading the UI with hundreds of results at once.

Search Scope Boundaries

Hard Scope Boundary

Search results are always bounded to the actor's visible data. The query planner applies WHERE UserId = @actorId AND TenantId = @tenantId as the outermost condition before full-text matching. This is enforced server-side and cannot be bypassed by URL manipulation.

Extending Search

To add a new searchable section (e.g., a custom app's data), implement the IWorkDeskSearchProvider interface and register it in the DI container. The unified search endpoint will automatically include results from all registered providers:

// IWorkDeskSearchProvider — interface for extending search
public interface IWorkDeskSearchProvider
{
    string SectionKey { get; }   // e.g., "my-custom-section"

    Task<SearchSectionResult> SearchAsync(
        string query,
        Guid actorId,
        Guid tenantId,
        int limit,
        CancellationToken ct);
}

// Register in DI
services.AddWorkDeskSearchProvider<MyCustomSearchProvider>();