< 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 />
 52818public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolver, IPropertyUIHandlerResolver propertyUI
 19{
 20    /// <inheritdoc />
 21    public async Task<ActivityDescriptor> DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTyp
 22    {
 1197623        var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
 1197624        var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa";
 1197625        var friendlyName = GetFriendlyActivityName(activityType);
 1197626        var typeName = activityAttr?.Type ?? friendlyName;
 1197627        var typeVersion = activityAttr?.Version ?? 1;
 1197628        var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
 1197629        var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>();
 1197630        var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasin
 1197631        var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>();
 1197632        var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespa
 1197633        var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>();
 1197634        var description = descriptionAttr?.Description ?? activityAttr?.Description;
 35
 1197636        var embeddedPorts =
 1197637            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
 1197649        var flowNodeAttr = activityType.GetCustomAttribute<FlowNodeAttribute>();
 1197650        var flowPorts =
 1197651            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
 1197660        var allPorts = embeddedPorts.Concat(flowPorts.Values);
 1197661        var inputProperties = GetInputProperties(activityType).ToList();
 1197662        var outputProperties = GetOutputProperties(activityType).ToList();
 1197663        var isTrigger = activityType.IsAssignableTo(typeof(ITrigger));
 1197664        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();
 1197667        var attributes = activityType.GetCustomAttributes(true).Cast<Attribute>().ToList();
 1197668        var outputAttribute = attributes.OfType<OutputAttribute>().FirstOrDefault();
 69
 1197670        var descriptor = new ActivityDescriptor
 1197671        {
 1197672            TypeName = fullTypeName,
 1197673            ClrType = activityType,
 1197674            Namespace = ns,
 1197675            Name = typeName,
 1197676            Category = category,
 1197677            Description = description,
 1197678            Version = typeVersion,
 1197679            DisplayName = displayName,
 1197680            Kind = isTrigger ? ActivityKind.Trigger : activityAttr?.Kind ?? ActivityKind.Action,
 1197681            Ports = allPorts.ToList(),
 1197682            Inputs = (await DescribeInputPropertiesAsync(inputProperties, cancellationToken)).ToList(),
 1197683            Outputs = (await DescribeOutputPropertiesAsync(outputProperties, cancellationToken)).ToList(),
 1197684            IsContainer = typeof(Container).IsAssignableFrom(activityType),
 1197685            IsBrowsable = browsableAttr == null || browsableAttr.Browsable,
 1197686            IsStart = isStart,
 1197687            IsTerminal = isTerminal,
 1197688            Attributes = attributes,
 1197689            CustomProperties =
 1197690            {
 1197691                ["Type"] = activityType
 1197692            },
 1197693            Constructor = context =>
 1197694            {
 207595                var activity = context.CreateActivity(activityType);
 207596                activity.Type = fullTypeName;
 207597                return activity;
 1197698            },
 1197699        };
 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
 11976104        if (defaultOutputDescriptor != null)
 105        {
 2816106            var isResultSerializable = outputAttribute?.IsSerializable;
 2816107            defaultOutputDescriptor.IsSerializable = isResultSerializable;
 108        }
 109
 11976110        return descriptor;
 11976111    }
 112
 113    /// <inheritdoc />
 211280114    public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publi
 115
 116    /// <inheritdoc />
 198411117    public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publ
 118
 119    /// <inheritdoc />
 120    public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationT
 121    {
 4468122        var outputAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>();
 4468123        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 4468124        var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
 4468125        var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
 126
 4468127        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    {
 17337133        var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>();
 17337134        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 17337135        var propertyType = propertyInfo.PropertyType;
 17337136        var isWrappedProperty = typeof(Input).IsAssignableFrom(propertyType);
 17337137        var autoEvaluate = inputAttribute?.AutoEvaluate ?? true;
 17337138        var wrappedPropertyType = !isWrappedProperty ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0];
 139
 17337140        if (wrappedPropertyType.IsNullableType())
 66141            wrappedPropertyType = wrappedPropertyType.GetTypeOfNullable();
 142
 17337143        var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken
 144
 17337145        return new(
 17337146            inputAttribute?.Name ?? propertyInfo.Name,
 17337147            wrappedPropertyType,
 17337148            propertyInfo.GetValue,
 17337149            propertyInfo.SetValue,
 17337150            isWrappedProperty,
 17337151            GetUIHint(wrappedPropertyType, inputAttribute),
 17337152            inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
 17337153            descriptionAttribute?.Description ?? inputAttribute?.Description,
 17337154            inputAttribute?.Category,
 17337155            inputAttribute?.Order ?? 0,
 17337156            defaultValueResolver.GetDefaultValue(propertyInfo),
 17337157            inputAttribute?.DefaultSyntax,
 17337158            inputAttribute?.IsReadOnly ?? false,
 17337159            inputAttribute?.IsBrowsable ?? true,
 17337160            inputAttribute?.IsSerializable ?? true,
 17337161            false,
 17337162            autoEvaluate,
 17337163            inputAttribute?.EvaluatorType,
 17337164            null,
 17337165            propertyInfo,
 17337166            uiSpecification
 17337167        );
 17337168    }
 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    {
 34674185        if (inputAttribute?.UIHint != null)
 11962186            return inputAttribute.UIHint;
 187
 22712188        if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?))
 4262189            return InputUIHints.Checkbox;
 190
 18450191        if (wrappedPropertyType == typeof(string))
 8856192            return InputUIHints.SingleLine;
 193
 9594194        if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType))
 2508195            return InputUIHints.DropDown;
 196
 7086197        if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable(
 0198            return InputUIHints.DropDown;
 199
 7086200        if (wrappedPropertyType == typeof(Variable))
 498201            return InputUIHints.VariablePicker;
 202
 6588203        if (wrappedPropertyType == typeof(WorkflowInput))
 0204            return InputUIHints.InputPicker;
 205
 6588206        if (wrappedPropertyType == typeof(Type))
 0207            return InputUIHints.TypePicker;
 208
 6588209        return InputUIHints.SingleLine;
 210    }
 211
 212    private static string GetFriendlyActivityName(Type t)
 213    {
 11976214        if (!t.IsGenericType)
 11943215            return t.Name;
 33216        var baseName = t.Name.Substring(0, t.Name.IndexOf('`'));
 66217        var argNames = string.Join(", ", t.GetGenericArguments().Select(a => a.Name));
 33218        return $"{baseName}<{argNames}>";
 219    }
 220
 221    private async Task<IEnumerable<InputDescriptor>> DescribeInputPropertiesAsync(IEnumerable<PropertyInfo> properties, 
 222    {
 29313223        return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken)));
 11976224    }
 225
 226    private async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync(IEnumerable<PropertyInfo> properties
 227    {
 16444228        return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken)))
 11976229    }
 230}