< Summary

Information
Class: Elsa.UserTasks.Activities.UserTask
Assembly: Elsa.UserTasks
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.UserTasks/Activities/UserTask.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 85
Coverable lines: 85
Total lines: 144
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 22
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Title()100%210%
get_Summary()100%210%
get_Reference()100%210%
get_Tags()100%210%
get_TaskType()100%210%
get_Requester()100%210%
get_Assignee()100%210%
get_CandidateUsers()100%210%
get_CandidateGroups()100%210%
get_ExcludedUsers()100%210%
get_MembershipResolutionMode()100%210%
get_AllowManagerExclusionOverride()100%210%
get_Priority()100%210%
get_DueAt()100%210%
get_Instructions()100%210%
get_TaskData()100%210%
get_Form()100%210%
get_ActionDefinitions()100%210%
get_Invitations()100%210%
get_EnableTimeoutOutcome()100%210%
get_EnableCancellationOutcome()100%210%
get_TaskId()100%210%
.ctor()100%210%
.ctor(...)100%210%
ExecuteAsync()0%342180%
ResumeAsync()0%2040%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.UserTasks/Activities/UserTask.cs

#LineLine coverage
 1using System.Runtime.CompilerServices;
 2using System.Text.Json;
 3using Elsa.Expressions.Models;
 4using Elsa.Extensions;
 5using Elsa.UserTasks.Contracts;
 6using Elsa.UserTasks.Models;
 7using Elsa.UserTasks.Options;
 8using Elsa.Workflows;
 9using Elsa.Workflows.Attributes;
 10using Elsa.Workflows.Memory;
 11using Elsa.Workflows.Models;
 12using Microsoft.Extensions.Options;
 13
 14namespace Elsa.UserTasks.Activities;
 15
 16/// <summary>
 17/// Creates a workflow-bound human task and suspends the workflow until a governed outcome resumes it.
 18/// </summary>
 19[Activity("Elsa", "User Tasks", "Creates a user task and waits for a human outcome.", Kind = ActivityKind.Task)]
 20public sealed class UserTask : Activity<UserTaskResult>
 21{
 22    public const string InputKey = "UserTaskInput";
 23
 24    [Input(Description = "The task title.")]
 025    public Input<string> Title { get; set; } = null!;
 026    [Input] public Input<string?> Summary { get; set; } = null!;
 027    [Input] public Input<string?> Reference { get; set; } = null!;
 028    [Input] public Input<IReadOnlyCollection<string>?> Tags { get; set; } = null!;
 029    [Input] public Input<string?> TaskType { get; set; } = null!;
 030    [Input] public Input<ParticipantReference?> Requester { get; set; } = null!;
 031    [Input] public Input<ParticipantReference?> Assignee { get; set; } = null!;
 032    [Input] public Input<IReadOnlyCollection<ParticipantReference>?> CandidateUsers { get; set; } = null!;
 033    [Input] public Input<IReadOnlyCollection<ParticipantReference>?> CandidateGroups { get; set; } = null!;
 034    [Input] public Input<IReadOnlyCollection<ParticipantReference>?> ExcludedUsers { get; set; } = null!;
 035    [Input] public Input<UserTaskMembershipResolutionMode> MembershipResolutionMode { get; set; } = new(new Literal<User
 036    [Input] public Input<bool> AllowManagerExclusionOverride { get; set; } = null!;
 037    [Input] public Input<int> Priority { get; set; } = new(new Literal<int>(50));
 038    [Input] public Input<DateTimeOffset?> DueAt { get; set; } = null!;
 039    [Input] public Input<string?> Instructions { get; set; } = null!;
 040    [Input] public Input<JsonElement?> TaskData { get; set; } = null!;
 041    [Input] public Input<UserTaskFormReference?> Form { get; set; } = null!;
 42    [Input(Description = "Completion actions. Keys are literal; labels may use expressions.", DefaultSyntax = "Literal",
 043    public Input<IReadOnlyCollection<UserTaskActionDefinition>?> ActionDefinitions { get; set; } = null!;
 044    [Input] public Input<IReadOnlyCollection<UserTaskInvitationDefinition>?> Invitations { get; set; } = null!;
 045    [Input] public Input<bool> EnableTimeoutOutcome { get; set; } = null!;
 046    [Input] public Input<bool> EnableCancellationOutcome { get; set; } = null!;
 047    [Output] public Output<string> TaskId { get; set; } = null!;
 48
 049    public UserTask()
 50    {
 051    }
 52
 053    public UserTask(string title, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(so
 54    {
 055        Title = new Input<string>(new Literal<string>(title));
 056    }
 57
 58    protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
 59    {
 060        var identityGenerator = context.GetRequiredService<IIdentityGenerator>();
 061        var taskId = identityGenerator.GenerateId();
 062        var bookmarkId = identityGenerator.GenerateId();
 063        var options = context.GetService<IOptions<UserTasksOptions>>()?.Value ?? new UserTasksOptions();
 064        var definition = new UserTaskDefinitionSnapshot
 065        {
 066            Title = Title.Get(context),
 067            Summary = Summary.GetOrDefault(context),
 068            Reference = Reference.GetOrDefault(context),
 069            Tags = Tags.GetOrDefault(context) ?? [],
 070            TaskType = TaskType.GetOrDefault(context),
 071            Requester = Requester.GetOrDefault(context),
 072            Assignee = Assignee.GetOrDefault(context),
 073            CandidateUsers = CandidateUsers.GetOrDefault(context) ?? [],
 074            CandidateGroups = CandidateGroups.GetOrDefault(context) ?? [],
 075            ExcludedUsers = ExcludedUsers.GetOrDefault(context) ?? [],
 076            MembershipResolutionMode = MembershipResolutionMode.GetOrDefault(context),
 077            AllowManagerExclusionOverride = AllowManagerExclusionOverride.GetOrDefault(context),
 078            Priority = Priority.GetOrDefault(context),
 079            DueAt = DueAt.GetOrDefault(context),
 080            Instructions = Instructions.GetOrDefault(context),
 081            TaskData = TaskData.GetOrDefault(context),
 082            FormReference = Form.GetOrDefault(context),
 083            Actions = (ActionDefinitions.GetOrDefault(context) ?? []).Select(x => x.Materialize(context)).ToArray(),
 084            Invitations = Invitations.GetOrDefault(context) ?? [],
 085            EnableTimeoutOutcome = EnableTimeoutOutcome.GetOrDefault(context),
 086            EnableCancellationOutcome = EnableCancellationOutcome.GetOrDefault(context)
 087        };
 088        var workflow = context.WorkflowExecutionContext.Workflow;
 089        var materialization = new UserTaskMaterialization(
 090            // The workflow's own tenant wins; the configured default is only a fallback for single-tenant hosts.
 091            workflow.Identity.TenantId ?? options.DefaultTenantId,
 092            workflow.Identity.DefinitionId,
 093            context.WorkflowExecutionContext.Id,
 094            context.Id,
 095            bookmarkId,
 096            definition,
 097            [],
 098            [],
 099            context.WorkflowExecutionContext.SystemClock.UtcNow,
 0100            taskId,
 0101            workflow.WorkflowMetadata.Name,
 0102            workflow.Identity.Version,
 0103            context.WorkflowExecutionContext.CorrelationId);
 0104        context.CreateBookmark(new CreateBookmarkArgs
 0105        {
 0106            BookmarkId = bookmarkId,
 0107            BookmarkName = nameof(UserTask),
 0108            Stimulus = materialization,
 0109            Callback = ResumeAsync,
 0110            IncludeActivityInstanceId = false
 0111        });
 0112        TaskId.Set(context, taskId);
 0113    }
 114
 115    private async ValueTask ResumeAsync(ActivityExecutionContext context)
 116    {
 0117        var stimulus = context.GetWorkflowInput<UserTaskStimulus>(InputKey);
 0118        Result?.Set(context, new UserTaskResult(stimulus.ActionKey, stimulus.CompletionData, stimulus.CompletedBy, stimu
 0119        await context.CompleteActivityAsync();
 0120    }
 121}
 122
 123/// <summary>
 124/// Design-time action definition. Key is deliberately a plain literal so workflow expressions cannot
 125/// change the contract of an already activated task; only the label is expression-capable.
 126/// </summary>
 127public sealed class UserTaskActionDefinition
 128{
 129    public string Key { get; set; } = "";
 130    public Input<string> Label { get; set; } = null!;
 131    public IReadOnlyDictionary<string, object?>? Metadata { get; set; }
 132
 133    public UserTaskActionDefinition()
 134    {
 135    }
 136
 137    public UserTaskActionDefinition(string key, string label)
 138    {
 139        Key = key;
 140        Label = new Input<string>(new Literal<string>(label));
 141    }
 142
 143    internal UserTaskAction Materialize(ActivityExecutionContext context) => new(Key, Label.Get(context), Metadata);
 144}