< 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 />
 64718public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolver, IPropertyUIHandlerResolver propertyUI
 19{
 20    /// <inheritdoc />
 21    public async Task<ActivityDescriptor> DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTyp
 22    {
 843423        var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
 843424        var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa";
 843425        var friendlyName = GetFriendlyActivityName(activityType);
 843426        var typeName = activityAttr?.Type ?? friendlyName;
 843427        var typeVersion = activityAttr?.Version ?? 1;
 843428        var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
 843429        var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>();
 843430        var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasin
 843431        var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>();
 843432        var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespa
 843433        var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>();
 843434        var description = descriptionAttr?.Description ?? activityAttr?.Description;
 35
 843436        var embeddedPorts =
 843437            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
 843449        var flowNodeAttr = activityType.GetCustomAttribute<FlowNodeAttribute>();
 843450        var flowPorts =
 843451            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
 843460        var allPorts = embeddedPorts.Concat(flowPorts.Values);
 843461        var inputProperties = GetInputProperties(activityType).ToList();
 843462        var outputProperties = GetOutputProperties(activityType).ToList();
 843463        var isTrigger = activityType.IsAssignableTo(typeof(ITrigger));
 843464        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();
 843467        var attributes = activityType.GetCustomAttributes(true).Cast<Attribute>().ToList();
 843468        var outputAttribute = attributes.OfType<OutputAttribute>().FirstOrDefault();
 69
 843470        var descriptor = new ActivityDescriptor
 843471        {
 843472            TypeName = fullTypeName,
 843473            ClrType = activityType,
 843474            Namespace = ns,
 843475            Name = typeName,
 843476            Category = category,
 843477            Description = description,
 843478            Version = typeVersion,
 843479            DisplayName = displayName,
 843480            Kind = isTrigger ? ActivityKind.Trigger : activityAttr?.Kind ?? ActivityKind.Action,
 843481            Ports = allPorts.ToList(),
 843482            Inputs = (await DescribeInputPropertiesAsync(inputProperties, cancellationToken)).ToList(),
 843483            Outputs = (await DescribeOutputPropertiesAsync(outputProperties, cancellationToken)).ToList(),
 843484            IsContainer = typeof(Container).IsAssignableFrom(activityType),
 843485            IsBrowsable = browsableAttr == null || browsableAttr.Browsable,
 843486            IsStart = isStart,
 843487            IsTerminal = isTerminal,
 843488            Attributes = attributes,
 843489            CustomProperties =
 843490            {
 843491                ["Type"] = activityType
 843492            },
 843493            Constructor = context =>
 843494            {
 389695                var activityResult = context.CreateActivity(activityType);
 389696                activityResult.Activity.Type = fullTypeName;
 389697                return activityResult;
 843498            },
 843499        };
 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
 8434104        if (defaultOutputDescriptor != null)
 105        {
 2043106            var isResultSerializable = outputAttribute?.IsSerializable;
 2043107            defaultOutputDescriptor.IsSerializable = isResultSerializable;
 108        }
 109
 8434110        return descriptor;
 8434111    }
 112
 113    /// <inheritdoc />
 149859114    public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publi
 115
 116    /// <inheritdoc />
 141742117    public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publ
 118
 119    /// <inheritdoc />
 120    public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationT
 121    {
 3191122        var outputAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>();
 3191123        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 3191124        var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
 3191125        var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
 126
 3191127        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    {
 11308133        var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>();
 11308134        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 11308135        var propertyType = propertyInfo.PropertyType;
 11308136        var isWrappedProperty = typeof(Input).IsAssignableFrom(propertyType);
 11308137        var autoEvaluate = inputAttribute?.AutoEvaluate ?? true;
 11308138        var wrappedPropertyType = !isWrappedProperty ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0];
 139
 11308140        if (wrappedPropertyType.IsNullableType())
 45141            wrappedPropertyType = wrappedPropertyType.GetTypeOfNullable();
 142
 11308143        var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken
 144
 11308145        return new(
 11308146            inputAttribute?.Name ?? propertyInfo.Name,
 11308147            wrappedPropertyType,
 11308148            propertyInfo.GetValue,
 11308149            propertyInfo.SetValue,
 11308150            isWrappedProperty,
 11308151            GetUIHint(wrappedPropertyType, inputAttribute),
 11308152            inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
 11308153            descriptionAttribute?.Description ?? inputAttribute?.Description,
 11308154            inputAttribute?.Category,
 11308155            inputAttribute?.Order ?? 0,
 11308156            defaultValueResolver.GetDefaultValue(propertyInfo),
 11308157            inputAttribute?.DefaultSyntax,
 11308158            inputAttribute?.IsReadOnly ?? false,
 11308159            inputAttribute?.IsBrowsable ?? true,
 11308160            inputAttribute?.IsSerializable ?? true,
 11308161            false,
 11308162            autoEvaluate,
 11308163            inputAttribute?.EvaluatorType,
 11308164            null,
 11308165            propertyInfo,
 11308166            uiSpecification
 11308167        )
 11308168        {
 11308169            IsSensitive = inputAttribute?.CanContainSecrets ?? false
 11308170        };
 11308171    }
 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    {
 22659188        if (inputAttribute?.UIHint != null)
 7726189            return inputAttribute.UIHint;
 190
 14933191        if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?))
 2784192            return InputUIHints.Checkbox;
 193
 12149194        if (wrappedPropertyType == typeof(string))
 5891195            return InputUIHints.SingleLine;
 196
 6258197        if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType))
 1576198            return InputUIHints.DropDown;
 199
 4682200        if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable(
 0201            return InputUIHints.DropDown;
 202
 4682203        if (wrappedPropertyType == typeof(Variable))
 310204            return InputUIHints.VariablePicker;
 205
 4372206        if (wrappedPropertyType == typeof(WorkflowInput))
 0207            return InputUIHints.InputPicker;
 208
 4372209        if (wrappedPropertyType == typeof(Type))
 0210            return InputUIHints.TypePicker;
 211
 4372212        return InputUIHints.SingleLine;
 213    }
 214
 215    private static string GetFriendlyActivityName(Type t)
 216    {
 8434217        if (!t.IsGenericType)
 8395218            return t.Name;
 39219        var baseName = t.Name.Substring(0, t.Name.IndexOf('`'));
 78220        var argNames = string.Join(", ", t.GetGenericArguments().Select(a => a.Name));
 39221        return $"{baseName}<{argNames}>";
 222    }
 223
 224    private async Task<IEnumerable<InputDescriptor>> DescribeInputPropertiesAsync(IEnumerable<PropertyInfo> properties, 
 225    {
 19742226        return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken)));
 8434227    }
 228
 229    private async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync(IEnumerable<PropertyInfo> properties
 230    {
 11625231        return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken)))
 8434232    }
 233}