From 6c7cbd3c9ed1708adec7f1971a439d45812eb8c8 Mon Sep 17 00:00:00 2001 From: tomy2105 Date: Wed, 23 Jun 2021 15:00:08 +0200 Subject: [PATCH 1/6] Enabling directly named properties used using SetupActivity (not just through C# property name) --- .../Builders/ISetupActivity.cs | 35 ++++++++++++++++++- src/core/Elsa.Core/Builders/SetupActivity.cs | 28 ++++++++------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/core/Elsa.Abstractions/Builders/ISetupActivity.cs b/src/core/Elsa.Abstractions/Builders/ISetupActivity.cs index d8dfa98a2..21b713519 100644 --- a/src/core/Elsa.Abstractions/Builders/ISetupActivity.cs +++ b/src/core/Elsa.Abstractions/Builders/ISetupActivity.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq.Expressions; using System.Threading.Tasks; using Elsa.Services; @@ -12,12 +12,45 @@ namespace Elsa.Builders public interface ISetupActivity : ISetupActivity where T : IActivity { + ISetupActivity Set(string propertyName, Func> valueFactory); + ISetupActivity WithStorageFor(string propertyName, string? storageProviderName); + ISetupActivity Set(Expression> propertyAccessor, Func> valueFactory); ISetupActivity WithStorageFor(Expression> propertyAccessor, string? storageProviderName); } public static class SetupActivityExtensions { + public static ISetupActivity Set( + this ISetupActivity builder, + string propertyName, + Func> valueFactory) where T : IActivity => + builder.Set(propertyName, async context => await valueFactory(context)); + + public static ISetupActivity Set( + this ISetupActivity builder, + string propertyName, + Func valueFactory) where T : IActivity => + builder.Set(propertyName, context => new ValueTask(valueFactory(context))); + + public static ISetupActivity Set( + this ISetupActivity builder, + string propertyName, + Func valueFactory) where T : IActivity => + builder.Set(propertyName, context => new ValueTask(valueFactory())); + + public static ISetupActivity Set( + this ISetupActivity builder, + string propertyName, + Func> valueFactory) where T : IActivity => + builder.Set(propertyName, async _ => await valueFactory()); + + public static ISetupActivity Set( + this ISetupActivity builder, + string propertyName, + TProperty? value) where T : IActivity => + builder.Set(propertyName, _ => new ValueTask(value)); + public static ISetupActivity Set( this ISetupActivity builder, Expression> propertyAccessor, diff --git a/src/core/Elsa.Core/Builders/SetupActivity.cs b/src/core/Elsa.Core/Builders/SetupActivity.cs index 6c530e09a..2cb0f4b05 100644 --- a/src/core/Elsa.Core/Builders/SetupActivity.cs +++ b/src/core/Elsa.Core/Builders/SetupActivity.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; @@ -12,23 +12,27 @@ namespace Elsa.Builders public IDictionary>> ValueProviders { get; } = new Dictionary>>(); public IDictionary StorageProviders { get; } = new Dictionary(); - public ISetupActivity Set(Expression> propertyAccessor, Func> valueFactory) + + public ISetupActivity Set(string propertyName, Func> valueFactory) { - var propertyInfo = propertyAccessor.GetProperty()!; - ValueProviders[propertyInfo.Name] = async context => await valueFactory(context); + ValueProviders[propertyName] = async context => await valueFactory(context); return this; } - - public ISetupActivity WithStorageFor(Expression> propertyAccessor, string? storageProviderName) + + public ISetupActivity WithStorageFor(string propertyName, string? storageProviderName) { - var propertyInfo = propertyAccessor.GetProperty()!; - - if(storageProviderName != null) - StorageProviders[propertyInfo.Name] = storageProviderName; + if (storageProviderName != null) + StorageProviders[propertyName] = storageProviderName; else - StorageProviders.Remove(propertyInfo.Name); - + StorageProviders.Remove(propertyName); + return this; } + + public ISetupActivity Set(Expression> propertyAccessor, Func> valueFactory) => + Set(propertyAccessor.GetProperty()!.Name, valueFactory); + + public ISetupActivity WithStorageFor(Expression> propertyAccessor, string? storageProviderName) => + WithStorageFor(propertyAccessor.GetProperty()!.Name, storageProviderName); } } \ No newline at end of file From 3f862f413758bb2092ff54ed12f5b5aaf9ed72bf Mon Sep 17 00:00:00 2001 From: tomy2105 Date: Wed, 23 Jun 2021 16:04:55 +0200 Subject: [PATCH 2/6] State sets default value if get doesn't find it Which allows for normal usage of Dictionaries inside state.... --- .../Extensions/StateDictionaryExtensions.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/core/Elsa.Abstractions/Extensions/StateDictionaryExtensions.cs b/src/core/Elsa.Abstractions/Extensions/StateDictionaryExtensions.cs index 1df6db8f7..fed252ca5 100644 --- a/src/core/Elsa.Abstractions/Extensions/StateDictionaryExtensions.cs +++ b/src/core/Elsa.Abstractions/Extensions/StateDictionaryExtensions.cs @@ -16,8 +16,16 @@ namespace Elsa { var item = state?.ContainsKey(key) == true ? state![key] : default; - if (item == null) - return defaultValue(); + if (item == null) + { + if (state != null) + { + state.SetState(key, defaultValue()); + item = state![key]; + } + else + return defaultValue(); + } return item.ConvertTo()!; } From 307b75148c2048c2ede6ebb2255661e090266f19 Mon Sep 17 00:00:00 2001 From: tomy2105 Date: Wed, 23 Jun 2021 16:25:04 +0200 Subject: [PATCH 3/6] 2.0 to 2.1 code backward compatibility related to IActivityTypeProvider --- .../Metadata/ActivityDescriptor.cs | 8 ++++ .../Metadata/ActivityInputDescriptor.cs | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/core/Elsa.Abstractions/Metadata/ActivityDescriptor.cs b/src/core/Elsa.Abstractions/Metadata/ActivityDescriptor.cs index 0bfba28da..278224720 100644 --- a/src/core/Elsa.Abstractions/Metadata/ActivityDescriptor.cs +++ b/src/core/Elsa.Abstractions/Metadata/ActivityDescriptor.cs @@ -1,3 +1,5 @@ +using System; + namespace Elsa.Metadata { public class ActivityDescriptor @@ -19,6 +21,12 @@ namespace Elsa.Metadata public string Category { get; set; } public ActivityTraits Traits { get; set; } public string[] Outcomes { get; set; } + [Obsolete("Use InputProperties instead.")] + public ActivityInputDescriptor[] Properties + { + get => InputProperties; + set => InputProperties = value; + } public ActivityInputDescriptor[] InputProperties { get; set; } public ActivityOutputDescriptor[] OutputProperties { get; set; } } diff --git a/src/core/Elsa.Abstractions/Metadata/ActivityInputDescriptor.cs b/src/core/Elsa.Abstractions/Metadata/ActivityInputDescriptor.cs index f9da7d7e7..6ef196833 100644 --- a/src/core/Elsa.Abstractions/Metadata/ActivityInputDescriptor.cs +++ b/src/core/Elsa.Abstractions/Metadata/ActivityInputDescriptor.cs @@ -4,6 +4,43 @@ using System.Linq; namespace Elsa.Metadata { + [Obsolete("Use ActivityInputDescriptor instead.")] + public class ActivityPropertyDescriptor : ActivityInputDescriptor + { + public ActivityPropertyDescriptor() : base() + { + } + + public ActivityPropertyDescriptor( + string name, + Type type, + string uiHint, + string label, + string? hint = default, + object? options = default, + string? category = default, + object? defaultValue = default, + string? defaultSyntax = "Literal", + IEnumerable? supportedSyntaxes = default, + bool? isDisabled = default) + : base ( + name, + type, + uiHint, + label, + hint, + options, + category, + 0, + defaultValue, + defaultSyntax, + supportedSyntaxes, + isDisabled + ) + { + } + } + public class ActivityInputDescriptor { public ActivityInputDescriptor() From 7f6c555307ca3157cc898f512167501c223444ae Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 25 Jun 2021 16:10:08 +0200 Subject: [PATCH 4/6] Remove Input and State logging This potentially generates a lot of data. Better to let the developer decide what they want included --- src/core/Elsa.Core/Handlers/WriteWorkflowExecutionLog.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/core/Elsa.Core/Handlers/WriteWorkflowExecutionLog.cs b/src/core/Elsa.Core/Handlers/WriteWorkflowExecutionLog.cs index 6d579dec3..11e4ec1dd 100644 --- a/src/core/Elsa.Core/Handlers/WriteWorkflowExecutionLog.cs +++ b/src/core/Elsa.Core/Handlers/WriteWorkflowExecutionLog.cs @@ -24,13 +24,7 @@ namespace Elsa.Handlers { var activityExecutionContext = notification.ActivityExecutionContext; - var data = new - { - Input = notification.ActivityExecutionContext.Input, - State = activityExecutionContext.GetActivityData() - }; - - await WriteEntryAsync(notification.Resuming ? "Resuming" : "Executing", default, activityExecutionContext, data, cancellationToken); + await WriteEntryAsync(notification.Resuming ? "Resuming" : "Executing", default, activityExecutionContext, null, cancellationToken); } public async Task Handle(ActivityExecutionResultExecuted notification, CancellationToken cancellationToken) From 7958ecf49a7a924865c86be3a6e0c559973f2340 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 25 Jun 2021 22:39:50 +0200 Subject: [PATCH 5/6] Improve workflow journal details - Display actual property values. - Display output properties too. --- .../Expressions/ExpressionEvaluator.cs | 2 +- .../ElsaServiceCollectionExtensions.cs | 2 +- .../elsa-designer-tree/elsa-designer-tree.tsx | 13 +++- .../elsa-workflow-instance-journal.tsx | 5 +- .../elsa-workflow-instance-viewer-screen.tsx | 60 +++++++++++-------- .../src/models/domain.ts | 1 + .../src/plugins/switch-plugin.tsx | 6 +- .../Elsa.Samples.Server.Host/appsettings.json | 3 +- .../GetByDefinitionAndVersion.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 2 +- .../Mapping/ActivityBlueprintConverter.cs | 12 ++-- .../Services/WorkflowBlueprintMapper.cs | 41 +++++++++---- 12 files changed, 94 insertions(+), 55 deletions(-) diff --git a/src/core/Elsa.Core/Expressions/ExpressionEvaluator.cs b/src/core/Elsa.Core/Expressions/ExpressionEvaluator.cs index 5d523b1cb..212eba292 100644 --- a/src/core/Elsa.Core/Expressions/ExpressionEvaluator.cs +++ b/src/core/Elsa.Core/Expressions/ExpressionEvaluator.cs @@ -62,7 +62,7 @@ namespace Elsa.Expressions } catch (Exception e) { - _logger.LogWarning(e, "Failed to evaluate expression {Expression} using syntax {Syntax}", expression, syntax); + _logger.LogDebug(e, "Failed to evaluate expression {Expression} using syntax {Syntax}", expression, syntax); throw; } } diff --git a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs index 06cde782c..dbc2d8254 100644 --- a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs @@ -167,7 +167,7 @@ namespace Microsoft.Extensions.DependencyInjection .AddSingleton() .AddScoped() .AddScoped() - .AddTransient() + .AddScoped() .AddActivityTypeProvider() .AddScoped() .AddTransient() diff --git a/src/designer/elsa-workflows-studio/src/components/designers/tree/elsa-designer-tree/elsa-designer-tree.tsx b/src/designer/elsa-workflows-studio/src/components/designers/tree/elsa-designer-tree/elsa-designer-tree.tsx index 35bd74a56..45abf6d77 100644 --- a/src/designer/elsa-workflows-studio/src/components/designers/tree/elsa-designer-tree/elsa-designer-tree.tsx +++ b/src/designer/elsa-workflows-studio/src/components/designers/tree/elsa-designer-tree/elsa-designer-tree.tsx @@ -49,7 +49,7 @@ export class ElsaWorkflowDesigner { parentActivityId?: string; parentActivityOutcome?: string; addingActivity: boolean = false; - activityDisplayContexts: Map = {}; + activityDisplayContexts: Map = null; selectedActivities: Map = {}; handleContextMenuChange(state: ActivityContextMenuState) { @@ -113,6 +113,9 @@ export class ElsaWorkflowDesigner { } componentWillRender() { + if(!!this.activityDisplayContexts) + return; + const activityModels = this.workflowModel.activities; const displayContexts: Map = {}; const activityDescriptors: Array = state.activityDescriptors; @@ -150,6 +153,7 @@ export class ElsaWorkflowDesigner { updateWorkflowModel(model: WorkflowModel, emitEvent: boolean = true) { this.workflowModel = this.cleanWorkflowModel(model); + this.activityDisplayContexts = null; if (emitEvent) this.workflowChanged.emit(model); @@ -378,9 +382,11 @@ export class ElsaWorkflowDesigner { }); // Connections between activities and their outcomes. + const activityDisplayContexts = this.activityDisplayContexts || {}; + this.workflowModel.activities.forEach(activity => { this.graph.setNode(activity.activityId, this.createActivityOptions(activity)); - const displayContext = this.activityDisplayContexts[activity.activityId] || undefined; + const displayContext = activityDisplayContexts[activity.activityId] || undefined; const outcomes = !!displayContext ? displayContext.outcomes : activity.outcomes || []; outcomes.forEach(outcome => { @@ -554,7 +560,8 @@ export class ElsaWorkflowDesigner { } renderActivity(activity: ActivityModel) { - const displayContext = this.activityDisplayContexts[activity.activityId] || undefined; + const activityDisplayContexts = this.activityDisplayContexts || {}; + const displayContext = activityDisplayContexts[activity.activityId] || undefined; const activityContextMenuButton = !!this.activityContextMenuButton ? this.activityContextMenuButton(activity) : ''; const activityBorderColor = !!this.activityBorderColor ? this.activityBorderColor(activity) : 'gray'; const selectedColor = !!this.activityBorderColor ? activityBorderColor : 'blue'; diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-journal/elsa-workflow-instance-journal.tsx b/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-journal/elsa-workflow-instance-journal.tsx index f066a6552..3b6bd386e 100644 --- a/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-journal/elsa-workflow-instance-journal.tsx +++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-journal/elsa-workflow-instance-journal.tsx @@ -395,10 +395,13 @@ export class ElsaWorkflowInstanceJournal { if (!activityModel) return

No activity selected

; + + // Hide expressions field from properties so that we only display the evaluated value. + const model = {...activityModel, properties: activityModel.properties.map(x => ({ name: x.name, value: x.value }))} return (
-
{JSON.stringify(activityModel, null, 2)}
+
{JSON.stringify(model, null, 2)}
); }; diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-viewer-screen.tsx b/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-viewer-screen.tsx index f182acd12..f9449724f 100644 --- a/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-viewer-screen.tsx +++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-viewer-screen.tsx @@ -8,10 +8,12 @@ import { ActivityModel, Connection, ConnectionModel, - EventTypes, SimpleException, + EventTypes, + SimpleException, SyntaxNames, WorkflowBlueprint, - WorkflowExecutionLogRecord, WorkflowFault, + WorkflowExecutionLogRecord, + WorkflowFault, WorkflowInstance, WorkflowModel, WorkflowPersistenceBehavior, @@ -133,40 +135,46 @@ export class ElsaWorkflowInstanceViewerScreen { updateModels(workflowInstance: WorkflowInstance, workflowBlueprint: WorkflowBlueprint) { this.workflowInstance = workflowInstance; this.workflowBlueprint = workflowBlueprint; - this.workflowModel = this.mapWorkflowModel(workflowBlueprint); + this.workflowModel = this.mapWorkflowModel(workflowBlueprint, workflowInstance); } - mapWorkflowModel(workflowBlueprint: WorkflowBlueprint): WorkflowModel { + mapWorkflowModel(workflowBlueprint: WorkflowBlueprint, workflowInstance: WorkflowInstance): WorkflowModel { + const activities = workflowBlueprint.activities.filter(x => x.parentId == workflowBlueprint.id || !x.parentId).map(x => this.mapActivityModel(x, workflowInstance)); + const connections = workflowBlueprint.connections.filter(c => activities.findIndex(a => a.activityId == c.sourceActivityId || a.activityId == c.targetActivityId) > -1).map(this.mapConnectionModel); + return { - activities: workflowBlueprint.activities.filter(x => x.parentId == workflowBlueprint.id || !x.parentId).map(this.mapActivityModel), - connections: workflowBlueprint.connections.map(this.mapConnectionModel), + activities: activities, + connections: connections, persistenceBehavior: workflowBlueprint.persistenceBehavior, }; } - mapActivityModel(source: ActivityBlueprint): ActivityModel { + mapActivityModel(activityBlueprint: ActivityBlueprint, workflowInstance: WorkflowInstance): ActivityModel { const activityDescriptors: Array = state.activityDescriptors; - const activityDescriptor = activityDescriptors.find(x => x.type == source.type); - const properties: Array = collection.map(source.properties.data, (value, key) => { - const propertyDescriptor = activityDescriptor.inputProperties.find(x => x.name == key); - const defaultSyntax = propertyDescriptor.defaultSyntax || SyntaxNames.Literal; + const activityDescriptor = activityDescriptors.find(x => x.type == activityBlueprint.type); + const activityData = workflowInstance.activityData[activityBlueprint.id] || {}; + + const properties: Array = collection.map(activityBlueprint.properties.data, (value, key) => { + const propertyDescriptor = activityDescriptor.inputProperties.find(x => x.name == key) || activityDescriptor.outputProperties.find(x => x.name == key); + const defaultSyntax = propertyDescriptor.defaultSyntax || SyntaxNames.Literal; const expressions = {}; - expressions[defaultSyntax] = value; - return ({name: key, expressions: expressions, syntax: defaultSyntax}); + const v = activityData[key] || value; + expressions[defaultSyntax] = v; + return ({name: key, value: v, expressions: expressions, syntax: defaultSyntax}); }); return { - activityId: source.id, - description: source.description, - displayName: source.displayName || source.name || source.type, - name: source.name, - type: source.type, + activityId: activityBlueprint.id, + description: activityBlueprint.description, + displayName: activityBlueprint.displayName || activityBlueprint.name || activityBlueprint.type, + name: activityBlueprint.name, + type: activityBlueprint.type, properties: properties, outcomes: [...activityDescriptor.outcomes], - persistWorkflow: source.persistWorkflow, - saveWorkflowContext: source.saveWorkflowContext, - loadWorkflowContext: source.loadWorkflowContext, - propertyStorageProviders: source.propertyStorageProviders + persistWorkflow: activityBlueprint.persistWorkflow, + saveWorkflowContext: activityBlueprint.saveWorkflowContext, + loadWorkflowContext: activityBlueprint.loadWorkflowContext, + propertyStorageProviders: activityBlueprint.propertyStorageProviders } } @@ -242,8 +250,8 @@ export class ElsaWorkflowInstanceViewerScreen { const workflowFault = !!workflowInstance ? workflowInstance.fault : null; const activityData = workflowInstance.activityData[activity.activityId] || {}; const lifecycle = activityData['_Lifecycle'] || {}; - const executing = !!lifecycle.Executing; - const executed = !!lifecycle.Executed; + const executing = !!lifecycle.executing; + const executed = !!lifecycle.executed; if (!!workflowFault && workflowFault.faultedActivityId == activity.activityId) return 'red'; @@ -262,8 +270,8 @@ export class ElsaWorkflowInstanceViewerScreen { const workflowFault = !!workflowInstance ? workflowInstance.fault : null; const activityData = workflowInstance.activityData[activity.activityId] || {}; const lifecycle = activityData['_Lifecycle'] || {}; - const executing = !!lifecycle.Executing; - const executed = !!lifecycle.Executed; + const executing = !!lifecycle.executing; + const executed = !!lifecycle.executed; let icon: string; diff --git a/src/designer/elsa-workflows-studio/src/models/domain.ts b/src/designer/elsa-workflows-studio/src/models/domain.ts index ab13db9ff..99b21ed47 100644 --- a/src/designer/elsa-workflows-studio/src/models/domain.ts +++ b/src/designer/elsa-workflows-studio/src/models/domain.ts @@ -173,6 +173,7 @@ export interface ActivityDefinitionProperty { name: string; syntax?: string; expressions: Map; + value?: any; } interface BlockingActivity { diff --git a/src/designer/elsa-workflows-studio/src/plugins/switch-plugin.tsx b/src/designer/elsa-workflows-studio/src/plugins/switch-plugin.tsx index f317234ce..65a55fdee 100644 --- a/src/designer/elsa-workflows-studio/src/plugins/switch-plugin.tsx +++ b/src/designer/elsa-workflows-studio/src/plugins/switch-plugin.tsx @@ -15,12 +15,12 @@ export class SwitchPlugin implements ElsaPlugin { if (activityModel.type !== 'Switch') return; - + const props = activityModel.properties || []; const syntax = 'Switch'; const casesProp = props.find(x => x.name == 'Cases') || { expressions: {'Switch': ''}, syntax: syntax }; - const expression = casesProp.expressions[syntax] || '[]'; - const cases: Array = !!expression['$values'] ? expression['$values'] : parseJson(expression) || []; + const expression: any = casesProp.expressions[syntax] || []; + const cases: Array = !!expression['$values'] ? expression['$values'] : Array.isArray(expression) ? expression : parseJson(expression) || []; context.outcomes = [...cases.map(x => x.name), 'Default']; } } diff --git a/src/samples/server/Elsa.Samples.Server.Host/appsettings.json b/src/samples/server/Elsa.Samples.Server.Host/appsettings.json index f4cbce741..3d302938b 100644 --- a/src/samples/server/Elsa.Samples.Server.Host/appsettings.json +++ b/src/samples/server/Elsa.Samples.Server.Host/appsettings.json @@ -17,11 +17,10 @@ }, "Elsa": { "Features": [ - "Persistence:MongoDb", + "Persistence:EntityFrameworkCore:Sqlite", "Console", "Http", "Email", - "Webhooks", "Temporal:Quartz", "JavaScript:Activities", "UserTask", diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/GetByDefinitionAndVersion.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/GetByDefinitionAndVersion.cs index 0db84052f..e51966345 100644 --- a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/GetByDefinitionAndVersion.cs +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/GetByDefinitionAndVersion.cs @@ -40,7 +40,7 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions public async Task Handle(string workflowDefinitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) { var workflowDefinition = await _workflowDefinitionStore.FindAsync(new WorkflowDefinitionIdSpecification(workflowDefinitionId).WithVersionOptions(versionOptions), cancellationToken); - return workflowDefinition == null ? (IActionResult) NotFound() : Json(workflowDefinition, _serializer.GetSettings()); + return workflowDefinition == null ? NotFound() : Json(workflowDefinition, _serializer.GetSettings()); } } } \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Extensions/ServiceCollectionExtensions.cs b/src/server/Elsa.Server.Api/Extensions/ServiceCollectionExtensions.cs index 231427487..100413842 100644 --- a/src/server/Elsa.Server.Api/Extensions/ServiceCollectionExtensions.cs +++ b/src/server/Elsa.Server.Api/Extensions/ServiceCollectionExtensions.cs @@ -48,7 +48,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddScoped(); services.AddSingleton(); services.AddAutoMapperProfile(); return services; diff --git a/src/server/Elsa.Server.Api/Mapping/ActivityBlueprintConverter.cs b/src/server/Elsa.Server.Api/Mapping/ActivityBlueprintConverter.cs index 1d7365140..ba88cf277 100644 --- a/src/server/Elsa.Server.Api/Mapping/ActivityBlueprintConverter.cs +++ b/src/server/Elsa.Server.Api/Mapping/ActivityBlueprintConverter.cs @@ -9,13 +9,13 @@ namespace Elsa.Server.Api.Mapping public class ActivityBlueprintConverter : ITypeConverter { public const string ActivityPropertiesKey = "ActivityProperties"; - + public ActivityBlueprintModel Convert(IActivityBlueprint source, ActivityBlueprintModel? destination, ResolutionContext context) { destination ??= new ActivityBlueprintModel(); - + destination.Id = source.Id; - destination. Name = source.Name; + destination.Name = source.Name; destination.DisplayName = source.DisplayName; destination.Description = source.Description; destination.Type = source.Type; @@ -24,15 +24,15 @@ namespace Elsa.Server.Api.Mapping destination.LoadWorkflowContext = source.LoadWorkflowContext; destination.SaveWorkflowContext = source.SaveWorkflowContext; destination.Properties = GetProperties(source, context); - + return destination; } private Variables GetProperties(IActivityBlueprint activityBlueprint, ResolutionContext context) { - if (!context.Items.ContainsKey(ActivityPropertiesKey)) + if (!context.Items.ContainsKey(ActivityPropertiesKey)) return new Variables(); - + var dictionary = (IDictionary) context.Items[ActivityPropertiesKey]; return dictionary[activityBlueprint.Id]; } diff --git a/src/server/Elsa.Server.Api/Services/WorkflowBlueprintMapper.cs b/src/server/Elsa.Server.Api/Services/WorkflowBlueprintMapper.cs index 19e0d7d60..79aab91a2 100644 --- a/src/server/Elsa.Server.Api/Services/WorkflowBlueprintMapper.cs +++ b/src/server/Elsa.Server.Api/Services/WorkflowBlueprintMapper.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using AutoMapper; @@ -14,12 +16,14 @@ namespace Elsa.Server.Api.Services public class WorkflowBlueprintMapper : IWorkflowBlueprintMapper { private readonly IWorkflowBlueprintReflector _workflowBlueprintReflector; + private readonly IActivityTypeService _activityTypeService; private readonly IMapper _mapper; private readonly IServiceScopeFactory _serviceScopeFactory; - public WorkflowBlueprintMapper(IWorkflowBlueprintReflector workflowBlueprintReflector, IMapper mapper, IServiceScopeFactory serviceScopeFactory) + public WorkflowBlueprintMapper(IWorkflowBlueprintReflector workflowBlueprintReflector, IActivityTypeService activityTypeService, IMapper mapper, IServiceScopeFactory serviceScopeFactory) { _workflowBlueprintReflector = workflowBlueprintReflector; + _activityTypeService = activityTypeService; _mapper = mapper; _serviceScopeFactory = serviceScopeFactory; } @@ -36,23 +40,40 @@ namespace Elsa.Server.Api.Services private async ValueTask GetActivityPropertiesAsync(IWorkflowBlueprintWrapper workflowBlueprintWrapper, IActivityBlueprintWrapper activityBlueprintWrapper, CancellationToken cancellationToken) { - var workflowBlueprint = workflowBlueprintWrapper.WorkflowBlueprint; var activityBlueprint = activityBlueprintWrapper.ActivityBlueprint; + var activityType = await _activityTypeService.GetActivityTypeAsync(activityBlueprint.Type, cancellationToken); + var activityDescriptor = await _activityTypeService.DescribeActivityType(activityType, cancellationToken); var activityId = activityBlueprint.Id; - var activityPropertyValueProviders = workflowBlueprint.ActivityPropertyProviders.GetProviders(activityId); var activityWrapper = workflowBlueprintWrapper.GetActivity(activityId)!; var properties = new Variables(); - if (activityPropertyValueProviders == null) - return properties; - - foreach (var valueProvider in activityPropertyValueProviders) + foreach (var property in activityDescriptor.InputProperties) { - var value = await activityWrapper.EvaluatePropertyValueAsync(valueProvider.Key, cancellationToken); - properties.Set(valueProvider.Key, value); + var value = await TryEvaluatePropertyAsync(activityWrapper, property.Name, cancellationToken); + properties.Set(property.Name, value); + } + + foreach (var property in activityDescriptor.OutputProperties) + { + // Declare output properties to have at least a complete schema. + properties.Set(property.Name, null); } return properties; } + + private async Task TryEvaluatePropertyAsync(IActivityBlueprintWrapper activityWrapper, string propertyName, CancellationToken cancellationToken) + { + try + { + return await activityWrapper.EvaluatePropertyValueAsync(propertyName, cancellationToken); + } + catch + { + // ignored + } + + return null; + } } } \ No newline at end of file From 1c20b8fbb4951958f4c1598ccce870ecd03ecf2d Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sat, 26 Jun 2021 11:19:34 +0200 Subject: [PATCH 6/6] Rename SaveRequest models to fix ambiguity for Swagger Fixes #1178 --- .../Endpoints/WebhookDefinitions/Models.cs | 2 +- .../Endpoints/WebhookDefinitions/Post.cs | 5 ++--- .../Endpoints/WebhookDefinitions/Put.cs | 2 +- .../Endpoints/WorkflowDefinitions/Save.Request.cs | 2 +- .../Elsa.Server.Api/Endpoints/WorkflowDefinitions/Save.cs | 4 ++-- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Models.cs b/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Models.cs index 73e2ca645..e88c858ab 100644 --- a/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Models.cs +++ b/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Models.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; namespace Elsa.Activities.Webhooks.Endpoints.WebhookDefinitions { - public sealed record SaveRequest + public sealed record SaveWebhookDefinitionRequest { public string? Id { get; init; } [Required] public string Path { get; init; } = default!; diff --git a/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Post.cs b/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Post.cs index 4fc5089b1..2bccae227 100644 --- a/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Post.cs +++ b/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Post.cs @@ -33,12 +33,11 @@ namespace Elsa.Activities.Webhooks.Endpoints.WebhookDefinitions [SwaggerResponseExample(StatusCodes.Status200OK, typeof(WebhookDefinitionExample))] [SwaggerOperation( Summary = "Creates a new webhook definition or updates an existing one.", - Description = - "Creates a new webhook definition or updates an existing one.", + Description = "Creates a new webhook definition or updates an existing one.", OperationId = "WebhookDefinitions.Post", Tags = new[] { "WebhookDefinitions" }) ] - public async Task> Handle([FromBody] SaveRequest request, [FromRoute] ApiVersion apiVersion, CancellationToken cancellationToken) + public async Task> Handle([FromBody] SaveWebhookDefinitionRequest request, [FromRoute] ApiVersion apiVersion, CancellationToken cancellationToken) { var webhookId = request.Id; var webhookDefinition = !string.IsNullOrWhiteSpace(webhookId) ? await _store.FindAsync(new EntityIdSpecification(webhookId), cancellationToken) : default; diff --git a/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Put.cs b/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Put.cs index c3c0af6e6..cb26304ca 100644 --- a/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Put.cs +++ b/src/activities/webhooks/Elsa.Activities.Webhooks/Endpoints/WebhookDefinitions/Put.cs @@ -30,7 +30,7 @@ namespace Elsa.Activities.Webhooks.Endpoints.WebhookDefinitions OperationId = "WebhookDefinitions.Put", Tags = new[] { "WebhookDefinitions" }) ] - public async Task> Handle([FromBody] SaveRequest request, [FromRoute] ApiVersion apiVersion, CancellationToken cancellationToken) + public async Task> Handle([FromBody] SaveWebhookDefinitionRequest request, [FromRoute] ApiVersion apiVersion, CancellationToken cancellationToken) { var webhookId = request.Id; var webhookDefinition = await _store.FindAsync(new EntityIdSpecification(webhookId), cancellationToken); diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Save.Request.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Save.Request.cs index 2633b8fcd..70b043293 100644 --- a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Save.Request.cs +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Save.Request.cs @@ -5,7 +5,7 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions { public partial class Save { - public sealed record SaveRequest + public sealed record SaveWorkflowDefinitionRequest { public string? WorkflowDefinitionId { get; init; } public string? Name { get; init; } diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Save.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Save.cs index 8e73b2a6b..4b19c7f2e 100644 --- a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Save.cs +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Save.cs @@ -35,7 +35,7 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions OperationId = "WorkflowDefinitions.Post", Tags = new[] { "WorkflowDefinitions" }) ] - public async Task> Handle([FromBody]SaveRequest request, [FromRoute]ApiVersion apiVersion, CancellationToken cancellationToken) + public async Task> Handle([FromBody]SaveWorkflowDefinitionRequest request, [FromRoute]ApiVersion apiVersion, CancellationToken cancellationToken) { var workflowDefinitionId = request.WorkflowDefinitionId; var workflowDefinition = !string.IsNullOrWhiteSpace(workflowDefinitionId) ? await _workflowPublisher.GetDraftAsync(workflowDefinitionId, cancellationToken) : default; @@ -68,7 +68,7 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions return CreatedAtAction("Handle", "GetByVersionId", new { versionId = workflowDefinition.Id, apiVersion = apiVersion.ToString() }, workflowDefinition); } - private IEnumerable FilterInvalidConnections(SaveRequest request) + private IEnumerable FilterInvalidConnections(SaveWorkflowDefinitionRequest request) { var validConnections = from connection in request.Connections