This commit is contained in:
Sipke Schoorstra 2021-03-22 20:15:25 +01:00
parent 009f840477
commit 5d8d05aec5
30 changed files with 561 additions and 43 deletions

View file

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Elsa.Activities.ControlFlow;
using Elsa.Attributes;
using Elsa.Builders;
using Elsa.Design;
using Elsa.Metadata;
using Elsa.Services;
using NodaTime;
namespace Elsa.Activities.Telnyx.Activities
{
[Action(
Category = Constants.Category,
Description = "Call a ring group.",
Outcomes = new[] { "Connected", "NoResponse" },
DisplayName = "Call Ring Group"
)]
public class CallRingGroup : CompositeActivity, IActivityPropertyDefaultValueProvider
{
[ActivityProperty(UIHint = ActivityPropertyUIHints.MultiText)]
public IList<string> Extensions
{
get => GetState<IList<string>>(() => new List<string>());
set => SetState(value);
}
[ActivityProperty]
public RingGroupStrategy Strategy
{
get => GetState<RingGroupStrategy>();
set => SetState(value);
}
[ActivityProperty(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.")]
public string? From
{
get => GetState<string>();
set => SetState(value);
}
[ActivityProperty(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.")]
public string? FromDisplayName
{
get => GetState<string>();
set => SetState(value);
}
[ActivityProperty(DefaultValueProvider = typeof(CallRingGroup))]
public Duration RingTime
{
get => GetState(() => Duration.FromSeconds(20));
set => SetState(value);
}
public override void Build(ICompositeActivityBuilder builder)
{
builder.Switch(cases =>
{
cases.Add(RingGroupStrategy.PrioritizedHunt.ToString(), () => Strategy == RingGroupStrategy.PrioritizedHunt, BuildSerialFlow);
cases.Add(RingGroupStrategy.RingAll.ToString(), () => Strategy == RingGroupStrategy.RingAll, BuildRingAllFlow);
});
}
private void BuildSerialFlow(IOutcomeBuilder builder)
{
builder
.ForEach(() => Extensions, DialExtension);
}
private void BuildRingAllFlow(IOutcomeBuilder builder)
{
builder.Then(() => Console.Write("Let's do parallel"));
}
private void DialExtension(IOutcomeBuilder builder)
{
builder
.Then<ResolveExtension>(a => a.WithExtension(context => context.GetInput<string>()))
.Then<Dial>(a => a
.WithTo(context => context.GetInput<string>())
.WithFrom(() => From)
.WithFromDisplayName(() => FromDisplayName)
)
.Then();
}
object IActivityPropertyDefaultValueProvider.GetDefaultValue(PropertyInfo property) => Duration.FromSeconds(20);
}
}

View file

@ -0,0 +1,216 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Telnyx.Client.Models;
using Elsa.Activities.Telnyx.Client.Services;
using Elsa.Activities.Telnyx.Options;
using Elsa.ActivityResults;
using Elsa.Attributes;
using Elsa.Builders;
using Elsa.Design;
using Elsa.Exceptions;
using Elsa.Services;
using Elsa.Services.Models;
using Refit;
namespace Elsa.Activities.Telnyx.Activities
{
[Action(
Category = Constants.Category,
Description = "Dial a number or SIP URI from a given connection.",
Outcomes = new[] { OutcomeNames.Done },
DisplayName = "Dial"
)]
public class Dial : Activity
{
private readonly ITelnyxClient _telnyxClient;
private readonly TelnyxOptions _telnyxOptions;
public Dial(ITelnyxClient telnyxClient, TelnyxOptions telnyxOptions)
{
_telnyxClient = telnyxClient;
_telnyxOptions = telnyxOptions;
}
[ActivityProperty(Label = "Connection ID", Hint = "The ID of the Call Control App (formerly ID of the connection) to be used when dialing the destination.", Category = PropertyCategories.Advanced)]
public string CallControlAppId { get; set; } = default!;
[ActivityProperty(Label = "To", Hint = "The DID or SIP URI to dial out and bridge to the given call.")]
public string To { get; set; }
[ActivityProperty(Label = "From",
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.")]
public string? From { get; set; }
[ActivityProperty(Label = "From Display Name",
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.")]
public string? FromDisplayName { get; set; }
[ActivityProperty(Label = "Answering Machine Detection", Hint = "Enables Answering Machine Detection.", UIHint = ActivityPropertyUIHints.Dropdown,
Options = new[] { "disabled", "detect", "detect_beep", "detect_words", "greeting_end" })]
public string? AnsweringMachineDetection { get; set; }
[ActivityProperty(Label = "Answering Machine Detection Configuration", Hint = "Optional configuration parameters to modify answering machine detection performance.", Category = PropertyCategories.Advanced)]
public AnsweringMachineConfig? AnsweringMachineDetectionConfig { get; set; }
[ActivityProperty(Label = "Command ID", Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.", Category = PropertyCategories.Advanced)]
public string? CommandId { get; set; }
[ActivityProperty(Label = "Client State", Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.", Category = PropertyCategories.Advanced)]
public string? ClientState { get; set; }
[ActivityProperty(Label = "Custom Headers", Hint = "Custom headers to be added to the SIP INVITE.", Category = PropertyCategories.Advanced)]
public IList<Header>? CustomHeaders { get; set; }
[ActivityProperty(Label = "SIP Authentication Username", Hint = "SIP Authentication username used for SIP challenges.", Category = "SIP Authentication")]
public string? SipAuthUsername { get; set; }
[ActivityProperty(Label = "SIP Authentication Password", Hint = "SIP Authentication password used for SIP challenges.", Category = "SIP Authentication")]
public string? SipAuthPassword { get; set; }
[ActivityProperty(Label = "Time Limit", Hint = "Sets the maximum duration of a Call Control Leg in seconds.", Category = PropertyCategories.Advanced)]
public int? TimeLimitSecs { get; set; }
[ActivityProperty(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)]
public int? TimeoutSecs { get; set; }
[ActivityProperty(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)]
public string? WebhookUrl { get; set; }
[ActivityProperty(Label = "Webhook URL Method", Hint = "HTTP request type used for Webhook URL", UIHint = ActivityPropertyUIHints.Dropdown, Options = new[] { "GET", "POST" }, Category = PropertyCategories.Advanced)]
public string? WebhookUrlMethod { get; set; }
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
{
await DialAsync(context);
return Done();
}
private async ValueTask DialAsync(ActivityExecutionContext context)
{
var connectionId = CallControlAppId is not null and not "" ? CallControlAppId : _telnyxOptions.CallControlAppId ?? throw new Exception("No Call Control ID specified and no default value configured");
var request = new DialRequest(
connectionId,
To,
From,
FromDisplayName,
AnsweringMachineDetection,
AnsweringMachineDetectionConfig,
ClientState,
CommandId,
CustomHeaders,
SipAuthUsername,
SipAuthPassword,
TimeLimitSecs,
TimeoutSecs,
WebhookUrl,
WebhookUrlMethod
);
try
{
await _telnyxClient.Calls.DialAsync(request, context.CancellationToken);
}
catch (ApiException e)
{
throw new WorkflowException(e.Content ?? e.Message, e);
}
}
}
public static class DialExtensions
{
public static ISetupActivity<Dial> WithCallControlAppId(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.CallControlAppId, value);
public static ISetupActivity<Dial> WithCallControlAppId(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.CallControlAppId, value);
public static ISetupActivity<Dial> WithCallControlAppId(this ISetupActivity<Dial> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.CallControlAppId, value);
public static ISetupActivity<Dial> WithCallControlAppId(this ISetupActivity<Dial> setup, Func<string?> value) => setup.Set(x => x.CallControlAppId, value);
public static ISetupActivity<Dial> WithCallControlAppId(this ISetupActivity<Dial> setup, string? value) => setup.Set(x => x.CallControlAppId, value);
public static ISetupActivity<Dial> WithTo(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.To, value);
public static ISetupActivity<Dial> WithTo(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.To, value);
public static ISetupActivity<Dial> WithTo(this ISetupActivity<Dial> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.To, value);
public static ISetupActivity<Dial> WithTo(this ISetupActivity<Dial> setup, Func<string?> value) => setup.Set(x => x.To, value);
public static ISetupActivity<Dial> WithTo(this ISetupActivity<Dial> setup, string? value) => setup.Set(x => x.To, value);
public static ISetupActivity<Dial> WithFrom(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.From, value);
public static ISetupActivity<Dial> WithFrom(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.From, value);
public static ISetupActivity<Dial> WithFrom(this ISetupActivity<Dial> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.From, value);
public static ISetupActivity<Dial> WithFrom(this ISetupActivity<Dial> setup, Func<string?> value) => setup.Set(x => x.From, value);
public static ISetupActivity<Dial> WithFrom(this ISetupActivity<Dial> setup, string? value) => setup.Set(x => x.From, value);
public static ISetupActivity<Dial> WithFromDisplayName(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.FromDisplayName, value);
public static ISetupActivity<Dial> WithFromDisplayName(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.FromDisplayName, value);
public static ISetupActivity<Dial> WithFromDisplayName(this ISetupActivity<Dial> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.FromDisplayName, value);
public static ISetupActivity<Dial> WithFromDisplayName(this ISetupActivity<Dial> setup, Func<string?> value) => setup.Set(x => x.FromDisplayName, value);
public static ISetupActivity<Dial> WithFromDisplayName(this ISetupActivity<Dial> setup, string? value) => setup.Set(x => x.FromDisplayName, value);
public static ISetupActivity<Dial> WithAnsweringMachineDetection(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.AnsweringMachineDetection, value);
public static ISetupActivity<Dial> WithAnsweringMachineDetection(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.AnsweringMachineDetection, value);
public static ISetupActivity<Dial> WithAnsweringMachineDetection(this ISetupActivity<Dial> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.AnsweringMachineDetection, value);
public static ISetupActivity<Dial> WithAnsweringMachineDetection(this ISetupActivity<Dial> setup, Func<string?> value) => setup.Set(x => x.AnsweringMachineDetection, value);
public static ISetupActivity<Dial> WithAnsweringMachineDetection(this ISetupActivity<Dial> setup, string? value) => setup.Set(x => x.AnsweringMachineDetection, value);
public static ISetupActivity<Dial> WithAnsweringMachineDetectionConfig(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<AnsweringMachineConfig?>> value) => setup.Set(x => x.AnsweringMachineDetectionConfig, value);
public static ISetupActivity<Dial> WithAnsweringMachineDetectionConfig(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, AnsweringMachineConfig?> value) => setup.Set(x => x.AnsweringMachineDetectionConfig, value);
public static ISetupActivity<Dial> WithAnsweringMachineDetectionConfig(this ISetupActivity<Dial> setup, Func<ValueTask<AnsweringMachineConfig?>> value) => setup.Set(x => x.AnsweringMachineDetectionConfig, value);
public static ISetupActivity<Dial> WithAnsweringMachineDetectionConfig(this ISetupActivity<Dial> setup, Func<AnsweringMachineConfig?> value) => setup.Set(x => x.AnsweringMachineDetectionConfig, value);
public static ISetupActivity<Dial> WithAnsweringMachineDetectionConfig(this ISetupActivity<Dial> setup, AnsweringMachineConfig? value) => setup.Set(x => x.AnsweringMachineDetectionConfig, value);
public static ISetupActivity<Dial> WithCommandId(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.CommandId, value);
public static ISetupActivity<Dial> WithCommandId(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.CommandId, value);
public static ISetupActivity<Dial> WithCommandId(this ISetupActivity<Dial> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.CommandId, value);
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);
public static ISetupActivity<Dial> WithCustomHeaders(this ISetupActivity<Dial> setup, Func<IList<Header>?> value) => setup.Set(x => x.CustomHeaders, value);
public static ISetupActivity<Dial> WithCustomHeaders(this ISetupActivity<Dial> setup, IList<Header>? value) => setup.Set(x => x.CustomHeaders, value);
public static ISetupActivity<Dial> WithSipAuthUsername(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.SipAuthUsername, value);
public static ISetupActivity<Dial> WithSipAuthUsername(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.SipAuthUsername, value);
public static ISetupActivity<Dial> WithSipAuthUsername(this ISetupActivity<Dial> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.SipAuthUsername, value);
public static ISetupActivity<Dial> WithSipAuthUsername(this ISetupActivity<Dial> setup, Func<string?> value) => setup.Set(x => x.SipAuthUsername, value);
public static ISetupActivity<Dial> WithSipAuthUsername(this ISetupActivity<Dial> setup, string? value) => setup.Set(x => x.SipAuthUsername, value);
public static ISetupActivity<Dial> WithSipAuthPassword(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.SipAuthPassword, value);
public static ISetupActivity<Dial> WithSipAuthPassword(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.SipAuthPassword, value);
public static ISetupActivity<Dial> WithSipAuthPassword(this ISetupActivity<Dial> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.SipAuthPassword, value);
public static ISetupActivity<Dial> WithSipAuthPassword(this ISetupActivity<Dial> setup, Func<string?> value) => setup.Set(x => x.SipAuthPassword, value);
public static ISetupActivity<Dial> WithSipAuthPassword(this ISetupActivity<Dial> setup, string? value) => setup.Set(x => x.SipAuthPassword, value);
public static ISetupActivity<Dial> WithTimeLimitSecs(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<int?>> value) => setup.Set(x => x.TimeLimitSecs, value);
public static ISetupActivity<Dial> WithTimeLimitSecs(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, int?> value) => setup.Set(x => x.TimeLimitSecs, value);
public static ISetupActivity<Dial> WithTimeLimitSecs(this ISetupActivity<Dial> setup, Func<ValueTask<int?>> value) => setup.Set(x => x.TimeLimitSecs, value);
public static ISetupActivity<Dial> WithTimeLimitSecs(this ISetupActivity<Dial> setup, Func<int?> value) => setup.Set(x => x.TimeLimitSecs, value);
public static ISetupActivity<Dial> WithTimeLimitSecs(this ISetupActivity<Dial> setup, int? value) => setup.Set(x => x.TimeLimitSecs, value);
public static ISetupActivity<Dial> WithTimeoutSecs(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<int?>> value) => setup.Set(x => x.TimeoutSecs, value);
public static ISetupActivity<Dial> WithTimeoutSecs(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, int?> value) => setup.Set(x => x.TimeoutSecs, value);
public static ISetupActivity<Dial> WithTimeoutSecs(this ISetupActivity<Dial> setup, Func<ValueTask<int?>> value) => setup.Set(x => x.TimeoutSecs, value);
public static ISetupActivity<Dial> WithTimeoutSecs(this ISetupActivity<Dial> setup, Func<int?> value) => setup.Set(x => x.TimeoutSecs, value);
public static ISetupActivity<Dial> WithTimeoutSecs(this ISetupActivity<Dial> setup, int? value) => setup.Set(x => x.TimeoutSecs, value);
public static ISetupActivity<Dial> WithWebhookUrl(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.WebhookUrl, value);
public static ISetupActivity<Dial> WithWebhookUrl(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.WebhookUrl, value);
public static ISetupActivity<Dial> WithWebhookUrl(this ISetupActivity<Dial> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.WebhookUrl, value);
public static ISetupActivity<Dial> WithWebhookUrl(this ISetupActivity<Dial> setup, Func<string?> value) => setup.Set(x => x.WebhookUrl, value);
public static ISetupActivity<Dial> WithWebhookUrl(this ISetupActivity<Dial> setup, string? value) => setup.Set(x => x.WebhookUrl, value);
public static ISetupActivity<Dial> WithWebhookUrlMethod(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.WebhookUrlMethod, value);
public static ISetupActivity<Dial> WithWebhookUrlMethod(this ISetupActivity<Dial> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.WebhookUrlMethod, value);
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);
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Elsa.Activities.Telnyx.Services;
using Elsa.ActivityResults;
using Elsa.Attributes;
using Elsa.Builders;
using Elsa.Services;
using Elsa.Services.Models;
namespace Elsa.Activities.Telnyx.Activities
{
[Action(
Category = Constants.Category,
Description = "Resolve an extension number into a real number (DID or SIP url).",
Outcomes = new[] { OutcomeNames.Done, "Resolved", "Unresolved" },
DisplayName = "Resolve Extension"
)]
public class ResolveExtension : Activity
{
private readonly IExtensionProvider _extensionProvider;
public ResolveExtension(IExtensionProvider extensionProvider)
{
_extensionProvider = extensionProvider;
}
[ActivityProperty(Hint = "The extension to resolve. If the extension could not be resolved, it is returned as-is.")]
public string Extension { get; set; }
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
{
var resolvedNumber = await _extensionProvider.GetAsync(Extension, context.CancellationToken);
var result = resolvedNumber != null ? Outcome("Resolved", resolvedNumber) : Outcome("Unresolved", Extension);
return Combine(result, Done());
}
}
public static class ResolveExtensionExtensions
{
public static ISetupActivity<ResolveExtension> WithExtension(this ISetupActivity<ResolveExtension> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.Extension, value);
public static ISetupActivity<ResolveExtension> WithExtension(this ISetupActivity<ResolveExtension> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.Extension, value);
public static ISetupActivity<ResolveExtension> WithExtension(this ISetupActivity<ResolveExtension> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.Extension, value);
public static ISetupActivity<ResolveExtension> WithExtension(this ISetupActivity<ResolveExtension> setup, Func<string?> value) => setup.Set(x => x.Extension, value);
public static ISetupActivity<ResolveExtension> WithExtension(this ISetupActivity<ResolveExtension> setup, string? value) => setup.Set(x => x.Extension, value);
}
}

View file

@ -37,4 +37,22 @@ namespace Elsa.Activities.Telnyx.Client.Models
string? WebhookUrl,
string? WebhookUrlMethod
);
public record DialRequest(
string ConnectionId,
string To,
string? From,
string? FromDisplayName,
string? AnsweringMachineDetection,
AnsweringMachineConfig? AnsweringMachineConfig,
string? ClientState,
string? CommandId,
IList<Header>? CustomHeaders,
string? SipAuthUsername,
string? SipAuthPassword,
int? TimeLimitSecs,
int? TimeoutSecs,
string? WebhookUrl,
string? WebhookUrlMethod
);
}

View file

@ -7,6 +7,9 @@ namespace Elsa.Activities.Telnyx.Client.Services
{
public interface ICallsApi
{
[Post("/v2/calls")]
Task DialAsync([Body]DialRequest request, CancellationToken cancellationToken = default);
[Post("/v2/calls/{callControlId}/actions/answer")]
Task AnswerCallAsync(string callControlId, [Body]AnswerCallRequest request, CancellationToken cancellationToken = default);

View file

@ -9,6 +9,7 @@ using Elsa.Activities.Telnyx.ActivityTypes;
using Elsa.Activities.Telnyx.Bookmarks;
using Elsa.Activities.Telnyx.Client.Services;
using Elsa.Activities.Telnyx.Options;
using Elsa.Activities.Telnyx.Services;
using Elsa.Activities.Telnyx.Webhooks.Consumers;
using Elsa.Activities.Telnyx.Webhooks.Events;
using Elsa.Activities.Telnyx.Webhooks.Services;
@ -44,7 +45,8 @@ namespace Elsa.Activities.Telnyx.Extensions
.AddActivityTypeProvider<NotificationActivityTypeProvider>()
.AddBookmarkProvider<NotificationBookmarkProvider>()
.AddNotificationHandlers(typeof(TriggerWorkflows))
.AddScoped<IWebhookHandler, WebhookHandler>();
.AddScoped<IWebhookHandler, WebhookHandler>()
.AddScoped(telnyxOptions.ExtensionProviderFactory);
// Telnyx API Client.
var refitSettings = CreateRefitSettings();

View file

@ -0,0 +1,22 @@
using System;
namespace Elsa.Activities.Telnyx.Models
{
public record Extension
{
public string Number { get; init; } = default!;
/// <summary>
/// The DID or SIP URL
/// </summary>
public string Destination { get; init; }
/// <summary>
/// An URL to a sound wave to play as a voicemail message when the recipient doesn't pick up.
/// </summary>
public Uri? VoicemailRecordingUrl { get; set; }
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
}

View file

@ -0,0 +1,8 @@
namespace Elsa.Activities.Telnyx.Activities
{
public enum RingGroupStrategy
{
PrioritizedHunt,
RingAll
}
}

View file

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using Elsa.Activities.Telnyx.Services;
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Activities.Telnyx.Options
{
@ -12,10 +14,14 @@ namespace Elsa.Activities.Telnyx.Options
{
typeof(CallInitiatedPayload)
};
ExtensionProviderFactory = sp => ActivatorUtilities.CreateInstance<NullExtensionProvider>(sp);
}
public IList<Type> PayloadTypes { get; set; }
public Uri ApiUrl { get; set; } = new("https://api.telnyx.com");
public string ApiKey { get; set; } = default!;
public string? CallControlAppId { get; set; }
internal Func<IServiceProvider, IExtensionProvider> ExtensionProviderFactory { get; set; }
}
}

View file

@ -0,0 +1,11 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Telnyx.Models;
namespace Elsa.Activities.Telnyx.Services
{
public interface IExtensionProvider
{
Task<Extension?> GetAsync(string extensionNumber, CancellationToken cancellationToken = default);
}
}

View file

@ -0,0 +1,11 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Telnyx.Models;
namespace Elsa.Activities.Telnyx.Services
{
public class NullExtensionProvider : IExtensionProvider
{
public Task<Extension?> GetAsync(string extensionNumber, CancellationToken cancellationToken = default) => Task.FromResult<Extension?>(default);
}
}

View file

@ -31,7 +31,7 @@ namespace Elsa.Attributes
public object? Options { get; set; }
/// <summary>
/// The name of a static method that provides options.
/// The type that provides options.
/// </summary>
public Type? OptionsProvider { get; set; }
@ -44,5 +44,10 @@ namespace Elsa.Attributes
/// The default value to set.
/// </summary>
public object? DefaultValue { get; set; }
/// <summary>
/// The type that provides a default value.
/// </summary>
public Type? DefaultValueProvider { get; set; }
}
}

View file

@ -33,11 +33,13 @@ namespace Elsa.Builders
IActivityBuilder Add<T>(
Action<ISetupActivity<T>>? setup,
Action<IActivityBuilder>? branch = default,
string? activityType = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity;
IActivityBuilder Add<T>(
Action<IActivityBuilder>? branch = default,
string? activityType = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)

View file

@ -17,11 +17,11 @@ namespace Elsa.Builders
public static class SetupActivityExtensions
{
public static ISetupActivity<T> Set<T, TProperty>(
this ISetupActivity<T> builder,
Expression<Func<T, TProperty?>> propertyAccessor,
Func<ActivityExecutionContext, ValueTask<TProperty?>> valueFactory) where T : IActivity =>
builder.Set(propertyAccessor, async context => await valueFactory(context));
// public static ISetupActivity<T> Set<T, TProperty>(
// this ISetupActivity<T> builder,
// Expression<Func<T, TProperty?>> propertyAccessor,
// Func<ActivityExecutionContext, ValueTask<TProperty?>> valueFactory) where T : IActivity =>
// builder.Set(propertyAccessor, async context => await valueFactory(context));
public static ISetupActivity<T> Set<T, TProperty>(
this ISetupActivity<T> builder,

View file

@ -0,0 +1,9 @@
using System.Reflection;
namespace Elsa.Metadata
{
public interface IActivityPropertyDefaultValueProvider
{
object GetDefaultValue(PropertyInfo property);
}
}

View file

@ -0,0 +1,9 @@
using System.Reflection;
namespace Elsa.Metadata
{
public interface IActivityPropertyDefaultValueResolver
{
object? GetDefaultValue(PropertyInfo activityPropertyInfo);
}
}

View file

@ -14,7 +14,7 @@ namespace Elsa.Services
{
internal const string Enter = "Enter";
public virtual void Build(ICompositeActivityBuilder activity)
public virtual void Build(ICompositeActivityBuilder builder)
{
}

View file

@ -7,7 +7,7 @@ namespace Elsa.Activities.Workflows.Workflow
[Browsable(false)]
public class Workflow : CompositeActivity
{
public override void Build(ICompositeActivityBuilder activity)
public override void Build(ICompositeActivityBuilder builder)
{
}
}

View file

@ -12,12 +12,14 @@ namespace Elsa.Builders
{
public ActivityBuilder(
Type activityType,
string? activityTypeName,
ICompositeActivityBuilder workflowBuilder,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders,
int lineNumber,
string? sourceFile)
{
ActivityType = activityType;
ActivityTypeName = activityTypeName ?? activityType.Name;
WorkflowBuilder = workflowBuilder;
PropertyValueProviders = propertyValueProviders;
LineNumber = lineNumber;
@ -29,6 +31,7 @@ namespace Elsa.Builders
}
public Type ActivityType { get; protected set; } = default!;
public string ActivityTypeName { get; set; } = default!;
public ICompositeActivityBuilder WorkflowBuilder { get; set; } = default!;
public string ActivityId { get; set; } = default!;
public string? Name { get; set; }
@ -45,22 +48,24 @@ namespace Elsa.Builders
public IActivityBuilder Add<T>(
Action<ISetupActivity<T>>? setup = default,
string? activityType = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity =>
WorkflowBuilder.Add(setup, null, lineNumber, sourceFile);
WorkflowBuilder.Add(setup, null, activityType, lineNumber, sourceFile);
public IOutcomeBuilder When(string outcome) => new OutcomeBuilder(WorkflowBuilder, this, outcome);
public virtual IActivityBuilder Then<T>(
Action<ISetupActivity<T>>? setup = null,
Action<IActivityBuilder>? branch = null,
string? activityType = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity =>
When(OutcomeNames.Done).Then(setup, branch, lineNumber, sourceFile);
When(OutcomeNames.Done).Then(setup, branch, act, lineNumber, sourceFile);
public virtual IActivityBuilder Then<T>(Action<IActivityBuilder>? branch = null, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
public virtual IActivityBuilder Then<T>(Action<IActivityBuilder>? branch = null, string? activityTypeName = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
where T : class, IActivity =>
When(OutcomeNames.Done).Then<T>(branch, lineNumber, sourceFile);

View file

@ -38,21 +38,28 @@ namespace Elsa.Builders
public IReadOnlyCollection<IActivityBuilder> Activities => ActivityBuilders.ToList().AsReadOnly();
public IActivityBuilder New(Type activityType, IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
public IActivityBuilder New(
Type activityType,
string? activityTypeName = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
{
var activityBuilder = new ActivityBuilder(activityType, this, propertyValueProviders, lineNumber, sourceFile);
var activityBuilder = new ActivityBuilder(activityType, activityTypeName, this, propertyValueProviders, lineNumber, sourceFile);
return activityBuilder;
}
public IActivityBuilder New<T>(
string? activityTypeName = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity =>
New(typeof(T), propertyValueProviders, lineNumber, sourceFile);
New(typeof(T), activityTypeName, propertyValueProviders, lineNumber, sourceFile);
public IActivityBuilder New<T>(
Action<ISetupActivity<T>>? setup = default,
string? activityTypeName = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity
{
@ -61,61 +68,68 @@ namespace Elsa.Builders
var valueProviders = propertyValuesBuilder.ValueProviders.ToDictionary(
x => x.Key,
x => (IActivityPropertyValueProvider)new DelegateActivityPropertyValueProvider(x.Value));
x => (IActivityPropertyValueProvider) new DelegateActivityPropertyValueProvider(x.Value));
return New<T>(valueProviders, lineNumber, sourceFile);
return New<T>(activityTypeName, valueProviders, lineNumber, sourceFile);
}
public IActivityBuilder StartWith<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default,
string? activityTypeName = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity
{
var activityBuilder = New(setup, lineNumber, sourceFile);
var activityBuilder = New(setup, activityTypeName, lineNumber, sourceFile);
return Add(activityBuilder, branch);
}
public IActivityBuilder StartWith<T>(Action<IActivityBuilder>? branch = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
public IActivityBuilder StartWith<T>(Action<IActivityBuilder>? branch = default, string? activityTypeName = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
where T : class, IActivity =>
Add<T>(branch, null, lineNumber, sourceFile);
Add<T>(branch, activityTypeName, null, lineNumber, sourceFile);
public IActivityBuilder Add<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default,
string? activityType = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity
{
var activityBuilder = New(setup, lineNumber, sourceFile);
var activityBuilder = New(setup, activityType, lineNumber, sourceFile);
return Add(activityBuilder, branch);
}
public IActivityBuilder Add<T>(
Action<IActivityBuilder>? branch = default,
string? activityType = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default)
where T : class, IActivity
{
var activityBuilder = new ActivityBuilder(typeof(T), this, propertyValueProviders, lineNumber, sourceFile);
var activityBuilder = new ActivityBuilder(typeof(T), activityType, this, propertyValueProviders, lineNumber, sourceFile);
return Add(activityBuilder, branch);
}
public IActivityBuilder Add(
IActivityBuilder activityBuilder,
Action<IActivityBuilder>? branch = default)
public IActivityBuilder Add(IActivityBuilder activityBuilder, Action<IActivityBuilder>? branch = default)
{
branch?.Invoke(activityBuilder);
ActivityBuilders.Add(activityBuilder);
return activityBuilder;
}
public override IActivityBuilder Then<T>(Action<ISetupActivity<T>>? setup = null, Action<IActivityBuilder>? branch = null, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) =>
StartWith(setup, branch, lineNumber, sourceFile);
public override IActivityBuilder Then<T>(
Action<ISetupActivity<T>>? setup = null,
Action<IActivityBuilder>? branch = null,
string? activityTypeName = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) =>
StartWith(setup, branch, activityTypeName, lineNumber, sourceFile);
public override IActivityBuilder Then(IActivityBuilder targetActivity) => Add(targetActivity);
public override IActivityBuilder Then<T>(Action<IActivityBuilder>? branch = null, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => StartWith<T>(branch, lineNumber, sourceFile);
public override IActivityBuilder Then<T>(Action<IActivityBuilder>? branch = null, string? activityTypeName = default, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default)
where T : class, IActivity => StartWith<T>(branch, activityTypeName, lineNumber, sourceFile);
public IConnectionBuilder Connect(
IActivityBuilder source,
@ -184,7 +198,7 @@ namespace Elsa.Builders
using var scope = ServiceProvider.CreateScope();
foreach (var activityBuilder in compositeActivityBuilders)
{
var compositeActivity = (CompositeActivity)ActivatorUtilities.CreateInstance(scope.ServiceProvider, activityBuilder.ActivityType);
var compositeActivity = (CompositeActivity) ActivatorUtilities.CreateInstance(scope.ServiceProvider, activityBuilder.ActivityType);
var compositeActivityBuilder = _compositeActivityBuilderFactory();
compositeActivityBuilder.ActivityId = activityBuilder.ActivityId;
compositeActivity.Build(compositeActivityBuilder);

View file

@ -23,10 +23,11 @@ namespace Elsa.Builders
public IActivityBuilder Then<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default,
string? activityType = default,
[CallerLineNumber] int lineNumber = default,
[CallerFilePath] string? sourceFile = default) where T : class, IActivity
{
var activityBuilder = WorkflowBuilder.Add(setup, null, lineNumber, sourceFile);
var activityBuilder = WorkflowBuilder.Add(setup, default, activityType, lineNumber, sourceFile);
Then(activityBuilder, branch);
return activityBuilder;
}

View file

@ -138,6 +138,7 @@ namespace Microsoft.Extensions.DependencyInjection
services
.AddSingleton<IDescribeActivityType, TypedActivityTypeDescriber>()
.AddSingleton<IActivityPropertyOptionsResolver, ActivityPropertyOptionsResolver>()
.AddSingleton<IActivityPropertyDefaultValueResolver, ActivityPropertyDefaultValueResolver>()
.AddSingleton<IActivityPropertyUIHintResolver, ActivityPropertyUIHintResolver>();
// Bookmarks.

View file

@ -0,0 +1,34 @@
using System;
using System.Reflection;
using Elsa.Attributes;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Metadata
{
public class ActivityPropertyDefaultValueResolver : IActivityPropertyDefaultValueResolver
{
private readonly IServiceProvider _serviceProvider;
public ActivityPropertyDefaultValueResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public object? GetDefaultValue(PropertyInfo activityPropertyInfo)
{
var activityPropertyAttribute = activityPropertyInfo.GetCustomAttribute<ActivityPropertyAttribute>();
if (activityPropertyAttribute == null)
return null;
if (activityPropertyAttribute.DefaultValueProvider == null)
return activityPropertyAttribute.DefaultValue;
var providerType = activityPropertyAttribute.DefaultValueProvider;
using var scope = _serviceProvider.CreateScope();
var provider = (IActivityPropertyDefaultValueProvider) ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType);
return provider.GetDefaultValue(activityPropertyInfo);
}
}
}

View file

@ -11,11 +11,13 @@ namespace Elsa.Metadata
{
private readonly IActivityPropertyOptionsResolver _optionsResolver;
private readonly IActivityPropertyUIHintResolver _uiHintResolver;
private readonly IActivityPropertyDefaultValueResolver _defaultValueResolver;
public TypedActivityTypeDescriber(IActivityPropertyOptionsResolver optionsResolver, IActivityPropertyUIHintResolver uiHintResolver)
public TypedActivityTypeDescriber(IActivityPropertyOptionsResolver optionsResolver, IActivityPropertyUIHintResolver uiHintResolver, IActivityPropertyDefaultValueResolver defaultValueResolver)
{
_optionsResolver = optionsResolver;
_uiHintResolver = uiHintResolver;
_defaultValueResolver = defaultValueResolver;
}
public ActivityDescriptor? Describe(Type activityType)
@ -60,7 +62,7 @@ namespace Elsa.Metadata
activityPropertyAttribute.Hint,
_optionsResolver.GetOptions(propertyInfo),
activityPropertyAttribute.Category,
activityPropertyAttribute.DefaultValue
_defaultValueResolver.GetDefaultValue(propertyInfo)
);
}
}

View file

@ -4,7 +4,6 @@ import {ConnectionModel} from "../models";
let jsPlumbInstance = null;
function onConnectionCreated(e, callback) {
debugger;
const connection: ConnectionModel = {
sourceId: e.connection.getParameter('sourceActivityId'),
targetId: e.connection.getParameter('targetActivityId'),

View file

@ -20,9 +20,9 @@ namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Activities
set => SetState(value);
}
public override void Build(ICompositeActivityBuilder activity)
public override void Build(ICompositeActivityBuilder builder)
{
activity
builder
.For(() => Start, () => 0, () => -1, iterate =>
{
iterate.WriteLine(context => $"{context.ForScope().CurrentValue}...");

View file

@ -14,9 +14,9 @@ namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Activities
[Action(Outcomes = new[] { "Left", "Right" })]
public class NavigateActivity : CompositeActivity
{
public override void Build(ICompositeActivityBuilder activity)
public override void Build(ICompositeActivityBuilder builder)
{
activity
builder
.StartWith(GetInstructions)
.WriteLine(context => (string)context.Input)
.ReadLine()

View file

@ -9,9 +9,9 @@ namespace Elsa.Samples.Temporal.Activities
{
public class MyContainer1 : CompositeActivity
{
public override void Build(ICompositeActivityBuilder activity)
public override void Build(ICompositeActivityBuilder builder)
{
activity
builder
.StartWith<Fork>(fork => fork.WithBranches("A", "B", "C"), fork =>
{
fork

View file

@ -9,9 +9,9 @@ namespace Elsa.Samples.Temporal.Activities
{
public class MyContainer2 : CompositeActivity
{
public override void Build(ICompositeActivityBuilder activity)
public override void Build(ICompositeActivityBuilder builder)
{
activity
builder
.StartWith<Fork>(fork => fork.WithBranches("D", "E"), fork =>
{
fork

View file

@ -25,9 +25,9 @@ namespace Elsa.Samples.WhileLoopWorker.Activities
_phoneCallService = phoneCallService;
}
public override void Build(ICompositeActivityBuilder activity)
public override void Build(ICompositeActivityBuilder builder)
{
activity
builder
.While(() => _phoneCallService.CallStatus != PhoneCallStatus.Finished,
@while =>
{