diff --git a/src/activities/Elsa.Activities.Http/Activities/HttpEndpoint/HttpEndpoint.cs b/src/activities/Elsa.Activities.Http/Activities/HttpEndpoint/HttpEndpoint.cs index bcb5373af..194187762 100644 --- a/src/activities/Elsa.Activities.Http/Activities/HttpEndpoint/HttpEndpoint.cs +++ b/src/activities/Elsa.Activities.Http/Activities/HttpEndpoint/HttpEndpoint.cs @@ -41,7 +41,7 @@ namespace Elsa.Activities.Http DefaultSyntax = SyntaxNames.Json, SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript, SyntaxNames.Liquid })] - public HashSet Methods { get; set; } = new HashSet { "GET" }; + public HashSet Methods { get; set; } = new() { "GET" }; /// /// A value indicating whether the HTTP request content body should be read and stored as part of the HTTP request model. diff --git a/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs b/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs index 03609ada0..f24b46dfc 100644 --- a/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs +++ b/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs @@ -10,6 +10,7 @@ using Elsa.Activities.Http.Parsers.Request; using Elsa.Activities.Http.Parsers.Response; using Elsa.Activities.Http.Services; using Elsa.Options; +using Elsa.Scripting.JavaScript.Providers; using Elsa.Scripting.Liquid.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -58,6 +59,7 @@ namespace Microsoft.Extensions.DependencyInjection .AddNotificationHandlers(typeof(ConfigureJavaScriptEngine)) .AddLiquidFilter("signal_url") .AddJavaScriptTypeDefinitionProvider() + .AddSingleton() .AddDataProtection(); return services; diff --git a/src/activities/Elsa.Activities.Http/JavaScript/HttpEndpointTypeDefinitionRenderer.cs b/src/activities/Elsa.Activities.Http/JavaScript/HttpEndpointTypeDefinitionRenderer.cs new file mode 100644 index 000000000..bdbc71c5e --- /dev/null +++ b/src/activities/Elsa.Activities.Http/JavaScript/HttpEndpointTypeDefinitionRenderer.cs @@ -0,0 +1,101 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Metadata; +using Elsa.Models; +using Elsa.Scripting.JavaScript.Events; +using Elsa.Scripting.JavaScript.Providers; +using Elsa.Services.Models; +using NJsonSchema; +using NJsonSchema.CodeGeneration.TypeScript; + +namespace Elsa.Activities.Http.JavaScript +{ + public class HttpEndpointTypeDefinitionRenderer : DefaultActivityTypeDefinitionRenderer + { + public class CustomTypeNameGenerator : DefaultTypeNameGenerator + { + public string Prefix { get; } + + public CustomTypeNameGenerator(string prefix) + { + Prefix = prefix; + } + + protected override string Generate(JsonSchema schema, string typeNameHint) + { + var result = base.Generate(schema, typeNameHint); + return result; + } + } + + public override async ValueTask RenderTypeDeclarationAsync( + RenderingTypeScriptDefinitions notification, + ActivityType activityType, + ActivityDescriptor activityDescriptor, + ActivityDefinition activityDefinition, + StringBuilder writer, + CancellationToken cancellationToken = default) + { + var targetTypeSchema = activityDefinition.Properties.First(x => x.Name == nameof(HttpEndpoint.Schema)).Expressions.Values.First(); + + if (!string.IsNullOrWhiteSpace(targetTypeSchema)) + { + var jsonSchema = await JsonSchema.FromJsonAsync(targetTypeSchema, cancellationToken); + var generator = new TypeScriptGenerator(jsonSchema, new TypeScriptGeneratorSettings + { + TypeStyle = TypeScriptTypeStyle.Interface, + TypeScriptVersion = 4, + TypeNameGenerator = new CustomTypeNameGenerator(activityDefinition.Name!) + }); + + var typeScriptType = $"{activityDefinition.Name}Output"; + + var jsonSchemaTypes = generator.GenerateFile(typeScriptType) + .Replace("\r\n", "\n") + .Replace("export interface", "declare class"); + + writer.AppendLine(jsonSchemaTypes); + } + + await base.RenderTypeDeclarationAsync(notification, activityType, activityDescriptor, activityDefinition, writer, cancellationToken); + } + + protected override async ValueTask RenderActivityPropertyAsync( + RenderingTypeScriptDefinitions notification, + StringBuilder writer, + string propertyName, + Type propertyType, + ActivityType activityType, + ActivityDescriptor activityDescriptor, + ActivityDefinition activityDefinition, + CancellationToken cancellationToken = default) + { + if (propertyName != nameof(HttpEndpoint.Output)) + { + await base.RenderActivityPropertyAsync(notification, writer, propertyName, propertyType, activityType, activityDescriptor, activityDefinition, cancellationToken); + return; + } + + var targetTypeName = activityDefinition.Properties.First(x => x.Name == nameof(HttpEndpoint.TargetType)).Expressions.Values.First(); + var targetTypeSchema = activityDefinition.Properties.First(x => x.Name == nameof(HttpEndpoint.Schema)).Expressions.Values.First(); + var typeScriptType = notification.GetTypeScriptType(propertyType); + + if (!string.IsNullOrWhiteSpace(targetTypeName)) + { + var type = Type.GetType(targetTypeName); + + if (type != null) + typeScriptType = notification.GetTypeScriptType(type); + } + else if (!string.IsNullOrWhiteSpace(targetTypeSchema)) + { + typeScriptType = $"{activityDefinition.Name}Output"; + } + + writer.AppendLine($"{propertyName}(): {typeScriptType}"); + } + } +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/JavaScript/HttpTypeDefinitionProvider.cs b/src/activities/Elsa.Activities.Http/JavaScript/HttpTypeDefinitionProvider.cs index b18edd1a3..6b7596ac0 100644 --- a/src/activities/Elsa.Activities.Http/JavaScript/HttpTypeDefinitionProvider.cs +++ b/src/activities/Elsa.Activities.Http/JavaScript/HttpTypeDefinitionProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Elsa.Activities.Http.Models; using Elsa.Scripting.JavaScript.Services; @@ -9,7 +10,29 @@ namespace Elsa.Activities.Http.JavaScript { public override IEnumerable CollectTypes(TypeDefinitionContext context) { - return new[] { typeof(HttpRequestModel), typeof(HttpResponseHeaders), typeof(HttpResponseModel) }; + var workflowDefinition = context.WorkflowDefinition; + + if (workflowDefinition != null) + { + var httpEndpointActivities = workflowDefinition.Activities.Where(x => x.Type == nameof(HttpEndpoint)).ToList(); + + foreach (var type in + from activityDefinition in httpEndpointActivities + select activityDefinition.Properties.First(x => x.Name == nameof(HttpEndpoint.TargetType)).Expressions.Values.First() + into targetTypeName + where !string.IsNullOrWhiteSpace(targetTypeName) + select Type.GetType(targetTypeName) + into type + where type != null + select type) + { + yield return type; + } + } + + yield return typeof(HttpRequestModel); + yield return typeof(HttpResponseHeaders); + yield return typeof(HttpResponseModel); } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Metadata/ActivityPropertyOptionsResolver.cs b/src/core/Elsa.Core/Metadata/ActivityPropertyOptionsResolver.cs index 494bed622..6f5fcd13c 100644 --- a/src/core/Elsa.Core/Metadata/ActivityPropertyOptionsResolver.cs +++ b/src/core/Elsa.Core/Metadata/ActivityPropertyOptionsResolver.cs @@ -27,12 +27,25 @@ namespace Elsa.Metadata return null; if (activityPropertyAttribute.OptionsProvider == null) - return activityPropertyAttribute.Options ?? (TryGetEnumOptions(activityPropertyInfo, out var items) ? items : null); + { + var options = activityPropertyAttribute.Options; + + if (options is IEnumerable enumerable) + return new SelectList + { + Items = enumerable.Select(x => new SelectListItem(x)).ToList() + }; + + if (TryGetEnumOptions(activityPropertyInfo, out var items)) + return items; + + return activityPropertyAttribute.Options; + } var providerType = activityPropertyAttribute.OptionsProvider; using var scope = _serviceProvider.CreateScope(); - var provider = (IActivityPropertyOptionsProvider) ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType); + var provider = (IActivityPropertyOptionsProvider)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, providerType); return provider.GetOptions(activityPropertyInfo); } diff --git a/src/designer/elsa-workflows-studio/src/utils/select-list-items.ts b/src/designer/elsa-workflows-studio/src/utils/select-list-items.ts index 5ef8738ec..9d64411e6 100644 --- a/src/designer/elsa-workflows-studio/src/utils/select-list-items.ts +++ b/src/designer/elsa-workflows-studio/src/utils/select-list-items.ts @@ -12,11 +12,16 @@ async function fetchRuntimeItems(serverUrl: string, options: RuntimeSelectListPr } export async function getSelectListItems(serverUrl: string, propertyDescriptor: ActivityPropertyDescriptor): Promise { - const options = propertyDescriptor.options; + const options: any = propertyDescriptor.options; let selectList: SelectList; if (!!options && options.runtimeSelectListProviderType) selectList = await fetchRuntimeItems(serverUrl, options); + else if (Array.isArray(options)) + selectList = { + items: options, + isFlagsEnum: false + }; else selectList = options as SelectList; diff --git a/src/scripting/Elsa.Scripting.JavaScript/Extensions/JavaScriptServiceCollectionExtensions.cs b/src/scripting/Elsa.Scripting.JavaScript/Extensions/JavaScriptServiceCollectionExtensions.cs index ae28ce4b3..88af124fc 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/Extensions/JavaScriptServiceCollectionExtensions.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/Extensions/JavaScriptServiceCollectionExtensions.cs @@ -28,6 +28,7 @@ namespace Microsoft.Extensions.DependencyInjection .AddJavaScriptTypeDefinitionProvider() .AddJavaScriptTypeDefinitionProvider() .AddJavaScriptTypeDefinitionProvider() + .AddSingleton() .AddNotificationHandlers(typeof(JavaScriptServiceCollectionExtensions)); } diff --git a/src/scripting/Elsa.Scripting.JavaScript/Handlers/RenderJavaScriptTypeDefinitions.cs b/src/scripting/Elsa.Scripting.JavaScript/Handlers/RenderJavaScriptTypeDefinitions.cs index 56ae855d0..33c327aed 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/Handlers/RenderJavaScriptTypeDefinitions.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/Handlers/RenderJavaScriptTypeDefinitions.cs @@ -1,9 +1,11 @@ -using System; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Elsa.Models; using Elsa.Scripting.JavaScript.Events; +using Elsa.Scripting.JavaScript.Providers; using Elsa.Services; using Elsa.Services.Models; using MediatR; @@ -13,10 +15,12 @@ namespace Elsa.Scripting.JavaScript.Handlers public class RenderJavaScriptTypeDefinitions : INotificationHandler { private readonly IActivityTypeService _activityTypeService; + private readonly IEnumerable _activityTypeDefinitionRenderers; - public RenderJavaScriptTypeDefinitions(IActivityTypeService activityTypeService) + public RenderJavaScriptTypeDefinitions(IActivityTypeService activityTypeService, IEnumerable activityTypeDefinitionRenderers) { _activityTypeService = activityTypeService; + _activityTypeDefinitionRenderers = activityTypeDefinitionRenderers.OrderByDescending(x => x.Priority).ToList(); } public async Task Handle(RenderingTypeScriptDefinitions notification, CancellationToken cancellationToken) @@ -72,17 +76,20 @@ namespace Elsa.Scripting.JavaScript.Handlers var activityTypes = await Task.WhenAll(activityTypeNames.Select(async activityTypeName => (activityTypeName, await _activityTypeService.GetActivityTypeAsync(activityTypeName, cancellationToken)))); var activityTypeDictionary = activityTypes.ToDictionary(x => x.activityTypeName, x => x.Item2); - foreach (var activityType in activityTypeDictionary.Values) - await RenderActivityTypeDeclarationAsync(activityType, output); + foreach (var namedActivity in namedActivities) + { + var activityType = activityTypeDictionary[namedActivity.Type]; + await RenderActivityTypeDeclarationAsync(namedActivity, activityType, output); + } output.AppendLine("declare interface Activities {"); foreach (var activity in namedActivities) { - var activityType = activityTypeDictionary[activity.Type]; - var typeScriptType = activityType.TypeName; - var query = $"{activity.Name}: {typeScriptType}"; - var interfaceActivity = FindInterface(notification, query); + //var activityType = activityTypeDictionary[activity.Type]; + //var typeScriptType = activityType.TypeName; + var typeScriptType = activity.Name; + var interfaceActivity = $"{activity.Name}: {typeScriptType}"; output.AppendLine($"{interfaceActivity};"); } @@ -90,39 +97,11 @@ namespace Elsa.Scripting.JavaScript.Handlers output.AppendLine("declare const activities: Activities"); } - async Task RenderActivityTypeDeclarationAsync(ActivityType type, StringBuilder writer) + async Task RenderActivityTypeDeclarationAsync(ActivityDefinition activityDefinition, ActivityType type, StringBuilder writer) { - var typeName = type.TypeName; var descriptor = await type.DescribeAsync(); - var inputProperties = descriptor.InputProperties; - var outputProperties = descriptor.OutputProperties; - - var query = $"declare interface {typeName}"; - var interfaceDeclaration = FindInterface(notification, query); - writer.AppendLine($"{interfaceDeclaration} {{"); - - foreach (var property in inputProperties) - RenderActivityProperty(writer, typeName, property.Name, property.Type); - - foreach (var property in outputProperties) - RenderActivityProperty(writer, typeName, property.Name, property.Type); - - writer.AppendLine("}"); - } - - void RenderActivityProperty(StringBuilder writer, string typeName, string propertyName, Type propertyType) - { - var typeScriptType = notification.GetTypeScriptType(propertyType); - - var query = $"{propertyName}(): {typeScriptType}"; - var interfaceDeclaration = FindInterface(notification, query); - writer.AppendLine($"{interfaceDeclaration};"); - } - - string FindInterface(RenderingTypeScriptDefinitions notification, string query) - { - var result = notification.DeclaredTypes.FirstOrDefault(x => x.Contains(query)); - return result ?? query; + var renderer = _activityTypeDefinitionRenderers.First(x => x.GetCanRenderType(type)); + await renderer.RenderTypeDeclarationAsync(notification, type, descriptor, activityDefinition, writer, cancellationToken); } } } diff --git a/src/scripting/Elsa.Scripting.JavaScript/Providers/DefaultActivityTypeDefinitionRenderer.cs b/src/scripting/Elsa.Scripting.JavaScript/Providers/DefaultActivityTypeDefinitionRenderer.cs new file mode 100644 index 000000000..69459537f --- /dev/null +++ b/src/scripting/Elsa.Scripting.JavaScript/Providers/DefaultActivityTypeDefinitionRenderer.cs @@ -0,0 +1,55 @@ +using System; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Metadata; +using Elsa.Models; +using Elsa.Scripting.JavaScript.Events; +using Elsa.Services.Models; + +namespace Elsa.Scripting.JavaScript.Providers +{ + public class DefaultActivityTypeDefinitionRenderer : IActivityTypeDefinitionRenderer + { + public int Priority => -1; + public bool GetCanRenderType(ActivityType activityType) => true; + + public virtual async ValueTask RenderTypeDeclarationAsync( + RenderingTypeScriptDefinitions notification, + ActivityType activityType, + ActivityDescriptor activityDescriptor, + ActivityDefinition activityDefinition, + StringBuilder writer, + CancellationToken cancellationToken = default) + { + var typeName = activityDefinition.Name; + var inputProperties = activityDescriptor.InputProperties; + var outputProperties = activityDescriptor.OutputProperties; + var interfaceDeclaration = $"declare interface {typeName}"; + writer.AppendLine($"{interfaceDeclaration} {{"); + + foreach (var property in inputProperties) + await RenderActivityPropertyAsync(notification, writer, property.Name, property.Type, activityType, activityDescriptor, activityDefinition, cancellationToken); + + foreach (var property in outputProperties) + await RenderActivityPropertyAsync(notification, writer, property.Name, property.Type, activityType, activityDescriptor, activityDefinition, cancellationToken); + + writer.AppendLine("}"); + } + + protected virtual ValueTask RenderActivityPropertyAsync( + RenderingTypeScriptDefinitions notification, + StringBuilder writer, + string propertyName, + Type propertyType, + ActivityType activityType, + ActivityDescriptor activityDescriptor, + ActivityDefinition activityDefinition, + CancellationToken cancellationToken = default) + { + var typeScriptType = notification.GetTypeScriptType(propertyType); + writer.AppendLine($"{propertyName}(): {typeScriptType}"); + return new ValueTask(); + } + } +} \ No newline at end of file diff --git a/src/scripting/Elsa.Scripting.JavaScript/Providers/IActivityTypeDefinitionRenderer.cs b/src/scripting/Elsa.Scripting.JavaScript/Providers/IActivityTypeDefinitionRenderer.cs new file mode 100644 index 000000000..238a958bd --- /dev/null +++ b/src/scripting/Elsa.Scripting.JavaScript/Providers/IActivityTypeDefinitionRenderer.cs @@ -0,0 +1,23 @@ +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Metadata; +using Elsa.Models; +using Elsa.Scripting.JavaScript.Events; +using Elsa.Services.Models; + +namespace Elsa.Scripting.JavaScript.Providers +{ + public interface IActivityTypeDefinitionRenderer + { + int Priority { get; } + bool GetCanRenderType(ActivityType activityType); + ValueTask RenderTypeDeclarationAsync( + RenderingTypeScriptDefinitions notification, + ActivityType activityType, + ActivityDescriptor activityDescriptor, + ActivityDefinition activityDefinition, + StringBuilder writer, + CancellationToken cancellationToken = default); + } +} \ No newline at end of file