< 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 />
 57718public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolver, IPropertyUIHandlerResolver propertyUI
 19{
 20    /// <inheritdoc />
 21    public async Task<ActivityDescriptor> DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTyp
 22    {
 1635923        var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
 1635924        var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa";
 1635925        var friendlyName = GetFriendlyActivityName(activityType);
 1635926        var typeName = activityAttr?.Type ?? friendlyName;
 1635927        var typeVersion = activityAttr?.Version ?? 1;
 1635928        var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
 1635929        var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>();
 1635930        var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasin
 1635931        var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>();
 1635932        var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespa
 1635933        var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>();
 1635934        var description = descriptionAttr?.Description ?? activityAttr?.Description;
 35
 1635936        var embeddedPorts =
 1635937            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
 1635949        var flowNodeAttr = activityType.GetCustomAttribute<FlowNodeAttribute>();
 1635950        var flowPorts =
 1635951            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
 1635960        var allPorts = embeddedPorts.Concat(flowPorts.Values);
 1635961        var inputProperties = GetInputProperties(activityType).ToList();
 1635962        var outputProperties = GetOutputProperties(activityType).ToList();
 1635963        var isTrigger = activityType.IsAssignableTo(typeof(ITrigger));
 1635964        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();
 1635967        var attributes = activityType.GetCustomAttributes(true).Cast<Attribute>().ToList();
 1635968        var outputAttribute = attributes.OfType<OutputAttribute>().FirstOrDefault();
 69
 1635970        var descriptor = new ActivityDescriptor
 1635971        {
 1635972            TypeName = fullTypeName,
 1635973            ClrType = activityType,
 1635974            Namespace = ns,
 1635975            Name = typeName,
 1635976            Category = category,
 1635977            Description = description,
 1635978            Version = typeVersion,
 1635979            DisplayName = displayName,
 1635980            Kind = isTrigger ? ActivityKind.Trigger : activityAttr?.Kind ?? ActivityKind.Action,
 1635981            Ports = allPorts.ToList(),
 1635982            Inputs = (await DescribeInputPropertiesAsync(inputProperties, cancellationToken)).ToList(),
 1635983            Outputs = (await DescribeOutputPropertiesAsync(outputProperties, cancellationToken)).ToList(),
 1635984            IsContainer = typeof(Container).IsAssignableFrom(activityType),
 1635985            IsBrowsable = browsableAttr == null || browsableAttr.Browsable,
 1635986            IsStart = isStart,
 1635987            IsTerminal = isTerminal,
 1635988            Attributes = attributes,
 1635989            CustomProperties =
 1635990            {
 1635991                ["Type"] = activityType
 1635992            },
 1635993            Constructor = context =>
 1635994            {
 324795                var activityResult = context.CreateActivity(activityType);
 324796                activityResult.Activity.Type = fullTypeName;
 324797                return activityResult;
 1635998            },
 1635999        };
 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
 16359104        if (defaultOutputDescriptor != null)
 105        {
 3575106            var isResultSerializable = outputAttribute?.IsSerializable;
 3575107            defaultOutputDescriptor.IsSerializable = isResultSerializable;
 108        }
 109
 16359110        return descriptor;
 16359111    }
 112
 113    /// <inheritdoc />
 285981114    public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publi
 115
 116    /// <inheritdoc />
 269155117    public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publ
 118
 119    /// <inheritdoc />
 120    public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationT
 121    {
 6130122        var outputAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>();
 6130123        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 6130124        var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
 6130125        var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
 126
 6130127        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    {
 22956133        var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>();
 22956134        var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
 22956135        var propertyType = propertyInfo.PropertyType;
 22956136        var isWrappedProperty = typeof(Input).IsAssignableFrom(propertyType);
 22956137        var autoEvaluate = inputAttribute?.AutoEvaluate ?? true;
 22956138        var wrappedPropertyType = !isWrappedProperty ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0];
 139
 22956140        if (wrappedPropertyType.IsNullableType())
 177141            wrappedPropertyType = wrappedPropertyType.GetTypeOfNullable();
 142
 22956143        var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken
 144
 22956145        return new(
 22956146            inputAttribute?.Name ?? propertyInfo.Name,
 22956147            wrappedPropertyType,
 22956148            propertyInfo.GetValue,
 22956149            propertyInfo.SetValue,
 22956150            isWrappedProperty,
 22956151            GetUIHint(wrappedPropertyType, inputAttribute),
 22956152            inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
 22956153            descriptionAttribute?.Description ?? inputAttribute?.Description,
 22956154            inputAttribute?.Category,
 22956155            inputAttribute?.Order ?? 0,
 22956156            defaultValueResolver.GetDefaultValue(propertyInfo),
 22956157            inputAttribute?.DefaultSyntax,
 22956158            inputAttribute?.IsReadOnly ?? false,
 22956159            inputAttribute?.IsBrowsable ?? true,
 22956160            inputAttribute?.IsSerializable ?? true,
 22956161            false,
 22956162            autoEvaluate,
 22956163            inputAttribute?.EvaluatorType,
 22956164            null,
 22956165            propertyInfo,
 22956166            uiSpecification
 22956167        )
 22956168        {
 22956169            IsSensitive = inputAttribute?.CanContainSecrets ?? false
 22956170        };
 22956171    }
 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    {
 46450188        if (inputAttribute?.UIHint != null)
 16086189            return inputAttribute.UIHint;
 190
 30364191        if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?))
 5908192            return InputUIHints.Checkbox;
 193
 24456194        if (wrappedPropertyType == typeof(string))
 11866195            return InputUIHints.SingleLine;
 196
 12590197        if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType))
 3114198            return InputUIHints.DropDown;
 199
 9476200        if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable(
 0201            return InputUIHints.DropDown;
 202
 9476203        if (wrappedPropertyType == typeof(Variable))
 618204            return InputUIHints.VariablePicker;
 205
 8858206        if (wrappedPropertyType == typeof(WorkflowInput))
 0207            return InputUIHints.InputPicker;
 208
 8858209        if (wrappedPropertyType == typeof(Type))
 0210            return InputUIHints.TypePicker;
 211
 8858212        return InputUIHints.SingleLine;
 213    }
 214
 215    private static string GetFriendlyActivityName(Type t)
 216    {
 16359217        if (!t.IsGenericType)
 16323218            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    {
 39315226        return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken)));
 16359227    }
 228
 229    private async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync(IEnumerable<PropertyInfo> properties
 230    {
 22489231        return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken)))
 16359232    }
 233}