Merge remote-tracking branch 'upstream/master' into elsa-2.0/fileactivities
This commit is contained in:
commit
b3cf8dda72
|
|
@ -1,8 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Telnyx.Client.Models;
|
||||
using Elsa.Activities.Telnyx.Client.Services;
|
||||
using Elsa.Activities.Telnyx.Extensions;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Builders;
|
||||
|
|
@ -18,7 +21,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
[Action(
|
||||
Category = Constants.Category,
|
||||
Description = "Bridge two call control calls.",
|
||||
Outcomes = new[] { OutcomeNames.Done, TelnyxOutcomeNames.CallIsNoLongerActive },
|
||||
Outcomes = new[] {TelnyxOutcomeNames.Bridging, TelnyxOutcomeNames.Bridged, TelnyxOutcomeNames.LegABridged, TelnyxOutcomeNames.LegBBridged, OutcomeNames.Done, TelnyxOutcomeNames.CallIsNoLongerActive},
|
||||
DisplayName = "Bridge Calls"
|
||||
)]
|
||||
public class BridgeCalls : Activity
|
||||
|
|
@ -30,40 +33,52 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
_telnyxClient = telnyxClient;
|
||||
}
|
||||
|
||||
[ActivityInput(Label = "Call Control ID A", Hint = "Unique identifier and token for controlling the call.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? CallControlIdA { get; set; } = default!;
|
||||
[ActivityInput(Label = "Call Control ID A", Hint = "Unique identifier and token for controlling the call.", SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string? CallControlIdA { get; set; }
|
||||
|
||||
[ActivityInput(Label = "Call Control ID B", Hint = "The Call Control ID of the call you want to bridge with.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string CallControlIdB { get; set; } = default!;
|
||||
[ActivityInput(Label = "Call Control ID B", Hint = "The Call Control ID of the call you want to bridge with.", SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string? CallControlIdB { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Command ID",
|
||||
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string? CommandId { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
|
||||
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string? ClientState { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Park After Unbridged",
|
||||
Hint = "HTTP request type used for Webhook URL",
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Options = new[] { "", "self" },
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
Label = "Park After Unbridged",
|
||||
Hint = "HTTP request type used for Webhook URL",
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Options = new[] {"", "self"},
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] {SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string? ParkAfterUnbridged { get; set; }
|
||||
|
||||
[ActivityOutput] public CallBridgedPayload? CallBridgedPayloadA { get; set; }
|
||||
[ActivityOutput] public CallBridgedPayload? CallBridgedPayloadB { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var callControlIdA = context.GetCallControlId(CallControlIdA);
|
||||
CallBridgedPayloadA = null;
|
||||
CallBridgedPayloadB = null;
|
||||
|
||||
var callControlIdA = CallControlIdA = context.GetCallControlId(CallControlIdA);
|
||||
var callControlIdB = CallControlIdB = GetCallControlB(context);
|
||||
|
||||
if (callControlIdB == null)
|
||||
throw new WorkflowException("Cannot bridge calls because the second leg's call control ID was not specified and no incoming activities provided this value");
|
||||
|
||||
CallControlIdA = callControlIdA;
|
||||
|
||||
var request = new BridgeCallsRequest(
|
||||
CallControlIdB,
|
||||
callControlIdB,
|
||||
ClientState,
|
||||
CommandId,
|
||||
ParkAfterUnbridged
|
||||
|
|
@ -72,7 +87,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
try
|
||||
{
|
||||
await _telnyxClient.Calls.BridgeCallsAsync(callControlIdA, request, context.CancellationToken);
|
||||
return Done();
|
||||
return Combine(Outcome(TelnyxOutcomeNames.Bridging), Suspend());
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
|
|
@ -82,6 +97,50 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
throw new WorkflowException(e.Content ?? e.Message, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IActivityExecutionResult OnResume(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallBridgedPayload>()!;
|
||||
var results = new List<IActivityExecutionResult>();
|
||||
|
||||
if (payload.CallControlId == CallControlIdA)
|
||||
{
|
||||
CallBridgedPayloadA = payload;
|
||||
results.Add(Outcome(TelnyxOutcomeNames.LegABridged, payload));
|
||||
}
|
||||
|
||||
if (payload.CallControlId == CallControlIdB)
|
||||
{
|
||||
CallBridgedPayloadB = payload;
|
||||
results.Add(Outcome(TelnyxOutcomeNames.LegBBridged, payload));
|
||||
}
|
||||
|
||||
if (CallBridgedPayloadA != null && CallBridgedPayloadB != null)
|
||||
{
|
||||
results.Add(Outcome(TelnyxOutcomeNames.Bridged));
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(Suspend());
|
||||
}
|
||||
|
||||
return Combine(results);
|
||||
}
|
||||
|
||||
private string? GetCallControlB(ActivityExecutionContext context)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(CallControlIdB))
|
||||
return CallControlIdB;
|
||||
|
||||
var input = context.GetInput<CallAnsweredPayload>();
|
||||
|
||||
if (input != null)
|
||||
return input.CallControlId;
|
||||
|
||||
var inboundCallActivityId = context.WorkflowExecutionContext.GetInboundConnections(Id).Where(x => x.Source.Activity.Type == nameof(Dial)).Select(x => x.Source.Activity.Id).FirstOrDefault();
|
||||
var inboundCallActivityResponse = inboundCallActivityId != null ? context.WorkflowExecutionContext.GetActivityProperty<Dial, DialResponse>(inboundCallActivityId, x => x.DialResponse) : default;
|
||||
return inboundCallActivityResponse != null ? inboundCallActivityResponse.CallControlId : null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BridgeCallsExtensions
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
[Action(
|
||||
Category = Constants.Category,
|
||||
Description = "Call a ring group.",
|
||||
Outcomes = new[] { "Connected", "No Response" },
|
||||
Outcomes = new[] {TelnyxOutcomeNames.Connected, TelnyxOutcomeNames.NoResponse},
|
||||
DisplayName = "Call Ring Group"
|
||||
)]
|
||||
public class CallRingGroup : CompositeActivity, IActivityPropertyDefaultValueProvider
|
||||
|
|
@ -38,14 +38,14 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
[ActivityInput(UIHint = ActivityInputUIHints.MultiText, DefaultSyntax = SyntaxNames.Json, SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(UIHint = ActivityInputUIHints.MultiText, DefaultSyntax = SyntaxNames.Json, SupportedSyntaxes = new[] {SyntaxNames.Json, SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public IList<string> Extensions
|
||||
{
|
||||
get => GetState<IList<string>>(() => new List<string>());
|
||||
set => SetState(value);
|
||||
}
|
||||
|
||||
[ActivityInput(Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call.", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Label = "Call Control ID", Hint = "Unique identifier and token for controlling the call.", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string CallControlId
|
||||
{
|
||||
get => GetState<string>()!;
|
||||
|
|
@ -56,7 +56,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
Label = "Call Control App ID",
|
||||
Hint = "The ID of the Call Control App (formerly ID of the connection) to be used when dialing the destination.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public string? CallControlAppId
|
||||
{
|
||||
|
|
@ -64,7 +64,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
set => SetState(value);
|
||||
}
|
||||
|
||||
[ActivityInput(SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(SupportedSyntaxes = new[] {SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public RingGroupStrategy Strategy
|
||||
{
|
||||
get => GetState<RingGroupStrategy>();
|
||||
|
|
@ -73,7 +73,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
|
||||
[ActivityInput(
|
||||
Hint = "The 'from' number to be used as the caller id presented to the destination ('To' number). The number should be in +E164 format. This attribute will default to the 'From' number of the original call if omitted.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public string? From
|
||||
{
|
||||
|
|
@ -84,7 +84,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
[ActivityInput(
|
||||
Hint =
|
||||
"The string to be used as the caller id name (SIP From Display Name) presented to the destination ('To' number). The string should have a maximum of 128 characters, containing only letters, numbers, spaces, and -_~!.+ special characters. If omitted, the display name will be the same as the number in the 'From' field.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public string? FromDisplayName
|
||||
{
|
||||
|
|
@ -92,18 +92,21 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
set => SetState(value);
|
||||
}
|
||||
|
||||
[ActivityInput(DefaultValueProvider = typeof(CallRingGroup), SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(DefaultValueProvider = typeof(CallRingGroup), SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public Duration RingTime
|
||||
{
|
||||
get => GetState(() => Duration.FromSeconds(20));
|
||||
set => SetState(value);
|
||||
}
|
||||
|
||||
private string? DialedControlId
|
||||
{
|
||||
get => GetState<string?>();
|
||||
set => SetState(value);
|
||||
}
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "Enables Answering Machine Detection.",
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Options = new[] {"disabled", "detect", "detect_beep", "detect_words", "greeting_end"},
|
||||
DefaultValue = "disabled",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] {SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string? AnsweringMachineDetection { get; set; } = "disabled";
|
||||
|
||||
private CallAnsweredPayload? CallAnsweredPayload
|
||||
{
|
||||
|
|
@ -148,64 +151,53 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
private void BuildPrioritizedHuntFlow(IOutcomeBuilder builder) =>
|
||||
builder
|
||||
.ForEach(() => Extensions, iterate => iterate
|
||||
.Then<Dial>(a => a
|
||||
.WithConnectionId(() => CallControlAppId)
|
||||
.WithTo(async context => await ResolveExtensionAsync(context, context.GetInput<string>()!))
|
||||
.WithTimeoutSecs(() => (int) RingTime.TotalSeconds)
|
||||
.WithFrom(() => From)
|
||||
.WithFromDisplayName(() => FromDisplayName)
|
||||
.WithClientState(context => new ClientStatePayload(context.CorrelationId!).ToBase64())
|
||||
.Then<Dial>(dial => dial
|
||||
.WithConnectionId(() => CallControlAppId)
|
||||
.WithTo(ResolveExtensionAsync)
|
||||
.WithTimeoutSecs(() => (int) RingTime.TotalSeconds)
|
||||
.WithFrom(() => From)
|
||||
.WithFromDisplayName(() => FromDisplayName),
|
||||
dial =>
|
||||
{
|
||||
dial
|
||||
.When(TelnyxOutcomeNames.Answered)
|
||||
.Then<BridgeCalls>(bridgeCalls =>
|
||||
bridgeCalls.When(TelnyxOutcomeNames.Bridged)
|
||||
.Finish(TelnyxOutcomeNames.Connected));
|
||||
}
|
||||
)
|
||||
.Then(context => DialedControlId = context.GetInput<DialResponse>()!.CallControlId)
|
||||
.Then<Fork>(fork => fork.WithBranches("Connected", "No Response"), fork =>
|
||||
{
|
||||
fork
|
||||
.When("Connected")
|
||||
.ThenTypeNamed(CallAnsweredPayload.ActivityTypeName)
|
||||
.Then(context => CallAnsweredPayload = (CallAnsweredPayload) context.GetInput<TelnyxWebhook>()!.Data.Payload)
|
||||
.Then<BridgeCalls>(bridge => bridge
|
||||
.WithCallControlIdA(() => CallControlId)
|
||||
.WithCallControlIdB(() => DialedControlId))
|
||||
.ThenTypeNamed(CallBridgedPayload.ActivityTypeName)
|
||||
.ThenTypeNamed(CallBridgedPayload.ActivityTypeName)
|
||||
.Then<Finish>(finish => finish.WithOutcome("Connected").WithOutput(() => CallAnsweredPayload));
|
||||
|
||||
fork
|
||||
.When("No Response")
|
||||
.ThenTypeNamed(CallHangupPayload.ActivityTypeName);
|
||||
})
|
||||
)
|
||||
.Finish("No Response");
|
||||
.Finish(TelnyxOutcomeNames.NoResponse);
|
||||
|
||||
private void BuildRingAllFlow(IOutcomeBuilder builder) =>
|
||||
builder
|
||||
.Then<Fork>(fork => fork.WithBranches("Connected", "Timeout", "Dial Everyone"), fork =>
|
||||
{
|
||||
fork
|
||||
.When("Connected")
|
||||
.When(TelnyxOutcomeNames.Connected)
|
||||
.ThenTypeNamed(CallAnsweredPayload.ActivityTypeName)
|
||||
.Then(context => CallAnsweredPayload = (CallAnsweredPayload) context.GetInput<TelnyxWebhook>()!.Data.Payload)
|
||||
.Then<BridgeCalls>(bridge => bridge
|
||||
.WithCallControlIdA(() => CallControlId)
|
||||
.WithCallControlIdB(() => CallAnsweredPayload!.CallControlId))
|
||||
.ThenTypeNamed(CallBridgedPayload.ActivityTypeName)
|
||||
.Then<Finish>(finish => finish.WithOutcome("Connected").WithOutput(() => CallAnsweredPayload));
|
||||
.WithCallControlIdB(() => CallAnsweredPayload!.CallControlId), bridge => bridge
|
||||
.When(TelnyxOutcomeNames.Bridged)
|
||||
.Finish(TelnyxOutcomeNames.Connected));
|
||||
|
||||
fork
|
||||
.When("Timeout")
|
||||
.StartIn(() => RingTime)
|
||||
.Finish("No Response");
|
||||
.Finish(TelnyxOutcomeNames.NoResponse);
|
||||
|
||||
fork
|
||||
.When("Dial Everyone")
|
||||
.ParallelForEach(() => Extensions, iterate => iterate
|
||||
.Then<Dial>(a => a
|
||||
.WithSuspendWorkflow(false)
|
||||
.WithConnectionId(() => CallControlAppId)
|
||||
.WithTo(async context => await ResolveExtensionAsync(context, context.GetInput<string>()!))
|
||||
.WithTo(ResolveExtensionAsync)
|
||||
.WithTimeoutSecs(() => (int) RingTime.TotalSeconds)
|
||||
.WithFrom(() => From)
|
||||
.WithFromDisplayName(() => FromDisplayName)
|
||||
.WithClientState(context => new ClientStatePayload(context.CorrelationId!).ToBase64())
|
||||
)
|
||||
.Then(CollectCallControlIds));
|
||||
});
|
||||
|
|
@ -217,9 +209,13 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
collection.Add(dialResponse);
|
||||
CollectedDialResponses = collection;
|
||||
}
|
||||
|
||||
private static async Task<string> ResolveExtensionAsync(ActivityExecutionContext context, string extension)
|
||||
|
||||
private static async ValueTask<string> ResolveExtensionAsync(ActivityExecutionContext context)
|
||||
{
|
||||
if (context.Resuming)
|
||||
return context.GetActivityProperty<Dial, string>(x => x.To)!;
|
||||
|
||||
var extension = context.GetInput<string>()!;
|
||||
var extensionProvider = context.GetService<IExtensionProvider>();
|
||||
var resolvedExtension = await extensionProvider.GetAsync(extension, context.CancellationToken);
|
||||
return resolvedExtension?.Number ?? extension;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ using Elsa.Activities.Telnyx.Client.Models;
|
|||
using Elsa.Activities.Telnyx.Client.Services;
|
||||
using Elsa.Activities.Telnyx.Exceptions;
|
||||
using Elsa.Activities.Telnyx.Extensions;
|
||||
using Elsa.Activities.Telnyx.Models;
|
||||
using Elsa.Activities.Telnyx.Options;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Builders;
|
||||
|
|
@ -21,7 +23,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
[Action(
|
||||
Category = Constants.Category,
|
||||
Description = "Dial a number or SIP URI from a given connection.",
|
||||
Outcomes = new[] { OutcomeNames.Done },
|
||||
Outcomes = new[] { TelnyxOutcomeNames.Dialing, TelnyxOutcomeNames.CallInitiated, TelnyxOutcomeNames.Answered, TelnyxOutcomeNames.Hangup, OutcomeNames.Done },
|
||||
DisplayName = "Dial"
|
||||
)]
|
||||
public class Dial : Activity
|
||||
|
|
@ -58,9 +60,10 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
[ActivityInput(
|
||||
Hint = "Enables Answering Machine Detection.",
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Options = new[] { "disabled", "detect", "detect_beep", "detect_words", "greeting_end" },
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? AnsweringMachineDetection { get; set; }
|
||||
Options = new[] {"disabled", "detect", "detect_beep", "detect_words", "greeting_end"},
|
||||
DefaultValue = "disabled",
|
||||
SupportedSyntaxes = new[] {SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string? AnsweringMachineDetection { get; set; } = "disabled";
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Answering Machine Detection Configuration",
|
||||
|
|
@ -76,12 +79,6 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? CommandId { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? ClientState { get; set; }
|
||||
|
||||
[ActivityInput(Label = "Custom Headers", Hint = "Custom headers to be added to the SIP INVITE.", Category = PropertyCategories.Advanced, UIHint = ActivityInputUIHints.Json)]
|
||||
public IList<Header>? CustomHeaders { get; set; }
|
||||
|
||||
|
|
@ -116,10 +113,36 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? WebhookUrlMethod { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "A flag indicating whether this activity should complete immediately or suspend the workflow.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
DefaultValue = true,
|
||||
SupportedSyntaxes = new[] {SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public bool SuspendWorkflow { get; set; } = true;
|
||||
|
||||
[ActivityOutput] public DialResponse DialResponse { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var response = await DialAsync(context);
|
||||
return Done(response);
|
||||
DialResponse = response;
|
||||
|
||||
return !SuspendWorkflow
|
||||
? Done(response)
|
||||
: Combine(Outcome(TelnyxOutcomeNames.Dialing, response), Suspend());
|
||||
}
|
||||
|
||||
protected override IActivityExecutionResult OnResume(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallPayload>();
|
||||
|
||||
return payload switch
|
||||
{
|
||||
CallAnsweredPayload callAnsweredPayload => Outcome(TelnyxOutcomeNames.Answered, callAnsweredPayload),
|
||||
CallHangupPayload callHangupPayload => Outcome(TelnyxOutcomeNames.Hangup, callHangupPayload),
|
||||
CallInitiatedPayload callInitiatedPayload => Combine(Outcome(TelnyxOutcomeNames.CallInitiated, callInitiatedPayload), Suspend()),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(payload))
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<DialResponse> DialAsync(ActivityExecutionContext context)
|
||||
|
|
@ -131,6 +154,8 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
|
||||
var fromNumber = context.GetFromNumber(From);
|
||||
|
||||
var clientState = new ClientStatePayload(context.CorrelationId!).ToBase64();
|
||||
|
||||
var request = new DialRequest(
|
||||
connectionId,
|
||||
To,
|
||||
|
|
@ -138,7 +163,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
FromDisplayName,
|
||||
AnsweringMachineDetection,
|
||||
AnsweringMachineDetectionConfig,
|
||||
ClientState,
|
||||
clientState,
|
||||
CommandId,
|
||||
CustomHeaders,
|
||||
SipAuthUsername,
|
||||
|
|
@ -207,12 +232,6 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
public static ISetupActivity<Dial> WithCommandId(this ISetupActivity<Dial> setup, Func<string?> value) => setup.Set(x => x.CommandId, value);
|
||||
public static ISetupActivity<Dial> WithCommandId(this ISetupActivity<Dial> setup, string? value) => setup.Set(x => x.CommandId, value);
|
||||
|
||||
public static ISetupActivity<Dial> WithClientState(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.ClientState, value);
|
||||
public static ISetupActivity<Dial> WithClientState(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.ClientState, value);
|
||||
public static ISetupActivity<Dial> WithClientState(this ISetupActivity<Dial> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.ClientState, value);
|
||||
public static ISetupActivity<Dial> WithClientState(this ISetupActivity<Dial> setup, Func<string?> value) => setup.Set(x => x.ClientState, value);
|
||||
public static ISetupActivity<Dial> WithClientState(this ISetupActivity<Dial> setup, string? value) => setup.Set(x => x.ClientState, value);
|
||||
|
||||
public static ISetupActivity<Dial> WithCustomHeaders(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<IList<Header>?>> value) => setup.Set(x => x.CustomHeaders, value);
|
||||
public static ISetupActivity<Dial> WithCustomHeaders(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, IList<Header>?> value) => setup.Set(x => x.CustomHeaders, value);
|
||||
public static ISetupActivity<Dial> WithCustomHeaders(this ISetupActivity<Dial> setup, Func<ValueTask<IList<Header>?>> value) => setup.Set(x => x.CustomHeaders, value);
|
||||
|
|
@ -254,5 +273,11 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
public static ISetupActivity<Dial> WithWebhookUrlMethod(this ISetupActivity<Dial> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.WebhookUrlMethod, value);
|
||||
public static ISetupActivity<Dial> WithWebhookUrlMethod(this ISetupActivity<Dial> setup, Func<string?> value) => setup.Set(x => x.WebhookUrlMethod, value);
|
||||
public static ISetupActivity<Dial> WithWebhookUrlMethod(this ISetupActivity<Dial> setup, string? value) => setup.Set(x => x.WebhookUrlMethod, value);
|
||||
|
||||
public static ISetupActivity<Dial> WithSuspendWorkflow(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<bool>> value) => setup.Set(x => x.SuspendWorkflow, value);
|
||||
public static ISetupActivity<Dial> WithSuspendWorkflow(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, bool> value) => setup.Set(x => x.SuspendWorkflow, value);
|
||||
public static ISetupActivity<Dial> WithSuspendWorkflow(this ISetupActivity<Dial> setup, Func<ValueTask<bool>> value) => setup.Set(x => x.SuspendWorkflow, value);
|
||||
public static ISetupActivity<Dial> WithSuspendWorkflow(this ISetupActivity<Dial> setup, Func<bool> value) => setup.Set(x => x.SuspendWorkflow, value);
|
||||
public static ISetupActivity<Dial> WithSuspendWorkflow(this ISetupActivity<Dial> setup, bool value) => setup.Set(x => x.SuspendWorkflow, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
[Job(
|
||||
Category = Constants.Category,
|
||||
Description = "Play an audio file on the call until the required DTMF signals are gathered to build interactive menus.",
|
||||
Outcomes = new[] { TelnyxOutcomeNames.Pending, TelnyxOutcomeNames.GatherCompleted, TelnyxOutcomeNames.CallIsNoLongerActive },
|
||||
Outcomes = new[] { TelnyxOutcomeNames.GatheringInput, TelnyxOutcomeNames.GatherCompleted, TelnyxOutcomeNames.CallIsNoLongerActive },
|
||||
DisplayName = "Gather Using Audio"
|
||||
)]
|
||||
public class GatherUsingAudio : Activity
|
||||
|
|
@ -121,7 +121,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
try
|
||||
{
|
||||
await _telnyxClient.Calls.GatherUsingAudioAsync(callControlId, request, context.CancellationToken);
|
||||
return Combine(Outcome(TelnyxOutcomeNames.Pending), Suspend());
|
||||
return Combine(Outcome(TelnyxOutcomeNames.GatheringInput), Suspend());
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
[Action(
|
||||
Category = Constants.Category,
|
||||
Description = "Convert text to speech and play it on the call until the required DTMF signals are gathered to build interactive menus.",
|
||||
Outcomes = new[] { TelnyxOutcomeNames.Pending, TelnyxOutcomeNames.GatherCompleted, TelnyxOutcomeNames.CallIsNoLongerActive },
|
||||
Outcomes = new[] { TelnyxOutcomeNames.GatheringInput, TelnyxOutcomeNames.GatherCompleted, TelnyxOutcomeNames.CallIsNoLongerActive },
|
||||
DisplayName = "Gather Using Speak"
|
||||
)]
|
||||
public class GatherUsingSpeak : Activity
|
||||
|
|
@ -151,7 +151,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
try
|
||||
{
|
||||
await _telnyxClient.Calls.GatherUsingSpeakAsync(callControlId, request, context.CancellationToken);
|
||||
return Combine(Outcome(TelnyxOutcomeNames.Pending), Suspend());
|
||||
return Combine(Outcome(TelnyxOutcomeNames.GatheringInput), Suspend());
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
try
|
||||
{
|
||||
await _telnyxClient.Calls.SpeakTextAsync(callControlId, request, context.CancellationToken);
|
||||
return Combine(Outcome(TelnyxOutcomeNames.Pending), Suspend());
|
||||
return Combine(Outcome(TelnyxOutcomeNames.Speaking), Suspend());
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
|||
using Elsa.Activities.Telnyx.Client.Models;
|
||||
using Elsa.Activities.Telnyx.Client.Services;
|
||||
using Elsa.Activities.Telnyx.Extensions;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
using Elsa.ActivityResults;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Builders;
|
||||
|
|
@ -19,7 +20,14 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
[Action(
|
||||
Category = Constants.Category,
|
||||
Description = "Transfer a call to a new destination",
|
||||
Outcomes = new[] { OutcomeNames.Done },
|
||||
Outcomes = new[]
|
||||
{
|
||||
TelnyxOutcomeNames.Transferring,
|
||||
TelnyxOutcomeNames.CallInitiated,
|
||||
TelnyxOutcomeNames.Bridged,
|
||||
TelnyxOutcomeNames.Answered,
|
||||
TelnyxOutcomeNames.Hangup
|
||||
},
|
||||
DisplayName = "Transfer Call"
|
||||
)]
|
||||
public class TransferCall : Activity
|
||||
|
|
@ -35,23 +43,23 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
Label = "Call Control ID",
|
||||
Hint = "Unique identifier and token for controlling the call.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public string? CallControlId { get; set; } = default!;
|
||||
|
||||
[ActivityInput(Label = "To", Hint = "The DID or SIP URI to dial out and bridge to the given call.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Label = "To", Hint = "The DID or SIP URI to dial out and bridge to the given call.", SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string To { get; set; } = default!;
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "The 'from' number to be used as the caller id presented to the destination ('To' number). The number should be in +E164 format. This attribute will default to the 'From' number of the original call if omitted.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public string? From { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Hint =
|
||||
"The string to be used as the caller id name (SIP From Display Name) presented to the destination ('To' number). The string should have a maximum of 128 characters, containing only letters, numbers, spaces, and -_~!.+ special characters. If omitted, the display name will be the same as the number in the 'From' field.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public string? FromDisplayName { get; set; }
|
||||
|
||||
|
|
@ -59,8 +67,8 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
Label = "Answering Machine Detection",
|
||||
Hint = "Enables Answering Machine Detection.",
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Options = new[] { "disabled", "detect", "detect_beep", "detect_words", "greeting_end" },
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
Options = new[] {"disabled", "detect", "detect_beep", "detect_words", "greeting_end"},
|
||||
SupportedSyntaxes = new[] {SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public string? AnsweringMachineDetection { get; set; }
|
||||
|
||||
|
|
@ -76,46 +84,46 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
Label = "Command ID",
|
||||
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public string? CommandId { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Audio URL",
|
||||
Hint = "Audio URL to be played back when the transfer destination answers before bridging the call. The URL can point to either a WAV or MP3 file.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public Uri? AudioUrl { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string? ClientState { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "Use this field to add state to every subsequent webhook for the new leg. It must be a valid Base-64 encoded string.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public string? TargetLegClientState { get; set; }
|
||||
|
||||
[ActivityInput(Hint = "Custom headers to be added to the SIP INVITE.", Category = PropertyCategories.Advanced, UIHint = ActivityInputUIHints.Json)]
|
||||
public IList<Header>? CustomHeaders { get; set; }
|
||||
|
||||
[ActivityInput(Label = "SIP Authentication Username", Hint = "SIP Authentication username used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Label = "SIP Authentication Username", Hint = "SIP Authentication username used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string? SipAuthUsername { get; set; }
|
||||
|
||||
[ActivityInput(Label = "SIP Authentication Password", Hint = "SIP Authentication password used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Label = "SIP Authentication Password", Hint = "SIP Authentication password used for SIP challenges.", Category = "SIP Authentication", SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public string? SipAuthPassword { get; set; }
|
||||
|
||||
[ActivityInput(Label = "Time Limit", Hint = "Sets the maximum duration of a Call Control Leg in seconds.", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
[ActivityInput(Label = "Time Limit", Hint = "Sets the maximum duration of a Call Control Leg in seconds.", Category = PropertyCategories.Advanced, SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
|
||||
public int? TimeLimitSecs { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Timeout",
|
||||
Hint = "The number of seconds that Telnyx will wait for the call to be answered by the destination to which it is being transferred.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public int? TimeoutSecs { get; set; }
|
||||
|
||||
|
|
@ -123,7 +131,7 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
Label = "Webhook URL",
|
||||
Hint = "Use this field to override the URL for which Telnyx will send subsequent webhooks to for this call.",
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public string? WebhookUrl { get; set; }
|
||||
|
||||
|
|
@ -131,22 +139,36 @@ namespace Elsa.Activities.Telnyx.Activities
|
|||
Label = "Webhook URL Method",
|
||||
Hint = "HTTP request type used for Webhook URL",
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
Options = new[] { "GET", "POST" },
|
||||
Options = new[] {"GET", "POST"},
|
||||
Category = PropertyCategories.Advanced,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
SupportedSyntaxes = new[] {SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid}
|
||||
)]
|
||||
public string? WebhookUrlMethod { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await TransferCallAsync(context);
|
||||
return Done();
|
||||
return Combine(Outcome(TelnyxOutcomeNames.Transferring), Suspend());
|
||||
}
|
||||
|
||||
protected override IActivityExecutionResult OnResume(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallPayload>();
|
||||
|
||||
return payload switch
|
||||
{
|
||||
CallAnsweredPayload callAnsweredPayload => Outcome(TelnyxOutcomeNames.Answered, callAnsweredPayload),
|
||||
CallBridgedPayload callBridgedPayload => Outcome(TelnyxOutcomeNames.Bridged, callBridgedPayload),
|
||||
CallHangupPayload callHangupPayload => Outcome(TelnyxOutcomeNames.Hangup, callHangupPayload),
|
||||
CallInitiatedPayload callInitiatedPayload => Outcome(TelnyxOutcomeNames.CallInitiated, callInitiatedPayload),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(payload))
|
||||
};
|
||||
}
|
||||
|
||||
private async ValueTask TransferCallAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var fromNumber = context.GetFromNumber(From);
|
||||
|
||||
|
||||
var request = new TransferCallRequest(
|
||||
To,
|
||||
fromNumber,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
using Elsa.Activities.Telnyx.Activities;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
using Elsa.Services;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Handlers
|
||||
{
|
||||
public class ResumeBridgeCalls : ResumeWebhookDrivenActivity<BridgeCalls, CallBridgedPayload>
|
||||
{
|
||||
public ResumeBridgeCalls(IWorkflowLaunchpad workflowLaunchpad) : base(workflowLaunchpad)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/activities/Elsa.Activities.Telnyx/Handlers/ResumeDial.cs
Normal file
17
src/activities/Elsa.Activities.Telnyx/Handlers/ResumeDial.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Elsa.Activities.Telnyx.Activities;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
using Elsa.Services;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Handlers
|
||||
{
|
||||
public class ResumeDial : ResumeWebhookDrivenActivity<Dial>
|
||||
{
|
||||
public ResumeDial(IWorkflowLaunchpad workflowLaunchpad) : base(workflowLaunchpad)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IEnumerable<Type> GetSupportedPayloadTypes() => new[] {typeof(CallInitiatedPayload), typeof(CallAnsweredPayload), typeof(CallHangupPayload)};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Elsa.Activities.Telnyx.Activities;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
|
||||
using Elsa.Services;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Handlers
|
||||
{
|
||||
public class ResumeTransferCall : ResumeWebhookDrivenActivity<TransferCall>
|
||||
{
|
||||
public ResumeTransferCall(IWorkflowLaunchpad workflowLaunchpad) : base(workflowLaunchpad)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IEnumerable<Type> GetSupportedPayloadTypes() => new[] {typeof(CallAnsweredPayload), typeof(CallInitiatedPayload), typeof(CallBridgedPayload), typeof(CallHangupPayload)};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Telnyx.Activities;
|
||||
using Elsa.Activities.Telnyx.Models;
|
||||
using Elsa.Activities.Telnyx.Providers.Bookmarks;
|
||||
using Elsa.Activities.Telnyx.Webhooks.Events;
|
||||
|
|
@ -11,27 +13,41 @@ using MediatR;
|
|||
|
||||
namespace Elsa.Activities.Telnyx.Handlers
|
||||
{
|
||||
public abstract class ResumeWebhookDrivenActivity<TActivity, TPayload> : INotificationHandler<TelnyxWebhookReceived> where TPayload: CallPayload
|
||||
public abstract class ResumeWebhookDrivenActivity<TActivity, TPayload> : ResumeWebhookDrivenActivity<TActivity> where TPayload : CallPayload
|
||||
{
|
||||
protected ResumeWebhookDrivenActivity(IWorkflowLaunchpad workflowLaunchpad) : base(workflowLaunchpad)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IEnumerable<Type> GetSupportedPayloadTypes() => new[] {typeof(TPayload)};
|
||||
}
|
||||
|
||||
public abstract class ResumeWebhookDrivenActivity<TActivity> : INotificationHandler<TelnyxWebhookReceived>
|
||||
{
|
||||
private readonly IWorkflowLaunchpad _workflowLaunchpad;
|
||||
protected ResumeWebhookDrivenActivity(IWorkflowLaunchpad workflowLaunchpad) => _workflowLaunchpad = workflowLaunchpad;
|
||||
protected virtual string ActivityTypeName => typeof(TActivity).Name;
|
||||
protected abstract IEnumerable<Type> GetSupportedPayloadTypes();
|
||||
|
||||
public async Task Handle(TelnyxWebhookReceived notification, CancellationToken cancellationToken)
|
||||
{
|
||||
if (notification.Webhook.Data.Payload is not TPayload payload)
|
||||
var supportedPayloadTypes = GetSupportedPayloadTypes().ToHashSet();
|
||||
var receivedPayload = (CallPayload) notification.Webhook.Data.Payload;
|
||||
var receivedPayloadType = receivedPayload.GetType();
|
||||
|
||||
if (!supportedPayloadTypes.Contains(receivedPayloadType))
|
||||
return;
|
||||
|
||||
var correlationId = GetCorrelationId(payload);
|
||||
|
||||
var correlationId = GetCorrelationId(receivedPayload);
|
||||
var trigger = CreateBookmark();
|
||||
var bookmark = CreateBookmark();
|
||||
var context = new CollectWorkflowsContext(ActivityTypeName, bookmark, trigger, correlationId);
|
||||
await _workflowLaunchpad.CollectAndDispatchWorkflowsAsync(context, payload, cancellationToken);
|
||||
await _workflowLaunchpad.CollectAndDispatchWorkflowsAsync(context, receivedPayload, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
protected virtual IBookmark CreateBookmark() => new GatherUsingSpeakBookmark();
|
||||
|
||||
private string GetCorrelationId(TPayload payload)
|
||||
|
||||
private string GetCorrelationId(CallPayload payload)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(payload.ClientState))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,9 +4,21 @@
|
|||
{
|
||||
public const string CallIsNoLongerActive = "Call Is No Longer Active";
|
||||
public const string Pending = "Pending";
|
||||
public const string Connected = "Connected";
|
||||
public const string NoResponse = "No Response";
|
||||
public const string Dialing = "Dialing";
|
||||
public const string CallInitiated = "CallInitiated";
|
||||
public const string Answered = "Answered";
|
||||
public const string Hangup = "Hangup";
|
||||
public const string GatheringInput = "Gathering Input";
|
||||
public const string GatherCompleted = "Gather Completed";
|
||||
public const string InputReceived = "Input Received";
|
||||
public const string Transferring = "Transferring";
|
||||
public const string Bridging = "Bridging";
|
||||
public const string Bridged = "Bridged";
|
||||
public const string LegABridged = "Call A Answered";
|
||||
public const string LegBBridged = "Call B Answered";
|
||||
public const string MachineDetectionEnded = "Machine Detection Ended";
|
||||
public const string MachineGreetingEnded = "Machine Greeting Ended";
|
||||
public const string FinishedSpeaking = "Finished Speaking";
|
||||
public const string Speaking = "Speaking";
|
||||
public const string Recording = "Recording";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
using Elsa.Activities.Telnyx.Activities;
|
||||
using Elsa.Bookmarks;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Providers.Bookmarks
|
||||
{
|
||||
public class BridgeCallsBookmark : IBookmark
|
||||
{
|
||||
}
|
||||
|
||||
public class BridgeCallsBookmarkProvider : DefaultBookmarkProvider<BridgeCallsBookmark, BridgeCalls>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using Elsa.Activities.Telnyx.Activities;
|
||||
using Elsa.Bookmarks;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Providers.Bookmarks
|
||||
{
|
||||
public class DialBookmark : IBookmark
|
||||
{
|
||||
}
|
||||
|
||||
public class DialBookmarkProvider : DefaultBookmarkProvider<DialBookmark, Dial>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using Elsa.Activities.Telnyx.Activities;
|
||||
using Elsa.Bookmarks;
|
||||
|
||||
namespace Elsa.Activities.Telnyx.Providers.Bookmarks
|
||||
{
|
||||
public class TransferCallBookmark : IBookmark
|
||||
{
|
||||
}
|
||||
|
||||
public class TransferCallBookmarkProvider : DefaultBookmarkProvider<TransferCallBookmark, TransferCall>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ using Elsa.Services.Models;
|
|||
|
||||
namespace Elsa.ActivityResults
|
||||
{
|
||||
[Obsolete("Use activity output properties to return output.")]
|
||||
public class OutputResult : ActivityExecutionResult
|
||||
{
|
||||
public OutputResult(object? output) => Output = output;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using Elsa.Serialization.Converters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NodaTime;
|
||||
|
|
@ -19,6 +20,16 @@ namespace Elsa
|
|||
|
||||
return item.ToObject<T>(CreateSerializer())!;
|
||||
}
|
||||
|
||||
public static object? GetState(this JObject? state, string key, Type targetType)
|
||||
{
|
||||
var item = state?.GetValue(key, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (item == null || item.Type == JTokenType.Null)
|
||||
return null;
|
||||
|
||||
return item.ToObject(targetType, CreateSerializer())!;
|
||||
}
|
||||
|
||||
public static T? GetState<T>(this JObject? state, Type type, string key) => state.GetState<T>(type, key, () => default!);
|
||||
|
||||
|
|
@ -32,16 +43,19 @@ namespace Elsa
|
|||
|
||||
public static bool HasKey(this JObject state, string key) => state.ContainsKey(key);
|
||||
|
||||
private static JsonSerializer CreateSerializer()
|
||||
public static JObject CreateState(object value) => JObject.FromObject(value, CreateSerializer());
|
||||
|
||||
public static JsonSerializer CreateSerializer()
|
||||
{
|
||||
var serializer = new JsonSerializer();
|
||||
|
||||
|
||||
serializer.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
serializer.NullValueHandling = NullValueHandling.Ignore;
|
||||
serializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
|
||||
serializer.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
|
||||
serializer.TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple;
|
||||
serializer.TypeNameHandling = TypeNameHandling.Auto;
|
||||
serializer.Converters.Add(new TypeJsonConverter());
|
||||
|
||||
return serializer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,18 +34,12 @@ namespace Elsa.Services
|
|||
protected virtual IActivityExecutionResult OnResume() => Done();
|
||||
protected virtual NoopResult Noop() => new();
|
||||
protected virtual OutcomeResult Done() => Outcome(OutcomeNames.Done);
|
||||
|
||||
[Obsolete("Do not use methods that accept an output. Use Done() instead and activity output properties to return output.")]
|
||||
protected virtual CombinedResult Done(object? output) => Combine(Output(output), Done());
|
||||
|
||||
protected virtual OutcomeResult Outcomes(IEnumerable<string> outcomes) => new(outcomes);
|
||||
protected virtual OutcomeResult Outcomes(params string[] outcomes) => Outcomes((IEnumerable<string>) outcomes);
|
||||
protected virtual OutcomeResult Outcome(string outcome) => Outcomes(outcome);
|
||||
|
||||
[Obsolete("Do not use methods that accept an output. Use Outcome(string outcome) instead and activity output properties to return output.")]
|
||||
protected virtual CombinedResult Outcome(string outcome, object? output) => Combine(Output(output), Outcome(outcome));
|
||||
|
||||
[Obsolete("Use activity output properties to return output.")]
|
||||
protected virtual OutputResult Output(object? output) => new(output);
|
||||
|
||||
protected virtual SuspendResult Suspend() => new();
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ namespace Elsa.Services
|
|||
|
||||
public async ValueTask SetActivityPropertiesAsync(IActivity activity, ActivityExecutionContext activityExecutionContext, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var properties = activity.GetType().GetProperties().Where(IsActivityProperty).ToList();
|
||||
var properties = activity.GetType().GetProperties().Where(IsActivityInputProperty).ToList();
|
||||
var providers = GetProviders(activity.Id);
|
||||
|
||||
foreach (var property in properties)
|
||||
|
|
@ -85,7 +85,7 @@ namespace Elsa.Services
|
|||
}
|
||||
}
|
||||
|
||||
private bool IsActivityProperty(PropertyInfo property) => property.GetCustomAttribute<ActivityInputAttribute>() != null;
|
||||
private bool IsActivityInputProperty(PropertyInfo property) => property.GetCustomAttribute<ActivityInputAttribute>() != null;
|
||||
public IEnumerator<KeyValuePair<string, IDictionary<string, IActivityPropertyValueProvider>>> GetEnumerator() => _providers.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,17 +40,18 @@ namespace Elsa.Services.Models
|
|||
get => WorkflowExecutionContext.ContextId;
|
||||
set => WorkflowExecutionContext.ContextId = value;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<string> Outcomes { get; set; }
|
||||
public object? Input { get; }
|
||||
public bool Resuming { get; }
|
||||
public bool IsFirstPass => WorkflowExecutionContext.IsFirstPass;
|
||||
|
||||
|
||||
public string? CorrelationId
|
||||
{
|
||||
get => WorkflowExecutionContext.CorrelationId;
|
||||
set => WorkflowExecutionContext.CorrelationId = value;
|
||||
}
|
||||
|
||||
|
||||
public CancellationToken CancellationToken { get; }
|
||||
|
||||
public JObject GetData() => WorkflowInstance.ActivityData.GetItem(ActivityBlueprint.Id, () => new JObject());
|
||||
|
|
@ -60,19 +61,25 @@ namespace Elsa.Services.Models
|
|||
var data = GetData();
|
||||
data.SetState(propertyName, value);
|
||||
}
|
||||
|
||||
|
||||
public T? GetState<T>(string propertyName)
|
||||
{
|
||||
var data = GetData();
|
||||
return data.GetState<T>(propertyName);
|
||||
}
|
||||
|
||||
|
||||
public T GetState<T>(string propertyName, Func<T> defaultValue)
|
||||
{
|
||||
var data = GetData();
|
||||
return data.GetState<T>(propertyName, defaultValue);
|
||||
}
|
||||
|
||||
|
||||
public object? GetState(string propertyName, Type targetType)
|
||||
{
|
||||
var data = GetData();
|
||||
return data.GetState(propertyName, targetType);
|
||||
}
|
||||
|
||||
public T? GetState<TActivity, T>(Expression<Func<TActivity, T>> propertyExpression) where TActivity : IActivity
|
||||
{
|
||||
var expression = (MemberExpression) propertyExpression.Body;
|
||||
|
|
@ -81,27 +88,27 @@ namespace Elsa.Services.Models
|
|||
}
|
||||
|
||||
public T? GetContainerState<T>() => GetContainerState<T>(typeof(T).Name);
|
||||
|
||||
|
||||
public T? GetContainerState<T>(string key)
|
||||
{
|
||||
var parentActivityId = ActivityBlueprint.Parent?.Id;
|
||||
|
||||
if (parentActivityId == null)
|
||||
return default;
|
||||
|
||||
|
||||
var parentData = WorkflowExecutionContext.WorkflowInstance.ActivityData.GetItem(parentActivityId);
|
||||
return parentData.GetState<T>(key);
|
||||
}
|
||||
|
||||
public void SetContainerState<T>(object? value) => SetContainerState(typeof(T).Name, value);
|
||||
|
||||
public void SetContainerState<T>(object? value) => SetContainerState(typeof(T).Name, value);
|
||||
|
||||
public void SetContainerState(string key, object? value)
|
||||
{
|
||||
var parentActivityId = ActivityBlueprint.Parent?.Id;
|
||||
|
||||
if (parentActivityId == null)
|
||||
return;
|
||||
|
||||
|
||||
var parentData = WorkflowExecutionContext.WorkflowInstance.ActivityData.GetItem(parentActivityId);
|
||||
parentData?.SetState(key, value);
|
||||
}
|
||||
|
|
@ -119,7 +126,7 @@ namespace Elsa.Services.Models
|
|||
public ActivityScope GetNamedScope(string activityName) => WorkflowExecutionContext.GetNamedScope(activityName);
|
||||
|
||||
public void SetVariable(string name, object? value) => WorkflowExecutionContext.SetVariable(name, value);
|
||||
|
||||
|
||||
public T? SetVariable<T>(string name, Func<T?, T?> updater)
|
||||
{
|
||||
var value = GetVariable<T>(name);
|
||||
|
|
@ -151,7 +158,7 @@ namespace Elsa.Services.Models
|
|||
var activityTypeService = ServiceProvider.GetRequiredService<IActivityTypeService>();
|
||||
return await activityTypeService.ActivateActivityAsync(ActivityBlueprint, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
public T? GetInput<T>() => Input.ConvertTo<T>();
|
||||
public T? GetInput<T>(Func<T?> defaultValue) => Input != null ? Input.ConvertTo<T>() : defaultValue();
|
||||
public T? GetInput<T>(T? defaultValue) => Input != null ? Input.ConvertTo<T>() : defaultValue;
|
||||
|
|
@ -167,6 +174,7 @@ namespace Elsa.Services.Models
|
|||
public T GetWorkflowContext<T>() => WorkflowExecutionContext.GetWorkflowContext<T>();
|
||||
public JObject GetActivityData() => GetActivityData(ActivityId);
|
||||
public JObject GetActivityData(string activityId) => WorkflowExecutionContext.GetActivityData(activityId);
|
||||
public T? GetActivityProperty<TActivity, T>(Expression<Func<TActivity, T>> propertyExpression) where TActivity : IActivity => WorkflowExecutionContext.GetActivityProperty<TActivity, T>(ActivityId, propertyExpression);
|
||||
public void Fault(Exception exception) => WorkflowExecutionContext.Fault(exception, ActivityId, Input, Resuming);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Events;
|
||||
|
|
@ -203,6 +204,20 @@ namespace Elsa.Services.Models
|
|||
}
|
||||
|
||||
public T GetOutputFrom<T>(string activityName) => (T) GetOutputFrom(activityName)!;
|
||||
|
||||
public T? GetActivityProperty<TActivity, T>(string activityId, Expression<Func<TActivity, T>> propertyExpression) where TActivity : IActivity
|
||||
{
|
||||
var expression = (MemberExpression) propertyExpression.Body;
|
||||
string propertyName = expression.Member.Name;
|
||||
return GetActivityProperty<T>(activityId, propertyName);
|
||||
}
|
||||
|
||||
public T? GetActivityProperty<T>(string activityId, string propertyName)
|
||||
{
|
||||
var data = GetActivityData(activityId);
|
||||
return data.GetState<T>(propertyName);
|
||||
}
|
||||
|
||||
public void SetWorkflowContext(object? value) => WorkflowContext = value;
|
||||
public object? GetWorkflowContext() => WorkflowContext;
|
||||
public T GetWorkflowContext<T>() => (T) WorkflowContext!;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ namespace Elsa.Handlers
|
|||
{
|
||||
Output = activityExecutionContext.Output,
|
||||
Outcomes = activityExecutionContext.Outcomes,
|
||||
State = activityExecutionContext.GetActivityData()
|
||||
};
|
||||
|
||||
var resuming = activityExecutionContext.Resuming;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.ControlFlow;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Services
|
||||
|
|
@ -14,15 +17,17 @@ namespace Elsa.Services
|
|||
{
|
||||
_elsaOptions = options;
|
||||
}
|
||||
|
||||
|
||||
public async Task<IActivity> ActivateActivityAsync(ActivityExecutionContext context, Type type, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var activity = _elsaOptions.ActivityFactory.CreateService(type, context.ServiceProvider);
|
||||
activity.Data = context.GetData();
|
||||
activity.Id = context.ActivityId;
|
||||
|
||||
ApplyStoredValues(context, activity);
|
||||
|
||||
// TODO: Make extensible / apply open/closed.
|
||||
if(ShouldSetProperties(activity))
|
||||
if (ShouldSetProperties(context, activity))
|
||||
{
|
||||
// TODO: Figure out how to deal with dynamically defined properties and what it means to set values to these.
|
||||
// ActivityTypes can have dynamic properties, so they need to be able to "intercept" when values are being applied.
|
||||
|
|
@ -30,11 +35,24 @@ namespace Elsa.Services
|
|||
//var activityType = await _activityTypeService.GetActivityTypeAsync(activity.Type, cancellationToken);
|
||||
await context.WorkflowExecutionContext.WorkflowBlueprint.ActivityPropertyProviders.SetActivityPropertiesAsync(activity, context, context.CancellationToken);
|
||||
}
|
||||
|
||||
|
||||
return activity;
|
||||
}
|
||||
|
||||
private bool ShouldSetProperties(IActivity activity)
|
||||
private void ApplyStoredValues(ActivityExecutionContext context, IActivity activity)
|
||||
{
|
||||
var properties = activity.GetType().GetProperties().Where(IsActivityProperty).ToList();
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var value = context.GetState(property.Name, property.PropertyType);
|
||||
|
||||
if (value != null)
|
||||
property.SetValue(activity, value);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldSetProperties(ActivityExecutionContext context, IActivity activity)
|
||||
{
|
||||
if (IsReturningComposite(activity))
|
||||
return false;
|
||||
|
|
@ -44,7 +62,7 @@ namespace Elsa.Services
|
|||
activity.Data.SetState("Unwinding", false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (IsReturningSwitch(activity))
|
||||
{
|
||||
activity.Data.SetState("Unwinding", false);
|
||||
|
|
@ -53,9 +71,10 @@ namespace Elsa.Services
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private bool IsReturningComposite(IActivity activity) => activity is CompositeActivity && activity.Data.GetState<bool>(nameof(CompositeActivity.IsScheduled));
|
||||
private bool IsReturningIf(IActivity activity) => activity is If && activity.Data.GetState<bool>("Unwinding");
|
||||
private bool IsReturningSwitch(IActivity activity) => activity is Switch && activity.Data.GetState<bool>("Unwinding");
|
||||
private bool IsActivityProperty(PropertyInfo property) => property.GetCustomAttribute<ActivityInputAttribute>() != null || property.GetCustomAttribute<ActivityOutputAttribute>() != null;
|
||||
}
|
||||
}
|
||||
|
|
@ -247,20 +247,23 @@ namespace Elsa.Services
|
|||
var correlationId = context.CorrelationId!;
|
||||
var lockKey = correlationId;
|
||||
|
||||
await using var handle = await AcquireLockAsync(lockKey, cancellationToken);
|
||||
|
||||
var correlatedWorkflowInstanceCount = !string.IsNullOrWhiteSpace(correlationId)
|
||||
? await _workflowInstanceStore.CountAsync(new CorrelationIdSpecification<WorkflowInstance>(correlationId).WithStatus(WorkflowStatus.Suspended), cancellationToken)
|
||||
: 0;
|
||||
|
||||
_logger.LogDebug("Found {CorrelatedWorkflowCount} workflows with correlation ID {CorrelationId}", correlatedWorkflowInstanceCount, correlationId);
|
||||
|
||||
if (correlatedWorkflowInstanceCount > 0)
|
||||
await using (var handle = await AcquireLockAsync(lockKey, cancellationToken))
|
||||
{
|
||||
var bookmarkResults = context.Bookmark != null ? await _bookmarkFinder.FindBookmarksAsync(context.ActivityType, context.Bookmark, correlationId, context.TenantId, cancellationToken).ToList() : new List<BookmarkFinderResult>();
|
||||
_logger.LogDebug("Found {BookmarkCount} bookmarks for activity type {ActivityType}", bookmarkResults.Count, context.ActivityType);
|
||||
return bookmarkResults.Select(x => new PendingWorkflow(x.WorkflowInstanceId, x.ActivityId)).ToList();
|
||||
}
|
||||
var correlatedWorkflowInstanceCount = !string.IsNullOrWhiteSpace(correlationId)
|
||||
? await _workflowInstanceStore.CountAsync(new CorrelationIdSpecification<WorkflowInstance>(correlationId).WithStatus(WorkflowStatus.Suspended), cancellationToken)
|
||||
: 0;
|
||||
|
||||
_logger.LogDebug("Found {CorrelatedWorkflowCount} workflows with correlation ID {CorrelationId}", correlatedWorkflowInstanceCount, correlationId);
|
||||
|
||||
if (correlatedWorkflowInstanceCount > 0)
|
||||
{
|
||||
var bookmarkResults = context.Bookmark != null
|
||||
? await _bookmarkFinder.FindBookmarksAsync(context.ActivityType, context.Bookmark, correlationId, context.TenantId, cancellationToken).ToList()
|
||||
: new List<BookmarkFinderResult>();
|
||||
_logger.LogDebug("Found {BookmarkCount} bookmarks for activity type {ActivityType}", bookmarkResults.Count, context.ActivityType);
|
||||
return bookmarkResults.Select(x => new PendingWorkflow(x.WorkflowInstanceId, x.ActivityId)).ToList();
|
||||
}
|
||||
} // This ensures the lock handle is released before calling the next line (CollectStartableWorkflowsAsync), which also acquires a lock on the correlation ID.
|
||||
|
||||
var startableWorkflows = await CollectStartableWorkflowsAsync(context, cancellationToken);
|
||||
return startableWorkflows.Select(x => new PendingWorkflow(x.WorkflowInstance.Id, x.ActivityId)).ToList();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<elsa-workflows-studio server-url="@ServerUrl" monaco-lib-path="@MonacoLibPath"></elsa-workflows-studio>
|
||||
<elsa-studio-root server-url="@ServerUrl" monaco-lib-path="@MonacoLibPath"></elsa-studio-root>
|
||||
|
||||
@code{
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function initializeMonacoWorker(libPath?: string) {
|
|||
return;
|
||||
|
||||
const origin = document.location.origin;
|
||||
const baseUrl = `${origin}/${libPath}`;
|
||||
const baseUrl = libPath.startsWith('http') ? libPath : `${origin}/${libPath}`;
|
||||
|
||||
require.config({paths: {'vs': `${baseUrl}/vs`}});
|
||||
win.MonacoEnvironment = {getWorkerUrl: () => proxy};
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<script type="module" src="/build/elsa-workflows-studio.esm.js"></script>
|
||||
<script nomodule src="/build/elsa-workflows-studio.js"></script>
|
||||
</head>
|
||||
<body class="elsa-h-screen" style="background-size: 30px 30px; background-image: url(/build/assets/images/tile.png); background-color: #FBFBFB;">
|
||||
<body>
|
||||
|
||||
<!--<elsa-studio-root server-url="https://localhost:11000" monaco-lib-path="build/assets/js/monaco-editor/min"></elsa-studio-root>-->
|
||||
<elsa-studio-root server-url="https://localhost:6082" monaco-lib-path="build/assets/js/monaco-editor/min"></elsa-studio-root>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ namespace ElsaDashboard.Samples.AspNetCore.Monolith
|
|||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
|
||||
.ConfigureWebHostDefaults(webBuilder => { webBuilder
|
||||
.UseStaticWebAssets()
|
||||
.UseStartup<Startup>(); });
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css.map" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\FONT-LICENSE" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\css\open-iconic-bootstrap.min.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.eot" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.otf" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.svg" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.ttf" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.woff" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\ICON-LICENSE" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\README.md" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\site.css" />
|
||||
<ProjectReference Include="..\..\..\..\designer\bindings\aspnet\Elsa.Designer.Components.Web\Elsa.Designer.Components.Web.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
@page "/"
|
||||
|
||||
<elsa-studio-root server-url="https://localhost:11000" monaco-lib-path="https://unpkg.com/browse/monaco-editor/min"></elsa-studio-root>
|
||||
<WorkflowStudio ServerUrl="https://localhost:11000"/>
|
||||
|
|
@ -12,12 +12,12 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>ElsaDashboard.Samples.BlazorServer</title>
|
||||
<base href="~/"/>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="https://unpkg.com/@@elsa-workflows/elsa-workflows-studio@2.0.0/dist/elsa-workflows-studio/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="https://unpkg.com/@@elsa-workflows/elsa-workflows-studio@2.0.0/dist/elsa-workflows-studio/assets/images/favicon-16x16.png">
|
||||
<link rel="stylesheet" href="https://unpkg.com/@@elsa-workflows/elsa-workflows-studio@2.0.0/dist/elsa-workflows-studio/assets/fonts/inter/inter.css">
|
||||
<link href="https://unpkg.com/@@elsa-workflows/elsa-workflows-studio@2.0.0/dist/elsa-workflows-studio/assets/styles/tailwind.css" rel="stylesheet">
|
||||
<script src="https://unpkg.com/browse/monaco-editor/min/vs/loader.js"></script>
|
||||
<script type="module" src="https://unpkg.com/@@elsa-workflows/elsa-workflows-studio@2.0.0/dist/elsa-workflows-studio/elsa-workflows-studio.esm.js"></script>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-16x16.png">
|
||||
<link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/fonts/inter/inter.css">
|
||||
<link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/styles/tailwind.css">
|
||||
<script src="/_content/Elsa.Designer.Components.Web/monaco-editor/min/vs/loader.js"></script>
|
||||
<script type="module" src="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.esm.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<component type="typeof(App)" render-mode="ServerPrerendered"/>
|
||||
|
|
|
|||
|
|
@ -6,5 +6,6 @@
|
|||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using Elsa.Designer.Components.Web
|
||||
@using ElsaDashboard.Samples.BlazorServer
|
||||
@using ElsaDashboard.Samples.BlazorServer.Shared
|
||||
|
|
@ -1,29 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0-preview.2.21154.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0-preview.2.21154.6" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.6" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\app.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css.map" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\FONT-LICENSE" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\css\open-iconic-bootstrap.min.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.eot" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.otf" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.svg" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.ttf" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.woff" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\ICON-LICENSE" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\README.md" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\designer\bindings\aspnet\Elsa.Designer.Components.Web\Elsa.Designer.Components.Web.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
@page "/"
|
||||
|
||||
<elsa-studio-root server-url="https://localhost:11000" monaco-lib-path="https://unpkg.com/browse/monaco-editor/min"></elsa-studio-root>
|
||||
<WorkflowStudio ServerUrl="https://localhost:11000"/>
|
||||
|
|
@ -6,5 +6,6 @@
|
|||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||
@using Microsoft.JSInterop
|
||||
@using Elsa.Designer.Components.Web
|
||||
@using ElsaDashboard.Samples.BlazorWasm
|
||||
@using ElsaDashboard.Samples.BlazorWasm.Shared
|
||||
|
|
@ -22,7 +22,10 @@ namespace Elsa.Scripting.JavaScript.Extensions
|
|||
where value != null && value.GetType() == typeof(JArray)
|
||||
select key).ToList();
|
||||
|
||||
jArrayKeys.ForEach(key => result[key] = ((JArray) result[key]).Values().Select(x => ((JValue) x).Value).ToArray());
|
||||
jArrayKeys.ForEach(key => result[key] = ((JArray) result[key]).Values().Select(x =>
|
||||
{
|
||||
return x is JValue jValue ? jValue.Value : x is JProperty jProperty ? ((JValue) jProperty.Value).Value : x;
|
||||
}).ToArray());
|
||||
jObjectKeys.ForEach(key => result[key] = ToDictionary((JObject) result[key]));
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -82,19 +82,18 @@ namespace Elsa.Scripting.JavaScript.Handlers
|
|||
foreach (var variable in variables.Data)
|
||||
engine.SetValue(variable.Key, variable.Value);
|
||||
|
||||
// TODO: Deprecated. Remove with next breaking version.
|
||||
// Activity outputs.
|
||||
foreach (var activity in workflowBlueprint.Activities.Where(x => x.Name is not null and not "" && workflowInstance.ActivityOutput.ContainsKey(x.Id)))
|
||||
{
|
||||
var output = new { Output = workflowInstance.ActivityOutput[activity.Id!] };
|
||||
engine.SetValue(activity.Name, output);
|
||||
}
|
||||
|
||||
// Named activities.
|
||||
foreach (var activity in workflowBlueprint.Activities.Where(x => !string.IsNullOrWhiteSpace(x.Name)))
|
||||
{
|
||||
var state = activityExecutionContext.GetActivityData(activity.Id);
|
||||
var dictionary = state.ToDictionary();
|
||||
|
||||
// Output.
|
||||
if (workflowInstance.ActivityOutput.ContainsKey(activity.Id))
|
||||
{
|
||||
var output = workflowInstance.ActivityOutput[activity.Id];
|
||||
dictionary["Output"] = output;
|
||||
}
|
||||
|
||||
engine.SetValue(activity.Name, dictionary);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,11 +76,10 @@ namespace Elsa.Scripting.Liquid.Handlers
|
|||
{
|
||||
var activityExecutionContext = activityModel.ActivityExecutionContext;
|
||||
|
||||
// Deprecated.
|
||||
if (name == "Output")
|
||||
{
|
||||
var output = activityExecutionContext.GetOutputFrom(activityModel.ActivityName!);
|
||||
return output != null ? new JObject(output) : default;
|
||||
return output != null ? JObjectExtensions.CreateState(output) : default;
|
||||
}
|
||||
|
||||
var workflowExecutionContext = activityExecutionContext.WorkflowExecutionContext;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,74 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Server.Api.Endpoints.Signals
|
||||
{
|
||||
public record DispatchSignalRequest(string? WorkflowInstanceId, string? CorrelationId, object? Input);
|
||||
public record DispatchSignalRequest
|
||||
{
|
||||
// Need this for Swagger.
|
||||
public DispatchSignalRequest()
|
||||
{
|
||||
}
|
||||
|
||||
public DispatchSignalRequest(string? workflowInstanceId, string? correlationId, object? input)
|
||||
{
|
||||
WorkflowInstanceId = workflowInstanceId;
|
||||
CorrelationId = correlationId;
|
||||
Input = input;
|
||||
}
|
||||
|
||||
public record DispatchSignalResponse(ICollection<PendingWorkflow> StartedWorkflows);
|
||||
public string? WorkflowInstanceId { get; init; }
|
||||
public string? CorrelationId { get; init; }
|
||||
public object? Input { get; init; }
|
||||
}
|
||||
|
||||
public record ExecuteSignalRequest(string? WorkflowInstanceId, string? CorrelationId, object? Input);
|
||||
public record DispatchSignalResponse
|
||||
{
|
||||
// Need this for Swagger.
|
||||
public DispatchSignalResponse()
|
||||
{
|
||||
}
|
||||
|
||||
public DispatchSignalResponse(ICollection<PendingWorkflow> startedWorkflows)
|
||||
{
|
||||
StartedWorkflows = startedWorkflows;
|
||||
}
|
||||
|
||||
public record ExecuteSignalResponse(ICollection<StartedWorkflow> StartedWorkflows);
|
||||
public ICollection<PendingWorkflow> StartedWorkflows { get; init; } = default!;
|
||||
}
|
||||
|
||||
public record ExecuteSignalRequest
|
||||
{
|
||||
// Need this for Swagger.
|
||||
public ExecuteSignalRequest()
|
||||
{
|
||||
}
|
||||
|
||||
public ExecuteSignalRequest(string? workflowInstanceId, string? correlationId, object? input)
|
||||
{
|
||||
WorkflowInstanceId = workflowInstanceId;
|
||||
CorrelationId = correlationId;
|
||||
Input = input;
|
||||
}
|
||||
|
||||
public string? WorkflowInstanceId { get; init; }
|
||||
public string? CorrelationId { get; init; }
|
||||
public object? Input { get; init; }
|
||||
}
|
||||
|
||||
public record ExecuteSignalResponse
|
||||
{
|
||||
// Need this for Swagger.
|
||||
public ExecuteSignalResponse()
|
||||
{
|
||||
}
|
||||
|
||||
public ExecuteSignalResponse(ICollection<StartedWorkflow> startedWorkflows)
|
||||
{
|
||||
StartedWorkflows = startedWorkflows;
|
||||
}
|
||||
|
||||
public ICollection<StartedWorkflow> StartedWorkflows { get; init; } = new List<StartedWorkflow>();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue