< 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 />
 54218public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolver, IPropertyUIHandlerResolver propertyUI
 19{
 20    /// <inheritdoc />
 21    public async Task<ActivityDescriptor> DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTyp
 22    {
 1710423        var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
 1710424        var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa";
 1710425        var friendlyName = GetFriendlyActivityName(activityType);
 1710426        var typeName = activityAttr?.Type ?? friendlyName;
 1710427        var typeVersion = activityAttr?.Version ?? 1;
 1710428        var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
 1710429        var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>();
 1710430        var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasin
 1710431        var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>();
 1710432        var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespa
 1710433        var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>();
 1710434        var description = descriptionAttr?.Description ?? activityAttr?.Description;
 35
 1710436        var embeddedPorts =
 1710437            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
 1710449        var flowNodeAttr = activityType.GetCustomAttribute<FlowNodeAttribute>();
 1710450        var flowPorts =
 1710451            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
 1710460        var allPorts = embeddedPorts.Concat(flowPorts.Values);
 1710461        var inputProperties = GetInputProperties(activityType).ToList();
 1710462        var outputProperties = GetOutputProperties(activityType).ToList();
 1710463        var isTrigger = activityType.IsAssignableTo(typeof(ITrigger));
 1710464        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();
 1710467        var attributes = activityType.GetCustomAttributes(true).Cast<Attribute>().ToList();
 1710468        var outputAttribute = attributes.OfType<OutputAttribute>().FirstOrDefault();
 69
 1710470        var descriptor = new ActivityDescriptor
 1710471        {
 1710472            TypeName = fullTypeName,
 1710473            ClrType = activityType,
 1710474            Namespace = ns,
 1710475            Name = typeName,
 1710476            Category = category,
 1710477            Description = description,
 1710478            Version = typeVersion,
 1710479            DisplayName = displayName,
 1710480            Kind = isTrigger ? ActivityKind.Trigger : activityAttr?.Kind ?? ActivityKind.Action,
 1710481            Ports = allPorts.ToList(),
 1710482            Inputs = (await DescribeInputPropertiesAsync(inputProperties, cancellationToken)).ToList(),
 1710483            Outputs = (await DescribeOutputPropertiesAsync(outputProperties, cancellationToken)).ToList(),
 1710484            IsContainer = typeof(Container).IsAssignableFrom(activityType),
 1710485            IsBrowsable = browsableAttr == null || browsableAttr.Browsable,
 1710486            IsStart = isStart,
 1710487            IsTerminal = isTerminal,
 1710488            Attributes = attributes,
 1710489            CustomProperties =
 1710490            {
 1710491                ["Type"] = activityType
 1710492            },
 1710493            Constructor = context =>
 1710494            {
 483595                var activityResult = context.CreateActivity(activityType);
 483596                activityResult.Activity.Type = fullTypeName;
 483597                return activityResult;
 1710498            },
 1710499        };
 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
 17104104        if (defaultOutputDescriptor != null)
 105        {
 3746106            var isResultSerializable = outputAttribute?.IsSerializable;
 3746107            defaultOutputDescriptor.IsSerializable = isResultSerializable;
 108        }
 109
 17104110        return descriptor;
 17104111    }
 112
 113    /// <inheritdoc />
 298113114    public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publi
 115
 116    /// <inheritdoc />
 280154117    public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publ
 118
 119    /// <inheritdoc />
 120    public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationT
 121    {
 6670122        var outputAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>();
 6670123        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 6670124        var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
 6670125        var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
 126
 6670127        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    {
 24629133        var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>();
 24629134        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 24629135        var propertyType = propertyInfo.PropertyType;
 24629136        var isWrappedProperty = typeof(Input).IsAssignableFrom(propertyType);
 24629137        var autoEvaluate = inputAttribute?.AutoEvaluate ?? true;
 24629138        var wrappedPropertyType = !isWrappedProperty ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0];
 139
 24629140        if (wrappedPropertyType.IsNullableType())
 231141            wrappedPropertyType = wrappedPropertyType.GetTypeOfNullable();
 142
 24629143        var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken
 144
 24629145        return new(
 24629146            inputAttribute?.Name ?? propertyInfo.Name,
 24629147            wrappedPropertyType,
 24629148            propertyInfo.GetValue,
 24629149            propertyInfo.SetValue,
 24629150            isWrappedProperty,
 24629151            GetUIHint(wrappedPropertyType, inputAttribute),
 24629152            inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
 24629153            descriptionAttribute?.Description ?? inputAttribute?.Description,
 24629154            inputAttribute?.Category,
 24629155            inputAttribute?.Order ?? 0,
 24629156            defaultValueResolver.GetDefaultValue(propertyInfo),
 24629157            inputAttribute?.DefaultSyntax,
 24629158            inputAttribute?.IsReadOnly ?? false,
 24629159            inputAttribute?.IsBrowsable ?? true,
 24629160            inputAttribute?.IsSerializable ?? true,
 24629161            false,
 24629162            autoEvaluate,
 24629163            inputAttribute?.EvaluatorType,
 24629164            null,
 24629165            propertyInfo,
 24629166            uiSpecification
 24629167        );
 24629168    }
 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    {
 50098185        if (inputAttribute?.UIHint != null)
 17372186            return inputAttribute.UIHint;
 187
 32726188        if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?))
 6464189            return InputUIHints.Checkbox;
 190
 26262191        if (wrappedPropertyType == typeof(string))
 12782192            return InputUIHints.SingleLine;
 193
 13480194        if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType))
 3240195            return InputUIHints.DropDown;
 196
 10240197        if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable(
 0198            return InputUIHints.DropDown;
 199
 10240200        if (wrappedPropertyType == typeof(Variable))
 642201            return InputUIHints.VariablePicker;
 202
 9598203        if (wrappedPropertyType == typeof(WorkflowInput))
 0204            return InputUIHints.InputPicker;
 205
 9598206        if (wrappedPropertyType == typeof(Type))
 0207            return InputUIHints.TypePicker;
 208
 9598209        return InputUIHints.SingleLine;
 210    }
 211
 212    private static string GetFriendlyActivityName(Type t)
 213    {
 17104214        if (!t.IsGenericType)
 17065215            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    {
 41733223        return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken)));
 17104224    }
 225
 226    private async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync(IEnumerable<PropertyInfo> properties
 227    {
 23774228        return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken)))
 17104229    }
 230}