< 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 />
 54418public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolver, IPropertyUIHandlerResolver propertyUI
 19{
 20    /// <inheritdoc />
 21    public async Task<ActivityDescriptor> DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTyp
 22    {
 1400023        var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
 1400024        var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa";
 1400025        var friendlyName = GetFriendlyActivityName(activityType);
 1400026        var typeName = activityAttr?.Type ?? friendlyName;
 1400027        var typeVersion = activityAttr?.Version ?? 1;
 1400028        var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
 1400029        var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>();
 1400030        var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasin
 1400031        var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>();
 1400032        var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespa
 1400033        var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>();
 1400034        var description = descriptionAttr?.Description ?? activityAttr?.Description;
 35
 1400036        var embeddedPorts =
 1400037            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
 1400049        var flowNodeAttr = activityType.GetCustomAttribute<FlowNodeAttribute>();
 1400050        var flowPorts =
 1400051            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
 1400060        var allPorts = embeddedPorts.Concat(flowPorts.Values);
 1400061        var inputProperties = GetInputProperties(activityType).ToList();
 1400062        var outputProperties = GetOutputProperties(activityType).ToList();
 1400063        var isTrigger = activityType.IsAssignableTo(typeof(ITrigger));
 1400064        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();
 1400067        var attributes = activityType.GetCustomAttributes(true).Cast<Attribute>().ToList();
 1400068        var outputAttribute = attributes.OfType<OutputAttribute>().FirstOrDefault();
 69
 1400070        var descriptor = new ActivityDescriptor
 1400071        {
 1400072            TypeName = fullTypeName,
 1400073            ClrType = activityType,
 1400074            Namespace = ns,
 1400075            Name = typeName,
 1400076            Category = category,
 1400077            Description = description,
 1400078            Version = typeVersion,
 1400079            DisplayName = displayName,
 1400080            Kind = isTrigger ? ActivityKind.Trigger : activityAttr?.Kind ?? ActivityKind.Action,
 1400081            Ports = allPorts.ToList(),
 1400082            Inputs = (await DescribeInputPropertiesAsync(inputProperties, cancellationToken)).ToList(),
 1400083            Outputs = (await DescribeOutputPropertiesAsync(outputProperties, cancellationToken)).ToList(),
 1400084            IsContainer = typeof(Container).IsAssignableFrom(activityType),
 1400085            IsBrowsable = browsableAttr == null || browsableAttr.Browsable,
 1400086            IsStart = isStart,
 1400087            IsTerminal = isTerminal,
 1400088            Attributes = attributes,
 1400089            CustomProperties =
 1400090            {
 1400091                ["Type"] = activityType
 1400092            },
 1400093            Constructor = context =>
 1400094            {
 303095                var activity = context.CreateActivity(activityType);
 303096                activity.Type = fullTypeName;
 303097                return activity;
 1400098            },
 1400099        };
 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
 14000104        if (defaultOutputDescriptor != null)
 105        {
 3160106            var isResultSerializable = outputAttribute?.IsSerializable;
 3160107            defaultOutputDescriptor.IsSerializable = isResultSerializable;
 108        }
 109
 14000110        return descriptor;
 14000111    }
 112
 113    /// <inheritdoc />
 244709114    public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publi
 115
 116    /// <inheritdoc />
 230016117    public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publ
 118
 119    /// <inheritdoc />
 120    public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationT
 121    {
 5160122        var outputAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>();
 5160123        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 5160124        var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
 5160125        var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
 126
 5160127        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    {
 19853133        var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>();
 19853134        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 19853135        var propertyType = propertyInfo.PropertyType;
 19853136        var isWrappedProperty = typeof(Input).IsAssignableFrom(propertyType);
 19853137        var autoEvaluate = inputAttribute?.AutoEvaluate ?? true;
 19853138        var wrappedPropertyType = !isWrappedProperty ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0];
 139
 19853140        if (wrappedPropertyType.IsNullableType())
 105141            wrappedPropertyType = wrappedPropertyType.GetTypeOfNullable();
 142
 19853143        var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken
 144
 19853145        return new(
 19853146            inputAttribute?.Name ?? propertyInfo.Name,
 19853147            wrappedPropertyType,
 19853148            propertyInfo.GetValue,
 19853149            propertyInfo.SetValue,
 19853150            isWrappedProperty,
 19853151            GetUIHint(wrappedPropertyType, inputAttribute),
 19853152            inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
 19853153            descriptionAttribute?.Description ?? inputAttribute?.Description,
 19853154            inputAttribute?.Category,
 19853155            inputAttribute?.Order ?? 0,
 19853156            defaultValueResolver.GetDefaultValue(propertyInfo),
 19853157            inputAttribute?.DefaultSyntax,
 19853158            inputAttribute?.IsReadOnly ?? false,
 19853159            inputAttribute?.IsBrowsable ?? true,
 19853160            inputAttribute?.IsSerializable ?? true,
 19853161            false,
 19853162            autoEvaluate,
 19853163            inputAttribute?.EvaluatorType,
 19853164            null,
 19853165            propertyInfo,
 19853166            uiSpecification
 19853167        );
 19853168    }
 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    {
 39916185        if (inputAttribute?.UIHint != null)
 13760186            return inputAttribute.UIHint;
 187
 26156188        if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?))
 4956189            return InputUIHints.Checkbox;
 190
 21200191        if (wrappedPropertyType == typeof(string))
 10236192            return InputUIHints.SingleLine;
 193
 10964194        if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType))
 2820195            return InputUIHints.DropDown;
 196
 8144197        if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable(
 0198            return InputUIHints.DropDown;
 199
 8144200        if (wrappedPropertyType == typeof(Variable))
 558201            return InputUIHints.VariablePicker;
 202
 7586203        if (wrappedPropertyType == typeof(WorkflowInput))
 0204            return InputUIHints.InputPicker;
 205
 7586206        if (wrappedPropertyType == typeof(Type))
 0207            return InputUIHints.TypePicker;
 208
 7586209        return InputUIHints.SingleLine;
 210    }
 211
 212    private static string GetFriendlyActivityName(Type t)
 213    {
 14000214        if (!t.IsGenericType)
 13961215            return t.Name;
 39216        var baseName = t.Name.Substring(0, t.Name.IndexOf('`'));
 78217        var argNames = string.Join(", ", t.GetGenericArguments().Select(a => a.Name));
 39218        return $"{baseName}<{argNames}>";
 219    }
 220
 221    private async Task<IEnumerable<InputDescriptor>> DescribeInputPropertiesAsync(IEnumerable<PropertyInfo> properties, 
 222    {
 33853223        return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken)));
 14000224    }
 225
 226    private async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync(IEnumerable<PropertyInfo> properties
 227    {
 19160228        return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken)))
 14000229    }
 230}