< Summary

Information
Class: Elsa.Workflows.ActivityDescriber
Assembly: Elsa.Workflows.Core
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs
Line coverage
93%
Covered lines: 123
Uncovered lines: 8
Coverable lines: 131
Total lines: 230
Line coverage: 93.8%
Branch coverage
93%
Covered branches: 123
Total branches: 132
Branch coverage: 93.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs

#LineLine coverage
 1using System.Collections;
 2using System.ComponentModel;
 3using System.Diagnostics.CodeAnalysis;
 4using System.Reflection;
 5using Elsa.Extensions;
 6using Elsa.Workflows.Activities.Flowchart.Attributes;
 7using Elsa.Workflows.Attributes;
 8using Elsa.Workflows.Helpers;
 9using Elsa.Workflows.Memory;
 10using Elsa.Workflows.Models;
 11using Elsa.Workflows.UIHints;
 12using Humanizer;
 13using Container = Elsa.Workflows.Activities.Container;
 14
 15namespace Elsa.Workflows;
 16
 17/// <inheritdoc />
 54118public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolver, IPropertyUIHandlerResolver propertyUI
 19{
 20    /// <inheritdoc />
 21    public async Task<ActivityDescriptor> DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTyp
 22    {
 1573623        var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
 1573624        var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa";
 1573625        var friendlyName = GetFriendlyActivityName(activityType);
 1573626        var typeName = activityAttr?.Type ?? friendlyName;
 1573627        var typeVersion = activityAttr?.Version ?? 1;
 1573628        var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
 1573629        var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>();
 1573630        var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasin
 1573631        var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>();
 1573632        var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespa
 1573633        var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>();
 1573634        var description = descriptionAttr?.Description ?? activityAttr?.Description;
 35
 1573636        var embeddedPorts =
 1573637            from prop in activityType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
 38            where typeof(IActivity).IsAssignableFrom(prop.PropertyType)
 39            let portAttr = prop.GetCustomAttribute<PortAttribute>()
 40            let portBrowsableAttr = prop.GetCustomAttribute<BrowsableAttribute>()
 41            select new Port
 42            {
 43                Name = portAttr?.Name ?? prop.Name,
 44                DisplayName = portAttr?.DisplayName ?? portAttr?.Name ?? prop.Name,
 45                Type = PortType.Embedded,
 46                IsBrowsable = portAttr != null && (portBrowsableAttr == null || portBrowsableAttr.Browsable),
 47            };
 48
 1573649        var flowNodeAttr = activityType.GetCustomAttribute<FlowNodeAttribute>();
 1573650        var flowPorts =
 1573651            flowNodeAttr
 52                ?.Outcomes.Select(x => new Port
 53                {
 54                    Type = PortType.Flow,
 55                    Name = x,
 56                    DisplayName = x,
 57                })
 58                .ToDictionary(x => x.Name) ?? new Dictionary<string, Port>();
 59
 1573660        var allPorts = embeddedPorts.Concat(flowPorts.Values);
 1573661        var inputProperties = GetInputProperties(activityType).ToList();
 1573662        var outputProperties = GetOutputProperties(activityType).ToList();
 1573663        var isTrigger = activityType.IsAssignableTo(typeof(ITrigger));
 1573664        var browsableAttr = activityType.GetCustomAttribute<BrowsableAttribute>();
 65        var isTerminal = activityType.FindInterfaces((type, criteria) => type == typeof(ITerminalNode), null).Any();
 66        var isStart = activityType.FindInterfaces((type, criteria) => type == typeof(IStartNode), null).Any();
 1573667        var attributes = activityType.GetCustomAttributes(true).Cast<Attribute>().ToList();
 1573668        var outputAttribute = attributes.OfType<OutputAttribute>().FirstOrDefault();
 69
 1573670        var descriptor = new ActivityDescriptor
 1573671        {
 1573672            TypeName = fullTypeName,
 1573673            ClrType = activityType,
 1573674            Namespace = ns,
 1573675            Name = typeName,
 1573676            Category = category,
 1573677            Description = description,
 1573678            Version = typeVersion,
 1573679            DisplayName = displayName,
 1573680            Kind = isTrigger ? ActivityKind.Trigger : activityAttr?.Kind ?? ActivityKind.Action,
 1573681            Ports = allPorts.ToList(),
 1573682            Inputs = (await DescribeInputPropertiesAsync(inputProperties, cancellationToken)).ToList(),
 1573683            Outputs = (await DescribeOutputPropertiesAsync(outputProperties, cancellationToken)).ToList(),
 1573684            IsContainer = typeof(Container).IsAssignableFrom(activityType),
 1573685            IsBrowsable = browsableAttr == null || browsableAttr.Browsable,
 1573686            IsStart = isStart,
 1573687            IsTerminal = isTerminal,
 1573688            Attributes = attributes,
 1573689            CustomProperties =
 1573690            {
 1573691                ["Type"] = activityType
 1573692            },
 1573693            Constructor = context =>
 1573694            {
 419695                var activityResult = context.CreateActivity(activityType);
 419696                activityResult.Activity.Type = fullTypeName;
 419697                return activityResult;
 1573698            },
 1573699        };
 100
 101        // If the activity has a default output, set its IsSerializable property to the value of the OutputAttribute.IsS
 102        var defaultOutputDescriptor = descriptor.Outputs.FirstOrDefault(x => x.Name == ActivityOutputRegister.DefaultOut
 103
 15736104        if (defaultOutputDescriptor != null)
 105        {
 3487106            var isResultSerializable = outputAttribute?.IsSerializable;
 3487107            defaultOutputDescriptor.IsSerializable = isResultSerializable;
 108        }
 109
 15736110        return descriptor;
 15736111    }
 112
 113    /// <inheritdoc />
 274610114    public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publi
 115
 116    /// <inheritdoc />
 258069117    public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publ
 118
 119    /// <inheritdoc />
 120    public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationT
 121    {
 5979122        var outputAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>();
 5979123        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 5979124        var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
 5979125        var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
 126
 5979127        return Task.FromResult(new OutputDescriptor((outputAttribute?.Name ?? propertyInfo.Name).Pascalize(), outputAttr
 128    }
 129
 130    /// <inheritdoc />
 131    public async Task<InputDescriptor> DescribeInputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellat
 132    {
 22520133        var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>();
 22520134        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 22520135        var propertyType = propertyInfo.PropertyType;
 22520136        var isWrappedProperty = typeof(Input).IsAssignableFrom(propertyType);
 22520137        var autoEvaluate = inputAttribute?.AutoEvaluate ?? true;
 22520138        var wrappedPropertyType = !isWrappedProperty ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0];
 139
 22520140        if (wrappedPropertyType.IsNullableType())
 171141            wrappedPropertyType = wrappedPropertyType.GetTypeOfNullable();
 142
 22520143        var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken
 144
 22520145        return new(
 22520146            inputAttribute?.Name ?? propertyInfo.Name,
 22520147            wrappedPropertyType,
 22520148            propertyInfo.GetValue,
 22520149            propertyInfo.SetValue,
 22520150            isWrappedProperty,
 22520151            GetUIHint(wrappedPropertyType, inputAttribute),
 22520152            inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
 22520153            descriptionAttribute?.Description ?? inputAttribute?.Description,
 22520154            inputAttribute?.Category,
 22520155            inputAttribute?.Order ?? 0,
 22520156            defaultValueResolver.GetDefaultValue(propertyInfo),
 22520157            inputAttribute?.DefaultSyntax,
 22520158            inputAttribute?.IsReadOnly ?? false,
 22520159            inputAttribute?.IsBrowsable ?? true,
 22520160            inputAttribute?.IsSerializable ?? true,
 22520161            false,
 22520162            autoEvaluate,
 22520163            inputAttribute?.EvaluatorType,
 22520164            null,
 22520165            propertyInfo,
 22520166            uiSpecification
 22520167        );
 22520168    }
 169
 170    /// <inheritdoc />
 171    public async Task<IEnumerable<InputDescriptor>> DescribeInputPropertiesAsync([DynamicallyAccessedMembers(Dynamically
 172    {
 0173        var properties = GetInputProperties(activityType);
 0174        return await DescribeInputPropertiesAsync(properties, cancellationToken);
 0175    }
 176
 177    /// <inheritdoc />
 178    public async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync([DynamicallyAccessedMembers(Dynamical
 179    {
 0180        return await DescribeOutputPropertiesAsync(GetOutputProperties(activityType), cancellationToken);
 0181    }
 182
 183    public static string GetUIHint(Type wrappedPropertyType, InputAttribute? inputAttribute = null)
 184    {
 45580185        if (inputAttribute?.UIHint != null)
 15778186            return inputAttribute.UIHint;
 187
 29802188        if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?))
 5790189            return InputUIHints.Checkbox;
 190
 24012191        if (wrappedPropertyType == typeof(string))
 11642192            return InputUIHints.SingleLine;
 193
 12370194        if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType))
 3062195            return InputUIHints.DropDown;
 196
 9308197        if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable(
 0198            return InputUIHints.DropDown;
 199
 9308200        if (wrappedPropertyType == typeof(Variable))
 608201            return InputUIHints.VariablePicker;
 202
 8700203        if (wrappedPropertyType == typeof(WorkflowInput))
 0204            return InputUIHints.InputPicker;
 205
 8700206        if (wrappedPropertyType == typeof(Type))
 0207            return InputUIHints.TypePicker;
 208
 8700209        return InputUIHints.SingleLine;
 210    }
 211
 212    private static string GetFriendlyActivityName(Type t)
 213    {
 15736214        if (!t.IsGenericType)
 15701215            return t.Name;
 35216        var baseName = t.Name.Substring(0, t.Name.IndexOf('`'));
 70217        var argNames = string.Join(", ", t.GetGenericArguments().Select(a => a.Name));
 35218        return $"{baseName}<{argNames}>";
 219    }
 220
 221    private async Task<IEnumerable<InputDescriptor>> DescribeInputPropertiesAsync(IEnumerable<PropertyInfo> properties, 
 222    {
 38256223        return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken)));
 15736224    }
 225
 226    private async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync(IEnumerable<PropertyInfo> properties
 227    {
 21715228        return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken)))
 15736229    }
 230}