Increase javascript intellisense coverage

This commit is contained in:
Sipke Schoorstra 2021-05-29 17:11:00 +02:00
parent 7f420f4f42
commit d033b56d29
33 changed files with 423 additions and 158 deletions

View file

@ -44,6 +44,7 @@ namespace Microsoft.Extensions.DependencyInjection
.AddHttpContextAccessor()
.AddNotificationHandlers(typeof(ConfigureJavaScriptEngine))
.AddLiquidFilter<SignalUrlFilter>("signal_url")
.AddJavaScriptTypeDefinitionProvider<HttpTypeDefinitionProvider>()
.AddDataProtection();
return services;

View file

@ -3,13 +3,14 @@ using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Http.Extensions;
using Elsa.Activities.Http.Services;
using Elsa.Scripting.JavaScript.Events;
using Elsa.Scripting.JavaScript.Messages;
using MediatR;
using Microsoft.AspNetCore.Http;
namespace Elsa.Activities.Http.JavaScript
{
public class ConfigureJavaScriptEngine : INotificationHandler<EvaluatingJavaScriptExpression>
public class ConfigureJavaScriptEngine : INotificationHandler<EvaluatingJavaScriptExpression>, INotificationHandler<RenderingTypeScriptDefinitions>
{
private readonly IAbsoluteUrlProvider _absoluteUrlProvider;
private readonly IHttpContextAccessor _httpContextAccessor;
@ -42,5 +43,16 @@ namespace Elsa.Activities.Http.JavaScript
return Task.CompletedTask;
}
public Task Handle(RenderingTypeScriptDefinitions notification, CancellationToken cancellationToken)
{
var output = notification.Output;
output.AppendLine("declare function queryString(name: string): string;");
output.AppendLine("declare function absoluteUrl(url: string): string;");
output.AppendLine("declare function signalUrl(signal: string): string;");
return Task.CompletedTask;
}
}
}

View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using Elsa.Activities.Http.Models;
using Elsa.Scripting.JavaScript.Services;
namespace Elsa.Activities.Http.JavaScript
{
public class HttpTypeDefinitionProvider : TypeDefinitionProvider
{
public override IEnumerable<Type> CollectTypes(TypeDefinitionContext context)
{
return new[] { typeof(HttpRequestModel) };
}
}
}

View file

@ -15,7 +15,7 @@ namespace Elsa.Activities.Temporal.Quartz.Handlers
if (activityType.Type != typeof(Cron))
return Task.CompletedTask;
var cronExpressionProperty = notification.ActivityDescriptor.Properties.First(x => x.Name == nameof(Cron.CronExpression));
var cronExpressionProperty = notification.ActivityDescriptor.InputProperties.First(x => x.Name == nameof(Cron.CronExpression));
cronExpressionProperty.DefaultValue = "0 * 0 ? * * *";
cronExpressionProperty.Hint = "Specify a Quartz CRON expression. Go to https://www.freeformatter.com/cron-expression-generator-quartz.html to generate valid Quartz cron expressions.";

View file

