From ab151690b10e9c7003a03419d9d59247e8becc6a Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 17 Jun 2021 16:59:34 +0200 Subject: [PATCH] Telnyx activities improvements including Queue Wait Time --- .../Activities/BridgeCalls.cs | 4 +- .../Activities/CallRingGroup.cs | 46 +++++++++++++++---- .../Elsa.Activities.Telnyx/Activities/Dial.cs | 27 +++++++++-- .../Activities/Webhook.cs | 4 ++ .../ActivityExecutionContextExtensions.cs | 6 ++- .../Models/Extension.cs | 1 + .../TelnyxTypeDefinitionProvider.cs | 2 + .../Payloads/Call/CallHangupPayload.cs | 2 + .../Payloads/Call/CallInitiatedPayload.cs | 1 + .../Payloads/Call/CallRecordingSaved.cs | 1 + 10 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/BridgeCalls.cs b/src/activities/Elsa.Activities.Telnyx/Activities/BridgeCalls.cs index 77a1ccd33..80ecd25f8 100644 --- a/src/activities/Elsa.Activities.Telnyx/Activities/BridgeCalls.cs +++ b/src/activities/Elsa.Activities.Telnyx/Activities/BridgeCalls.cs @@ -120,7 +120,7 @@ namespace Elsa.Activities.Telnyx.Activities if (CallBridgedPayloadA != null && CallBridgedPayloadB != null) { - results.Add(Outcome(TelnyxOutcomeNames.Bridged)); + results.Add(Outcome(TelnyxOutcomeNames.Bridged, new BridgedCallsOutput(CallBridgedPayloadA, CallBridgedPayloadB))); } else { @@ -146,6 +146,8 @@ namespace Elsa.Activities.Telnyx.Activities } } + public record BridgedCallsOutput(CallBridgedPayload PayloadA, CallBridgedPayload PayloadB); + public static class BridgeCallsExtensions { public static ISetupActivity WithCallControlIdA(this ISetupActivity setup, Func> value) => setup.Set(x => x.CallControlIdA, value); diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/CallRingGroup.cs b/src/activities/Elsa.Activities.Telnyx/Activities/CallRingGroup.cs index becf08503..2bbc0c831 100644 --- a/src/activities/Elsa.Activities.Telnyx/Activities/CallRingGroup.cs +++ b/src/activities/Elsa.Activities.Telnyx/Activities/CallRingGroup.cs @@ -98,7 +98,17 @@ namespace Elsa.Activities.Telnyx.Activities get => GetState(() => Duration.FromSeconds(20)); set => SetState(value); } - + + [ActivityInput( + Hint = "The maximum time to wait for anyone to pickup before giving up.", + SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid} + )] + public Duration? MaxQueueWaitTime + { + get => GetState(); + set => SetState(value); + } + [ActivityInput( Hint = "Enables Answering Machine Detection.", UIHint = ActivityInputUIHints.Dropdown, @@ -121,11 +131,25 @@ namespace Elsa.Activities.Telnyx.Activities } public override void Build(ICompositeActivityBuilder builder) => - builder.Switch(cases => - { - cases.Add(RingGroupStrategy.PrioritizedHunt.ToString(), () => Strategy == RingGroupStrategy.PrioritizedHunt, BuildPrioritizedHuntFlow); - cases.Add(RingGroupStrategy.RingAll.ToString(), () => Strategy == RingGroupStrategy.RingAll, BuildRingAllFlow); - }); + builder + .StartWith(fork => fork.WithBranches("Ring", "Queue Timeout"), fork => + { + fork.When("Ring") + .While(true, iterate => iterate + .Switch(cases => + { + cases.Add(RingGroupStrategy.PrioritizedHunt.ToString(), () => Strategy == RingGroupStrategy.PrioritizedHunt, BuildPrioritizedHuntFlow); + cases.Add(RingGroupStrategy.RingAll.ToString(), () => Strategy == RingGroupStrategy.RingAll, BuildRingAllFlow); + })); + + fork.When("Queue Timeout") + .IfFalse(() => IsNullOrZero(MaxQueueWaitTime), whenTrue => + { + whenTrue + .Timer(() => MaxQueueWaitTime!.Value) + .Finish(TelnyxOutcomeNames.NoResponse); + }); + }); protected override async ValueTask OnExitAsync(ActivityExecutionContext context, object? output) { @@ -163,11 +187,11 @@ namespace Elsa.Activities.Telnyx.Activities .When(TelnyxOutcomeNames.Answered) .Then(bridgeCalls => bridgeCalls.When(TelnyxOutcomeNames.Bridged) - .Finish(TelnyxOutcomeNames.Connected)); + .Finish(activity => activity.WithOutcome(TelnyxOutcomeNames.Connected).WithOutput(context => context.GetInput()))); } ) ) - .Finish(TelnyxOutcomeNames.NoResponse); + .IfTrue(() => IsNullOrZero(MaxQueueWaitTime), whenTrue => whenTrue.Finish(TelnyxOutcomeNames.NoResponse)); private void BuildRingAllFlow(IOutcomeBuilder builder) => builder @@ -181,12 +205,12 @@ namespace Elsa.Activities.Telnyx.Activities .WithCallControlIdA(() => CallControlId) .WithCallControlIdB(() => CallAnsweredPayload!.CallControlId), bridge => bridge .When(TelnyxOutcomeNames.Bridged) - .Finish(TelnyxOutcomeNames.Connected)); + .Finish(activity => activity.WithOutcome(TelnyxOutcomeNames.Connected).WithOutput(context => context.GetInput()))); fork .When("Timeout") .StartIn(() => RingTime) - .Finish(TelnyxOutcomeNames.NoResponse); + .IfTrue(() => IsNullOrZero(MaxQueueWaitTime), whenTrue => whenTrue.Finish(TelnyxOutcomeNames.NoResponse)); fork .When("Dial Everyone") @@ -221,6 +245,8 @@ namespace Elsa.Activities.Telnyx.Activities return resolvedExtension?.Number ?? extension; } + private static bool IsNullOrZero(Duration? duration) => duration == null || duration.Value == Duration.Zero; + object IActivityPropertyDefaultValueProvider.GetDefaultValue(PropertyInfo property) => Duration.FromSeconds(20); } } \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/Dial.cs b/src/activities/Elsa.Activities.Telnyx/Activities/Dial.cs index 6be74ba71..bdf06c92c 100644 --- a/src/activities/Elsa.Activities.Telnyx/Activities/Dial.cs +++ b/src/activities/Elsa.Activities.Telnyx/Activities/Dial.cs @@ -122,6 +122,9 @@ namespace Elsa.Activities.Telnyx.Activities [ActivityOutput] public DialResponse? DialResponse { get; set; } [ActivityOutput] public CallPayload? Output { get; set; } + [ActivityOutput] public CallAnsweredPayload? AnsweredOutput { get; set; } + [ActivityOutput] public CallHangupPayload? HangupOutput { get; set; } + [ActivityOutput] public CallInitiatedPayload? InitiatedOutput { get; set; } protected override async ValueTask OnExecuteAsync(ActivityExecutionContext context) { @@ -140,13 +143,31 @@ namespace Elsa.Activities.Telnyx.Activities return payload switch { - CallAnsweredPayload => Outcome(TelnyxOutcomeNames.Answered), - CallHangupPayload => Outcome(TelnyxOutcomeNames.Hangup), - CallInitiatedPayload => Combine(Outcome(TelnyxOutcomeNames.CallInitiated), Suspend()), + CallAnsweredPayload answeredPayload => AnsweredOutcome(answeredPayload), + CallHangupPayload hangupPayload => HangupOutcome(hangupPayload), + CallInitiatedPayload initiatedPayload => Combine(InitiatedOutcome(initiatedPayload), Suspend()), _ => throw new ArgumentOutOfRangeException(nameof(payload)) }; } + private IActivityExecutionResult AnsweredOutcome(CallAnsweredPayload payload) + { + AnsweredOutput = payload; + return Outcome(TelnyxOutcomeNames.Answered, payload); + } + + private IActivityExecutionResult HangupOutcome(CallHangupPayload payload) + { + HangupOutput = payload; + return Outcome(TelnyxOutcomeNames.Hangup, payload); + } + + private IActivityExecutionResult InitiatedOutcome(CallInitiatedPayload payload) + { + InitiatedOutput = payload; + return Outcome(TelnyxOutcomeNames.CallInitiated, payload); + } + private async Task DialAsync(ActivityExecutionContext context) { var connectionId = string.IsNullOrWhiteSpace(ConnectionId) ? _telnyxOptions.CallControlAppId : ConnectionId; diff --git a/src/activities/Elsa.Activities.Telnyx/Activities/Webhook.cs b/src/activities/Elsa.Activities.Telnyx/Activities/Webhook.cs index 886697663..696dfa85c 100644 --- a/src/activities/Elsa.Activities.Telnyx/Activities/Webhook.cs +++ b/src/activities/Elsa.Activities.Telnyx/Activities/Webhook.cs @@ -32,8 +32,12 @@ namespace Elsa.Activities.Telnyx.Activities context.SetCallControlId(callPayload.CallControlId); if (callPayload is CallInitiatedPayload callInitiatedPayload) + { if (!context.HasFromNumber()) context.SetFromNumber(callInitiatedPayload.To); + + context.SetCallerNumber(callInitiatedPayload.From); + } } Model = webhookModel; diff --git a/src/activities/Elsa.Activities.Telnyx/Extensions/ActivityExecutionContextExtensions.cs b/src/activities/Elsa.Activities.Telnyx/Extensions/ActivityExecutionContextExtensions.cs index 09d02d7c9..5ebb5becf 100644 --- a/src/activities/Elsa.Activities.Telnyx/Extensions/ActivityExecutionContextExtensions.cs +++ b/src/activities/Elsa.Activities.Telnyx/Extensions/ActivityExecutionContextExtensions.cs @@ -7,6 +7,7 @@ namespace Elsa.Activities.Telnyx.Extensions { private const string CallControlIdVariableName = "TelnyxCallControlId"; private const string FromNumberVariableName = "TelnyxFromNumber"; + private const string CallerNumberVariableName = "TelnyxCallerNumber"; /// /// Returns the control ID of the active call session in the workflow if the specified call control ID is null or empty. @@ -50,7 +51,10 @@ namespace Elsa.Activities.Telnyx.Extensions /// /// Sets a workflow variable with the specified call control ID value. /// - public static void SetFromNumber(this ActivityExecutionContext context, string fromNumber) => context.SetVariable(FromNumberVariableName, fromNumber); + public static void SetFromNumber(this ActivityExecutionContext context, string number) => context.SetVariable(FromNumberVariableName, number); + + public static void SetCallerNumber(this ActivityExecutionContext context, string number) => context.SetVariable(CallerNumberVariableName, number); + public static string? GetCallerNumber(this ActivityExecutionContext context) => context.GetVariable(CallerNumberVariableName); public static bool HasFromNumber(this ActivityExecutionContext context) => context.HasVariable(FromNumberVariableName); } diff --git a/src/activities/Elsa.Activities.Telnyx/Models/Extension.cs b/src/activities/Elsa.Activities.Telnyx/Models/Extension.cs index 22e3b1609..fdb44860a 100644 --- a/src/activities/Elsa.Activities.Telnyx/Models/Extension.cs +++ b/src/activities/Elsa.Activities.Telnyx/Models/Extension.cs @@ -18,5 +18,6 @@ namespace Elsa.Activities.Telnyx.Models public string? FirstName { get; init; } public string? LastName { get; init; } + public string? Email { get; init; } } } \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Telnyx/Scripting/JavaScript/TelnyxTypeDefinitionProvider.cs b/src/activities/Elsa.Activities.Telnyx/Scripting/JavaScript/TelnyxTypeDefinitionProvider.cs index 9c0112106..9b7120089 100644 --- a/src/activities/Elsa.Activities.Telnyx/Scripting/JavaScript/TelnyxTypeDefinitionProvider.cs +++ b/src/activities/Elsa.Activities.Telnyx/Scripting/JavaScript/TelnyxTypeDefinitionProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Elsa.Activities.Telnyx.Models; using Elsa.Activities.Telnyx.Webhooks.Models; using Elsa.Activities.Telnyx.Webhooks.Payloads.Call; using Elsa.Scripting.JavaScript.Services; @@ -13,6 +14,7 @@ namespace Elsa.Activities.Telnyx.Scripting.JavaScript return new[] { typeof(TelnyxWebhook), + typeof(Extension), typeof(CallAnsweredPayload), typeof(CallBridgedPayload), diff --git a/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallHangupPayload.cs b/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallHangupPayload.cs index 63c9b1285..3fd2aabdb 100644 --- a/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallHangupPayload.cs +++ b/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallHangupPayload.cs @@ -13,5 +13,7 @@ namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call public string SipHangupCause { get; init; } = default!; public string HangupSource { get; init; } = default!; public string HangupCause { get; init; } = default!; + public string From { get; set; } = default!; + public string To { get; set; } = default!; } } \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallInitiatedPayload.cs b/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallInitiatedPayload.cs index efea98057..59dd735c5 100644 --- a/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallInitiatedPayload.cs +++ b/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallInitiatedPayload.cs @@ -10,5 +10,6 @@ namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call public string Direction { get; init; } = default!; public string State { get; init; } = default!; public string To { get; init; } = default!; + public string From { get; init; } = default!; } } \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallRecordingSaved.cs b/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallRecordingSaved.cs index 7ae5d3b80..375889ff2 100644 --- a/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallRecordingSaved.cs +++ b/src/activities/Elsa.Activities.Telnyx/Webhooks/Payloads/Call/CallRecordingSaved.cs @@ -13,5 +13,6 @@ namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call public CallRecordingUrls RecordingUrls { get; set; } = default!; public Instant RecordingEndedAt { get; set; } public Instant RecordingStartedAt { get; set; } + public Duration Duration => RecordingEndedAt - RecordingStartedAt; } } \ No newline at end of file