< 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 />
 57218public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolver, IPropertyUIHandlerResolver propertyUI
 19{
 20    /// <inheritdoc />
 21    public async Task<ActivityDescriptor> DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTyp
 22    {
 1611123        var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
 1611124        var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa";
 1611125        var friendlyName = GetFriendlyActivityName(activityType);
 1611126        var typeName = activityAttr?.Type ?? friendlyName;
 1611127        var typeVersion = activityAttr?.Version ?? 1;
 1611128        var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
 1611129        var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>();
 1611130        var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasin
 1611131        var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>();
 1611132        var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespa
 1611133        var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>();
 1611134        var description = descriptionAttr?.Description ?? activityAttr?.Description;
 35
 1611136        var embeddedPorts =
 1611137            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
 1611149        var flowNodeAttr = activityType.GetCustomAttribute<FlowNodeAttribute>();
 1611150        var flowPorts =
 1611151            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
 1611160        var allPorts = embeddedPorts.Concat(flowPorts.Values);
 1611161        var inputProperties = GetInputProperties(activityType).ToList();
 1611162        var outputProperties = GetOutputProperties(activityType).ToList();
 1611163        var isTrigger = activityType.IsAssignableTo(typeof(ITrigger));
 1611164        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();
 1611167        var attributes = activityType.GetCustomAttributes(true).Cast<Attribute>().ToList();
 1611168        var outputAttribute = attributes.OfType<OutputAttribute>().FirstOrDefault();
 69
 1611170        var descriptor = new ActivityDescriptor
 1611171        {
 1611172            TypeName = fullTypeName,
 1611173            ClrType = activityType,
 1611174            Namespace = ns,
 1611175            Name = typeName,
 1611176            Category = category,
 1611177            Description = description,
 1611178            Version = typeVersion,
 1611179            DisplayName = displayName,
 1611180            Kind = isTrigger ? ActivityKind.Trigger : activityAttr?.Kind ?? ActivityKind.Action,
 1611181            Ports = allPorts.ToList(),
 1611182            Inputs = (await DescribeInputPropertiesAsync(inputProperties, cancellationToken)).ToList(),
 1611183            Outputs = (await DescribeOutputPropertiesAsync(outputProperties, cancellationToken)).ToList(),
 1611184            IsContainer = typeof(Container).IsAssignableFrom(activityType),
 1611185            IsBrowsable = browsableAttr == null || browsableAttr.Browsable,
 1611186            IsStart = isStart,
 1611187            IsTerminal = isTerminal,
 1611188            Attributes = attributes,
 1611189            CustomProperties =
 1611190            {
 1611191                ["Type"] = activityType
 1611192            },
 1611193            Constructor = context =>
 1611194            {
 395295                var activityResult = context.CreateActivity(activityType);
 395296                activityResult.Activity.Type = fullTypeName;
 395297                return activityResult;
 1611198            },
 1611199        };
 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
 16111104        if (defaultOutputDescriptor != null)
 105        {
 3520106            var isResultSerializable = outputAttribute?.IsSerializable;
 3520107            defaultOutputDescriptor.IsSerializable = isResultSerializable;
 108        }
 109
 16111110        return descriptor;
 16111111    }
 112
 113    /// <inheritdoc />
 281539114    public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publi
 115
 116    /// <inheritdoc />
 265013117    public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publ
 118
 119    /// <inheritdoc />
 120    public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationT
 121    {
 6018122        var outputAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>();
 6018123        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 6018124        var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
 6018125        var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
 126
 6018127        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    {
 22544133        var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>();
 22544134        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 22544135        var propertyType = propertyInfo.PropertyType;
 22544136        var isWrappedProperty = typeof(Input).IsAssignableFrom(propertyType);
 22544137        var autoEvaluate = inputAttribute?.AutoEvaluate ?? true;
 22544138        var wrappedPropertyType = !isWrappedProperty ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0];
 139
 22544140        if (wrappedPropertyType.IsNullableType())
 171141            wrappedPropertyType = wrappedPropertyType.GetTypeOfNullable();
 142
 22544143        var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken
 144
 22544145        return new(
 22544146            inputAttribute?.Name ?? propertyInfo.Name,
 22544147            wrappedPropertyType,
 22544148            propertyInfo.GetValue,
 22544149            propertyInfo.SetValue,
 22544150            isWrappedProperty,
 22544151            GetUIHint(wrappedPropertyType, inputAttribute),
 22544152            inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
 22544153            descriptionAttribute?.Description ?? inputAttribute?.Description,
 22544154            inputAttribute?.Category,
 22544155            inputAttribute?.Order ?? 0,
 22544156            defaultValueResolver.GetDefaultValue(propertyInfo),
 22544157            inputAttribute?.DefaultSyntax,
 22544158            inputAttribute?.IsReadOnly ?? false,
 22544159            inputAttribute?.IsBrowsable ?? true,
 22544160            inputAttribute?.IsSerializable ?? true,
 22544161            false,
 22544162            autoEvaluate,
 22544163            inputAttribute?.EvaluatorType,
 22544164            null,
 22544165            propertyInfo,
 22544166            uiSpecification
 22544167        );
 22544168    }
 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    {
 45628185        if (inputAttribute?.UIHint != null)
 15796186            return inputAttribute.UIHint;
 187
 29832188        if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?))
 5794189            return InputUIHints.Checkbox;
 190
 24038191        if (wrappedPropertyType == typeof(string))
 11664192            return InputUIHints.SingleLine;
 193
 12374194        if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType))
 3062195            return InputUIHints.DropDown;
 196
 9312197        if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable(
 0198            return InputUIHints.DropDown;
 199
 9312200        if (wrappedPropertyType == typeof(Variable))
 608201            return InputUIHints.VariablePicker;
 202
 8704203        if (wrappedPropertyType == typeof(WorkflowInput))
 0204            return InputUIHints.InputPicker;
 205
 8704206        if (wrappedPropertyType == typeof(Type))
 0207            return InputUIHints.TypePicker;
 208
 8704209        return InputUIHints.SingleLine;
 210    }
 211
 212    private static string GetFriendlyActivityName(Type t)
 213    {
 16111214        if (!t.IsGenericType)
 16076215            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    {
 38655223        return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken)));
 16111224    }
 225
 226    private async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync(IEnumerable<PropertyInfo> properties
 227    {
 22129228        return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken)))
 16111229    }
 230}