@ -56,12 +56,12 @@ namespace Elsa.Activities.Webhooks.ActivityTypes
Category = WebhookActivityCategory,
Outcomes = new[] { OutcomeNames.Done },
Traits = ActivityTraits.Trigger,
Properties = new[]
InputProperties = new[]
{
new ActivityPropertyDescriptor(
new ActivityInputDescriptor(
nameof(HttpEndpoint.Methods),
typeof(HashSet<string>),
ActivityPropertyUIHints.Dropdown,
ActivityInputUIHints.Dropdown,
"Request Method",
"Specify what request method this webhook should handle. Leave empty to handle both GET and POST requests",
new[] { "", "GET", "POST" },

View file

@ -5,6 +5,11 @@ namespace Elsa.Attributes
[AttributeUsage(AttributeTargets.Property)]
public class ActivityOutputAttribute : Attribute
{
/// <summary>
/// The technical name of the activity property.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// A brief description about this property for workflow tooling to use when displaying activity editors.
/// </summary>

View file

@ -8,7 +8,8 @@ namespace Elsa.Metadata
Category = "Miscellaneous";
Traits = ActivityTraits.Action;
DisplayName = "Activity";
Properties = new ActivityPropertyDescriptor[0];
InputProperties = new ActivityInputDescriptor[0];
OutputProperties = new ActivityOutputDescriptor[0];
Outcomes = new string[0];
}
@ -18,6 +19,7 @@ namespace Elsa.Metadata
public string Category { get; set; }
public ActivityTraits Traits { get; set; }
public string[] Outcomes { get; set; }
public ActivityPropertyDescriptor[] Properties { get; set; }
public ActivityInputDescriptor[] InputProperties { get; set; }
public ActivityOutputDescriptor[] OutputProperties { get; set; }
}
}

View file

@ -4,13 +4,13 @@ using System.Linq;
namespace Elsa.Metadata
{
public class ActivityPropertyDescriptor
public class ActivityInputDescriptor
{
public ActivityPropertyDescriptor()
public ActivityInputDescriptor()
{
}
public ActivityPropertyDescriptor(
public ActivityInputDescriptor(
string name,
Type type,
string uiHint,

View file

@ -0,0 +1,25 @@
using System;
namespace Elsa.Metadata
{
public class ActivityOutputDescriptor
{
public ActivityOutputDescriptor()
{
}
public ActivityOutputDescriptor(
string name,
Type type,
string? hint = default)
{
Name = name;
Type = type;
Hint = hint;
}
public string Name { get; set; } = default!;
public Type Type { get; set; } = default!;
public string? Hint { get; set; }
}
}

View file

@ -164,23 +164,8 @@ namespace Elsa.Services.Models
public void SetWorkflowContext(object? value) => WorkflowExecutionContext.SetWorkflowContext(value);
public object? GetWorkflowContext() => WorkflowExecutionContext.GetWorkflowContext();
public T GetWorkflowContext<T>() => WorkflowExecutionContext.GetWorkflowContext<T>();
public JObject GetActivityData() => GetActivityData(ActivityId);
public JObject GetActivityData(string activityId)
{
var activityData = WorkflowInstance.ActivityData;
var state = activityData.ContainsKey(activityId) ? activityData[activityId] : default;
if (state != null)
return state;
state = new JObject();
activityData[activityId] = state;
return state;
}
public JObject GetActivityData(string activityId) => WorkflowExecutionContext.GetActivityData(activityId);
public void Fault(Exception exception) => WorkflowExecutionContext.Fault(exception, ActivityId, Input, Resuming);
}
}

View file

@ -8,6 +8,7 @@ using Elsa.Models;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NodaTime;
using Rebus.Extensions;
@ -205,6 +206,20 @@ namespace Elsa.Services.Models
public void SetWorkflowContext(object? value) => WorkflowContext = value;
public object? GetWorkflowContext() => WorkflowContext;
public T GetWorkflowContext<T>() => (T) WorkflowContext!;
public JObject GetActivityData(string activityId)
{
var activityData = WorkflowInstance.ActivityData;
var state = activityData.ContainsKey(activityId) ? activityData[activityId] : default;
if (state != null)
return state;
state = new JObject();
activityData[activityId] = state;
return state;
}
/// <summary>
/// Remove empty activity data to save on document size.

View file

@ -29,7 +29,9 @@ namespace Elsa.Metadata
var category = activityAttribute?.Category ?? "Miscellaneous";
var traits = activityAttribute?.Traits ?? ActivityTraits.Action;
var outcomes = activityAttribute?.Outcomes ?? new[] { OutcomeNames.Done };
var properties = DescribeProperties(activityType);
var properties = activityType.GetProperties();
var inputProperties = DescribeInputProperties(properties);
var outputProperties = DescribeOutputProperties(properties);
return new ActivityDescriptor
{
@ -38,15 +40,14 @@ namespace Elsa.Metadata
Description = description,
Category = category,
Traits = traits,
Properties = properties.ToArray(),
InputProperties = inputProperties.ToArray(),
OutputProperties = outputProperties.ToArray(),
Outcomes = outcomes,
};
}
private IEnumerable<ActivityPropertyDescriptor> DescribeProperties(Type activityType)
private IEnumerable<ActivityInputDescriptor> DescribeInputProperties(IEnumerable<PropertyInfo> properties)
{
var properties = activityType.GetProperties();
foreach (var propertyInfo in properties)
{
var activityPropertyAttribute = propertyInfo.GetCustomAttribute<ActivityInputAttribute>();
@ -54,7 +55,7 @@ namespace Elsa.Metadata
if (activityPropertyAttribute == null)
continue;
yield return new ActivityPropertyDescriptor
yield return new ActivityInputDescriptor
(
(activityPropertyAttribute.Name ?? propertyInfo.Name).Pascalize(),
propertyInfo.PropertyType,
@ -69,5 +70,23 @@ namespace Elsa.Metadata
);
}
}
private IEnumerable<ActivityOutputDescriptor> DescribeOutputProperties(IEnumerable<PropertyInfo> properties)
{
foreach (var propertyInfo in properties)
{
var activityPropertyAttribute = propertyInfo.GetCustomAttribute<ActivityOutputAttribute>();
if (activityPropertyAttribute == null)
continue;
yield return new ActivityOutputDescriptor
(
(activityPropertyAttribute.Name ?? propertyInfo.Name).Pascalize(),
propertyInfo.PropertyType,
activityPropertyAttribute.Hint
);
}
}
}
}

