< 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    {
 1574223        var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
 1574224        var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa";
 1574225        var friendlyName = GetFriendlyActivityName(activityType);
 1574226        var typeName = activityAttr?.Type ?? friendlyName;
 1574227        var typeVersion = activityAttr?.Version ?? 1;
 1574228        var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
 1574229        var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>();
 1574230        var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasin
 1574231        var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>();
 1574232        var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespa
 1574233        var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>();
 1574234        var description = descriptionAttr?.Description ?? activityAttr?.Description;
 35
 1574236        var embeddedPorts =
 1574237            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
 1574249        var flowNodeAttr = activityType.GetCustomAttribute<FlowNodeAttribute>();
 1574250        var flowPorts =
 1574251            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
 1574260        var allPorts = embeddedPorts.Concat(flowPorts.Values);
 1574261        var inputProperties = GetInputProperties(activityType).ToList();
 1574262        var outputProperties = GetOutputProperties(activityType).ToList();
 1574263        var isTrigger = activityType.IsAssignableTo(typeof(ITrigger));
 1574264        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();
 1574267        var attributes = activityType.GetCustomAttributes(true).Cast<Attribute>().ToList();
 1574268        var outputAttribute = attributes.OfType<OutputAttribute>().FirstOrDefault();
 69
 1574270        var descriptor = new ActivityDescriptor
 1574271        {
 1574272            TypeName = fullTypeName,
 1574273            ClrType = activityType,
 1574274            Namespace = ns,
 1574275            Name = typeName,
 1574276            Category = category,
 1574277            Description = description,
 1574278            Version = typeVersion,
 1574279            DisplayName = displayName,
 1574280            Kind = isTrigger ? ActivityKind.Trigger : activityAttr?.Kind ?? ActivityKind.Action,
 1574281            Ports = allPorts.ToList(),
 1574282            Inputs = (await DescribeInputPropertiesAsync(inputProperties, cancellationToken)).ToList(),
 1574283            Outputs = (await DescribeOutputPropertiesAsync(outputProperties, cancellationToken)).ToList(),
 1574284            IsContainer = typeof(Container).IsAssignableFrom(activityType),
 1574285            IsBrowsable = browsableAttr == null || browsableAttr.Browsable,
 1574286            IsStart = isStart,
 1574287            IsTerminal = isTerminal,
 1574288            Attributes = attributes,
 1574289            CustomProperties =
 1574290            {
 1574291                ["Type"] = activityType
 1574292            },
 1574293            Constructor = context =>
 1574294            {
 407195                var activityResult = context.CreateActivity(activityType);
 407196                activityResult.Activity.Type = fullTypeName;
 407197                return activityResult;
 1574298            },
 1574299        };
 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
 15742104        if (defaultOutputDescriptor != null)
 105        {
 3490106            var isResultSerializable = outputAttribute?.IsSerializable;
 3490107            defaultOutputDescriptor.IsSerializable = isResultSerializable;
 108        }
 109
 15742110        return descriptor;
 15742111    }
 112
 113    /// <inheritdoc />
 274716114    public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publi
 115
 116    /// <inheritdoc />
 258185117    public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publ
 118
 119    /// <inheritdoc />
 120    public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationT
 121    {
 5982122        var outputAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>();
 5982123        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 5982124        var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
 5982125        var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
 126
 5982127        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    {
 22513133        var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>();
 22513134        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 22513135        var propertyType = propertyInfo.PropertyType;
 22513136        var isWrappedProperty = typeof(Input).IsAssignableFrom(propertyType);
 22513137        var autoEvaluate = inputAttribute?.AutoEvaluate ?? true;
 22513138        var wrappedPropertyType = !isWrappedProperty ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0];
 139
 22513140        if (wrappedPropertyType.IsNullableType())
 171141            wrappedPropertyType = wrappedPropertyType.GetTypeOfNullable();
 142
 22513143        var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken
 144
 22513145        return new(
 22513146            inputAttribute?.Name ?? propertyInfo.Name,
 22513147            wrappedPropertyType,
 22513148            propertyInfo.GetValue,
 22513149            propertyInfo.SetValue,
 22513150            isWrappedProperty,
 22513151            GetUIHint(wrappedPropertyType, inputAttribute),
 22513152            inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
 22513153            descriptionAttribute?.Description ?? inputAttribute?.Description,
 22513154            inputAttribute?.Category,
 22513155            inputAttribute?.Order ?? 0,
 22513156            defaultValueResolver.GetDefaultValue(propertyInfo),
 22513157            inputAttribute?.DefaultSyntax,
 22513158            inputAttribute?.IsReadOnly ?? false,
 22513159            inputAttribute?.IsBrowsable ?? true,
 22513160            inputAttribute?.IsSerializable ?? true,
 22513161            false,
 22513162            autoEvaluate,
 22513163            inputAttribute?.EvaluatorType,
 22513164            null,
 22513165            propertyInfo,
 22513166            uiSpecification
 22513167        );
 22513168    }
 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    {
 45566185        if (inputAttribute?.UIHint != null)
 15778186            return inputAttribute.UIHint;
 187
 29788188        if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?))
 5786189            return InputUIHints.Checkbox;
 190
 24002191        if (wrappedPropertyType == typeof(string))
 11636192            return InputUIHints.SingleLine;
 193
 12366194        if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType))
 3062195            return InputUIHints.DropDown;
 196
 9304197        if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable(
 0198            return InputUIHints.DropDown;
 199
 9304200        if (wrappedPropertyType == typeof(Variable))
 608201            return InputUIHints.VariablePicker;
 202
 8696203        if (wrappedPropertyType == typeof(WorkflowInput))
 0204            return InputUIHints.InputPicker;
 205
 8696206        if (wrappedPropertyType == typeof(Type))
 0207            return InputUIHints.TypePicker;
 208
 8696209        return InputUIHints.SingleLine;
 210    }
 211
 212    private static string GetFriendlyActivityName(Type t)
 213    {
 15742214        if (!t.IsGenericType)
 15707215            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    {
 38255223        return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken)));
 15742224    }
 225
 226    private async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync(IEnumerable<PropertyInfo> properties
 227    {
 21724228        return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken)))
 15742229    }
 230}