From 67efc51ebbb633eac79e9e44c545dbdd7641668e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 2 Mar 2023 10:37:16 +0100 Subject: [PATCH] Update Dial activity and fix Switch (#3753) * Add GetCallStatus activity * Update Dial activity * Update Switch activity * Fix missing ports --- .../flowchart/default-activity-template.tsx | 2 +- .../modules/switch/switch-port-provider.ts | 4 +- .../components/toolbox-activities.tsx | 2 +- .../src/services/activity-walker.ts | 13 +++--- src/modules/Elsa.Telnyx/Activities/Dial.cs | 18 ++++++-- .../Elsa.Telnyx/Activities/GetCallStatus.cs | 45 +++++++++++++++++++ .../Elsa.Telnyx/Client/Models/Requests.cs | 1 + .../Elsa.Workflows.Core/Activities/Switch.cs | 8 ++-- 8 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 src/modules/Elsa.Telnyx/Activities/GetCallStatus.cs diff --git a/src/designer/elsa-workflows-designer/src/modules/flowchart/default-activity-template.tsx b/src/designer/elsa-workflows-designer/src/modules/flowchart/default-activity-template.tsx index df42bb96d..29d5694fb 100644 --- a/src/designer/elsa-workflows-designer/src/modules/flowchart/default-activity-template.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/flowchart/default-activity-template.tsx @@ -52,7 +52,7 @@ export class DefaultActivityTemplate { {({nodeMap}) => { const activity: Activity = nodeMap[activityId]; const ports = portProvider.getOutboundPorts({activityDescriptor, activity}); - const embeddedPorts = ports.filter(x => x.mode == PortMode.Embedded && x.isBrowsable); + const embeddedPorts = ports.filter(x => x.mode == PortMode.Embedded && x.isBrowsable !== false); const canStartWorkflow = activity?.canStartWorkflow; const icon = this.icon; const hasIcon = !!icon; diff --git a/src/designer/elsa-workflows-designer/src/modules/switch/switch-port-provider.ts b/src/designer/elsa-workflows-designer/src/modules/switch/switch-port-provider.ts index b6a12f240..db0104ba0 100644 --- a/src/designer/elsa-workflows-designer/src/modules/switch/switch-port-provider.ts +++ b/src/designer/elsa-workflows-designer/src/modules/switch/switch-port-provider.ts @@ -15,7 +15,9 @@ export class SwitchPortProvider implements PortProvider { return []; const cases = activity.cases ?? []; - return cases.map(x => ({name: x.label, displayName: x.label, mode: PortMode.Embedded})); + const ports = cases.map(x => ({name: x.label, displayName: x.label, mode: PortMode.Embedded})); + const defaultPort = {name: 'default', displayName: 'Default', mode: PortMode.Embedded}; + return [...ports, defaultPort]; } resolvePort(portName: string, context: PortProviderContext): Activity | Array { diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/toolbox-activities.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/toolbox-activities.tsx index 12d7e38a9..eecaed274 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/toolbox-activities.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/toolbox-activities.tsx @@ -52,7 +52,7 @@ export class ToolboxActivities { buildModel = (): any => { const browsableDescriptors = uniqBy(descriptorsStore.activityDescriptors - .filter(x => x.isBrowsable) + .filter(x => x.isBrowsable !== false) .sort((a, b) => a.version > b.version ? 1 : -1), x => `${x.typeName}:${x.version}`); const categorizedActivitiesLookup = groupBy(browsableDescriptors, x => x.category); diff --git a/src/designer/elsa-workflows-designer/src/services/activity-walker.ts b/src/designer/elsa-workflows-designer/src/services/activity-walker.ts index a18e11b1e..c027147c0 100644 --- a/src/designer/elsa-workflows-designer/src/services/activity-walker.ts +++ b/src/designer/elsa-workflows-designer/src/services/activity-walker.ts @@ -81,10 +81,12 @@ function walkRecursive(node: ActivityNode, activity: Activity, collectedActiviti } if (childNode !== node) { - childNode.parents.push(node); - node.children.push(childNode); - collectedActivities.add(port.activity); - walkRecursive(childNode, port.activity, collectedActivities, collectedNodes, descriptors); + if(!!childNode.activity) { + childNode.parents.push(node); + node.children.push(childNode); + collectedActivities.add(port.activity) + walkRecursive(childNode, port.activity, collectedActivities, collectedNodes, descriptors); + } } } } @@ -104,9 +106,6 @@ function getPorts(node: ActivityNode, activity: Activity, descriptors: Array; activityPorts = [...activityPorts, ...activities.map(x => ({port: port.name, activity: x}))]; diff --git a/src/modules/Elsa.Telnyx/Activities/Dial.cs b/src/modules/Elsa.Telnyx/Activities/Dial.cs index f43517654..130404bec 100644 --- a/src/modules/Elsa.Telnyx/Activities/Dial.cs +++ b/src/modules/Elsa.Telnyx/Activities/Dial.cs @@ -23,7 +23,7 @@ public class Dial : CodeActivity public Dial(string? source = default, int? line = default) : base(source, line) { } - + /// /// The DID or SIP URI to dial out and bridge to the given call. /// @@ -52,20 +52,31 @@ public class Dial : CodeActivity Options = new[] { "disabled", "detect", "detect_beep", "detect_words", "greeting_end", "premium" }, DefaultValue = "disabled")] public Input AnsweringMachineDetection { get; set; } = new("disabled"); - + /// /// Enables answering machine detection. /// [Input(Description = "Start recording automatically after an event. Disabled by default.")] public Input Record { get; set; } = default!; + /// + /// Defines the format of the recording ('wav' or 'mp3') when `record` is specified. + /// + [Input( + Description = "Defines the format of the recording ('wav' or 'mp3') when `record` is specified.", + UIHint = InputUIHints.Dropdown, + Options = new[] { "wav", "mp3" }, + DefaultValue = "mp3" + )] + public Input RecordFormat { get; set; } = new("mp3"); + /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { var response = await DialAsync(context); Result.Set(context, response); } - + private async Task DialAsync(ActivityExecutionContext context) { var telnyxOptions = context.GetRequiredService>().Value; @@ -84,6 +95,7 @@ public class Dial : CodeActivity FromDisplayName.TryGet(context).SanitizeCallerName(), AnsweringMachineDetection.TryGet(context), Record: Record.TryGet(context) == true ? "record-from-answer" : default, + RecordFormat: RecordFormat.TryGet(context) ?? "mp3", ClientState: clientState ); diff --git a/src/modules/Elsa.Telnyx/Activities/GetCallStatus.cs b/src/modules/Elsa.Telnyx/Activities/GetCallStatus.cs new file mode 100644 index 000000000..942a044c7 --- /dev/null +++ b/src/modules/Elsa.Telnyx/Activities/GetCallStatus.cs @@ -0,0 +1,45 @@ +using System.Runtime.CompilerServices; +using System.Text.Json.Serialization; +using Elsa.Extensions; +using Elsa.Telnyx.Client.Services; +using Elsa.Workflows.Core.Activities.Flowchart.Attributes; +using Elsa.Workflows.Core.Attributes; +using Elsa.Workflows.Core.Models; + +namespace Elsa.Telnyx.Activities; + +/// +[FlowNode("Alive", "Dead", "Done")] +[Activity(Constants.Namespace, "Get the status of a call.", Kind = ActivityKind.Task)] +public class GetCallStatus : CodeActivity +{ + /// + [JsonConstructor] + public GetCallStatus([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + { + } + + /// + /// Unique identifier and token for controlling the call. + /// + [Input( + DisplayName = "Call Control ID", + Description = "Unique identifier and token for controlling the call.", + Category = "Advanced" + )] + public Input CallControlId { get; set; } = default!; + + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var client = context.GetRequiredService(); + var callControlId = CallControlId.Get(context); + var response = await client.Calls.GetStatusAsync(callControlId, context.CancellationToken); + var isAlive = response.Data.IsAlive; + var outcome = isAlive ? "Alive" : "Dead"; + + Result.Set(context, isAlive); + + await context.CompleteActivityWithOutcomesAsync(outcome, "Done"); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Client/Models/Requests.cs b/src/modules/Elsa.Telnyx/Client/Models/Requests.cs index 44d8e536e..80e376b40 100644 --- a/src/modules/Elsa.Telnyx/Client/Models/Requests.cs +++ b/src/modules/Elsa.Telnyx/Client/Models/Requests.cs @@ -67,6 +67,7 @@ public record DialRequest( string? AnsweringMachineDetection = default, AnsweringMachineConfig? AnsweringMachineConfig = default, string? Record = default, + string? RecordFormat = default, string? ClientState = default, string? CommandId = default, IList
? CustomHeaders = default, diff --git a/src/modules/Elsa.Workflows.Core/Activities/Switch.cs b/src/modules/Elsa.Workflows.Core/Activities/Switch.cs index 72ee0c27b..68f4e1942 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/Switch.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/Switch.cs @@ -34,7 +34,7 @@ public class Switch : Activity public Output? Output { get; set; } [Input(UIHint = "switch-editor")] public ICollection Cases { get; set; } = new List(); - public IActivity? Default { get; set; } + [Port]public IActivity? Default { get; set; } /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) @@ -44,13 +44,11 @@ public class Switch : Activity if (matchingCase != null) { - if (matchingCase.Activity != null) - await context.ScheduleActivityAsync(matchingCase.Activity, OnChildActivityCompletedAsync); + await context.ScheduleActivityAsync(matchingCase.Activity, OnChildActivityCompletedAsync); return; } - if (Default != null) - await context.ScheduleActivityAsync(Default, OnChildActivityCompletedAsync); + await context.ScheduleActivityAsync(Default, OnChildActivityCompletedAsync); } private async Task FindMatchingCaseAsync(ExpressionExecutionContext context)