< 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
94%
Covered lines: 126
Uncovered lines: 8
Coverable lines: 134
Total lines: 233
Line coverage: 94%
Branch coverage
93%
Covered branches: 125
Total branches: 134
Branch coverage: 93.2%
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 />
 62418public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolver, IPropertyUIHandlerResolver propertyUI
 19{
 20    /// <inheritdoc />
 21    public async Task<ActivityDescriptor> DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTyp
 22    {
 811023        var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
 811024        var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa";
 811025        var friendlyName = GetFriendlyActivityName(activityType);
 811026        var typeName = activityAttr?.Type ?? friendlyName;
 811027        var typeVersion = activityAttr?.Version ?? 1;
 811028        var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
 811029        var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>();
 811030        var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasin
 811031        var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>();
 811032        var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespa
 811033        var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>();
 811034        var description = descriptionAttr?.Description ?? activityAttr?.Description;
 35
 811036        var embeddedPorts =
 811037            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
 811049        var flowNodeAttr = activityType.GetCustomAttribute<FlowNodeAttribute>();
 811050        var flowPorts =
 811051            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
 811060        var allPorts = embeddedPorts.Concat(flowPorts.Values);
 811061        var inputProperties = GetInputProperties(activityType).ToList();
 811062        var outputProperties = GetOutputProperties(activityType).ToList();
 811063        var isTrigger = activityType.IsAssignableTo(typeof(ITrigger));
 811064        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();
 811067        var attributes = activityType.GetCustomAttributes(true).Cast<Attribute>().ToList();
 811068        var outputAttribute = attributes.OfType<OutputAttribute>().FirstOrDefault();
 69
 811070        var descriptor = new ActivityDescriptor
 811071        {
 811072            TypeName = fullTypeName,
 811073            ClrType = activityType,
 811074            Namespace = ns,
 811075            Name = typeName,
 811076            Category = category,
 811077            Description = description,
 811078            Version = typeVersion,
 811079            DisplayName = displayName,
 811080            Kind = isTrigger ? ActivityKind.Trigger : activityAttr?.Kind ?? ActivityKind.Action,
 811081            Ports = allPorts.ToList(),
 811082            Inputs = (await DescribeInputPropertiesAsync(inputProperties, cancellationToken)).ToList(),
 811083            Outputs = (await DescribeOutputPropertiesAsync(outputProperties, cancellationToken)).ToList(),
 811084            IsContainer = typeof(Container).IsAssignableFrom(activityType),
 811085            IsBrowsable = browsableAttr == null || browsableAttr.Browsable,
 811086            IsStart = isStart,
 811087            IsTerminal = isTerminal,
 811088            Attributes = attributes,
 811089            CustomProperties =
 811090            {
 811091                ["Type"] = activityType
 811092            },
 811093            Constructor = context =>
 811094            {
 382895                var activityResult = context.CreateActivity(activityType);
 382896                activityResult.Activity.Type = fullTypeName;
 382897                return activityResult;
 811098            },
 811099        };
 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
 8110104        if (defaultOutputDescriptor != null)
 105        {
 1982106            var isResultSerializable = outputAttribute?.IsSerializable;
 1982107            defaultOutputDescriptor.IsSerializable = isResultSerializable;
 108        }
 109
 8110110        return descriptor;
 8110111    }
 112
 113    /// <inheritdoc />
 144349114    public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publi
 115
 116    /// <inheritdoc />
 136534117    public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publ
 118
 119    /// <inheritdoc />
 120    public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationT
 121    {
 3104122        var outputAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>();
 3104123        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 3104124        var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
 3104125        var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
 126
 3104127        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    {
 10919133        var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>();
 10919134        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 10919135        var propertyType = propertyInfo.PropertyType;
 10919136        var isWrappedProperty = typeof(Input).IsAssignableFrom(propertyType);
 10919137        var autoEvaluate = inputAttribute?.AutoEvaluate ?? true;
 10919138        var wrappedPropertyType = !isWrappedProperty ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0];
 139
 10919140        if (wrappedPropertyType.IsNullableType())
 45141            wrappedPropertyType = wrappedPropertyType.GetTypeOfNullable();
 142
 10919143        var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken
 144
 10919145        return new(
 10919146            inputAttribute?.Name ?? propertyInfo.Name,
 10919147            wrappedPropertyType,
 10919148            propertyInfo.GetValue,
 10919149            propertyInfo.SetValue,
 10919150            isWrappedProperty,
 10919151            GetUIHint(wrappedPropertyType, inputAttribute),
 10919152            inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
 10919153            descriptionAttribute?.Description ?? inputAttribute?.Description,
 10919154            inputAttribute?.Category,
 10919155            inputAttribute?.Order ?? 0,
 10919156            defaultValueResolver.GetDefaultValue(propertyInfo),
 10919157            inputAttribute?.DefaultSyntax,
 10919158            inputAttribute?.IsReadOnly ?? false,
 10919159            inputAttribute?.IsBrowsable ?? true,
 10919160            inputAttribute?.IsSerializable ?? true,
 10919161            false,
 10919162            autoEvaluate,
 10919163            inputAttribute?.EvaluatorType,
 10919164            null,
 10919165            propertyInfo,
 10919166            uiSpecification
 10919167        )
 10919168        {
 10919169            IsSensitive = inputAttribute?.CanContainSecrets ?? false
 10919170        };
 10919171    }
 172
 173    /// <inheritdoc />
 174    public async Task<IEnumerable<InputDescriptor>> DescribeInputPropertiesAsync([DynamicallyAccessedMembers(Dynamically
 175    {
 0176        var properties = GetInputProperties(activityType);
 0177        return await DescribeInputPropertiesAsync(properties, cancellationToken);
 0178    }
 179
 180    /// <inheritdoc />
 181    public async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync([DynamicallyAccessedMembers(Dynamical
 182    {
 0183        return await DescribeOutputPropertiesAsync(GetOutputProperties(activityType), cancellationToken);
 0184    }
 185
 186    public static string GetUIHint(Type wrappedPropertyType, InputAttribute? inputAttribute = null)
 187    {
 21881188        if (inputAttribute?.UIHint != null)
 7474189            return inputAttribute.UIHint;
 190
 14407191        if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?))
 2700192            return InputUIHints.Checkbox;
 193
 11707194        if (wrappedPropertyType == typeof(string))
 5659195            return InputUIHints.SingleLine;
 196
 6048197        if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType))
 1512198            return InputUIHints.DropDown;
 199
 4536200        if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable(
 0201            return InputUIHints.DropDown;
 202
 4536203        if (wrappedPropertyType == typeof(Variable))
 298204            return InputUIHints.VariablePicker;
 205
 4238206        if (wrappedPropertyType == typeof(WorkflowInput))
 0207            return InputUIHints.InputPicker;
 208
 4238209        if (wrappedPropertyType == typeof(Type))
 0210            return InputUIHints.TypePicker;
 211
 4238212        return InputUIHints.SingleLine;
 213    }
 214
 215    private static string GetFriendlyActivityName(Type t)
 216    {
 8110217        if (!t.IsGenericType)
 8074218            return t.Name;
 36219        var baseName = t.Name.Substring(0, t.Name.IndexOf('`'));
 72220        var argNames = string.Join(", ", t.GetGenericArguments().Select(a => a.Name));
 36221        return $"{baseName}<{argNames}>";
 222    }
 223
 224    private async Task<IEnumerable<InputDescriptor>> DescribeInputPropertiesAsync(IEnumerable<PropertyInfo> properties, 
 225    {
 19029226        return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken)));
 8110227    }
 228
 229    private async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync(IEnumerable<PropertyInfo> properties
 230    {
 11214231        return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken)))
 8110232    }
 233}