using System.Collections; using System.ComponentModel; using System.Reflection; using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Helpers; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Core.Services; using Elsa.Workflows.Management.Extensions; using Elsa.Workflows.Management.Models; using Elsa.Workflows.Management.Services; using Humanizer; namespace Elsa.Workflows.Management.Implementations; public class ActivityDescriber : IActivityDescriber { private readonly IPropertyOptionsResolver _optionsResolver; private readonly IPropertyDefaultValueResolver _defaultValueResolver; private readonly IActivityFactory _activityFactory; public ActivityDescriber(IPropertyOptionsResolver optionsResolver, IPropertyDefaultValueResolver defaultValueResolver, IActivityFactory activityFactory) { _optionsResolver = optionsResolver; _defaultValueResolver = defaultValueResolver; _activityFactory = activityFactory; } public ValueTask DescribeActivityAsync(Type activityType, CancellationToken cancellationToken = default) { var activityAttr = activityType.GetCustomAttribute(); var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType); var typeName = activityAttr?.TypeName ?? activityType.Name; var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType); var displayNameAttr = activityType.GetCustomAttribute(); var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? typeName.Humanize(LetterCasing.Title); var categoryAttr = activityType.GetCustomAttribute(); var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespace(ns) ?? "Miscellaneous"; var descriptionAttr = activityType.GetCustomAttribute(); var description = descriptionAttr?.Description ?? activityAttr?.Description; var outboundPorts = from prop in activityType.GetProperties(BindingFlags.Public | BindingFlags.Instance) where typeof(IActivity).IsAssignableFrom(prop.PropertyType) || typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) let portAttr = prop.GetCustomAttribute() where portAttr != null select new Port { Name = portAttr.Name ?? prop.Name, DisplayName = portAttr.DisplayName ?? portAttr.Name ?? prop.Name }; var properties = activityType.GetProperties(); var inputProperties = properties.Where(x => typeof(Input).IsAssignableFrom(x.PropertyType) || x.GetCustomAttribute() != null).ToList(); var outputProperties = properties.Where(x => typeof(Output).IsAssignableFrom(x.PropertyType)).ToList(); var isTrigger = activityType.IsAssignableTo(typeof(ITrigger)); var descriptor = new ActivityDescriptor { Category = category, Description = description, ActivityType = fullTypeName, DisplayName = displayName, Kind = isTrigger ? ActivityKind.Trigger : ActivityKind.Action, OutPorts = outboundPorts.ToList(), InputProperties = DescribeInputProperties(inputProperties).ToList(), OutputProperties = DescribeOutputProperties(outputProperties).ToList(), Constructor = context => { var activity = _activityFactory.Create(activityType, context); activity.TypeName = fullTypeName; return activity; } }; return ValueTask.FromResult(descriptor); } private IEnumerable DescribeInputProperties(IEnumerable properties) { foreach (var propertyInfo in properties) { var inputAttribute = propertyInfo.GetCustomAttribute(); var descriptionAttribute = propertyInfo.GetCustomAttribute(); var wrappedPropertyType = propertyInfo.PropertyType.GenericTypeArguments[0]; yield return new InputDescriptor ( inputAttribute?.Name ?? propertyInfo.Name, wrappedPropertyType, GetUIHint(wrappedPropertyType, inputAttribute), inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title), descriptionAttribute?.Description ?? inputAttribute?.Description, _optionsResolver.GetOptions(propertyInfo), inputAttribute?.Category, inputAttribute?.Order ?? 0, _defaultValueResolver.GetDefaultValue(propertyInfo), inputAttribute?.DefaultSyntax, //inputAttribute?.SupportedSyntaxes, TODO: Come up with a different way to specify support languages for activity inputs. By default, maybe all props should support all registered scripting languages? inputAttribute?.IsReadOnly ?? false, inputAttribute?.IsBrowsable ?? true ); } } private IEnumerable DescribeOutputProperties(IEnumerable properties) { foreach (var propertyInfo in properties) { var outputAttribute = propertyInfo.GetCustomAttribute(); var descriptionAttribute = propertyInfo.GetCustomAttribute(); var typeArgs = propertyInfo.PropertyType.GenericTypeArguments; var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object); yield return new OutputDescriptor ( (outputAttribute?.Name ?? propertyInfo.Name).Pascalize(), outputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title), wrappedPropertyType, descriptionAttribute?.Description ?? outputAttribute?.Description, outputAttribute?.IsBrowsable ?? true ); } } private string GetUIHint(Type wrappedPropertyType, InputAttribute? inputAttribute) { if (inputAttribute?.UIHint != null) return inputAttribute.UIHint; if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?)) return InputUIHints.Checkbox; if (wrappedPropertyType == typeof(string)) return InputUIHints.SingleLine; if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType)) return InputUIHints.Dropdown; if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable().IsEnum) return InputUIHints.Dropdown; return InputUIHints.SingleLine; } }