View file

@ -74,7 +74,7 @@ namespace Elsa.Services
{
var activityType = await _activityTypeService.GetActivityTypeAsync(activityDefinition.Type, cancellationToken);
var activityDescriptor = activityType.Describe();
var propertyDescriptors = activityDescriptor.Properties;
var propertyDescriptors = activityDescriptor.InputProperties;
foreach (var property in activityDefinition.Properties)
{

View file

@ -166,7 +166,7 @@ export class ElsaWorkflowDesigner {
properties: [],
};
for (const property of activityDescriptor.properties) {
for (const property of activityDescriptor.inputProperties) {
activity.properties[property.name] = {
syntax: '',
expression: '',

View file

@ -111,7 +111,7 @@ export class ElsaWorkflowBlueprintViewerScreen {
const activityDescriptors: Array<ActivityDescriptor> = state.activityDescriptors;
const activityDescriptor = activityDescriptors.find(x => x.type == source.type);
const properties: Array<ActivityDefinitionProperty> = collection.map(source.properties.data, (value, key) => {
const propertyDescriptor = activityDescriptor.properties.find(x => x.name == key);
const propertyDescriptor = activityDescriptor.inputProperties.find(x => x.name == key);
const defaultSyntax = propertyDescriptor.defaultSyntax || SyntaxNames.Literal;
const expressions = {};
expressions[defaultSyntax] = value;

View file

@ -37,7 +37,7 @@ export class ElsaActivityEditorModal {
updateActivity(formData: FormData) {
const activity = this.activityModel;
const activityDescriptor = this.activityDescriptor;
const properties: Array<ActivityPropertyDescriptor> = activityDescriptor.properties;
const properties: Array<ActivityPropertyDescriptor> = activityDescriptor.inputProperties;
for (const property of properties)
propertyDisplayManager.update(activity, property, formData);
@ -62,9 +62,9 @@ export class ElsaActivityEditorModal {
}
componentWillRender(){
const activityDescriptor = this.activityDescriptor || {displayName: '', type: '', outcomes: [], category: '', traits: 0, browsable: false, properties: [], description: ''};
const propertyCategories = activityDescriptor.properties.filter(x => x.category).map(x => x.category).distinct();
const defaultProperties = activityDescriptor.properties.filter(x => !x.category || x.category.length == 0);
const activityDescriptor: ActivityDescriptor = this.activityDescriptor || {displayName: '', type: '', outcomes: [], category: '', traits: 0, browsable: false, inputProperties: [], description: ''};
const propertyCategories = activityDescriptor.inputProperties.filter(x => x.category).map(x => x.category).distinct();
const defaultProperties = activityDescriptor.inputProperties.filter(x => !x.category || x.category.length == 0);
let tabs: Array<string> = [];
if(defaultProperties.length > 0) {
@ -93,7 +93,7 @@ export class ElsaActivityEditorModal {
render() {
const renderProps = this.renderProps;
const activityDescriptor = renderProps.activityDescriptor;
const activityDescriptor: ActivityDescriptor = renderProps.activityDescriptor;
const propertyCategories = renderProps.propertyCategories;
const tabs = renderProps.tabs;
const selectedTab = renderProps.selectedTab;
@ -211,7 +211,7 @@ export class ElsaActivityEditorModal {
}
renderCategoryTabs(activityModel: ActivityModel, activityDescriptor: ActivityDescriptor, categories: Array<string>) {
const propertyDescriptors: Array<ActivityPropertyDescriptor> = activityDescriptor.properties;
const propertyDescriptors: Array<ActivityPropertyDescriptor> = activityDescriptor.inputProperties;
return (
categories.map(category => {

View file

@ -267,6 +267,7 @@ export class ElsaWorkflowDefinitionEditorScreen {
const activityDescriptors: Array<ActivityDescriptor> = state.activityDescriptors;
const activityDescriptor = activityDescriptors.find(x => x.type == source.type);
debugger;
return {
activityId: source.activityId,
description: source.description,

View file

@ -148,7 +148,7 @@ export class ElsaWorkflowInstanceViewerScreen {
const activityDescriptors: Array<ActivityDescriptor> = state.activityDescriptors;
const activityDescriptor = activityDescriptors.find(x => x.type == source.type);
const properties: Array<ActivityDefinitionProperty> = collection.map(source.properties.data, (value, key) => {
const propertyDescriptor = activityDescriptor.properties.find(x => x.name == key);
const propertyDescriptor = activityDescriptor.inputProperties.find(x => x.name == key);
const defaultSyntax = propertyDescriptor.defaultSyntax || SyntaxNames.Literal;
const expressions = {};
expressions[defaultSyntax] = value;

View file

@ -259,7 +259,7 @@ export interface ActivityDescriptor {
traits: ActivityTraits;
outcomes: Array<string>;
browsable: boolean;
properties: Array<ActivityPropertyDescriptor>;
inputProperties: Array<ActivityPropertyDescriptor>;
}
export interface ActivityPropertyDescriptor {

View file

@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using Elsa.Models;
using MediatR;
namespace Elsa.Scripting.JavaScript.Events
{
public class CollectingTypeScriptDefinitionTypes : INotification
{
internal CollectingTypeScriptDefinitionTypes(WorkflowDefinition? workflowDefinition, Action<IEnumerable<Type>> collectTypes)
{
WorkflowDefinition = workflowDefinition;
CollectTypes = collectTypes;
}
private Action<IEnumerable<Type>> CollectTypes { get; }
public WorkflowDefinition? WorkflowDefinition { get; }
public void CollectType<T>() => CollectType(typeof(T));
public void CollectType(Type type) => CollectTypes(new[] { type });
}
}

View file

@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
namespace Elsa.Scripting.JavaScript.Extensions
{
public static class JObjectExtensions
{
public static IDictionary<string, object> ToDictionary(this JObject @object)
{
var result = @object.ToObject<Dictionary<string, object>>()!;
var jObjectKeys = (from r in result
let key = r.Key
let value = r.Value
where value != null && value.GetType() == typeof(JObject)
select key).ToList();
var jArrayKeys = (from r in result
let key = r.Key
let value = r.Value
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());
jObjectKeys.ForEach(key => result[key] = ToDictionary((JObject) result[key]));
return result;
}
}
}

View file

@ -15,16 +15,21 @@ namespace Microsoft.Extensions.DependencyInjection
{
return services
.AddScoped<ITypeScriptDefinitionService, TypeScriptDefinitionService>()
.AddScoped<ITypeDefinitionProvider, PrimitiveTypeDefinitionProvider>()
.AddScoped<ITypeDefinitionProvider, EnumTypeDefinitionProvider>()
.AddScoped<ITypeDefinitionProvider, EnumerableTypeDefinitionProvider>()
.AddScoped<IJavaScriptService, JintJavaScriptEvaluator>()
.AddTransient(s => new JintEvaluationResultConverterFactory(s).GetConverter())
.AddTransient<IConvertsEnumerableToObject>(s => new EnumerableResultConverter(default))
.TryAddProvider<IExpressionHandler, JavaScriptExpressionHandler>(ServiceLifetime.Scoped)
.AddJavaScriptTypeDefinitionProvider<PrimitiveTypeDefinitionProvider>()
.AddJavaScriptTypeDefinitionProvider<EnumTypeDefinitionProvider>()
.AddJavaScriptTypeDefinitionProvider<EnumerableTypeDefinitionProvider>()
.AddJavaScriptTypeDefinitionProvider<WorkflowContextTypeDefinitionProvider>()
.AddJavaScriptTypeDefinitionProvider<WorkflowVariablesTypeDefinitionProvider>()
.AddJavaScriptTypeDefinitionProvider<CommonTypesTypeDefinitionProvider>()
.AddNotificationHandlers(typeof(JavaScriptServiceCollectionExtensions));
}
public static IServiceCollection AddJavaScriptTypeDefinitionProvider<T>(this IServiceCollection services) where T: class, ITypeDefinitionProvider => services.AddScoped<ITypeDefinitionProvider, T>();
public static IServiceCollection WithJavaScriptOptions(this IServiceCollection services, Action<ScriptOptions> configureOptions)
{
services.Configure(configureOptions);

View file

@ -1,8 +1,15 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper.Internal;
using Elsa.Attributes;
using Elsa.Scripting.JavaScript.Events;
using Elsa.Scripting.JavaScript.Extensions;
using Elsa.Scripting.JavaScript.Messages;
using Elsa.Services;
using Elsa.Services.Models;
@ -10,20 +17,21 @@ using Jint;
using Jint.Runtime.Interop;
using MediatR;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
using NodaTime;
namespace Elsa.Scripting.JavaScript.Handlers
{
public class ConfigureJavaScriptEngine : INotificationHandler<EvaluatingJavaScriptExpression>
public class ConfigureJavaScriptEngine : INotificationHandler<EvaluatingJavaScriptExpression>, INotificationHandler<RenderingTypeScriptDefinitions>
{
private readonly IConfiguration _configuration;
private readonly IActivityTypeService _activityTypeService;
public ConfigureJavaScriptEngine(IConfiguration configuration)
public ConfigureJavaScriptEngine(IConfiguration configuration, IActivityTypeService activityTypeService)
{
_configuration = configuration;
_activityTypeService = activityTypeService;
}
public Task Handle(EvaluatingJavaScriptExpression notification, CancellationToken cancellationToken)
{
var activityExecutionContext = notification.ActivityExecutionContext;
@ -57,48 +65,150 @@ namespace Elsa.Scripting.JavaScript.Handlers
engine.SetValue("currentCulture", CultureInfo.InvariantCulture);
engine.SetValue("workflowContext", activityExecutionContext.GetWorkflowContext());
// NodaTime types.
// Types.
RegisterType<Instant>(engine);
RegisterType<Duration>(engine);
RegisterType<Period>(engine);
RegisterType<LocalDate>(engine);
RegisterType<LocalTime>(engine);
RegisterType<LocalDateTime>(engine);
RegisterType<Guid>(engine);
RegisterType<WorkflowExecutionContext>(engine);
RegisterType<ActivityExecutionContext>(engine);
// Workflow variables.
var variables = workflowExecutionContext.GetMergedVariables();
// Add workflow variables.
foreach (var variable in variables.Data)
engine.SetValue(variable.Key, variable.Value);
// TODO: Deprecated. Remove with next breaking version.
// Add activity outputs.
// 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 => x.Name is not null))
{
var state = activityExecutionContext.GetActivityData(activity.Id);
var dictionary = state.ToDictionary();
engine.SetValue(activity.Name, dictionary);
}
return Task.CompletedTask;
}
public async Task Handle(RenderingTypeScriptDefinitions notification, CancellationToken cancellationToken)
{
var output = notification.Output;
output.AppendLine("declare function guid(): string");
output.AppendLine("declare function parseGuid(text: string): Guid");
output.AppendLine("declare function setVariable(name: string, value?: any): void;");
output.AppendLine("declare function getVariable(name: string): any;");
output.AppendLine("declare function getConfig(section: string): any;");
output.AppendLine("declare function isNullOrWhiteSpace(text: string): boolean;");
output.AppendLine("declare function isNullOrEmpty(text: string): boolean;");
output.AppendLine("declare function getWorkflowDefinitionIdByName(name: string): string;");
output.AppendLine("declare function getWorkflowDefinitionIdByTag(tag: string): string;");
output.AppendLine("declare function getActivity(idOrName: string): any;");
output.AppendLine("declare function getInboundActivity(): any;");
output.AppendLine("declare const activityExecutionContext: ActivityExecutionContext;");
output.AppendLine("declare const workflowExecutionContext: WorkflowExecutionContext;");
output.AppendLine("declare const workflowInstance: WorkflowInstance;");
output.AppendLine("declare const workflowInstanceId: string;");
output.AppendLine("declare const workflowDefinitionId: string;");
output.AppendLine("declare const workflowDefinitionVersion: number;");
output.AppendLine("declare const correlationId: string;");
output.AppendLine("declare const currentCulture: CultureInfo;");
var workflowDefinition = notification.WorkflowDefinition;
if (workflowDefinition != null)
{
// Workflow Context
var contextType = workflowDefinition.ContextOptions?.ContextType;
if (contextType == null)
return;
var workflowContextTypeScriptType = notification.GetTypeScriptType(contextType);
output.AppendLine($"declare const workflowContext: {workflowContextTypeScriptType}");
// Workflow Variables.
foreach (var variable in workflowDefinition.Variables!.Data)
{
var variableType = variable.Value?.GetType() ?? typeof(object);
var typeScriptType = notification.GetTypeScriptType(variableType);
output.AppendLine($"declare const {variable.Key}: {typeScriptType}");
}
// Named Activities.
var namedActivities = workflowDefinition.Activities.Where(x => x.Name is not null).ToList();
var activityTypeNames = namedActivities.Select(x => x.Type).Distinct().ToList();
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)
RenderActivityTypeDeclaration(activityType, output);
foreach (var activity in namedActivities)
{
var activityType = activityTypeDictionary[activity.Type];
var typeScriptType = activityType.TypeName;
output.AppendLine($"declare const {activity.Name}: {typeScriptType}");
}
}
void RenderActivityTypeDeclaration(ActivityType type, StringBuilder writer)
{
var typeName = type.TypeName;
var descriptor = type.Describe();
var inputProperties = descriptor.InputProperties;
var outputProperties = descriptor.OutputProperties;
writer.AppendLine($"declare interface {typeName} {{");
foreach (var property in inputProperties)
{
var typeScriptType = notification.GetTypeScriptType(property.Type);
var propertyName = property.Name;
writer.AppendLine($"{propertyName}: {typeScriptType};");
}
foreach (var property in outputProperties)
{
var typeScriptType = notification.GetTypeScriptType(property.Type);
var propertyName = property.Name;
writer.AppendLine($"{propertyName}: {typeScriptType};");
}
writer.AppendLine("}");
}
}
private string? GetWorkflowDefinitionIdByTag(ActivityExecutionContext activityExecutionContext, string tag) => GetWorkflowDefinitionId(activityExecutionContext, x => string.Equals(x.Tag, tag, StringComparison.OrdinalIgnoreCase));
private string? GetWorkflowDefinitionIdByName(ActivityExecutionContext activityExecutionContext, string name) => GetWorkflowDefinitionId(activityExecutionContext, x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase));
private string? GetWorkflowDefinitionId(ActivityExecutionContext activityExecutionContext, Func<IWorkflowBlueprint, bool> filter)
{
var workflowRegistry = activityExecutionContext.GetService<IWorkflowRegistry>();
var workflowBlueprint = workflowRegistry.FindAsync(filter).Result;
return workflowBlueprint?.Id;
}
private object? GetActivityModel(ActivityExecutionContext context, string idOrName)
{
var workflowExecutionContext = context.WorkflowExecutionContext;
var activity = workflowExecutionContext.GetActivityBlueprintByName(idOrName) ?? workflowExecutionContext.GetActivityBlueprintById(idOrName);
return activity == null ? null : workflowExecutionContext.WorkflowInstance.ActivityData[activity.Id];
}
private object? GetInboundActivityModel(ActivityExecutionContext context)
{
var inboundActivityId = context.WorkflowExecutionContext.GetInboundActivityPath(context.ActivityId).FirstOrDefault();
@ -106,5 +216,6 @@ namespace Elsa.Scripting.JavaScript.Handlers
}
private void RegisterType<T>(Engine engine) => engine.SetValue(typeof(T).Name, TypeReference.CreateTypeReference(engine, typeof(T)));
private void RegisterType(Type type, Engine engine) => engine.SetValue(type.Name, TypeReference.CreateTypeReference(engine, type));
}
}

View file

@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices
{
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit
{
}
}

View file

@ -1,10 +1,17 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
namespace Elsa.Scripting.JavaScript.Services
{
public interface ITypeDefinitionProvider
{
bool SupportsType(Type type);
string GetTypeDefinition(Type type);
bool SupportsType(TypeDefinitionContext context, Type type);
string GetTypeDefinition(TypeDefinitionContext context, Type type);
ValueTask<IEnumerable<Type>> CollectTypesAsync(TypeDefinitionContext context, CancellationToken cancellationToken = default);
}
public record TypeDefinitionContext(WorkflowDefinition? WorkflowDefinition, string? context);
}

View file

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Elsa.Scripting.JavaScript.Services
{
public abstract class TypeDefinitionProvider : ITypeDefinitionProvider
{
public virtual bool SupportsType(TypeDefinitionContext context, Type type) => false;
public virtual string GetTypeDefinition(TypeDefinitionContext context, Type type) => "any";
public virtual ValueTask<IEnumerable<Type>> CollectTypesAsync(TypeDefinitionContext context, CancellationToken cancellationToken = default) => new(CollectTypes(context));
public virtual IEnumerable<Type> CollectTypes(TypeDefinitionContext context) => Enumerable.Empty<Type>();
}
}

View file

@ -9,7 +9,6 @@ using AutoMapper.Internal;
using Elsa.Models;
using Elsa.Scripting.JavaScript.Events;
using MediatR;
using NodaTime;
namespace Elsa.Scripting.JavaScript.Services
{
@ -27,79 +26,40 @@ namespace Elsa.Scripting.JavaScript.Services
public async Task<string> GenerateTypeScriptDefinitionsAsync(WorkflowDefinition? workflowDefinition = default, string? context = default, CancellationToken cancellationToken = default)
{
var builder = new StringBuilder();
var types = await CollectTypesAsync(workflowDefinition, cancellationToken);
var providerContext = new TypeDefinitionContext(workflowDefinition, context);
var types = await CollectTypesAsync(providerContext, cancellationToken);
// Render type declarations for anything except those listed in TypeConverters.
foreach (var type in types)
{
var shouldRenderDeclaration = ShouldRenderTypeDeclaration(type);
var shouldRenderDeclaration = ShouldRenderTypeDeclaration(providerContext, type);
if (shouldRenderDeclaration)
RenderTypeDeclaration(type, types, builder);
RenderTypeDeclaration(providerContext, type, types, builder);
}
string GetTypeScriptTypeInternal(Type type) => GetTypeScriptType(type, types);
if (workflowDefinition != null)
{
var contextType = workflowDefinition.ContextOptions?.ContextType;
if (contextType != null)
{
var typeScriptType = GetTypeScriptTypeInternal(contextType);
builder.AppendLine($"declare const workflowContext: {typeScriptType}");
}
foreach (var variable in workflowDefinition.Variables!.Data)
{
var variableType = variable.Value?.GetType() ?? typeof(object);
var typeScriptType = GetTypeScriptTypeInternal(variableType);
builder.AppendLine($"declare const {variable.Key}: {typeScriptType}");
}
}
string GetTypeScriptTypeInternal(Type type) => GetTypeScriptType(providerContext, type, types);
var renderingTypeScriptDefinitions = new RenderingTypeScriptDefinitions(workflowDefinition, GetTypeScriptTypeInternal, context, builder);
await _mediator.Publish(renderingTypeScriptDefinitions, cancellationToken);
return builder.ToString();
}
private async Task<ISet<Type>> CollectTypesAsync(WorkflowDefinition? workflowDefinition = default, CancellationToken cancellationToken = default)
private async Task<ISet<Type>> CollectTypesAsync(TypeDefinitionContext context, CancellationToken cancellationToken = default)
{
var collectedTypes = new HashSet<Type>();
if (workflowDefinition != null)
foreach (var provider in _providers)
{
var contextType = workflowDefinition.ContextOptions?.ContextType;
var providedTypes = await provider.CollectTypesAsync(context, cancellationToken);
if (contextType != null)
CollectType(contextType, collectedTypes);
foreach (var variable in workflowDefinition.Variables!.Data.Values)
CollectType(variable!.GetType(), collectedTypes);
foreach (var providedType in providedTypes)
CollectType(providedType, collectedTypes);
}
CollectType<Instant>(collectedTypes);
CollectType<Duration>(collectedTypes);
CollectType<Period>(collectedTypes);
CollectType<LocalDate>(collectedTypes);
CollectType<LocalTime>(collectedTypes);
CollectType<LocalDateTime>(collectedTypes);
void CollectTypesInternal(IEnumerable<Type> types)
{
foreach (var type in types)
CollectType(type, collectedTypes);
}
var collectTypesEvent = new CollectingTypeScriptDefinitionTypes(workflowDefinition, CollectTypesInternal);
await _mediator.Publish(collectTypesEvent, cancellationToken);
return collectedTypes;
}
private static void CollectType<T>(ISet<Type> collectedTypes) => CollectType(typeof(T), collectedTypes);
private static void CollectType(Type type, ISet<Type> collectedTypes)
{
if (type.IsNullableType())
@ -140,14 +100,11 @@ namespace Elsa.Scripting.JavaScript.Services
}
}
private void RenderTypeDeclaration(Type type, ISet<Type> collectedTypes, StringBuilder output)
{
RenderTypeDeclaration("class", type, collectedTypes, output);
}
private void RenderTypeDeclaration(TypeDefinitionContext context, Type type, ISet<Type> collectedTypes, StringBuilder output) => RenderTypeDeclaration(context, type.IsInterface ? "interface" : "class", type, collectedTypes, output);
private void RenderTypeDeclaration(string symbol, Type type, ISet<Type> collectedTypes, StringBuilder output)
private void RenderTypeDeclaration(TypeDefinitionContext context, string symbol, Type type, ISet<Type> collectedTypes, StringBuilder output)
{
var typeName = type.Name;
var typeName = type.Name.Replace("`", "");
var properties = type.GetProperties();
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static).Where(x => !x.IsSpecialName).ToList();
@ -155,7 +112,7 @@ namespace Elsa.Scripting.JavaScript.Services
foreach (var property in properties)
{
var typeScriptType = GetTypeScriptType(property.PropertyType, collectedTypes);
var typeScriptType = GetTypeScriptType(context, property.PropertyType, collectedTypes);
var propertyName = property.PropertyType.IsNullableType() ? $"{property.Name}?" : property.Name;
output.AppendLine($"{propertyName}: {typeScriptType};");
}
@ -167,13 +124,13 @@ namespace Elsa.Scripting.JavaScript.Services
output.Append($"{method.Name}(");
var arguments = method.GetParameters().Select(x => $"{x.Name}:{GetTypeScriptType(x.ParameterType, collectedTypes)}");
var arguments = method.GetParameters().Select(x => $"{x.Name}:{GetTypeScriptType(context, x.ParameterType, collectedTypes)}");
output.Append(string.Join(", ", arguments));
output.Append(")");
var returnType = method.ReturnType;
if (returnType != typeof(void))
output.AppendFormat(":{0}", GetTypeScriptType(returnType, collectedTypes));
output.AppendFormat(":{0}", GetTypeScriptType(context, returnType, collectedTypes));
output.AppendLine(";");
}
@ -181,18 +138,18 @@ namespace Elsa.Scripting.JavaScript.Services
output.AppendLine("}");
}
private string GetTypeScriptType(Type type, ISet<Type> collectedTypes)
private string GetTypeScriptType(TypeDefinitionContext context, Type type, ISet<Type> collectedTypes)
{
if (type.IsNullableType())
type = type.GetTypeOfNullable();
var provider = _providers.FirstOrDefault(x => x.SupportsType(type));
return provider != null ? provider.GetTypeDefinition(type) : collectedTypes.Contains(type) ? type.Name : "any";
var provider = _providers.FirstOrDefault(x => x.SupportsType(context, type));
return provider != null ? provider.GetTypeDefinition(context, type) : collectedTypes.Contains(type) ? type.Name : "any";
}
private bool ShouldRenderTypeDeclaration(Type type)
private bool ShouldRenderTypeDeclaration(TypeDefinitionContext context, Type type)
{
var provider = _providers.FirstOrDefault(x => x.SupportsType(type));
var provider = _providers.FirstOrDefault(x => x.SupportsType(context, type));
return provider == null;
}
}

View file

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Elsa.Models;
using Elsa.Scripting.JavaScript.Services;
using Elsa.Services.Models;
using NodaTime;
namespace Elsa.Scripting.JavaScript.Typings
{
public class CommonTypesTypeDefinitionProvider : TypeDefinitionProvider
{
public override IEnumerable<Type> CollectTypes(TypeDefinitionContext context) => new[]
{
typeof(Instant),
typeof(Duration),
typeof(Period),
typeof(LocalDate),
typeof(LocalTime),
typeof(LocalDateTime),
typeof(Guid),
typeof(CultureInfo),
typeof(ActivityExecutionContext),
typeof(WorkflowExecutionContext),
typeof(WorkflowInstance),
};
}
}

View file

@ -3,9 +3,9 @@ using Elsa.Scripting.JavaScript.Services;
namespace Elsa.Scripting.JavaScript.Typings
{
public class EnumTypeDefinitionProvider : ITypeDefinitionProvider
public class EnumTypeDefinitionProvider : TypeDefinitionProvider
{
public bool SupportsType(Type type) => type.IsEnum;
public string GetTypeDefinition(Type type) => "number";
public override bool SupportsType(TypeDefinitionContext context, Type type) => type.IsEnum;
public override string GetTypeDefinition(TypeDefinitionContext context, Type type) => "number";
}
}

View file

@ -4,13 +4,9 @@ using Elsa.Scripting.JavaScript.Services;
namespace Elsa.Scripting.JavaScript.Typings
{
public class EnumerableTypeDefinitionProvider : ITypeDefinitionProvider
public class EnumerableTypeDefinitionProvider : TypeDefinitionProvider
{
public bool SupportsType(Type type) => typeof(IEnumerable).IsAssignableFrom(type);
public string GetTypeDefinition(Type type)
{
return "[]";
}
public override bool SupportsType(TypeDefinitionContext context, Type type) => typeof(IEnumerable).IsAssignableFrom(type);
public override string GetTypeDefinition(TypeDefinitionContext context, Type type) => "[]";
}
}

View file

@ -4,7 +4,7 @@ using Elsa.Scripting.JavaScript.Services;
namespace Elsa.Scripting.JavaScript.Typings
{
public class PrimitiveTypeDefinitionProvider : ITypeDefinitionProvider
public class PrimitiveTypeDefinitionProvider : TypeDefinitionProvider
{
private static readonly IDictionary<Type, string> TypeMap = new Dictionary<Type, string>
{
@ -25,7 +25,7 @@ namespace Elsa.Scripting.JavaScript.Typings
[typeof(TimeSpan)] = "string",
};
public bool SupportsType(Type type) => TypeMap.ContainsKey(type);
public string GetTypeDefinition(Type type) => TypeMap[type];
public override bool SupportsType(TypeDefinitionContext context, Type type) => TypeMap.ContainsKey(type);
public override string GetTypeDefinition(TypeDefinitionContext context, Type type) => TypeMap[type];
}
}

View file

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using Elsa.Scripting.JavaScript.Services;
namespace Elsa.Scripting.JavaScript.Typings
{
public class WorkflowContextTypeDefinitionProvider : TypeDefinitionProvider
{
public override IEnumerable<Type> CollectTypes(TypeDefinitionContext context)
{
var workflowDefinition = context.WorkflowDefinition;
var contextType = workflowDefinition?.ContextOptions?.ContextType;
if (contextType != null)
yield return contextType;
}
}
}

View file

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Elsa.Scripting.JavaScript.Services;
namespace Elsa.Scripting.JavaScript.Typings
{
public class WorkflowVariablesTypeDefinitionProvider : TypeDefinitionProvider
{
public override IEnumerable<Type> CollectTypes(TypeDefinitionContext context)
{
var workflowDefinition = context.WorkflowDefinition;
if (workflowDefinition == null)
yield break;
foreach (var variable in workflowDefinition.Variables!.Data.Values)
yield return variable!.GetType();
}
}
}