From 27e0e768847e511cbfcf2c4aa79d299e5c090204 Mon Sep 17 00:00:00 2001 From: tomy2105 Date: Thu, 29 Apr 2021 12:06:28 +0200 Subject: [PATCH 01/24] Adding DbContextOptionsBuilderExtensions --- .../DbContextOptionsBuilderExtensions.cs | 13 +++++++++++++ .../Program.cs | 1 + 2 files changed, 14 insertions(+) create mode 100644 src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.SqlServer/DbContextOptionsBuilderExtensions.cs diff --git a/src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.SqlServer/DbContextOptionsBuilderExtensions.cs b/src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.SqlServer/DbContextOptionsBuilderExtensions.cs new file mode 100644 index 000000000..f16e322b7 --- /dev/null +++ b/src/persistence/Elsa.Persistence.EntityFramework/Elsa.Persistence.EntityFramework.SqlServer/DbContextOptionsBuilderExtensions.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; + +namespace Elsa.Persistence.EntityFramework.SqlServer +{ + public static class DbContextOptionsBuilderExtensions + { + /// + /// Configures the context to use SqlServer. + /// + public static DbContextOptionsBuilder UseSqlServer(this DbContextOptionsBuilder builder, string connectionString) => + builder.UseSqlServer(connectionString, db => db.MigrationsAssembly(typeof(SqlServerElsaContextFactory).Assembly.GetName().Name)); + } +} \ No newline at end of file diff --git a/src/samples/persistence/Elsa.Samples.Persistence.EntityFramework/Program.cs b/src/samples/persistence/Elsa.Samples.Persistence.EntityFramework/Program.cs index ee08ba59c..9e3eaee24 100644 --- a/src/samples/persistence/Elsa.Samples.Persistence.EntityFramework/Program.cs +++ b/src/samples/persistence/Elsa.Samples.Persistence.EntityFramework/Program.cs @@ -6,6 +6,7 @@ using Elsa.Persistence.Specifications.WorkflowInstances; using Elsa.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Elsa.Persistence.EntityFramework.SqlServer; namespace Elsa.Samples.Persistence.EntityFramework { From bbe3aa22fdc9d0101feb2c747947088341636ff6 Mon Sep 17 00:00:00 2001 From: tomy2105 Date: Wed, 28 Apr 2021 11:15:48 +0200 Subject: [PATCH 02/24] MultiText enhancement when Options are provided. --- .../elsa-input-tags-dropdown.css | 17 +++ .../elsa-input-tags-dropdown.tsx | 112 ++++++++++++++++++ .../elsa-multi-text-property.tsx | 31 ++++- .../src/models/domain.ts | 5 + 4 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css create mode 100644 src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx diff --git a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css new file mode 100644 index 000000000..a51ef25f4 --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css @@ -0,0 +1,17 @@ +:host { + display: block; +} + +.tag-input:focus { + outline: none !important; + box-shadow: none !important; +} + +.pr-8 { + padding-right: 2rem !important; +} + +.bg-green-350 { + --tw-bg-opacity: 1; + background-color: rgba(52,191,113,var(--tw-bg-opacity)); +} \ No newline at end of file diff --git a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx new file mode 100644 index 000000000..72f2dc58f --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx @@ -0,0 +1,112 @@ +import { Component, h, Prop, Event, EventEmitter, State, Watch } from '@stencil/core'; +import { MultiTextDefinition } from '../../../models/domain'; + +@Component({ + tag: 'elsa-input-tags-dropdown', + styleUrl: 'elsa-input-tags-dropdown.css', + shadow: false, +}) +export class ElsaInputTagsDropdown { + + @Prop() fieldName?: string; + @Prop() fieldId?: string; + @Prop() placeHolder?: string = 'Add tag'; + @Prop() values?: Array = []; + @Prop() dropdownValues?: Array = []; + @Event({ bubbles: true }) valueChanged: EventEmitter>; + @State() currentValues?: Array = []; + @State() dropdownTags?: Array = []; + + @Watch('values') + valuesChangedHandler(newValue: Array) { + let values: Array = []; + + newValue.forEach(value => { + this.dropdownValues.forEach(tag => { + if (value === tag.value) { + values.push(tag); + } + }) + }) + this.currentValues = values || []; + } + + componentWillLoad() { + this.dropdownTags = this.dropdownValues; + let values: Array = []; + + this.values.forEach(value => { + this.dropdownValues.forEach(tag => { + if (value === tag.value) { + values.push(tag); + } + }) + }) + this.currentValues = values; + + const tagsToRemove: Array = this.currentValues.map(tag => tag.value); + this.dropdownTags = this.dropdownTags.filter(tag => !tagsToRemove.includes(tag.value)); + } + + async onTagSelected(e: any) { + e.preventDefault(); + + const input = e.target as HTMLSelectElement; + const currentTag: MultiTextDefinition = { + text: input.options[input.selectedIndex].text.trim(), + value: input.value + } + + if (currentTag.value.length == 0) + return; + + const values: Array = [...this.currentValues]; + values.push(currentTag); + this.currentValues = values.distinct(); + input.value = "Add"; + this.dropdownTags = this.dropdownTags.filter(tag => tag.value !== currentTag.value); + await this.valueChanged.emit(values); + } + + async onDeleteTagClick(e: any, currentTag: MultiTextDefinition) { + e.preventDefault(); + + this.currentValues = this.currentValues.filter(tag => tag.value !== currentTag.value); + this.dropdownTags = [...this.dropdownTags, currentTag].sort((tag1, tag2) => tag1.value.localeCompare(tag2.value)); + await this.valueChanged.emit(this.currentValues); + } + + render() { + let values: Array = this.currentValues || []; + + if (!Array.isArray(values)) + values = []; + + const valuesJson = JSON.stringify(values.map(tag => tag.value)); + + return ( +
+ {values.map(tag => + ( + this.onDeleteTagClick(e, tag)} class="inline-block text-xs bg-blue-400 text-white py-2 px-3 mr-1 mb-1 rounded"> + + {tag.text} + × + + ))} + + + +
+ ) + } +} \ No newline at end of file diff --git a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx index 087c81494..d278e3896 100644 --- a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx +++ b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx @@ -1,5 +1,5 @@ import {Component, h, Prop, State} from '@stencil/core'; -import {ActivityDefinitionProperty, ActivityPropertyDescriptor, SyntaxNames} from "../../../../models"; +import {ActivityDefinitionProperty, ActivityPropertyDescriptor, SyntaxNames, MultiTextDefinition} from "../../../../models"; import {parseJson} from "../../../../utils/utils"; @Component({ @@ -11,14 +11,20 @@ export class ElsaMultiTextProperty { @Prop() propertyDescriptor: ActivityPropertyDescriptor; @Prop() propertyModel: ActivityDefinitionProperty; - @State() currentValue?: string + @State() currentValue?: string; async componentWillLoad() { this.currentValue = this.propertyModel.expressions[SyntaxNames.Json] || '[]'; } - onValueChanged(newValue: Array){ - this.currentValue = JSON.stringify(newValue); + onValueChanged(newValue: Array) { + const newValues = newValue.map(dropdown => { + if (typeof dropdown === 'string') return dropdown; + + return dropdown.value; + }) + + this.currentValue = JSON.stringify(newValues); this.propertyModel.expressions[SyntaxNames.Json] = this.currentValue; } @@ -26,6 +32,13 @@ export class ElsaMultiTextProperty { this.currentValue = e.detail; } + createKeyValueOptions(options: Array) { + if (options === null) + return options; + + return options.map(option => typeof option === 'string' ? { text: option, value: option } : option); + } + render() { const propertyDescriptor = this.propertyDescriptor; const propertyModel = this.propertyModel; @@ -33,14 +46,20 @@ export class ElsaMultiTextProperty { const fieldId = propertyName; const fieldName = propertyName; const values = parseJson(this.currentValue); - + const valueType = propertyDescriptor.options !== null ? 'dropdown' : 'multi-text'; + const propertyOptions = this.createKeyValueOptions(propertyDescriptor.options); + + const elsaInputTags = valueType === 'multi-text' ? + this.onValueChanged(e.detail)} /> : + this.onValueChanged(e.detail)} />; + return ( this.onDefaultSyntaxValueChanged(e)} editor-height="2em" single-line={true}> - this.onValueChanged(e.detail)}/> + {elsaInputTags} ) } diff --git a/src/designer/elsa-workflows-studio/src/models/domain.ts b/src/designer/elsa-workflows-studio/src/models/domain.ts index 75993deec..a83741811 100644 --- a/src/designer/elsa-workflows-studio/src/models/domain.ts +++ b/src/designer/elsa-workflows-studio/src/models/domain.ts @@ -168,6 +168,11 @@ export interface ConnectionDefinitionMapped { outcome: string; } +export interface MultiTextDefinition { + text: string; + value: string; +} + export interface ActivityDefinitionProperty { name: string; syntax?: string; From 2e8467b858ffbe822e2f240a586df4851990e4a3 Mon Sep 17 00:00:00 2001 From: tomy2105 Date: Wed, 28 Apr 2021 15:20:13 +0200 Subject: [PATCH 03/24] Using Tailwind CSS instead of custom one --- .../elsa-input-tags/elsa-input-tags-dropdown.css | 14 -------------- .../elsa-input-tags/elsa-input-tags-dropdown.tsx | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css index a51ef25f4..f09d32524 100644 --- a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css +++ b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css @@ -1,17 +1,3 @@ :host { display: block; -} - -.tag-input:focus { - outline: none !important; - box-shadow: none !important; -} - -.pr-8 { - padding-right: 2rem !important; -} - -.bg-green-350 { - --tw-bg-opacity: 1; - background-color: rgba(52,191,113,var(--tw-bg-opacity)); } \ No newline at end of file diff --git a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx index 72f2dc58f..ff838aa53 100644 --- a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx +++ b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx @@ -95,7 +95,7 @@ export class ElsaInputTagsDropdown { ))} - this.onTagSelected(e)}> {this.dropdownTags.map(tag => ( From 6f1f9d0ae2933cb41ba05204fdb49bd386d70107 Mon Sep 17 00:00:00 2001 From: tomy2105 Date: Wed, 28 Apr 2021 11:15:48 +0200 Subject: [PATCH 04/24] MultiText enhancement when Options are provided. --- .../controls/elsa-input-tags/elsa-input-tags-dropdown.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css index f09d32524..7f26ddcb5 100644 --- a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css +++ b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css @@ -1,3 +1,3 @@ :host { display: block; -} \ No newline at end of file +} From d43c34dab42795a48c8e2c2d68cb7c924f6c05b1 Mon Sep 17 00:00:00 2001 From: tomy2105 Date: Wed, 28 Apr 2021 15:20:13 +0200 Subject: [PATCH 05/24] Using Tailwind CSS instead of custom one --- .../controls/elsa-input-tags/elsa-input-tags-dropdown.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css index 7f26ddcb5..cce319279 100644 --- a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css +++ b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.css @@ -1,3 +1,4 @@ :host { display: block; } + From b7202d2de22174807af25f7425849163d29cd64b Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 29 Apr 2021 16:00:10 +0200 Subject: [PATCH 06/24] Add ListActive method to workflow registry that returns workflows that are published or have at least 1 non-finished workflow instance --- .../Services/IWorkflowRegistry.cs | 23 +++++++++++++++++++ .../Elsa.Core/Bookmarks/BookmarkIndexer.cs | 2 +- .../Decorators/CachingWorkflowRegistry.cs | 1 + .../Elsa.Core/Services/WorkflowRegistry.cs | 21 +++++++++++++++++ src/core/Elsa.Core/Triggers/TriggerIndexer.cs | 2 +- 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/core/Elsa.Abstractions/Services/IWorkflowRegistry.cs b/src/core/Elsa.Abstractions/Services/IWorkflowRegistry.cs index 0d442febb..cc846b578 100644 --- a/src/core/Elsa.Abstractions/Services/IWorkflowRegistry.cs +++ b/src/core/Elsa.Abstractions/Services/IWorkflowRegistry.cs @@ -7,11 +7,34 @@ using Elsa.Services.Models; namespace Elsa.Services { + /// + /// Represents a complete store of all versions of all workflow blueprints. + /// public interface IWorkflowRegistry { + /// + /// Lists all versions of all workflow blueprints. + /// Task> ListAsync(CancellationToken cancellationToken = default); + + /// + /// Lists only those workflow blueprints that are published or have at least one non-finished workflow. + /// + Task> ListActiveAsync(CancellationToken cancellationToken = default); + + /// + /// Gets a single workflow blueprint with the specified ID for the specified tenant and version. + /// Task GetAsync(string id, string? tenantId, VersionOptions version, CancellationToken cancellationToken = default); + + /// + /// Returns all workflow blueprints that fit the specified predicate. + /// Task> FindManyAsync(Func predicate, CancellationToken cancellationToken = default); + + /// + /// Returns a single workflow blueprint that fits the specified predicate. + /// Task FindAsync(Func predicate, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Bookmarks/BookmarkIndexer.cs b/src/core/Elsa.Core/Bookmarks/BookmarkIndexer.cs index 2085c07bf..ef3f095a8 100644 --- a/src/core/Elsa.Core/Bookmarks/BookmarkIndexer.cs +++ b/src/core/Elsa.Core/Bookmarks/BookmarkIndexer.cs @@ -63,7 +63,7 @@ namespace Elsa.Bookmarks var workflowInstanceIds = workflowInstanceList.Select(x => x.Id).ToList(); await DeleteBookmarksAsync(workflowInstanceIds, cancellationToken); - var workflowBlueprints = await _workflowRegistry.ListAsync(cancellationToken); + var workflowBlueprints = await _workflowRegistry.ListActiveAsync(cancellationToken); var workflowBlueprintsDictionary = workflowBlueprints.ToDictionary(x => (x.Id, x.Version)); var entities = new List(); diff --git a/src/core/Elsa.Core/Decorators/CachingWorkflowRegistry.cs b/src/core/Elsa.Core/Decorators/CachingWorkflowRegistry.cs index ae39a11ea..32bb57a3d 100644 --- a/src/core/Elsa.Core/Decorators/CachingWorkflowRegistry.cs +++ b/src/core/Elsa.Core/Decorators/CachingWorkflowRegistry.cs @@ -29,6 +29,7 @@ namespace Elsa.Decorators } public async Task> ListAsync(CancellationToken cancellationToken) => await GetWorkflowBlueprints(cancellationToken); + public Task> ListActiveAsync(CancellationToken cancellationToken = default) => _workflowRegistry.ListActiveAsync(cancellationToken); public async Task GetAsync(string id, string? tenantId, VersionOptions version, CancellationToken cancellationToken) => await FindAsync(x => x.Id == id && x.TenantId == tenantId && x.WithVersion(version), cancellationToken); diff --git a/src/core/Elsa.Core/Services/WorkflowRegistry.cs b/src/core/Elsa.Core/Services/WorkflowRegistry.cs index 34fb18a65..207abd265 100644 --- a/src/core/Elsa.Core/Services/WorkflowRegistry.cs +++ b/src/core/Elsa.Core/Services/WorkflowRegistry.cs @@ -24,6 +24,7 @@ namespace Elsa.Services } public async Task> ListAsync(CancellationToken cancellationToken) => await GetWorkflowsInternalAsync(cancellationToken).ToListAsync(cancellationToken); + public async Task> ListActiveAsync(CancellationToken cancellationToken) => await ListActiveWorkflowsAsync(cancellationToken).ToListAsync(cancellationToken); public async Task GetAsync(string id, string? tenantId, VersionOptions version, CancellationToken cancellationToken) => await FindAsync(x => x.Id == id && x.TenantId == tenantId && x.WithVersion(version), cancellationToken); @@ -33,6 +34,26 @@ namespace Elsa.Services public async Task FindAsync(Func predicate, CancellationToken cancellationToken) => (await ListAsync(cancellationToken).Where(predicate).OrderByDescending(x => x.Version)).FirstOrDefault(); + + private async IAsyncEnumerable ListActiveWorkflowsAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var workflows = await ListAsync(cancellationToken); + + foreach (var workflow in workflows) + { + // If a workflow is not published, only consider it for processing if it has at least one non-ended workflow instance. + if (!workflow.IsPublished && !await WorkflowHasNonFinishedWorkflowsAsync(workflow, cancellationToken)) + continue; + + yield return workflow; + } + } + + private async Task WorkflowHasNonFinishedWorkflowsAsync(IWorkflowBlueprint workflowBlueprint, CancellationToken cancellationToken) + { + var count = await _workflowInstanceStore.CountAsync(new NonFinalizedWorkflowSpecification().WithWorkflowDefinition(workflowBlueprint.Id), cancellationToken); + return count > 0; + } private async IAsyncEnumerable GetWorkflowsInternalAsync([EnumeratorCancellation] CancellationToken cancellationToken) { diff --git a/src/core/Elsa.Core/Triggers/TriggerIndexer.cs b/src/core/Elsa.Core/Triggers/TriggerIndexer.cs index 71c35d375..cd5cf8046 100644 --- a/src/core/Elsa.Core/Triggers/TriggerIndexer.cs +++ b/src/core/Elsa.Core/Triggers/TriggerIndexer.cs @@ -36,7 +36,7 @@ namespace Elsa.Triggers public async Task IndexTriggersAsync(CancellationToken cancellationToken = default) { - var allWorkflowBlueprints = await _workflowRegistry.ListAsync(cancellationToken); + var allWorkflowBlueprints = await _workflowRegistry.ListActiveAsync(cancellationToken); var publishedWorkflowBlueprints = allWorkflowBlueprints.Where(x => x.IsPublished).ToList(); await IndexTriggersAsync(publishedWorkflowBlueprints, cancellationToken); await _mediator.Publish(new TriggerIndexingFinished(), cancellationToken); From 22aa10c65f970724a85f46279145e4a15587e504 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 29 Apr 2021 16:01:01 +0200 Subject: [PATCH 07/24] Prevent service bus starters from crashing program in case of invalid queue/topic names --- .../Services/ServiceBusQueuesStarter.cs | 14 ++++++++++-- .../Services/ServiceBusTopicsStarter.cs | 22 +++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusQueuesStarter.cs b/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusQueuesStarter.cs index 5b6db2caf..e16e04a27 100644 --- a/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusQueuesStarter.cs +++ b/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusQueuesStarter.cs @@ -41,12 +41,22 @@ namespace Elsa.Activities.AzureServiceBus.Services await DisposeExistingWorkersAsync(); var queueNames = (await GetQueueNamesAsync(cancellationToken).ToListAsync(cancellationToken)).Distinct(); - foreach (var queueName in queueNames) + foreach (var queueName in queueNames) + await CreateAndAddWorkerAsync(queueName, cancellationToken); + } + + private async Task CreateAndAddWorkerAsync(string queueName, CancellationToken cancellationToken) + { + try { var receiver = await _messageReceiverClientFactory.GetReceiverAsync(queueName, cancellationToken); var worker = ActivatorUtilities.CreateInstance(_serviceProvider, receiver, (Func) DisposeReceiverAsync); _workers.Add(worker); } + catch (Exception e) + { + _logger.LogWarning(e, "Failed to create a receiver for queue {QueueName}", queueName); + } } private async Task DisposeExistingWorkersAsync() @@ -65,7 +75,7 @@ namespace Elsa.Activities.AzureServiceBus.Services using var scope = _scopeFactory.CreateScope(); var workflowRegistry = scope.ServiceProvider.GetRequiredService(); var workflowBlueprintReflector = scope.ServiceProvider.GetRequiredService(); - var workflows = await workflowRegistry.ListAsync(cancellationToken); + var workflows = await workflowRegistry.ListActiveAsync(cancellationToken); var query = from workflow in workflows diff --git a/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusTopicsStarter.cs b/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusTopicsStarter.cs index 078e07635..012860a1f 100644 --- a/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusTopicsStarter.cs +++ b/src/activities/Elsa.Activities.AzureServiceBus/Services/ServiceBusTopicsStarter.cs @@ -10,6 +10,7 @@ using Elsa.Services; using Elsa.Services.Models; using Microsoft.Azure.ServiceBus.Core; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Elsa.Activities.AzureServiceBus.Services { @@ -19,16 +20,19 @@ namespace Elsa.Activities.AzureServiceBus.Services private readonly ITopicMessageReceiverFactory _receiverFactory; private readonly IServiceScopeFactory _scopeFactory; private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; private readonly ICollection _workers; public ServiceBusTopicsStarter( ITopicMessageReceiverFactory receiverFactory, IServiceScopeFactory scopeFactory, - IServiceProvider serviceProvider) + IServiceProvider serviceProvider, + ILogger logger) { _receiverFactory = receiverFactory; _scopeFactory = scopeFactory; _serviceProvider = serviceProvider; + _logger = logger; _workers = new List(); } @@ -38,12 +42,22 @@ namespace Elsa.Activities.AzureServiceBus.Services await DisposeExistingWorkersAsync(); var entities = (await GetTopicSubscriptionNamesAsync(cancellationToken).ToListAsync(cancellationToken)).Distinct(); - foreach (var entity in entities) + foreach (var entity in entities) + await CreateAndAddWorkerAsync(entity.topicName, entity.subscriptionName, cancellationToken); + } + + private async Task CreateAndAddWorkerAsync(string topicName, string subscriptionName, CancellationToken cancellationToken) + { + try { - var receiver = await _receiverFactory.GetTopicReceiverAsync(entity.topicName, entity.subscriptionName, cancellationToken); + var receiver = await _receiverFactory.GetTopicReceiverAsync(topicName, subscriptionName, cancellationToken); var worker = ActivatorUtilities.CreateInstance(_serviceProvider, receiver, (Func) DisposeReceiverAsync); _workers.Add(worker); } + catch (Exception e) + { + _logger.LogWarning(e, "Failed to create a receiver for topic {TopicName} and subscription {SubscriptionName}", topicName, subscriptionName); + } } private async Task DisposeExistingWorkersAsync() @@ -63,7 +77,7 @@ namespace Elsa.Activities.AzureServiceBus.Services var workflowRegistry = scope.ServiceProvider.GetRequiredService(); var workflowBlueprintReflector = scope.ServiceProvider.GetRequiredService(); var workflowInstanceStore = scope.ServiceProvider.GetRequiredService(); - var workflows = await workflowRegistry.ListAsync(cancellationToken); + var workflows = await workflowRegistry.ListActiveAsync(cancellationToken); var query = from workflow in workflows From 21a733b822f14cb0cfec2d7867a9748a6e8eb148 Mon Sep 17 00:00:00 2001 From: tomy2105 Date: Thu, 29 Apr 2021 14:46:24 +0200 Subject: [PATCH 08/24] Fix MultiText without options --- .../elsa-multi-text-property/elsa-multi-text-property.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx index d278e3896..93797514a 100644 --- a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx +++ b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx @@ -46,6 +46,7 @@ export class ElsaMultiTextProperty { const fieldId = propertyName; const fieldName = propertyName; const values = parseJson(this.currentValue); + propertyDescriptor.options = propertyDescriptor.options || null; const valueType = propertyDescriptor.options !== null ? 'dropdown' : 'multi-text'; const propertyOptions = this.createKeyValueOptions(propertyDescriptor.options); From 96227cb481401141af15bb99eeb8c0b006382f5e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 29 Apr 2021 20:37:17 +0200 Subject: [PATCH 09/24] Generate new activity editor key upon display Fixes #912 --- .../elsa-workflows-studio/src/components.d.ts | 25 +++++++++++++++++++ .../elsa-activity-editor-modal.tsx | 11 +++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/designer/elsa-workflows-studio/src/components.d.ts b/src/designer/elsa-workflows-studio/src/components.d.ts index 86b3c462e..cf7184c26 100644 --- a/src/designer/elsa-workflows-studio/src/components.d.ts +++ b/src/designer/elsa-workflows-studio/src/components.d.ts @@ -9,6 +9,7 @@ import { ActivityDefinitionProperty, ActivityDescriptor, ActivityModel, Activity import { LocationSegments, MatchResults, RouterHistory } from "@stencil/router"; import { MenuItem } from "./components/controls/elsa-context-menu/models"; import { DropdownButtonItem, DropdownButtonOrigin } from "./components/controls/elsa-dropdown-button/models"; +import { MultiTextDefinition } from "./models/domain"; import { MonacoValueChangedArgs } from "./components/controls/elsa-monaco/elsa-monaco"; import { Map } from "./utils/utils"; import { ToastNotificationOptions } from "./components/shared/elsa-toast-notification/elsa-toast-notification"; @@ -65,6 +66,13 @@ export namespace Components { "placeHolder"?: string; "values"?: Array; } + interface ElsaInputTagsDropdown { + "dropdownValues"?: Array; + "fieldId"?: string; + "fieldName"?: string; + "placeHolder"?: string; + "values"?: Array; + } interface ElsaModalDialog { "hide": (animate: boolean) => Promise; "show": (animate: boolean) => Promise; @@ -272,6 +280,12 @@ declare global { prototype: HTMLElsaInputTagsElement; new (): HTMLElsaInputTagsElement; }; + interface HTMLElsaInputTagsDropdownElement extends Components.ElsaInputTagsDropdown, HTMLStencilElement { + } + var HTMLElsaInputTagsDropdownElement: { + prototype: HTMLElsaInputTagsDropdownElement; + new (): HTMLElsaInputTagsDropdownElement; + }; interface HTMLElsaModalDialogElement extends Components.ElsaModalDialog, HTMLStencilElement { } var HTMLElsaModalDialogElement: { @@ -440,6 +454,7 @@ declare global { "elsa-dropdown-property": HTMLElsaDropdownPropertyElement; "elsa-expression-editor": HTMLElsaExpressionEditorElement; "elsa-input-tags": HTMLElsaInputTagsElement; + "elsa-input-tags-dropdown": HTMLElsaInputTagsDropdownElement; "elsa-modal-dialog": HTMLElsaModalDialogElement; "elsa-monaco": HTMLElsaMonacoElement; "elsa-multi-expression-editor": HTMLElsaMultiExpressionEditorElement; @@ -524,6 +539,14 @@ declare namespace LocalJSX { "placeHolder"?: string; "values"?: Array; } + interface ElsaInputTagsDropdown { + "dropdownValues"?: Array; + "fieldId"?: string; + "fieldName"?: string; + "onValueChanged"?: (event: CustomEvent>) => void; + "placeHolder"?: string; + "values"?: Array; + } interface ElsaModalDialog { } interface ElsaMonaco { @@ -671,6 +694,7 @@ declare namespace LocalJSX { "elsa-dropdown-property": ElsaDropdownProperty; "elsa-expression-editor": ElsaExpressionEditor; "elsa-input-tags": ElsaInputTags; + "elsa-input-tags-dropdown": ElsaInputTagsDropdown; "elsa-modal-dialog": ElsaModalDialog; "elsa-monaco": ElsaMonaco; "elsa-multi-expression-editor": ElsaMultiExpressionEditor; @@ -714,6 +738,7 @@ declare module "@stencil/core" { "elsa-dropdown-property": LocalJSX.ElsaDropdownProperty & JSXBase.HTMLAttributes; "elsa-expression-editor": LocalJSX.ElsaExpressionEditor & JSXBase.HTMLAttributes; "elsa-input-tags": LocalJSX.ElsaInputTags & JSXBase.HTMLAttributes; + "elsa-input-tags-dropdown": LocalJSX.ElsaInputTagsDropdown & JSXBase.HTMLAttributes; "elsa-modal-dialog": LocalJSX.ElsaModalDialog & JSXBase.HTMLAttributes; "elsa-monaco": LocalJSX.ElsaMonaco & JSXBase.HTMLAttributes; "elsa-multi-expression-editor": LocalJSX.ElsaMultiExpressionEditor & JSXBase.HTMLAttributes; diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-definition-editor/elsa-activity-editor-modal/elsa-activity-editor-modal.tsx b/src/designer/elsa-workflows-studio/src/components/screens/workflow-definition-editor/elsa-activity-editor-modal/elsa-activity-editor-modal.tsx index 112edcd71..0a998080f 100644 --- a/src/designer/elsa-workflows-studio/src/components/screens/workflow-definition-editor/elsa-activity-editor-modal/elsa-activity-editor-modal.tsx +++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-definition-editor/elsa-activity-editor-modal/elsa-activity-editor-modal.tsx @@ -19,13 +19,18 @@ export class ElsaActivityEditorModal { form: HTMLFormElement; formContext: FormContext; renderProps: any; + + // Force a new key every time we show the editor to make sure Stencil creates new components. + // This prevents the issue where the designer has e.g. one activity where the user edits the properties, cancels out, then opens the editor again, seeing the entered value still there. + timestamp: Date = new Date(); componentDidLoad() { eventBus.on(EventTypes.ShowActivityEditor, async (activity: ActivityModel, animate: boolean) => { this.activityModel = JSON.parse(JSON.stringify(activity)); this.activityDescriptor = state.activityDescriptors.find(x => x.type == activity.type); this.formContext = new FormContext(this.activityModel, newValue => this.activityModel = newValue); - this.selectedTab = 'Properties'; + this.selectedTab = 'Properties'; + this.timestamp = new Date(); await this.dialog.show(animate); }); } @@ -99,9 +104,9 @@ export class ElsaActivityEditorModal { return ( - this.dialog = el}> + this.dialog = el} >
-
this.onSubmit(e)} ref={el => this.form = el}> + this.onSubmit(e)} ref={el => this.form = el} key={this.timestamp.getTime().toString()}>
From bc3cf0898bfc723c8300cc81c325d622bdb5bd07 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 29 Apr 2021 21:20:31 +0200 Subject: [PATCH 10/24] Fix activity design view update after property changes and display green hover color when pressing shift --- .../elsa-designer-tree/elsa-designer-tree.tsx | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) 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 565c92474..92278bc3b 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 @@ -1,5 +1,5 @@ import {Component, Host, h, Prop, State, Event, EventEmitter, Watch} from '@stencil/core'; -import { v4 as uuid } from 'uuid'; +import {v4 as uuid} from 'uuid'; import {Map, addConnection, findActivity, getChildActivities, getInboundConnections, getOutboundConnections, removeActivity, removeConnection} from '../../../../utils/utils'; import { ActivityDescriptor, @@ -94,7 +94,9 @@ export class ElsaWorkflowDesigner { updateWorkflowModel(model: WorkflowModel) { this.workflowModel = model; this.workflowChanged.emit(model); - this.rerenderTree(); + setTimeout(() => { + this.rerenderTree(); + }, 50); } connectedCallback() { @@ -339,13 +341,12 @@ export class ElsaWorkflowDesigner { this.workflowModel.connections.forEach(({sourceId, targetId, outcome}) => { const sourceName = `${sourceId}/${outcome}`; - - if(!this.graph.hasNode(sourceName)) - { + + if (!this.graph.hasNode(sourceName)) { console.warn(`No source node with ID '${sourceName}' exists.`); return; } - + this.graph.setEdge(sourceName, targetId, {arrowhead: 'undirected'}); }); } @@ -373,7 +374,9 @@ export class ElsaWorkflowDesigner { if (this.editMode) { d3.selectAll('.node.add').each((n: any) => { const node = this.graph.node(n) as any; - d3.select(node.elem).on('click', e => { + + d3.select(node.elem) + .on('click', e => { e.preventDefault(); d3.selectAll('.node.add svg').classed('text-green-400', false).classed('text-gray-400', true).classed('hover:text-blue-500', true); this.parentActivityId = node.activity.activityId; @@ -385,6 +388,14 @@ export class ElsaWorkflowDesigner { } this.showActivityPicker(); + }) + .on("mouseover", e => { + if(this.editMode && e.shiftKey) + d3.select(node.elem).select('svg').classed('text-green-400', true).classed('hover:text-blue-500', false); + }) + .on("mouseout", e => { + if(this.editMode) + d3.select(node.elem).select('svg').classed('text-green-400', false).classed('hover:text-blue-500', true); }); }); @@ -515,27 +526,18 @@ export class ElsaWorkflowDesigner { ${contextButton}
- ${this.renderActivityBody(activity.description)} + ${this.renderActivityBody(displayContext)}
`; } - renderActivityBody(description: string | null) { - if (!description) return ''; + renderActivityBody(displayContext: ActivityDesignDisplayContext) { return ( `
- ${description} + ${displayContext.bodyDisplay}
` ); } - // private onActivitySelected(e: CustomEvent) { - // this.activitySelected.emit(e.detail); - // } - - // private onActivityDeselected(e: CustomEvent) { - // this.activityDeselected.emit(e.detail); - // } - render() { return ( (this.el = el)}> From 178a408668d1bd9c7d69fc035883ca6be6feb523 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 29 Apr 2021 21:20:48 +0200 Subject: [PATCH 11/24] Remove outdated comment --- .../elsa-workflow-definition-editor-screen.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-definition-editor/elsa-workflow-definition-editor-screen/elsa-workflow-definition-editor-screen.tsx b/src/designer/elsa-workflows-studio/src/components/screens/workflow-definition-editor/elsa-workflow-definition-editor-screen/elsa-workflow-definition-editor-screen.tsx index 0569cbd23..a622d1cf6 100644 --- a/src/designer/elsa-workflows-studio/src/components/screens/workflow-definition-editor/elsa-workflow-definition-editor-screen/elsa-workflow-definition-editor-screen.tsx +++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-definition-editor/elsa-workflow-definition-editor-screen/elsa-workflow-definition-editor-screen.tsx @@ -62,9 +62,6 @@ export class ElsaWorkflowDefinitionEditorScreen { this.imported = false; this.networkError = null; - // A small hack to make sure JS Plumb is cleaned up before HTML elements get removed. - // await this.designer.destroyJsPlumb(); - try { const workflowDefinition = await client.workflowDefinitionsApi.import(this.workflowDefinition.definitionId, file); this.workflowDefinition = workflowDefinition; From bc0a10c0efb2f382d5147d223f0848886c7a7ecc Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 29 Apr 2021 21:21:02 +0200 Subject: [PATCH 12/24] Fix activity body display --- src/designer/elsa-workflows-studio/src/models/events.ts | 2 +- .../elsa-workflows-studio/src/plugins/cron-plugin.tsx | 2 +- .../src/plugins/http-endpoint-plugin.tsx | 7 ++++--- .../elsa-workflows-studio/src/plugins/if-plugin.tsx | 2 +- .../src/plugins/send-email-plugin.tsx | 2 +- .../elsa-workflows-studio/src/plugins/start-at-plugin.tsx | 2 +- .../elsa-workflows-studio/src/plugins/timer-plugin.tsx | 2 +- .../elsa-workflows-studio/src/plugins/while-plugin.tsx | 2 +- .../src/plugins/write-line-plugin.tsx | 2 +- 9 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/designer/elsa-workflows-studio/src/models/events.ts b/src/designer/elsa-workflows-studio/src/models/events.ts index 434b874c4..472db377a 100644 --- a/src/designer/elsa-workflows-studio/src/models/events.ts +++ b/src/designer/elsa-workflows-studio/src/models/events.ts @@ -27,7 +27,7 @@ export interface ActivityPickedEventArgs { export interface ActivityDesignDisplayContext { activityModel: ActivityModel; activityIcon: any; - bodyDisplay: any; + bodyDisplay: string; outcomes: Array; } diff --git a/src/designer/elsa-workflows-studio/src/plugins/cron-plugin.tsx b/src/designer/elsa-workflows-studio/src/plugins/cron-plugin.tsx index 2bfe34f84..10bb8d3cb 100644 --- a/src/designer/elsa-workflows-studio/src/plugins/cron-plugin.tsx +++ b/src/designer/elsa-workflows-studio/src/plugins/cron-plugin.tsx @@ -17,6 +17,6 @@ export class CronPlugin implements ElsaPlugin { const props = activityModel.properties || []; const condition = props.find(x => x.name == 'CronExpression') || { name: 'CronExpression', expressions: {'Literal': ''}, syntax: 'Literal'}; const expression = condition.expressions[condition.syntax] || ''; - context.bodyDisplay =

{expression}

; + context.bodyDisplay = `

${expression}

`; } } diff --git a/src/designer/elsa-workflows-studio/src/plugins/http-endpoint-plugin.tsx b/src/designer/elsa-workflows-studio/src/plugins/http-endpoint-plugin.tsx index a3379e3e6..4ba7bd28f 100644 --- a/src/designer/elsa-workflows-studio/src/plugins/http-endpoint-plugin.tsx +++ b/src/designer/elsa-workflows-studio/src/plugins/http-endpoint-plugin.tsx @@ -1,6 +1,6 @@ import {ElsaPlugin} from "../services/elsa-plugin"; import {eventBus} from '../services/event-bus'; -import {ActivityDesignDisplayContext, EventTypes} from "../models"; +import {ActivityDesignDisplayContext, EventTypes, SyntaxNames} from "../models"; import {h} from "@stencil/core"; export class HttpEndpointPlugin implements ElsaPlugin { @@ -15,7 +15,8 @@ export class HttpEndpointPlugin implements ElsaPlugin { return; const props = activityModel.properties || []; - const path = props.find(x => x.name == 'Path') || { name: 'Path', expressions: { 'Literal': '', syntax: 'Literal' } }; - context.bodyDisplay =

{path.expressions[path.syntax]}

; + const path = props.find(x => x.name == 'Path') || { name: 'Path', expressions: { 'Literal': '', syntax: SyntaxNames.Literal } }; + const syntax = path.syntax || SyntaxNames.Literal; + context.bodyDisplay = `

${path.expressions[syntax]}

`; } } diff --git a/src/designer/elsa-workflows-studio/src/plugins/if-plugin.tsx b/src/designer/elsa-workflows-studio/src/plugins/if-plugin.tsx index 47481fcca..3e169ddcb 100644 --- a/src/designer/elsa-workflows-studio/src/plugins/if-plugin.tsx +++ b/src/designer/elsa-workflows-studio/src/plugins/if-plugin.tsx @@ -19,6 +19,6 @@ export class IfPlugin implements ElsaPlugin { const expression = condition.expressions[condition.syntax] || ''; const description = activityModel.description; const bodyText = description && description.length > 0 ? description : expression; - context.bodyDisplay =

{bodyText}

; + context.bodyDisplay = `

${bodyText}

`; } } diff --git a/src/designer/elsa-workflows-studio/src/plugins/send-email-plugin.tsx b/src/designer/elsa-workflows-studio/src/plugins/send-email-plugin.tsx index 00d26e8db..3263e978c 100644 --- a/src/designer/elsa-workflows-studio/src/plugins/send-email-plugin.tsx +++ b/src/designer/elsa-workflows-studio/src/plugins/send-email-plugin.tsx @@ -19,6 +19,6 @@ export class SendEmailPlugin implements ElsaPlugin { const expression = to.expressions[to.syntax || SyntaxNames.Json] || ''; const description = activityModel.description; const bodyText = description && description.length > 0 ? description : expression; - context.bodyDisplay =

To: {bodyText}

; + context.bodyDisplay = `

To: ${bodyText}

`; } } diff --git a/src/designer/elsa-workflows-studio/src/plugins/start-at-plugin.tsx b/src/designer/elsa-workflows-studio/src/plugins/start-at-plugin.tsx index cbb63e7da..457d1e6e5 100644 --- a/src/designer/elsa-workflows-studio/src/plugins/start-at-plugin.tsx +++ b/src/designer/elsa-workflows-studio/src/plugins/start-at-plugin.tsx @@ -17,6 +17,6 @@ export class StartAtPlugin implements ElsaPlugin { const props = activityModel.properties || []; const condition = props.find(x => x.name == 'Instant') || { name: 'Instant', expressions: {'Literal': ''}, syntax: 'Literal'}; const expression = condition.expressions[condition.syntax] || ''; - context.bodyDisplay =

{expression}

; + context.bodyDisplay = `

${expression}

`; } } diff --git a/src/designer/elsa-workflows-studio/src/plugins/timer-plugin.tsx b/src/designer/elsa-workflows-studio/src/plugins/timer-plugin.tsx index 2af15e4ec..7707af4c7 100644 --- a/src/designer/elsa-workflows-studio/src/plugins/timer-plugin.tsx +++ b/src/designer/elsa-workflows-studio/src/plugins/timer-plugin.tsx @@ -17,6 +17,6 @@ export class TimerPlugin implements ElsaPlugin { const props = activityModel.properties || []; const condition = props.find(x => x.name == 'Timeout') || { name: 'Timeout', expressions: {'Literal': ''}, syntax: 'Literal'}; const expression = condition.expressions[condition.syntax] || ''; - context.bodyDisplay =

{expression}

; + context.bodyDisplay = `

${expression}

`; } } diff --git a/src/designer/elsa-workflows-studio/src/plugins/while-plugin.tsx b/src/designer/elsa-workflows-studio/src/plugins/while-plugin.tsx index 43f24c876..39b0ed7cb 100644 --- a/src/designer/elsa-workflows-studio/src/plugins/while-plugin.tsx +++ b/src/designer/elsa-workflows-studio/src/plugins/while-plugin.tsx @@ -19,6 +19,6 @@ export class WhilePlugin implements ElsaPlugin { const expression = condition.expressions[condition.syntax] || ''; const description = activityModel.description; const bodyText = description && description.length > 0 ? description : expression; - context.bodyDisplay =

{bodyText}

; + context.bodyDisplay = `

${bodyText}

`; } } diff --git a/src/designer/elsa-workflows-studio/src/plugins/write-line-plugin.tsx b/src/designer/elsa-workflows-studio/src/plugins/write-line-plugin.tsx index 1971f2529..c02f77343 100644 --- a/src/designer/elsa-workflows-studio/src/plugins/write-line-plugin.tsx +++ b/src/designer/elsa-workflows-studio/src/plugins/write-line-plugin.tsx @@ -17,6 +17,6 @@ export class WriteLinePlugin implements ElsaPlugin { const props = activityModel.properties || []; const condition = props.find(x => x.name == 'Text') || { name: 'Text', expressions: {'Literal': ''}, syntax: SyntaxNames.Literal }; const expression = condition.expressions[condition.syntax || 'Literal'] || ''; - context.bodyDisplay =

{expression}

; + context.bodyDisplay = `

${expression}

`; } } From 1277003ca5929dbc96ff3630929b9b08a3589025 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 29 Apr 2021 21:39:45 +0200 Subject: [PATCH 13/24] Implement workflow blueprint viewer --- .../elsa-workflows-studio/src/components.d.ts | 35 ++++ .../elsa-studio-root/elsa-studio-root.tsx | 1 + .../elsa-studio-workflow-blueprint-view.tsx | 25 +++ .../elsa-workflow-blueprint-viewer-screen.tsx | 156 ++++++++++++++++++ .../readme.md | 51 ++++++ .../elsa-workflow-instance-journal.tsx | 1 - .../elsa-workflow-registry-list-screen.tsx | 2 +- 7 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 src/designer/elsa-workflows-studio/src/components/dashboard/pages/elsa-studio-workflow-blueprint-view/elsa-studio-workflow-blueprint-view.tsx create mode 100644 src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx create mode 100644 src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/readme.md rename src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/{elsa-workflow-instance-viewer-screen => elsa-workflow-instance-journal}/elsa-workflow-instance-journal.tsx (99%) diff --git a/src/designer/elsa-workflows-studio/src/components.d.ts b/src/designer/elsa-workflows-studio/src/components.d.ts index cf7184c26..ec5cf6d43 100644 --- a/src/designer/elsa-workflows-studio/src/components.d.ts +++ b/src/designer/elsa-workflows-studio/src/components.d.ts @@ -138,6 +138,10 @@ export namespace Components { "monacoLibPath": string; "serverUrl": string; } + interface ElsaStudioWorkflowBlueprintView { + "match": MatchResults; + "serverUrl": string; + } interface ElsaStudioWorkflowDefinitionsEdit { "match": MatchResults; "monacoLibPath": string; @@ -167,6 +171,11 @@ export namespace Components { "hide": () => Promise; "show": (options: ToastNotificationOptions) => Promise; } + interface ElsaWorkflowBlueprintViewerScreen { + "getServerUrl": () => Promise; + "serverUrl": string; + "workflowDefinitionId": string; + } interface ElsaWorkflowDefinitionEditorNotifications { } interface ElsaWorkflowDefinitionEditorScreen { @@ -346,6 +355,12 @@ declare global { prototype: HTMLElsaStudioRootElement; new (): HTMLElsaStudioRootElement; }; + interface HTMLElsaStudioWorkflowBlueprintViewElement extends Components.ElsaStudioWorkflowBlueprintView, HTMLStencilElement { + } + var HTMLElsaStudioWorkflowBlueprintViewElement: { + prototype: HTMLElsaStudioWorkflowBlueprintViewElement; + new (): HTMLElsaStudioWorkflowBlueprintViewElement; + }; interface HTMLElsaStudioWorkflowDefinitionsEditElement extends Components.ElsaStudioWorkflowDefinitionsEdit, HTMLStencilElement { } var HTMLElsaStudioWorkflowDefinitionsEditElement: { @@ -388,6 +403,12 @@ declare global { prototype: HTMLElsaToastNotificationElement; new (): HTMLElsaToastNotificationElement; }; + interface HTMLElsaWorkflowBlueprintViewerScreenElement extends Components.ElsaWorkflowBlueprintViewerScreen, HTMLStencilElement { + } + var HTMLElsaWorkflowBlueprintViewerScreenElement: { + prototype: HTMLElsaWorkflowBlueprintViewerScreenElement; + new (): HTMLElsaWorkflowBlueprintViewerScreenElement; + }; interface HTMLElsaWorkflowDefinitionEditorNotificationsElement extends Components.ElsaWorkflowDefinitionEditorNotifications, HTMLStencilElement { } var HTMLElsaWorkflowDefinitionEditorNotificationsElement: { @@ -465,6 +486,7 @@ declare global { "elsa-script-property": HTMLElsaScriptPropertyElement; "elsa-single-line-property": HTMLElsaSingleLinePropertyElement; "elsa-studio-root": HTMLElsaStudioRootElement; + "elsa-studio-workflow-blueprint-view": HTMLElsaStudioWorkflowBlueprintViewElement; "elsa-studio-workflow-definitions-edit": HTMLElsaStudioWorkflowDefinitionsEditElement; "elsa-studio-workflow-definitions-list": HTMLElsaStudioWorkflowDefinitionsListElement; "elsa-studio-workflow-instances-list": HTMLElsaStudioWorkflowInstancesListElement; @@ -472,6 +494,7 @@ declare global { "elsa-studio-workflow-registry": HTMLElsaStudioWorkflowRegistryElement; "elsa-switch-cases-property": HTMLElsaSwitchCasesPropertyElement; "elsa-toast-notification": HTMLElsaToastNotificationElement; + "elsa-workflow-blueprint-viewer-screen": HTMLElsaWorkflowBlueprintViewerScreenElement; "elsa-workflow-definition-editor-notifications": HTMLElsaWorkflowDefinitionEditorNotificationsElement; "elsa-workflow-definition-editor-screen": HTMLElsaWorkflowDefinitionEditorScreenElement; "elsa-workflow-definitions-list-screen": HTMLElsaWorkflowDefinitionsListScreenElement; @@ -612,6 +635,10 @@ declare namespace LocalJSX { "monacoLibPath"?: string; "serverUrl"?: string; } + interface ElsaStudioWorkflowBlueprintView { + "match"?: MatchResults; + "serverUrl"?: string; + } interface ElsaStudioWorkflowDefinitionsEdit { "match"?: MatchResults; "monacoLibPath"?: string; @@ -639,6 +666,10 @@ declare namespace LocalJSX { } interface ElsaToastNotification { } + interface ElsaWorkflowBlueprintViewerScreen { + "serverUrl"?: string; + "workflowDefinitionId"?: string; + } interface ElsaWorkflowDefinitionEditorNotifications { } interface ElsaWorkflowDefinitionEditorScreen { @@ -705,6 +736,7 @@ declare namespace LocalJSX { "elsa-script-property": ElsaScriptProperty; "elsa-single-line-property": ElsaSingleLineProperty; "elsa-studio-root": ElsaStudioRoot; + "elsa-studio-workflow-blueprint-view": ElsaStudioWorkflowBlueprintView; "elsa-studio-workflow-definitions-edit": ElsaStudioWorkflowDefinitionsEdit; "elsa-studio-workflow-definitions-list": ElsaStudioWorkflowDefinitionsList; "elsa-studio-workflow-instances-list": ElsaStudioWorkflowInstancesList; @@ -712,6 +744,7 @@ declare namespace LocalJSX { "elsa-studio-workflow-registry": ElsaStudioWorkflowRegistry; "elsa-switch-cases-property": ElsaSwitchCasesProperty; "elsa-toast-notification": ElsaToastNotification; + "elsa-workflow-blueprint-viewer-screen": ElsaWorkflowBlueprintViewerScreen; "elsa-workflow-definition-editor-notifications": ElsaWorkflowDefinitionEditorNotifications; "elsa-workflow-definition-editor-screen": ElsaWorkflowDefinitionEditorScreen; "elsa-workflow-definitions-list-screen": ElsaWorkflowDefinitionsListScreen; @@ -749,6 +782,7 @@ declare module "@stencil/core" { "elsa-script-property": LocalJSX.ElsaScriptProperty & JSXBase.HTMLAttributes; "elsa-single-line-property": LocalJSX.ElsaSingleLineProperty & JSXBase.HTMLAttributes; "elsa-studio-root": LocalJSX.ElsaStudioRoot & JSXBase.HTMLAttributes; + "elsa-studio-workflow-blueprint-view": LocalJSX.ElsaStudioWorkflowBlueprintView & JSXBase.HTMLAttributes; "elsa-studio-workflow-definitions-edit": LocalJSX.ElsaStudioWorkflowDefinitionsEdit & JSXBase.HTMLAttributes; "elsa-studio-workflow-definitions-list": LocalJSX.ElsaStudioWorkflowDefinitionsList & JSXBase.HTMLAttributes; "elsa-studio-workflow-instances-list": LocalJSX.ElsaStudioWorkflowInstancesList & JSXBase.HTMLAttributes; @@ -756,6 +790,7 @@ declare module "@stencil/core" { "elsa-studio-workflow-registry": LocalJSX.ElsaStudioWorkflowRegistry & JSXBase.HTMLAttributes; "elsa-switch-cases-property": LocalJSX.ElsaSwitchCasesProperty & JSXBase.HTMLAttributes; "elsa-toast-notification": LocalJSX.ElsaToastNotification & JSXBase.HTMLAttributes; + "elsa-workflow-blueprint-viewer-screen": LocalJSX.ElsaWorkflowBlueprintViewerScreen & JSXBase.HTMLAttributes; "elsa-workflow-definition-editor-notifications": LocalJSX.ElsaWorkflowDefinitionEditorNotifications & JSXBase.HTMLAttributes; "elsa-workflow-definition-editor-screen": LocalJSX.ElsaWorkflowDefinitionEditorScreen & JSXBase.HTMLAttributes; "elsa-workflow-definitions-list-screen": LocalJSX.ElsaWorkflowDefinitionsListScreen & JSXBase.HTMLAttributes; diff --git a/src/designer/elsa-workflows-studio/src/components/dashboard/pages/elsa-studio-root/elsa-studio-root.tsx b/src/designer/elsa-workflows-studio/src/components/dashboard/pages/elsa-studio-root/elsa-studio-root.tsx index aea910a81..32709cfe3 100644 --- a/src/designer/elsa-workflows-studio/src/components/dashboard/pages/elsa-studio-root/elsa-studio-root.tsx +++ b/src/designer/elsa-workflows-studio/src/components/dashboard/pages/elsa-studio-root/elsa-studio-root.tsx @@ -52,6 +52,7 @@ export class ElsaStudioRoot { } exact={true}/> + diff --git a/src/designer/elsa-workflows-studio/src/components/dashboard/pages/elsa-studio-workflow-blueprint-view/elsa-studio-workflow-blueprint-view.tsx b/src/designer/elsa-workflows-studio/src/components/dashboard/pages/elsa-studio-workflow-blueprint-view/elsa-studio-workflow-blueprint-view.tsx new file mode 100644 index 000000000..b26148455 --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/components/dashboard/pages/elsa-studio-workflow-blueprint-view/elsa-studio-workflow-blueprint-view.tsx @@ -0,0 +1,25 @@ +import {Component, Prop, h} from '@stencil/core'; +import {MatchResults} from '@stencil/router'; + +@Component({ + tag: 'elsa-studio-workflow-blueprint-view', + shadow: false, +}) +export class ElsaStudioWorkflowBlueprintView { + @Prop() match: MatchResults; + @Prop() serverUrl: string; + + id?: string; + + componentWillLoad() { + this.id = this.match.params.id; + } + + render() { + const id = this.id; + + return
+ +
; + } +} diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx b/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx new file mode 100644 index 000000000..5c60b8104 --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx @@ -0,0 +1,156 @@ +import {Component, h, Host, Method, Prop, State, Watch} from '@stencil/core'; +import {eventBus} from '../../../../services/event-bus'; +import * as collection from 'lodash/collection'; +import { + ActivityBlueprint, ActivityDefinitionProperty, + ActivityDescriptor, + ActivityModel, Connection, + ConnectionModel, + EventTypes, getVersionOptionsString, SyntaxNames, + WorkflowBlueprint, WorkflowExecutionLogRecord, + WorkflowInstance, + WorkflowModel, + WorkflowPersistenceBehavior, + WorkflowStatus +} from "../../../../models"; +import {createElsaClient} from "../../../../services/elsa-client"; +import {pluginManager} from '../../../../services/plugin-manager'; +import state from '../../../../utils/store'; + +@Component({ + tag: 'elsa-workflow-blueprint-viewer-screen', + shadow: false, +}) +export class ElsaWorkflowBlueprintViewerScreen { + + constructor() { + pluginManager.initialize(); + } + + @Prop() workflowDefinitionId: string; + @Prop({attribute: 'server-url', reflect: true}) serverUrl: string; + @State() workflowBlueprint: WorkflowBlueprint; + @State() workflowModel: WorkflowModel; + el: HTMLElement; + designer: HTMLElsaDesignerTreeElement; + + @Method() + async getServerUrl(): Promise { + return this.serverUrl; + } + + @Watch('workflowDefinitionId') + async workflowDefinitionIdChangedHandler(newValue: string) { + const workflowDefinitionId = newValue; + let workflowBlueprint: WorkflowBlueprint = { + id: null, + version: 1, + activities: [], + connections: [], + persistenceBehavior: WorkflowPersistenceBehavior.WorkflowBurst, + customAttributes: {data: {}}, + persistWorkflow: false, + isLatest: false, + isPublished: false, + loadWorkflowContext: false, + isSingleton: false, + persistOutput: false, + saveWorkflowContext: false, + variables: {data: {}}, + type: null, + properties: {data: {}} + }; + + const client = createElsaClient(this.serverUrl); + + if (workflowDefinitionId && workflowDefinitionId.length > 0) { + try { + workflowBlueprint = await client.workflowRegistryApi.get(workflowDefinitionId, {isLatest: true}); + } catch { + console.warn(`The specified workflow blueprint does not exist. Creating a new one.`); + } + } + + this.updateModels(workflowBlueprint); + } + + @Watch("serverUrl") + async serverUrlChangedHandler(newValue: string) { + if (newValue && newValue.length > 0) + await this.loadActivityDescriptors(); + } + + async componentWillLoad() { + await this.serverUrlChangedHandler(this.serverUrl); + await this.workflowDefinitionIdChangedHandler(this.workflowDefinitionId); + } + + componentDidLoad() { + if (!this.designer) { + this.designer = this.el.querySelector("elsa-designer-tree") as HTMLElsaDesignerTreeElement; + this.designer.model = this.workflowModel; + } + } + + async loadActivityDescriptors() { + const client = createElsaClient(this.serverUrl); + state.activityDescriptors = await client.activitiesApi.list(); + } + + updateModels(workflowBlueprint: WorkflowBlueprint) { + this.workflowBlueprint = workflowBlueprint; + this.workflowModel = this.mapWorkflowModel(workflowBlueprint); + } + + mapWorkflowModel(workflowBlueprint: WorkflowBlueprint): WorkflowModel { + return { + activities: workflowBlueprint.activities.map(this.mapActivityModel), + connections: workflowBlueprint.connections.map(this.mapConnectionModel), + persistenceBehavior: workflowBlueprint.persistenceBehavior, + }; + } + + mapActivityModel(source: ActivityBlueprint): ActivityModel { + const descriptors: Array = state.activityDescriptors; + const descriptor = descriptors.find(x => x.type == source.type); + const properties: Array = collection.map(source.properties.data, (v, k) => ({name: k, expressions: {'Literal': v}, syntax: SyntaxNames.Literal})); + + return { + activityId: source.id, + description: source.description, + displayName: source.displayName || source.name || source.type, + name: source.name, + type: source.type, + properties: properties, + outcomes: [...descriptor.outcomes], + persistOutput: source.persistOutput, + persistWorkflow: source.persistWorkflow, + saveWorkflowContext: source.saveWorkflowContext, + loadWorkflowContext: source.loadWorkflowContext + } + } + + mapConnectionModel(connection: Connection): ConnectionModel { + return { + sourceId: connection.sourceActivityId, + targetId: connection.targetActivityId, + outcome: connection.outcome + } + } + + render() { + return ( + this.el = el}> + {this.renderCanvas()} + + ); + } + + renderCanvas() { + return ( +
+ this.designer = el} editMode={false}/> +
+ ); + } +} diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/readme.md b/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/readme.md new file mode 100644 index 000000000..3d723dc3d --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/readme.md @@ -0,0 +1,51 @@ +# elsa-workflow-instance-viewer-screen + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| -------------------- | ---------------------- | ----------- | -------- | ----------- | +| `serverUrl` | `server-url` | | `string` | `undefined` | +| `workflowInstanceId` | `workflow-instance-id` | | `string` | `undefined` | + + +## Methods + +### `getServerUrl() => Promise` + + + +#### Returns + +Type: `Promise` + + + + +## Dependencies + +### Used by + + - [elsa-studio-workflow-instances-view](../../../dashboard/pages/elsa-studio-workflow-instances-view) + +### Depends on + +- [elsa-workflow-instance-journal](.) +- [elsa-designer-tree](../../../designers/tree/elsa-designer-tree) + +### Graph +```mermaid +graph TD; + elsa-workflow-instance-viewer-screen --> elsa-workflow-instance-journal + elsa-workflow-instance-viewer-screen --> elsa-designer-tree + elsa-studio-workflow-instances-view --> elsa-workflow-instance-viewer-screen + style elsa-workflow-instance-viewer-screen fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/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 similarity index 99% rename from src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-journal.tsx rename to src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-journal/elsa-workflow-instance-journal.tsx index 163fa4483..5a3d6db01 100644 --- a/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/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 @@ -2,7 +2,6 @@ import {Component, EventEmitter, h, Host, Method, Prop, State, Watch, Event} fro import * as collection from 'lodash/collection'; import moment from 'moment'; import {enter, leave, toggle} from 'el-transition' -import {registerClickOutside} from "stencil-click-outside"; import { ActivityBlueprint, ActivityDescriptor, PagedList, WorkflowBlueprint, WorkflowExecutionLogRecord, WorkflowModel, diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-registry-list/elsa-workflow-registry-list-screen/elsa-workflow-registry-list-screen.tsx b/src/designer/elsa-workflows-studio/src/components/screens/workflow-registry-list/elsa-workflow-registry-list-screen/elsa-workflow-registry-list-screen.tsx index b6af33ff0..6d4ef756a 100644 --- a/src/designer/elsa-workflows-studio/src/components/screens/workflow-registry-list/elsa-workflow-registry-list-screen/elsa-workflow-registry-list-screen.tsx +++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-registry-list/elsa-workflow-registry-list-screen/elsa-workflow-registry-list-screen.tsx @@ -67,7 +67,7 @@ export class ElsaWorkflowRegistryListScreen { if (!workflowDisplayName || workflowDisplayName.trim().length == 0) workflowDisplayName = 'Untitled'; - const editUrl = `/workflow-definitions/${workflowBlueprint.id}`; + const editUrl = `/workflow-registry/${workflowBlueprint.id}`; const instancesUrl = `/workflow-instances?workflow=${workflowBlueprint.id}`; const editIcon = ( From 2f6ce896c68fa29988c6262040c867f14b495c2e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 29 Apr 2021 22:43:41 +0200 Subject: [PATCH 14/24] Implement runtime list items provider --- .../Design/IRuntimeSelectListItemsProvider.cs | 11 +++++ .../RuntimeSelectListItemsProviderSettings.cs | 29 +++++++++++++ .../Converters/FullTypeJsonConverter.cs | 23 ++++++++++ .../ElsaServiceCollectionExtensions.cs | 2 + .../elsa-workflows-studio/src/components.d.ts | 2 + .../elsa-dropdown-property.tsx | 38 +++++++++++++--- .../elsa-workflow-blueprint-viewer-screen.tsx | 10 ++--- .../src/models/domain.ts | 10 +++++ .../src/services/elsa-client.ts | 19 +++++++- .../Activities/VehicleActivity.cs | 37 ++++++++++++++++ .../Elsa.Samples.Server.Host/Startup.cs | 5 +++ .../Designer/RuntimeSelectListItems/Get.cs | 43 +++++++++++++++++++ 12 files changed, 215 insertions(+), 14 deletions(-) create mode 100644 src/core/Elsa.Abstractions/Design/IRuntimeSelectListItemsProvider.cs create mode 100644 src/core/Elsa.Abstractions/Design/RuntimeSelectListItemsProviderSettings.cs create mode 100644 src/core/Elsa.Abstractions/Serialization/Converters/FullTypeJsonConverter.cs create mode 100644 src/samples/server/Elsa.Samples.Server.Host/Activities/VehicleActivity.cs create mode 100644 src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs diff --git a/src/core/Elsa.Abstractions/Design/IRuntimeSelectListItemsProvider.cs b/src/core/Elsa.Abstractions/Design/IRuntimeSelectListItemsProvider.cs new file mode 100644 index 000000000..20722e405 --- /dev/null +++ b/src/core/Elsa.Abstractions/Design/IRuntimeSelectListItemsProvider.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Elsa.Design +{ + public interface IRuntimeSelectListItemsProvider + { + ValueTask> GetItemsAsync(object? context = default, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Design/RuntimeSelectListItemsProviderSettings.cs b/src/core/Elsa.Abstractions/Design/RuntimeSelectListItemsProviderSettings.cs new file mode 100644 index 000000000..e35fb3d02 --- /dev/null +++ b/src/core/Elsa.Abstractions/Design/RuntimeSelectListItemsProviderSettings.cs @@ -0,0 +1,29 @@ +using System; +using Elsa.Serialization.Converters; +using Newtonsoft.Json; + +namespace Elsa.Design +{ + /// + /// Represents settings to be used by the designer to invoke an Elsa's API endpoint to retrieve list options at runtime. + /// + public class RuntimeSelectListItemsProviderSettings + { + public RuntimeSelectListItemsProviderSettings(Type providerType, object? context = default) + { + RuntimeSelectListItemsProviderType = providerType; + Context = context; + } + + /// + /// The type of the list items provider. + /// + [JsonConverter(typeof(FullTypeJsonConverter))] + public Type RuntimeSelectListItemsProviderType { get; } + + /// + /// Optionally provide an object containing useful information for the list select items provider to determine what items to provide. + /// + public object? Context { get; } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Serialization/Converters/FullTypeJsonConverter.cs b/src/core/Elsa.Abstractions/Serialization/Converters/FullTypeJsonConverter.cs new file mode 100644 index 000000000..8247a71eb --- /dev/null +++ b/src/core/Elsa.Abstractions/Serialization/Converters/FullTypeJsonConverter.cs @@ -0,0 +1,23 @@ +using System; +using Newtonsoft.Json; + +namespace Elsa.Serialization.Converters +{ + public class FullTypeJsonConverter : JsonConverter + { + public override bool CanRead => true; + public override bool CanWrite => true; + + public override void WriteJson(JsonWriter writer, Type? value, JsonSerializer serializer) + { + var typeName = value!.AssemblyQualifiedName; + serializer.Serialize(writer, typeName); + } + + public override Type ReadJson(JsonReader reader, Type objectType, Type? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var typeName = serializer.Deserialize(reader)!; + return typeName is not null and not "" ? Type.GetType(typeName)! : default!; + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs index d22362682..9294698b5 100644 --- a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs @@ -11,6 +11,7 @@ using Elsa.Builders; using Elsa.Consumers; using Elsa.Converters; using Elsa.Decorators; +using Elsa.Design; using Elsa.Dispatch; using Elsa.Dispatch.Consumers; using Elsa.Events; @@ -102,6 +103,7 @@ namespace Microsoft.Extensions.DependencyInjection } public static IServiceCollection AddActivityPropertyOptionsProvider(this IServiceCollection services) where T : class, IActivityPropertyOptionsProvider => services.AddSingleton(); + public static IServiceCollection AddRuntimeSelectItemsProvider(this IServiceCollection services) where T : class, IRuntimeSelectListItemsProvider => services.AddScoped(); public static IServiceCollection AddActivityTypeProvider(this IServiceCollection services) where T : class, IActivityTypeProvider => services.AddSingleton(); private static ElsaOptionsBuilder AddWorkflowsCore(this ElsaOptionsBuilder options) diff --git a/src/designer/elsa-workflows-studio/src/components.d.ts b/src/designer/elsa-workflows-studio/src/components.d.ts index ec5cf6d43..3ac60a9c7 100644 --- a/src/designer/elsa-workflows-studio/src/components.d.ts +++ b/src/designer/elsa-workflows-studio/src/components.d.ts @@ -48,6 +48,7 @@ export namespace Components { interface ElsaDropdownProperty { "propertyDescriptor": ActivityPropertyDescriptor; "propertyModel": ActivityDefinitionProperty; + "serverUrl": string; } interface ElsaExpressionEditor { "context"?: string; @@ -543,6 +544,7 @@ declare namespace LocalJSX { interface ElsaDropdownProperty { "propertyDescriptor"?: ActivityPropertyDescriptor; "propertyModel"?: ActivityDefinitionProperty; + "serverUrl"?: string; } interface ElsaExpressionEditor { "context"?: string; diff --git a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx index 7c1a25615..dc5ffcd12 100644 --- a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx +++ b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx @@ -1,5 +1,7 @@ import {Component, h, Prop, State} from '@stencil/core'; -import {ActivityDefinitionProperty, ActivityPropertyDescriptor, SyntaxNames} from "../../../../models"; +import {ActivityDefinitionProperty, ActivityPropertyDescriptor, RuntimeSelectListItemsProviderSettings, SelectListItem, SyntaxNames} from "../../../../models"; +import Tunnel from "../../../../data/workflow-editor"; +import {createElsaClient} from "../../../../services/elsa-client"; @Component({ tag: 'elsa-dropdown-property', @@ -10,7 +12,10 @@ export class ElsaDropdownProperty { @Prop() propertyDescriptor: ActivityPropertyDescriptor; @Prop() propertyModel: ActivityDefinitionProperty; + @Prop({mutable: true}) serverUrl: string; @State() currentValue?: string; + + items: any[]; async componentWillLoad() { const defaultSyntax = this.propertyDescriptor.defaultSyntax || SyntaxNames.Literal; @@ -26,6 +31,25 @@ export class ElsaDropdownProperty { onDefaultSyntaxValueChanged(e: CustomEvent) { this.currentValue = e.detail; } + + async componentWillRender(){ + const propertyDescriptor = this.propertyDescriptor; + const options = propertyDescriptor.options; + let items = []; + + if (!!options.runtimeSelectListItemsProviderType) { + items = await this.fetchRuntimeItems(options); + } else { + items = options as Array || []; + } + + this.items = items; + } + + async fetchRuntimeItems(options: RuntimeSelectListItemsProviderSettings): Promise>{ + const elsaClient = createElsaClient(this.serverUrl); + return await elsaClient.designerApi.runtimeSelectItemsApi.get(options.runtimeSelectListItemsProviderType, options.context || {}); + } render() { const propertyDescriptor = this.propertyDescriptor; @@ -33,8 +57,8 @@ export class ElsaDropdownProperty { const propertyName = propertyDescriptor.name; const fieldId = propertyName; const fieldName = propertyName; - const options = propertyDescriptor.options as Array || []; const currentValue = this.currentValue; + const items = this.items; return ( @@ -54,3 +78,5 @@ export class ElsaDropdownProperty { ); } } + +Tunnel.injectProps(ElsaDropdownProperty, ['serverUrl']); \ No newline at end of file diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx b/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx index 5c60b8104..524ab92bc 100644 --- a/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx +++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx @@ -1,17 +1,13 @@ import {Component, h, Host, Method, Prop, State, Watch} from '@stencil/core'; -import {eventBus} from '../../../../services/event-bus'; import * as collection from 'lodash/collection'; import { ActivityBlueprint, ActivityDefinitionProperty, ActivityDescriptor, ActivityModel, Connection, ConnectionModel, - EventTypes, getVersionOptionsString, SyntaxNames, - WorkflowBlueprint, WorkflowExecutionLogRecord, - WorkflowInstance, - WorkflowModel, - WorkflowPersistenceBehavior, - WorkflowStatus + SyntaxNames, + WorkflowBlueprint, WorkflowModel, + WorkflowPersistenceBehavior } from "../../../../models"; import {createElsaClient} from "../../../../services/elsa-client"; import {pluginManager} from '../../../../services/plugin-manager'; diff --git a/src/designer/elsa-workflows-studio/src/models/domain.ts b/src/designer/elsa-workflows-studio/src/models/domain.ts index a83741811..a0d36f9eb 100644 --- a/src/designer/elsa-workflows-studio/src/models/domain.ts +++ b/src/designer/elsa-workflows-studio/src/models/domain.ts @@ -291,6 +291,16 @@ export enum ActivityTraits { Job = 4 } +export interface SelectListItem { + text: string; + value: string; +} + +export interface RuntimeSelectListItemsProviderSettings { + runtimeSelectListItemsProviderType: string; + context?: any; +} + export class SyntaxNames { static readonly Literal = 'Literal'; static readonly JavaScript = 'JavaScript'; diff --git a/src/designer/elsa-workflows-studio/src/services/elsa-client.ts b/src/designer/elsa-workflows-studio/src/services/elsa-client.ts index 1c13ac2cc..5aef65bab 100644 --- a/src/designer/elsa-workflows-studio/src/services/elsa-client.ts +++ b/src/designer/elsa-workflows-studio/src/services/elsa-client.ts @@ -5,7 +5,7 @@ import { ActivityDescriptor, ConnectionDefinition, getVersionOptionsString, OrderBy, - PagedList, + PagedList, SelectListItem, Variables, VersionOptions, WorkflowBlueprint, WorkflowBlueprintSummary, WorkflowContextOptions, @@ -152,6 +152,14 @@ export const createElsaClient = function (serverUrl: string): ElsaClient { const response = await httpClient.get(`v1/scripting/javascript/type-definitions/${workflowDefinitionId}?t=${new Date().getTime()}&context=${context}`); return response.data; } + }, + designerApi:{ + runtimeSelectItemsApi:{ + get: async (providerTypeName: string, context?: any): Promise> => { + const response = await httpClient.post(`v1/designer/runtime-select-list-items/${providerTypeName}`, context); + return response.data; + } + } } } } @@ -163,6 +171,7 @@ export interface ElsaClient { workflowInstancesApi: WorkflowInstancesApi; workflowExecutionLogApi: WorkflowExecutionLogApi; scriptingApi: ScriptingApi; + designerApi: DesignerApi; } export interface ActivitiesApi { @@ -220,6 +229,14 @@ export interface ScriptingApi { getJavaScriptTypeDefinitions(workflowDefinitionId: string, context?: string): Promise } +export interface DesignerApi { + runtimeSelectItemsApi: RuntimeSelectItemsApi; +} + +export interface RuntimeSelectItemsApi { + get(providerTypeName: string, context?: any): Promise> +} + export interface SaveWorkflowDefinitionRequest { workflowDefinitionId?: string; name?: string; diff --git a/src/samples/server/Elsa.Samples.Server.Host/Activities/VehicleActivity.cs b/src/samples/server/Elsa.Samples.Server.Host/Activities/VehicleActivity.cs new file mode 100644 index 000000000..a49d845cb --- /dev/null +++ b/src/samples/server/Elsa.Samples.Server.Host/Activities/VehicleActivity.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Elsa.ActivityResults; +using Elsa.Attributes; +using Elsa.Design; +using Elsa.Expressions; +using Elsa.Metadata; +using Elsa.Services; + +namespace Elsa.Samples.Server.Host.Activities +{ + [Action] + public class VehicleActivity : Activity, IActivityPropertyOptionsProvider, IRuntimeSelectListItemsProvider + { + [ActivityProperty( + UIHint = ActivityPropertyUIHints.Dropdown, + OptionsProvider = typeof(VehicleActivity), + DefaultSyntax = SyntaxNames.Literal, + SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.Json, SyntaxNames.JavaScript, SyntaxNames.Liquid } + )] + public string? Brand { get; set; } + + public object GetOptions(PropertyInfo property) => new RuntimeSelectListItemsProviderSettings(GetType()); + + public ValueTask> GetItemsAsync(object? context, CancellationToken cancellationToken = default) + { + var brands = new[] { "BMW", "Peugot", "Tesla" }; + var items = brands.Select(x => new SelectListItem(x)).ToList(); + return new ValueTask>(items); + } + + protected override IActivityExecutionResult OnExecute() => Done(Brand); + } +} \ No newline at end of file diff --git a/src/samples/server/Elsa.Samples.Server.Host/Startup.cs b/src/samples/server/Elsa.Samples.Server.Host/Startup.cs index b2ae8d5fb..4d6e3043a 100644 --- a/src/samples/server/Elsa.Samples.Server.Host/Startup.cs +++ b/src/samples/server/Elsa.Samples.Server.Host/Startup.cs @@ -3,6 +3,7 @@ using Elsa.Activities.UserTask.Extensions; using Elsa.Persistence.EntityFramework.Core.Extensions; using Elsa.Persistence.EntityFramework.Sqlite; using Elsa.Persistence.YesSql; +using Elsa.Samples.Server.Host.Activities; using Elsa.Server.Hangfire.Extensions; using Hangfire; using Microsoft.AspNetCore.Builder; @@ -48,6 +49,8 @@ namespace Elsa.Samples.Server.Host }); services + .AddActivityPropertyOptionsProvider() + .AddRuntimeSelectItemsProvider() .AddElsa(elsa => elsa //.UseEntityFrameworkPersistence(ef => ef.UseSqlite()) .UseYesSqlPersistence() @@ -62,9 +65,11 @@ namespace Elsa.Samples.Server.Host .AddJavaScriptActivities() .AddUserTaskActivities() .AddTelnyx() + .AddActivitiesFrom() .AddWorkflowsFrom() ); + // Elsa API endpoints. services .AddElsaApiEndpoints() .AddElsaSwagger(); diff --git a/src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs b/src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs new file mode 100644 index 000000000..9839b5dcb --- /dev/null +++ b/src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Design; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Swashbuckle.AspNetCore.Annotations; + +namespace Elsa.Server.Api.Endpoints.Designer.RuntimeSelectListItems +{ + [ApiController] + [ApiVersion("1")] + [Route("v{apiVersion:apiVersion}/designer/runtime-select-list-items/{providerTypeName}")] + [Produces("application/json")] + public class Get : Controller + { + private readonly IServiceProvider _serviceProvider; + + public Get(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(OkObjectResult))] + [SwaggerOperation( + Summary = "Returns a list of items to be used by the list control requesting the items.", + Description = "Returns a list of items to be used by the list control requesting the items.", + OperationId = "Designer.RuntimeSelectListItems.Get", + Tags = new[] { "Designer.RuntimeSelectListItems" }) + ] + public async Task Handle(string providerTypeName, object? context = default, CancellationToken cancellationToken = default) + { + var type = Type.GetType(providerTypeName)!; + var provider = (IRuntimeSelectListItemsProvider)ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, type); + var items = (await provider.GetItemsAsync(context, cancellationToken)).ToList(); + + return Ok(items); + } + } +} \ No newline at end of file From 1eb8b0b45e9a1c809953409610ac570df2fd2596 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 30 Apr 2021 22:29:58 +0200 Subject: [PATCH 15/24] Display activity IDs for troubleshooting --- .../tree/elsa-designer-tree/elsa-designer-tree.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 92278bc3b..7d6e3d406 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 @@ -390,11 +390,11 @@ export class ElsaWorkflowDesigner { this.showActivityPicker(); }) .on("mouseover", e => { - if(this.editMode && e.shiftKey) + if (this.editMode && e.shiftKey) d3.select(node.elem).select('svg').classed('text-green-400', true).classed('hover:text-blue-500', false); }) .on("mouseout", e => { - if(this.editMode) + if (this.editMode) d3.select(node.elem).select('svg').classed('text-green-400', false).classed('hover:text-blue-500', true); }); }); @@ -533,7 +533,8 @@ export class ElsaWorkflowDesigner { renderActivityBody(displayContext: ActivityDesignDisplayContext) { return ( `
- ${displayContext.bodyDisplay} +
${displayContext.activityModel.activityId}
+
${displayContext.bodyDisplay}
` ); } From b797dd8c43bcacc5e297c3640dfeeb168eec30a8 Mon Sep 17 00:00:00 2001 From: Cameron Cowen Date: Thu, 29 Apr 2021 01:03:16 +0100 Subject: [PATCH 16/24] Added builder extensions for sendhttprequest and added initial sample --- Elsa.sln | 17 +++ .../SendHttpRequestBuilderExtensions.cs | 90 +++++++++++++++ .../SendHttpRequestExtensions.cs | 108 ++++++++++++++++++ .../Elsa.Samples.SendHttp.csproj | 12 ++ .../aspnet/Elsa.Samples.SendHttp/Program.cs | 26 +++++ .../Properties/launchSettings.json | 28 +++++ .../Elsa.Samples.SendHttp/SendHttpWorkflow.cs | 26 +++++ .../aspnet/Elsa.Samples.SendHttp/Startup.cs | 29 +++++ .../appsettings.Development.json | 9 ++ .../Elsa.Samples.SendHttp/appsettings.json | 10 ++ 10 files changed, 355 insertions(+) create mode 100644 src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs create mode 100644 src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs create mode 100644 src/samples/aspnet/Elsa.Samples.SendHttp/Elsa.Samples.SendHttp.csproj create mode 100644 src/samples/aspnet/Elsa.Samples.SendHttp/Program.cs create mode 100644 src/samples/aspnet/Elsa.Samples.SendHttp/Properties/launchSettings.json create mode 100644 src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs create mode 100644 src/samples/aspnet/Elsa.Samples.SendHttp/Startup.cs create mode 100644 src/samples/aspnet/Elsa.Samples.SendHttp/appsettings.Development.json create mode 100644 src/samples/aspnet/Elsa.Samples.SendHttp/appsettings.json diff --git a/Elsa.sln b/Elsa.sln index 3e7233459..e2a777377 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -271,6 +271,8 @@ EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Persistence.EntityFramework.MySql", "src\persistence\Elsa.Persistence.EntityFramework\Elsa.Persistence.EntityFramework.MySql\Elsa.Persistence.EntityFramework.MySql.csproj", "{D55BC08B-CA82-478A-99F7-EDE90AC33160}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.SendHttp", "src\samples\aspnet\Elsa.Samples.SendHttp\Elsa.Samples.SendHttp.csproj", "{775EFD59-F99C-4D38-B54E-9734C9D9649A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -645,6 +647,18 @@ Global {D55BC08B-CA82-478A-99F7-EDE90AC33160}.Debug|Any CPU.Build.0 = Debug|Any CPU {D55BC08B-CA82-478A-99F7-EDE90AC33160}.Release|Any CPU.ActiveCfg = Release|Any CPU {D55BC08B-CA82-478A-99F7-EDE90AC33160}.Release|Any CPU.Build.0 = Release|Any CPU + {BE284508-9582-4FCB-B0DD-0761AB940AD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE284508-9582-4FCB-B0DD-0761AB940AD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE284508-9582-4FCB-B0DD-0761AB940AD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE284508-9582-4FCB-B0DD-0761AB940AD7}.Release|Any CPU.Build.0 = Release|Any CPU + {3EB488DF-3C28-4DAD-B43E-6062E39629AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3EB488DF-3C28-4DAD-B43E-6062E39629AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3EB488DF-3C28-4DAD-B43E-6062E39629AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3EB488DF-3C28-4DAD-B43E-6062E39629AB}.Release|Any CPU.Build.0 = Release|Any CPU + {775EFD59-F99C-4D38-B54E-9734C9D9649A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {775EFD59-F99C-4D38-B54E-9734C9D9649A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {775EFD59-F99C-4D38-B54E-9734C9D9649A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {775EFD59-F99C-4D38-B54E-9734C9D9649A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -771,6 +785,9 @@ Global {3A30EFFE-32D8-40B8-9C62-427037BB5424} = {EEBEA8AF-C8A8-4C71-A96C-D5836DE24359} {3DF05CFC-AC77-489F-9A42-C4FDB599AA69} = {8B6B40A8-DF21-4CD2-BABD-474B79D0C3AF} {D55BC08B-CA82-478A-99F7-EDE90AC33160} = {C865B0FD-E505-48F0-BFAF-0D4D7C1B5CA1} + {BE284508-9582-4FCB-B0DD-0761AB940AD7} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180} + {3EB488DF-3C28-4DAD-B43E-6062E39629AB} = {FC9F520F-BA51-4AD2-BFEE-EF787798E734} + {775EFD59-F99C-4D38-B54E-9734C9D9649A} = {22E75696-6FE9-436A-9097-EE21C603F818} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158} diff --git a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs new file mode 100644 index 000000000..e9983cf62 --- /dev/null +++ b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs @@ -0,0 +1,90 @@ +using Elsa.Activities.Http.Models; +using Elsa.Builders; +using Elsa.Services.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace Elsa.Activities.Http +{ + public static class SendHttpRequestBuilderExtensions + { + public static IActivityBuilder SendHttpRequest(this IActivityBuilder builder, Action> setup, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => + builder.Then(setup, null, lineNumber, sourceFile); + + public static IActivityBuilder SendHttpRequest(this IActivityBuilder builder, + Func url, + Func method, + Func content, + Func contentType, + Func authorization, + Func requestHeaders, + Func readContent, + Func> supportedHttpCodes, + [CallerLineNumber] int lineNumber = default, + [CallerFilePath] string? sourceFile = default) => + builder.SendHttpRequest( + setup => setup + .Set(x => x.Url, url) + .Set(x => x.Method, method) + .Set(x => x.Content, content) + .Set(x => x.ContentType, contentType) + .Set(x => x.Authorization, authorization) + .Set(x => x.RequestHeaders, requestHeaders) + .Set(x => x.ReadContent, readContent) + .Set(x => x.SupportedStatusCodes, supportedHttpCodes), + lineNumber, + sourceFile); + + public static IActivityBuilder SendHttpRequest(this IActivityBuilder builder, + Func url, + Func method, + Func content, + Func contentType, + Func authorization, + Func requestHeaders, + Func readContent, + Func> supportedHttpCodes, + [CallerLineNumber] int lineNumber = default, + [CallerFilePath] string? sourceFile = default) => + builder.SendHttpRequest( + setup => setup + .Set(x => x.Url, url) + .Set(x => x.Method, method) + .Set(x => x.Content, content) + .Set(x => x.ContentType, contentType) + .Set(x => x.Authorization, authorization) + .Set(x => x.RequestHeaders, requestHeaders) + .Set(x => x.ReadContent, readContent) + .Set(x => x.SupportedStatusCodes, supportedHttpCodes), + lineNumber, + sourceFile); + + public static IActivityBuilder SendHttpRequest(this IActivityBuilder builder, + Uri? url, + string? method, + string? content, + string? contentType, + string? authorization, + HttpRequestHeaders requestHeaders, + bool readContent, + ICollection supportedHttpCodes, + [CallerLineNumber] int lineNumber = default, + [CallerFilePath] string? sourceFile = default) => + builder.SendHttpRequest( + setup => setup + .Set(x => x.Url, url) + .Set(x => x.Method, method) + .Set(x => x.Content, content) + .Set(x => x.ContentType, contentType) + .Set(x => x.Authorization, authorization) + .Set(x => x.RequestHeaders, requestHeaders) + .Set(x => x.ReadContent, readContent) + .Set(x => x.SupportedStatusCodes, supportedHttpCodes), + lineNumber, + sourceFile); + } +} diff --git a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs new file mode 100644 index 000000000..3d99f5467 --- /dev/null +++ b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs @@ -0,0 +1,108 @@ +using Elsa.Activities.Http.Models; +using Elsa.Builders; +using Elsa.Services.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace Elsa.Activities.Http +{ + public static class SendHttpRequestExtensions + { + public static ISetupActivity WithUrl(this ISetupActivity activity, Func> url) => activity.Set(x => x.Url, url); + + public static ISetupActivity WithUrl(this ISetupActivity activity, Func> url) => activity.Set(x => x.Url, url); + + public static ISetupActivity WithUrl(this ISetupActivity activity, Func url) => activity.Set(x => x.Url, url); + + public static ISetupActivity WithUrl(this ISetupActivity activity, Func url) => activity.Set(x => x.Url, url); + + public static ISetupActivity WithUrl(this ISetupActivity activity, Uri? url) => activity.Set(x => x.Url, url); + + + public static ISetupActivity WithMethod(this ISetupActivity activity, Func> method) => activity.Set(x => x.Method, method); + + public static ISetupActivity WithMethod(this ISetupActivity activity, Func> method) => activity.Set(x => x.Method, method); + + public static ISetupActivity WithMethod(this ISetupActivity activity, Func method) => activity.Set(x => x.Method, method); + + public static ISetupActivity WithMethod(this ISetupActivity activity, Func method) => activity.Set(x => x.Method, method); + + public static ISetupActivity WithMethod(this ISetupActivity activity, string? method) => activity.Set(x => x.Method, method); + + + public static ISetupActivity WithContent(this ISetupActivity activity, Func> content + ) => activity.Set(x => x.Content, content); + + public static ISetupActivity WithContent(this ISetupActivity activity, Func> content) => activity.Set(x => x.Content, content); + + public static ISetupActivity WithContent(this ISetupActivity activity, Func content) => activity.Set(x => x.Content, content); + + public static ISetupActivity WithContent(this ISetupActivity activity, Func content) => activity.Set(x => x.Content, content); + + public static ISetupActivity WithContent(this ISetupActivity activity, string? content) => activity.Set(x => x.Content, content); + + + public static ISetupActivity WithContentType(this ISetupActivity activity, Func> contentType + ) => activity.Set(x => x.ContentType, contentType); + + public static ISetupActivity WithContentType(this ISetupActivity activity, Func> contentType) => activity.Set(x => x.ContentType, contentType); + + public static ISetupActivity WithContentType(this ISetupActivity activity, Func contentType) => activity.Set(x => x.ContentType, contentType); + + public static ISetupActivity WithContentType(this ISetupActivity activity, Func contentType) => activity.Set(x => x.ContentType, contentType); + + public static ISetupActivity WithContentType(this ISetupActivity activity, string? contentType) => activity.Set(x => x.ContentType, contentType); + + + public static ISetupActivity WithAuthorization(this ISetupActivity activity, Func> authorization + ) => activity.Set(x => x.Authorization, authorization); + + public static ISetupActivity WithAuthorization(this ISetupActivity activity, Func> authorization) => activity.Set(x => x.Authorization, authorization); + + public static ISetupActivity WithAuthorization(this ISetupActivity activity, Func authorization) => activity.Set(x => x.Authorization, authorization); + + public static ISetupActivity WithAuthorization(this ISetupActivity activity, Func authorization) => activity.Set(x => x.Authorization, authorization); + + public static ISetupActivity WithAuthorization(this ISetupActivity activity, string? authorization) => activity.Set(x => x.Authorization, authorization); + + + public static ISetupActivity WithRequestHeaders(this ISetupActivity activity, Func> requestHeaders + ) => activity.Set(x => x.RequestHeaders, requestHeaders); + + public static ISetupActivity WithRequestHeaders(this ISetupActivity activity, Func> requestHeaders) => activity.Set(x => x.RequestHeaders, requestHeaders); + + public static ISetupActivity WithRequestHeaders(this ISetupActivity activity, Func requestHeaders) => activity.Set(x => x.RequestHeaders, requestHeaders); + + public static ISetupActivity WithRequestHeaders(this ISetupActivity activity, Func requestHeaders) => activity.Set(x => x.RequestHeaders, requestHeaders); + + public static ISetupActivity WithRequestHeaders(this ISetupActivity activity, HttpRequestHeaders? requestHeaders) => activity.Set(x => x.RequestHeaders, requestHeaders); + + + public static ISetupActivity WithReadContent(this ISetupActivity activity, Func> readContent + ) => activity.Set(x => x.ReadContent, readContent); + + public static ISetupActivity WithReadContent(this ISetupActivity activity, Func> readContent) => activity.Set(x => x.ReadContent, readContent); + + public static ISetupActivity WithReadContent(this ISetupActivity activity, Func readContent) => activity.Set(x => x.ReadContent, readContent); + + public static ISetupActivity WithReadContent(this ISetupActivity activity, Func readContent) => activity.Set(x => x.ReadContent, readContent); + + public static ISetupActivity WithReadContent(this ISetupActivity activity, bool? readContent) => activity.Set(x => x.ReadContent, readContent); + + + public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func>> supportedHttpCodes + ) => activity.Set(x => x.SupportedStatusCodes, supportedHttpCodes); + + public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func>> supportedHttpCodes) => activity.Set(x => x.SupportedStatusCodes, supportedHttpCodes); + + public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func> supportedHttpCodes) => activity.Set(x => x.SupportedStatusCodes, supportedHttpCodes); + + public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func> supportedHttpCodes) => activity.Set(x => x.SupportedStatusCodes, supportedHttpCodes); + + public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, ICollection supportedHttpCodes) => activity.Set(x => x.SupportedStatusCodes, supportedHttpCodes); + } +} diff --git a/src/samples/aspnet/Elsa.Samples.SendHttp/Elsa.Samples.SendHttp.csproj b/src/samples/aspnet/Elsa.Samples.SendHttp/Elsa.Samples.SendHttp.csproj new file mode 100644 index 000000000..f8ceca7a9 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.SendHttp/Elsa.Samples.SendHttp.csproj @@ -0,0 +1,12 @@ + + + + net5.0 + + + + + + + + diff --git a/src/samples/aspnet/Elsa.Samples.SendHttp/Program.cs b/src/samples/aspnet/Elsa.Samples.SendHttp/Program.cs new file mode 100644 index 000000000..33f467d85 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.SendHttp/Program.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Elsa.Samples.SendHttp +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/src/samples/aspnet/Elsa.Samples.SendHttp/Properties/launchSettings.json b/src/samples/aspnet/Elsa.Samples.SendHttp/Properties/launchSettings.json new file mode 100644 index 000000000..142f1a8f1 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.SendHttp/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:8929", + "sslPort": 44312 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Elsa.Samples.SendHttp": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs b/src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs new file mode 100644 index 000000000..89cdf278f --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs @@ -0,0 +1,26 @@ +using Elsa.Activities.Http; +using Elsa.Builders; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Elsa.Samples.SendHttp +{ + public class SendHttpWorkflow : IWorkflow + { + public void Build(IWorkflowBuilder builder) + { + builder.WithWorkflowDefinitionId("SendHttp") + .WithVersion(1) + .WithDescription("Send test http request and return response") + .HttpEndpoint(setup => setup.WithPath("/helloworld") + .WithMethod("GET")) + .SendHttpRequest(setup => setup.WithUrl(new Uri("https://jsonplaceholder.typicode.com/todos/3")) + .WithMethod("GET") + .WithReadContent(true)) + .WriteHttpResponse(setup => setup.WithStatusCode(System.Net.HttpStatusCode.OK) + .WithContent("Hello World")); + } + } +} diff --git a/src/samples/aspnet/Elsa.Samples.SendHttp/Startup.cs b/src/samples/aspnet/Elsa.Samples.SendHttp/Startup.cs new file mode 100644 index 000000000..cf3a4a6a7 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.SendHttp/Startup.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Elsa.Samples.SendHttp +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.AddElsa(options => options.AddHttpActivities() + .AddWorkflowsFrom()); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseHttpActivities(); + } + } +} diff --git a/src/samples/aspnet/Elsa.Samples.SendHttp/appsettings.Development.json b/src/samples/aspnet/Elsa.Samples.SendHttp/appsettings.Development.json new file mode 100644 index 000000000..8983e0fc1 --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.SendHttp/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/samples/aspnet/Elsa.Samples.SendHttp/appsettings.json b/src/samples/aspnet/Elsa.Samples.SendHttp/appsettings.json new file mode 100644 index 000000000..d9d9a9bff --- /dev/null +++ b/src/samples/aspnet/Elsa.Samples.SendHttp/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} From 4ac6cd8a7f526d3e7feac83283df82be0da58ce2 Mon Sep 17 00:00:00 2001 From: Cameron Cowen Date: Thu, 29 Apr 2021 20:58:19 +0100 Subject: [PATCH 17/24] Minor tweaks to example --- src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs b/src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs index 89cdf278f..bef11ffe8 100644 --- a/src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs +++ b/src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs @@ -1,4 +1,5 @@ using Elsa.Activities.Http; +using Elsa.Activities.Http.Models; using Elsa.Builders; using System; using System.Collections.Generic; @@ -19,8 +20,9 @@ namespace Elsa.Samples.SendHttp .SendHttpRequest(setup => setup.WithUrl(new Uri("https://jsonplaceholder.typicode.com/todos/3")) .WithMethod("GET") .WithReadContent(true)) + .WithName("TestHttpRequest") .WriteHttpResponse(setup => setup.WithStatusCode(System.Net.HttpStatusCode.OK) - .WithContent("Hello World")); + .WithContent(x => System.Text.Json.JsonSerializer.Serialize(x.GetOutputFrom("TestHttpRequest").Content))); } } } From 326896b903ccad46dd22cce4940f7794dec5e42a Mon Sep 17 00:00:00 2001 From: Cameron Cowen Date: Thu, 29 Apr 2021 21:08:19 +0100 Subject: [PATCH 18/24] Fixed inconsistent parameter names --- .../SendHttpRequestBuilderExtensions.cs | 12 ++++++------ .../SendHttpRequest/SendHttpRequestExtensions.cs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs index e9983cf62..3b0f026c7 100644 --- a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs +++ b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestBuilderExtensions.cs @@ -23,7 +23,7 @@ namespace Elsa.Activities.Http Func authorization, Func requestHeaders, Func readContent, - Func> supportedHttpCodes, + Func> supportedStatusCodes, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => builder.SendHttpRequest( @@ -35,7 +35,7 @@ namespace Elsa.Activities.Http .Set(x => x.Authorization, authorization) .Set(x => x.RequestHeaders, requestHeaders) .Set(x => x.ReadContent, readContent) - .Set(x => x.SupportedStatusCodes, supportedHttpCodes), + .Set(x => x.SupportedStatusCodes, supportedStatusCodes), lineNumber, sourceFile); @@ -47,7 +47,7 @@ namespace Elsa.Activities.Http Func authorization, Func requestHeaders, Func readContent, - Func> supportedHttpCodes, + Func> supportedStatusCodes, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => builder.SendHttpRequest( @@ -59,7 +59,7 @@ namespace Elsa.Activities.Http .Set(x => x.Authorization, authorization) .Set(x => x.RequestHeaders, requestHeaders) .Set(x => x.ReadContent, readContent) - .Set(x => x.SupportedStatusCodes, supportedHttpCodes), + .Set(x => x.SupportedStatusCodes, supportedStatusCodes), lineNumber, sourceFile); @@ -71,7 +71,7 @@ namespace Elsa.Activities.Http string? authorization, HttpRequestHeaders requestHeaders, bool readContent, - ICollection supportedHttpCodes, + ICollection supportedStatusCodes, [CallerLineNumber] int lineNumber = default, [CallerFilePath] string? sourceFile = default) => builder.SendHttpRequest( @@ -83,7 +83,7 @@ namespace Elsa.Activities.Http .Set(x => x.Authorization, authorization) .Set(x => x.RequestHeaders, requestHeaders) .Set(x => x.ReadContent, readContent) - .Set(x => x.SupportedStatusCodes, supportedHttpCodes), + .Set(x => x.SupportedStatusCodes, supportedStatusCodes), lineNumber, sourceFile); } diff --git a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs index 3d99f5467..a82713aa3 100644 --- a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs +++ b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequestExtensions.cs @@ -94,15 +94,15 @@ namespace Elsa.Activities.Http public static ISetupActivity WithReadContent(this ISetupActivity activity, bool? readContent) => activity.Set(x => x.ReadContent, readContent); - public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func>> supportedHttpCodes - ) => activity.Set(x => x.SupportedStatusCodes, supportedHttpCodes); + public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func?>> supportedStatusCodes + ) => activity.Set(x => x.SupportedStatusCodes, supportedStatusCodes); - public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func>> supportedHttpCodes) => activity.Set(x => x.SupportedStatusCodes, supportedHttpCodes); + public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func?>> supportedStatusCodes) => activity.Set(x => x.SupportedStatusCodes, supportedStatusCodes); - public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func> supportedHttpCodes) => activity.Set(x => x.SupportedStatusCodes, supportedHttpCodes); + public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func> supportedStatusCodes) => activity.Set(x => x.SupportedStatusCodes, supportedStatusCodes); - public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func> supportedHttpCodes) => activity.Set(x => x.SupportedStatusCodes, supportedHttpCodes); + public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, Func> supportedStatusCodes) => activity.Set(x => x.SupportedStatusCodes, supportedStatusCodes); - public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, ICollection supportedHttpCodes) => activity.Set(x => x.SupportedStatusCodes, supportedHttpCodes); + public static ISetupActivity WithSupportedHttpCodes(this ISetupActivity activity, ICollection supportedStatusCodes) => activity.Set(x => x.SupportedStatusCodes, supportedStatusCodes); } } From db2aa70e0dc39d795e41689b1375748c42f239fc Mon Sep 17 00:00:00 2001 From: Cameron Cowen Date: Thu, 29 Apr 2021 21:09:35 +0100 Subject: [PATCH 19/24] Minor tweak --- src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs b/src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs index bef11ffe8..f6b9daf0b 100644 --- a/src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs +++ b/src/samples/aspnet/Elsa.Samples.SendHttp/SendHttpWorkflow.cs @@ -4,6 +4,7 @@ using Elsa.Builders; using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; namespace Elsa.Samples.SendHttp @@ -22,7 +23,7 @@ namespace Elsa.Samples.SendHttp .WithReadContent(true)) .WithName("TestHttpRequest") .WriteHttpResponse(setup => setup.WithStatusCode(System.Net.HttpStatusCode.OK) - .WithContent(x => System.Text.Json.JsonSerializer.Serialize(x.GetOutputFrom("TestHttpRequest").Content))); + .WithContent(x => JsonSerializer.Serialize(x.GetOutputFrom("TestHttpRequest").Content))); } } } From 88e9f4590f7951c13ea92c9f0400a9e3327b779a Mon Sep 17 00:00:00 2001 From: Cameron Cowen Date: Thu, 29 Apr 2021 23:30:08 +0100 Subject: [PATCH 20/24] Removed hanging project guid --- Elsa.sln | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Elsa.sln b/Elsa.sln index e2a777377..8803ad02a 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -647,10 +647,6 @@ Global {D55BC08B-CA82-478A-99F7-EDE90AC33160}.Debug|Any CPU.Build.0 = Debug|Any CPU {D55BC08B-CA82-478A-99F7-EDE90AC33160}.Release|Any CPU.ActiveCfg = Release|Any CPU {D55BC08B-CA82-478A-99F7-EDE90AC33160}.Release|Any CPU.Build.0 = Release|Any CPU - {BE284508-9582-4FCB-B0DD-0761AB940AD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE284508-9582-4FCB-B0DD-0761AB940AD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE284508-9582-4FCB-B0DD-0761AB940AD7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE284508-9582-4FCB-B0DD-0761AB940AD7}.Release|Any CPU.Build.0 = Release|Any CPU {3EB488DF-3C28-4DAD-B43E-6062E39629AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3EB488DF-3C28-4DAD-B43E-6062E39629AB}.Debug|Any CPU.Build.0 = Debug|Any CPU {3EB488DF-3C28-4DAD-B43E-6062E39629AB}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -785,7 +781,6 @@ Global {3A30EFFE-32D8-40B8-9C62-427037BB5424} = {EEBEA8AF-C8A8-4C71-A96C-D5836DE24359} {3DF05CFC-AC77-489F-9A42-C4FDB599AA69} = {8B6B40A8-DF21-4CD2-BABD-474B79D0C3AF} {D55BC08B-CA82-478A-99F7-EDE90AC33160} = {C865B0FD-E505-48F0-BFAF-0D4D7C1B5CA1} - {BE284508-9582-4FCB-B0DD-0761AB940AD7} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180} {3EB488DF-3C28-4DAD-B43E-6062E39629AB} = {FC9F520F-BA51-4AD2-BFEE-EF787798E734} {775EFD59-F99C-4D38-B54E-9734C9D9649A} = {22E75696-6FE9-436A-9097-EE21C603F818} EndGlobalSection From ef4fa495711bd32f1b8ded865c65ed965bd3b481 Mon Sep 17 00:00:00 2001 From: Cameron Cowen Date: Thu, 29 Apr 2021 23:40:25 +0100 Subject: [PATCH 21/24] Removed hanging project guids --- Elsa.sln | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Elsa.sln b/Elsa.sln index 8803ad02a..a8f878957 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -647,10 +647,6 @@ Global {D55BC08B-CA82-478A-99F7-EDE90AC33160}.Debug|Any CPU.Build.0 = Debug|Any CPU {D55BC08B-CA82-478A-99F7-EDE90AC33160}.Release|Any CPU.ActiveCfg = Release|Any CPU {D55BC08B-CA82-478A-99F7-EDE90AC33160}.Release|Any CPU.Build.0 = Release|Any CPU - {3EB488DF-3C28-4DAD-B43E-6062E39629AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3EB488DF-3C28-4DAD-B43E-6062E39629AB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3EB488DF-3C28-4DAD-B43E-6062E39629AB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3EB488DF-3C28-4DAD-B43E-6062E39629AB}.Release|Any CPU.Build.0 = Release|Any CPU {775EFD59-F99C-4D38-B54E-9734C9D9649A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {775EFD59-F99C-4D38-B54E-9734C9D9649A}.Debug|Any CPU.Build.0 = Debug|Any CPU {775EFD59-F99C-4D38-B54E-9734C9D9649A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -781,7 +777,6 @@ Global {3A30EFFE-32D8-40B8-9C62-427037BB5424} = {EEBEA8AF-C8A8-4C71-A96C-D5836DE24359} {3DF05CFC-AC77-489F-9A42-C4FDB599AA69} = {8B6B40A8-DF21-4CD2-BABD-474B79D0C3AF} {D55BC08B-CA82-478A-99F7-EDE90AC33160} = {C865B0FD-E505-48F0-BFAF-0D4D7C1B5CA1} - {3EB488DF-3C28-4DAD-B43E-6062E39629AB} = {FC9F520F-BA51-4AD2-BFEE-EF787798E734} {775EFD59-F99C-4D38-B54E-9734C9D9649A} = {22E75696-6FE9-436A-9097-EE21C603F818} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution From d71d89cda081fe49b4c07628f6b49ed92dd782a9 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 30 Apr 2021 22:11:52 +0200 Subject: [PATCH 22/24] Incremental work on enhancements --- .../RuntimeSelectListItemsProviderSettings.cs | 1 + .../elsa-input-tags-dropdown.tsx | 26 +++++++++---------- .../elsa-check-list-property.tsx | 22 ++++++++++++---- .../elsa-dropdown-property.tsx | 18 ++----------- .../elsa-multi-text-property.tsx | 23 ++++++++++++---- .../src/models/domain.ts | 5 ---- .../src/utils/select-list-items.ts | 20 ++++++++++++++ .../Activities/VehicleActivity.cs | 17 +++++++++--- .../Designer/RuntimeSelectListItems/Get.cs | 24 +++++++++++++---- 9 files changed, 104 insertions(+), 52 deletions(-) create mode 100644 src/designer/elsa-workflows-studio/src/utils/select-list-items.ts diff --git a/src/core/Elsa.Abstractions/Design/RuntimeSelectListItemsProviderSettings.cs b/src/core/Elsa.Abstractions/Design/RuntimeSelectListItemsProviderSettings.cs index e35fb3d02..883b54639 100644 --- a/src/core/Elsa.Abstractions/Design/RuntimeSelectListItemsProviderSettings.cs +++ b/src/core/Elsa.Abstractions/Design/RuntimeSelectListItemsProviderSettings.cs @@ -24,6 +24,7 @@ namespace Elsa.Design /// /// Optionally provide an object containing useful information for the list select items provider to determine what items to provide. /// + [JsonProperty(TypeNameHandling = TypeNameHandling.All)] public object? Context { get; } } } \ No newline at end of file diff --git a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx index ff838aa53..5334de298 100644 --- a/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx +++ b/src/designer/elsa-workflows-studio/src/components/controls/elsa-input-tags/elsa-input-tags-dropdown.tsx @@ -1,5 +1,5 @@ import { Component, h, Prop, Event, EventEmitter, State, Watch } from '@stencil/core'; -import { MultiTextDefinition } from '../../../models/domain'; +import { SelectListItem } from '../../../models'; @Component({ tag: 'elsa-input-tags-dropdown', @@ -11,15 +11,15 @@ export class ElsaInputTagsDropdown { @Prop() fieldName?: string; @Prop() fieldId?: string; @Prop() placeHolder?: string = 'Add tag'; - @Prop() values?: Array = []; - @Prop() dropdownValues?: Array = []; - @Event({ bubbles: true }) valueChanged: EventEmitter>; - @State() currentValues?: Array = []; - @State() dropdownTags?: Array = []; + @Prop() values?: Array = []; + @Prop() dropdownValues?: Array = []; + @Event({ bubbles: true }) valueChanged: EventEmitter>; + @State() currentValues?: Array = []; + @State() dropdownTags?: Array = []; @Watch('values') - valuesChangedHandler(newValue: Array) { - let values: Array = []; + valuesChangedHandler(newValue: Array) { + let values: Array = []; newValue.forEach(value => { this.dropdownValues.forEach(tag => { @@ -33,7 +33,7 @@ export class ElsaInputTagsDropdown { componentWillLoad() { this.dropdownTags = this.dropdownValues; - let values: Array = []; + let values: Array = []; this.values.forEach(value => { this.dropdownValues.forEach(tag => { @@ -52,7 +52,7 @@ export class ElsaInputTagsDropdown { e.preventDefault(); const input = e.target as HTMLSelectElement; - const currentTag: MultiTextDefinition = { + const currentTag: SelectListItem = { text: input.options[input.selectedIndex].text.trim(), value: input.value } @@ -60,7 +60,7 @@ export class ElsaInputTagsDropdown { if (currentTag.value.length == 0) return; - const values: Array = [...this.currentValues]; + const values: Array = [...this.currentValues]; values.push(currentTag); this.currentValues = values.distinct(); input.value = "Add"; @@ -68,7 +68,7 @@ export class ElsaInputTagsDropdown { await this.valueChanged.emit(values); } - async onDeleteTagClick(e: any, currentTag: MultiTextDefinition) { + async onDeleteTagClick(e: any, currentTag: SelectListItem) { e.preventDefault(); this.currentValues = this.currentValues.filter(tag => tag.value !== currentTag.value); @@ -77,7 +77,7 @@ export class ElsaInputTagsDropdown { } render() { - let values: Array = this.currentValues || []; + let values: Array = this.currentValues || []; if (!Array.isArray(values)) values = []; diff --git a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-check-list-property/elsa-check-list-property.tsx b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-check-list-property/elsa-check-list-property.tsx index b726f882d..7eb0196ef 100644 --- a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-check-list-property/elsa-check-list-property.tsx +++ b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-check-list-property/elsa-check-list-property.tsx @@ -1,6 +1,9 @@ import {Component, h, Prop, State} from '@stencil/core'; import {ActivityDefinitionProperty, ActivityPropertyDescriptor, SyntaxNames} from "../../../../models"; import {parseJson} from "../../../../utils/utils"; +import {getSelectListItems} from "../../../../utils/select-list-items"; +import Tunnel from "../../../../data/workflow-editor"; +import {ElsaDropdownProperty} from "../elsa-dropdown-property/elsa-dropdown-property"; @Component({ tag: 'elsa-check-list-property', @@ -11,8 +14,11 @@ export class ElsaCheckListProperty { @Prop() propertyDescriptor: ActivityPropertyDescriptor; @Prop() propertyModel: ActivityDefinitionProperty; + @Prop({mutable: true}) serverUrl: string; @State() currentValue?: string; + monacoEditor: HTMLElsaMonacoElement; + items: any[]; async componentWillLoad() { this.currentValue = this.propertyModel.expressions[SyntaxNames.Json] || '[]'; @@ -37,11 +43,15 @@ export class ElsaCheckListProperty { this.currentValue = e.detail; } + async componentWillRender(){ + this.items = await getSelectListItems(this.serverUrl, this.propertyDescriptor); + } + render() { const propertyDescriptor = this.propertyDescriptor; const propertyModel = this.propertyModel; const fieldId = propertyDescriptor.name; - const options = propertyDescriptor.options as Array; + const items = this.items; const values = parseJson(this.currentValue) || []; return ( @@ -51,11 +61,11 @@ export class ElsaCheckListProperty { editor-height="2.75em" single-line={true}>
- {options.map((option, index) => { + {items.map((item, index) => { const inputId = `${fieldId}_${index}`; - const optionIsString = typeof(option) == 'string'; - const value = optionIsString ? option : option.value; - const text = optionIsString ? option : option.text; + const optionIsString = typeof(item) == 'string'; + const value = optionIsString ? item : item.value; + const text = optionIsString ? item : item.text; const isSelected = values.findIndex(x => x == value) >= 0; return ( @@ -74,3 +84,5 @@ export class ElsaCheckListProperty { ); } } + +Tunnel.injectProps(ElsaCheckListProperty, ['serverUrl']); \ No newline at end of file diff --git a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx index dc5ffcd12..483b4b9a1 100644 --- a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx +++ b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx @@ -2,6 +2,7 @@ import {Component, h, Prop, State} from '@stencil/core'; import {ActivityDefinitionProperty, ActivityPropertyDescriptor, RuntimeSelectListItemsProviderSettings, SelectListItem, SyntaxNames} from "../../../../models"; import Tunnel from "../../../../data/workflow-editor"; import {createElsaClient} from "../../../../services/elsa-client"; +import {getSelectListItems} from "../../../../utils/select-list-items"; @Component({ tag: 'elsa-dropdown-property', @@ -33,22 +34,7 @@ export class ElsaDropdownProperty { } async componentWillRender(){ - const propertyDescriptor = this.propertyDescriptor; - const options = propertyDescriptor.options; - let items = []; - - if (!!options.runtimeSelectListItemsProviderType) { - items = await this.fetchRuntimeItems(options); - } else { - items = options as Array || []; - } - - this.items = items; - } - - async fetchRuntimeItems(options: RuntimeSelectListItemsProviderSettings): Promise>{ - const elsaClient = createElsaClient(this.serverUrl); - return await elsaClient.designerApi.runtimeSelectItemsApi.get(options.runtimeSelectListItemsProviderType, options.context || {}); + this.items = await getSelectListItems(this.serverUrl, this.propertyDescriptor); } render() { diff --git a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx index 93797514a..564f11c54 100644 --- a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx +++ b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-multi-text-property/elsa-multi-text-property.tsx @@ -1,6 +1,8 @@ import {Component, h, Prop, State} from '@stencil/core'; -import {ActivityDefinitionProperty, ActivityPropertyDescriptor, SyntaxNames, MultiTextDefinition} from "../../../../models"; +import {ActivityDefinitionProperty, ActivityPropertyDescriptor, SyntaxNames, SelectListItem} from "../../../../models"; import {parseJson} from "../../../../utils/utils"; +import Tunnel from "../../../../data/workflow-editor"; +import {getSelectListItems} from "../../../../utils/select-list-items"; @Component({ tag: 'elsa-multi-text-property', @@ -11,15 +13,20 @@ export class ElsaMultiTextProperty { @Prop() propertyDescriptor: ActivityPropertyDescriptor; @Prop() propertyModel: ActivityDefinitionProperty; + @Prop({mutable: true}) serverUrl: string; @State() currentValue?: string; + items: any[]; + async componentWillLoad() { this.currentValue = this.propertyModel.expressions[SyntaxNames.Json] || '[]'; } - onValueChanged(newValue: Array) { + onValueChanged(newValue: Array) { const newValues = newValue.map(dropdown => { if (typeof dropdown === 'string') return dropdown; + if (typeof dropdown === 'number') return dropdown.toString(); + if (typeof dropdown === 'boolean') return dropdown.toString(); return dropdown.value; }) @@ -32,13 +39,17 @@ export class ElsaMultiTextProperty { this.currentValue = e.detail; } - createKeyValueOptions(options: Array) { + createKeyValueOptions(options: Array) { if (options === null) return options; return options.map(option => typeof option === 'string' ? { text: option, value: option } : option); } + async componentWillRender(){ + this.items = await getSelectListItems(this.serverUrl, this.propertyDescriptor); + } + render() { const propertyDescriptor = this.propertyDescriptor; const propertyModel = this.propertyModel; @@ -46,9 +57,9 @@ export class ElsaMultiTextProperty { const fieldId = propertyName; const fieldName = propertyName; const values = parseJson(this.currentValue); - propertyDescriptor.options = propertyDescriptor.options || null; + const items = this.items; const valueType = propertyDescriptor.options !== null ? 'dropdown' : 'multi-text'; - const propertyOptions = this.createKeyValueOptions(propertyDescriptor.options); + const propertyOptions = this.createKeyValueOptions(items); const elsaInputTags = valueType === 'multi-text' ? this.onValueChanged(e.detail)} /> : @@ -65,3 +76,5 @@ export class ElsaMultiTextProperty { ) } } + +Tunnel.injectProps(ElsaMultiTextProperty, ['serverUrl']); diff --git a/src/designer/elsa-workflows-studio/src/models/domain.ts b/src/designer/elsa-workflows-studio/src/models/domain.ts index a0d36f9eb..16a1ec370 100644 --- a/src/designer/elsa-workflows-studio/src/models/domain.ts +++ b/src/designer/elsa-workflows-studio/src/models/domain.ts @@ -168,11 +168,6 @@ export interface ConnectionDefinitionMapped { outcome: string; } -export interface MultiTextDefinition { - text: string; - value: string; -} - export interface ActivityDefinitionProperty { name: string; syntax?: string; diff --git a/src/designer/elsa-workflows-studio/src/utils/select-list-items.ts b/src/designer/elsa-workflows-studio/src/utils/select-list-items.ts new file mode 100644 index 000000000..3d87cd470 --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/utils/select-list-items.ts @@ -0,0 +1,20 @@ +import {ActivityPropertyDescriptor, RuntimeSelectListItemsProviderSettings, SelectListItem} from "../models"; +import {createElsaClient, ElsaClient} from "../services/elsa-client"; + +async function fetchRuntimeItems(serverUrl: string, options: RuntimeSelectListItemsProviderSettings): Promise> { + const elsaClient = createElsaClient(serverUrl); + return await elsaClient.designerApi.runtimeSelectItemsApi.get(options.runtimeSelectListItemsProviderType, options.context || {}); +} + +export async function getSelectListItems(serverUrl: string, propertyDescriptor: ActivityPropertyDescriptor): Promise> { + const options = propertyDescriptor.options; + let items = []; + + if (!!options.runtimeSelectListItemsProviderType) { + items = await fetchRuntimeItems(serverUrl, options); + } else { + items = options as Array || []; + } + + return items || []; +} \ No newline at end of file diff --git a/src/samples/server/Elsa.Samples.Server.Host/Activities/VehicleActivity.cs b/src/samples/server/Elsa.Samples.Server.Host/Activities/VehicleActivity.cs index a49d845cb..f401a0e42 100644 --- a/src/samples/server/Elsa.Samples.Server.Host/Activities/VehicleActivity.cs +++ b/src/samples/server/Elsa.Samples.Server.Host/Activities/VehicleActivity.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; @@ -15,6 +16,13 @@ namespace Elsa.Samples.Server.Host.Activities [Action] public class VehicleActivity : Activity, IActivityPropertyOptionsProvider, IRuntimeSelectListItemsProvider { + private Random _random; + + public VehicleActivity() + { + _random = new Random(); + } + [ActivityProperty( UIHint = ActivityPropertyUIHints.Dropdown, OptionsProvider = typeof(VehicleActivity), @@ -23,15 +31,18 @@ namespace Elsa.Samples.Server.Host.Activities )] public string? Brand { get; set; } - public object GetOptions(PropertyInfo property) => new RuntimeSelectListItemsProviderSettings(GetType()); + public object GetOptions(PropertyInfo property) => new RuntimeSelectListItemsProviderSettings(GetType(), new VehicleContext(_random.Next(100))); public ValueTask> GetItemsAsync(object? context, CancellationToken cancellationToken = default) { - var brands = new[] { "BMW", "Peugot", "Tesla" }; + var vehicleContext = (VehicleContext) context!; + var brands = new[] { "BMW", "Peugot", "Tesla", vehicleContext.RandomNumber.ToString() }; var items = brands.Select(x => new SelectListItem(x)).ToList(); return new ValueTask>(items); } protected override IActivityExecutionResult OnExecute() => Done(Brand); } + + public record VehicleContext(int RandomNumber); } \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs b/src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs index 9839b5dcb..a5de8651d 100644 --- a/src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs +++ b/src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs @@ -3,9 +3,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Elsa.Design; +using Elsa.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; using Swashbuckle.AspNetCore.Annotations; namespace Elsa.Server.Api.Endpoints.Designer.RuntimeSelectListItems @@ -17,10 +19,12 @@ namespace Elsa.Server.Api.Endpoints.Designer.RuntimeSelectListItems public class Get : Controller { private readonly IServiceProvider _serviceProvider; + private readonly IContentSerializer _contentSerializer; - public Get(IServiceProvider serviceProvider) + public Get(IServiceProvider serviceProvider, IContentSerializer contentSerializer) { _serviceProvider = serviceProvider; + _contentSerializer = contentSerializer; } [HttpPost] @@ -31,13 +35,23 @@ namespace Elsa.Server.Api.Endpoints.Designer.RuntimeSelectListItems OperationId = "Designer.RuntimeSelectListItems.Get", Tags = new[] { "Designer.RuntimeSelectListItems" }) ] - public async Task Handle(string providerTypeName, object? context = default, CancellationToken cancellationToken = default) + public async Task Handle([FromBody]RuntimeSelectListItemsContextHolder model, CancellationToken cancellationToken = default) { - var type = Type.GetType(providerTypeName)!; - var provider = (IRuntimeSelectListItemsProvider)ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, type); + var type = Type.GetType(model.ProviderTypeName)!; + var provider = (IRuntimeSelectListItemsProvider) ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, type); + var context = model?.Context; var items = (await provider.GetItemsAsync(context, cancellationToken)).ToList(); + var serializerSettings = _contentSerializer.GetSettings(); - return Ok(items); + return Json(items, serializerSettings); } } + + public record RuntimeSelectListItemsContextHolder + { + [FromRoute] public string ProviderTypeName { get; set; } = default!; + + [JsonProperty(TypeNameHandling = TypeNameHandling.All)] + public object? Context { get; set; } + }; } \ No newline at end of file From 645842f08f14f926dccd7c0ec343ab907cbd6315 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sun, 2 May 2021 19:17:19 +0200 Subject: [PATCH 23/24] Fix context (de)serialization --- .../elsa-workflows-studio/src/components.d.ts | 17 ++++++++++------- .../elsa-dropdown-property.tsx | 5 +---- .../src/services/elsa-client.ts | 2 +- .../Designer/RuntimeSelectListItems/Get.cs | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/designer/elsa-workflows-studio/src/components.d.ts b/src/designer/elsa-workflows-studio/src/components.d.ts index 3ac60a9c7..0308d05b0 100644 --- a/src/designer/elsa-workflows-studio/src/components.d.ts +++ b/src/designer/elsa-workflows-studio/src/components.d.ts @@ -5,11 +5,10 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; -import { ActivityDefinitionProperty, ActivityDescriptor, ActivityModel, ActivityPropertyDescriptor, VersionOptions, WorkflowBlueprint, WorkflowDefinition, WorkflowExecutionLogRecord, WorkflowModel } from "./models"; +import { ActivityDefinitionProperty, ActivityDescriptor, ActivityModel, ActivityPropertyDescriptor, SelectListItem, VersionOptions, WorkflowBlueprint, WorkflowDefinition, WorkflowExecutionLogRecord, WorkflowModel } from "./models"; import { LocationSegments, MatchResults, RouterHistory } from "@stencil/router"; import { MenuItem } from "./components/controls/elsa-context-menu/models"; import { DropdownButtonItem, DropdownButtonOrigin } from "./components/controls/elsa-dropdown-button/models"; -import { MultiTextDefinition } from "./models/domain"; import { MonacoValueChangedArgs } from "./components/controls/elsa-monaco/elsa-monaco"; import { Map } from "./utils/utils"; import { ToastNotificationOptions } from "./components/shared/elsa-toast-notification/elsa-toast-notification"; @@ -21,6 +20,7 @@ export namespace Components { interface ElsaCheckListProperty { "propertyDescriptor": ActivityPropertyDescriptor; "propertyModel": ActivityDefinitionProperty; + "serverUrl": string; } interface ElsaCheckboxProperty { "propertyDescriptor": ActivityPropertyDescriptor; @@ -68,11 +68,11 @@ export namespace Components { "values"?: Array; } interface ElsaInputTagsDropdown { - "dropdownValues"?: Array; + "dropdownValues"?: Array; "fieldId"?: string; "fieldName"?: string; "placeHolder"?: string; - "values"?: Array; + "values"?: Array; } interface ElsaModalDialog { "hide": (animate: boolean) => Promise; @@ -105,6 +105,7 @@ export namespace Components { interface ElsaMultiTextProperty { "propertyDescriptor": ActivityPropertyDescriptor; "propertyModel": ActivityDefinitionProperty; + "serverUrl": string; } interface ElsaPager { "history"?: RouterHistory; @@ -515,6 +516,7 @@ declare namespace LocalJSX { interface ElsaCheckListProperty { "propertyDescriptor"?: ActivityPropertyDescriptor; "propertyModel"?: ActivityDefinitionProperty; + "serverUrl"?: string; } interface ElsaCheckboxProperty { "propertyDescriptor"?: ActivityPropertyDescriptor; @@ -565,12 +567,12 @@ declare namespace LocalJSX { "values"?: Array; } interface ElsaInputTagsDropdown { - "dropdownValues"?: Array; + "dropdownValues"?: Array; "fieldId"?: string; "fieldName"?: string; - "onValueChanged"?: (event: CustomEvent>) => void; + "onValueChanged"?: (event: CustomEvent>) => void; "placeHolder"?: string; - "values"?: Array; + "values"?: Array; } interface ElsaModalDialog { } @@ -602,6 +604,7 @@ declare namespace LocalJSX { interface ElsaMultiTextProperty { "propertyDescriptor"?: ActivityPropertyDescriptor; "propertyModel"?: ActivityDefinitionProperty; + "serverUrl"?: string; } interface ElsaPager { "history"?: RouterHistory; diff --git a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx index 483b4b9a1..75b472e06 100644 --- a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx +++ b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-dropdown-property/elsa-dropdown-property.tsx @@ -21,6 +21,7 @@ export class ElsaDropdownProperty { async componentWillLoad() { const defaultSyntax = this.propertyDescriptor.defaultSyntax || SyntaxNames.Literal; this.currentValue = this.propertyModel.expressions[defaultSyntax] || ''; + this.items = await getSelectListItems(this.serverUrl, this.propertyDescriptor); } onChange(e: Event) { @@ -33,10 +34,6 @@ export class ElsaDropdownProperty { this.currentValue = e.detail; } - async componentWillRender(){ - this.items = await getSelectListItems(this.serverUrl, this.propertyDescriptor); - } - render() { const propertyDescriptor = this.propertyDescriptor; const propertyModel = this.propertyModel; diff --git a/src/designer/elsa-workflows-studio/src/services/elsa-client.ts b/src/designer/elsa-workflows-studio/src/services/elsa-client.ts index 5aef65bab..44141ae9d 100644 --- a/src/designer/elsa-workflows-studio/src/services/elsa-client.ts +++ b/src/designer/elsa-workflows-studio/src/services/elsa-client.ts @@ -156,7 +156,7 @@ export const createElsaClient = function (serverUrl: string): ElsaClient { designerApi:{ runtimeSelectItemsApi:{ get: async (providerTypeName: string, context?: any): Promise> => { - const response = await httpClient.post(`v1/designer/runtime-select-list-items/${providerTypeName}`, context); + const response = await httpClient.post('v1/designer/runtime-select-list-items', { providerTypeName: providerTypeName, context: context }); return response.data; } } diff --git a/src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs b/src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs index a5de8651d..452d3891b 100644 --- a/src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs +++ b/src/server/Elsa.Server.Api/Endpoints/Designer/RuntimeSelectListItems/Get.cs @@ -14,7 +14,7 @@ namespace Elsa.Server.Api.Endpoints.Designer.RuntimeSelectListItems { [ApiController] [ApiVersion("1")] - [Route("v{apiVersion:apiVersion}/designer/runtime-select-list-items/{providerTypeName}")] + [Route("v{apiVersion:apiVersion}/designer/runtime-select-list-items")] [Produces("application/json")] public class Get : Controller { @@ -47,11 +47,11 @@ namespace Elsa.Server.Api.Endpoints.Designer.RuntimeSelectListItems } } + [JsonObject(ItemTypeNameHandling = TypeNameHandling.All)] public record RuntimeSelectListItemsContextHolder { - [FromRoute] public string ProviderTypeName { get; set; } = default!; - - [JsonProperty(TypeNameHandling = TypeNameHandling.All)] + public string ProviderTypeName { get; set; } = default!; + public object? Context { get; set; } }; } \ No newline at end of file From 7ec6ada35f3b00627a9f3314d911027b9bb933eb Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sun, 2 May 2021 20:05:31 +0200 Subject: [PATCH 24/24] Fix that composite child activities are displayed on workflow viewer Fixes #922 --- .../elsa-workflow-blueprint-viewer-screen.tsx | 3 ++- .../elsa-workflow-instance-journal.tsx | 14 +++++++++----- .../elsa-workflow-instance-viewer-screen.tsx | 6 ++++-- src/designer/elsa-workflows-studio/src/index.html | 2 ++ .../elsa-workflows-studio/src/models/domain.ts | 1 + .../Documents/WorkflowInstanceDocument.cs | 2 +- .../Activities/SampleCompositeActivity.cs | 15 +++++++++++++++ .../Endpoints/WorkflowRegistry/Models.cs | 1 + .../Mapping/ActivityBlueprintConverter.cs | 1 + 9 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 src/samples/server/Elsa.Samples.Server.Host/Activities/SampleCompositeActivity.cs diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx b/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx index 524ab92bc..45f99359c 100644 --- a/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx +++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-blueprint-viewer/elsa-workflow-blueprint-viewer-screen/elsa-workflow-blueprint-viewer-screen.tsx @@ -96,11 +96,12 @@ export class ElsaWorkflowBlueprintViewerScreen { updateModels(workflowBlueprint: WorkflowBlueprint) { this.workflowBlueprint = workflowBlueprint; this.workflowModel = this.mapWorkflowModel(workflowBlueprint); + debugger; } mapWorkflowModel(workflowBlueprint: WorkflowBlueprint): WorkflowModel { return { - activities: workflowBlueprint.activities.map(this.mapActivityModel), + activities: workflowBlueprint.activities.filter(x => x.parentId == null).map(this.mapActivityModel), connections: workflowBlueprint.connections.map(this.mapConnectionModel), persistenceBehavior: workflowBlueprint.persistenceBehavior, }; 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 5a3d6db01..447aa1218 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 @@ -70,8 +70,7 @@ export class ElsaWorkflowInstanceJournal { @Method() async selectActivityRecord(activityId?: string) { const record = !!activityId ? this.records.items.find(x => x.activityId == activityId) : null; - this.selectedRecordId = !!record ? record.id : null; - this.selectedActivityId = activityId; + this.selectActivityRecordInternal(record); await this.show(); } @@ -93,6 +92,12 @@ export class ElsaWorkflowInstanceJournal { await this.workflowInstanceIdChangedHandler(this.workflowInstanceId); } + selectActivityRecordInternal(record?: WorkflowExecutionLogRecord) { + const activity = !!record ? this.workflowBlueprint.activities.find(x => x.id === record.activityId) : null; + this.selectedRecordId = !!record ? record.id : null; + this.selectedActivityId = activity != null ? activity.parentId != null ? activity.parentId : activity.id : null; + } + getEventColor(eventName: string) { const map = { 'Executing': 'blue', @@ -114,8 +119,7 @@ export class ElsaWorkflowInstanceJournal { } onRecordClick(record: WorkflowExecutionLogRecord) { - this.selectedRecordId = record.id; - this.selectedActivityId = record.activityId; + this.selectActivityRecordInternal(record); this.recordSelected.emit(record); } @@ -245,7 +249,7 @@ export class ElsaWorkflowInstanceJournal { if (!value) continue; - + let valueText = null; if (typeof value == 'string') 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 bc3c9eb00..575422f8f 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 @@ -121,7 +121,7 @@ export class ElsaWorkflowInstanceViewerScreen { mapWorkflowModel(workflowBlueprint: WorkflowBlueprint): WorkflowModel { return { - activities: workflowBlueprint.activities.map(this.mapActivityModel), + activities: workflowBlueprint.activities.filter(x => x.parentId == null).map(this.mapActivityModel), connections: workflowBlueprint.connections.map(this.mapConnectionModel), persistenceBehavior: workflowBlueprint.persistenceBehavior, }; @@ -160,7 +160,9 @@ export class ElsaWorkflowInstanceViewerScreen { } onRecordSelected(e: CustomEvent) { - this.selectedActivityId = e.detail.activityId; + const record = e.detail; + const activity = !!record ? this.workflowBlueprint.activities.find(x => x.id === record.activityId) : null; + this.selectedActivityId = activity != null ? activity.parentId != null ? activity.parentId : activity.id : null; } onActivitySelected(e: CustomEvent) { diff --git a/src/designer/elsa-workflows-studio/src/index.html b/src/designer/elsa-workflows-studio/src/index.html index 258f94cb1..b6402d31f 100644 --- a/src/designer/elsa-workflows-studio/src/index.html +++ b/src/designer/elsa-workflows-studio/src/index.html @@ -15,6 +15,8 @@ + + diff --git a/src/designer/elsa-workflows-studio/src/models/domain.ts b/src/designer/elsa-workflows-studio/src/models/domain.ts index 16a1ec370..8dec2b078 100644 --- a/src/designer/elsa-workflows-studio/src/models/domain.ts +++ b/src/designer/elsa-workflows-studio/src/models/domain.ts @@ -42,6 +42,7 @@ export interface ActivityBlueprint { displayName?: string; description?: string; type: string; + parentId?: string; persistWorkflow: boolean; loadWorkflowContext: boolean; saveWorkflowContext: boolean; diff --git a/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowInstanceDocument.cs b/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowInstanceDocument.cs index 61815684a..2320f89e0 100644 --- a/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowInstanceDocument.cs +++ b/src/persistence/Elsa.Persistence.YesSql/Documents/WorkflowInstanceDocument.cs @@ -37,7 +37,7 @@ namespace Elsa.Persistence.YesSql.Documents public WorkflowFault? Fault { get; set; } public SimpleStack ScheduledActivities { get; set; } = new(); - public SimpleStack Scopes { get; set; } = new(); + public SimpleStack Scopes { get; set; } = new(); public ScheduledActivity? CurrentActivity { get; set; } } } \ No newline at end of file diff --git a/src/samples/server/Elsa.Samples.Server.Host/Activities/SampleCompositeActivity.cs b/src/samples/server/Elsa.Samples.Server.Host/Activities/SampleCompositeActivity.cs new file mode 100644 index 000000000..628c08246 --- /dev/null +++ b/src/samples/server/Elsa.Samples.Server.Host/Activities/SampleCompositeActivity.cs @@ -0,0 +1,15 @@ +using Elsa.Activities.Console; +using Elsa.Builders; +using Elsa.Services; + +namespace Elsa.Samples.Server.Host.Activities +{ + public class SampleCompositeActivity : CompositeActivity + { + public override void Build(ICompositeActivityBuilder builder) + { + builder.WriteLine("Line 1"); + builder.WriteLine("Line 2"); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowRegistry/Models.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowRegistry/Models.cs index c46a6f075..68e8e8d20 100644 --- a/src/server/Elsa.Server.Api/Endpoints/WorkflowRegistry/Models.cs +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowRegistry/Models.cs @@ -38,6 +38,7 @@ namespace Elsa.Server.Api.Endpoints.WorkflowRegistry public string? DisplayName { get; set; } public string? Description { get; set; } public string Type { get; set; } = default!; + public string? ParentId { get; set; } public bool PersistWorkflow { get; set; } public bool LoadWorkflowContext { get; set; } public bool SaveWorkflowContext { get; set; } diff --git a/src/server/Elsa.Server.Api/Mapping/ActivityBlueprintConverter.cs b/src/server/Elsa.Server.Api/Mapping/ActivityBlueprintConverter.cs index 63a759080..a6b52bd98 100644 --- a/src/server/Elsa.Server.Api/Mapping/ActivityBlueprintConverter.cs +++ b/src/server/Elsa.Server.Api/Mapping/ActivityBlueprintConverter.cs @@ -19,6 +19,7 @@ namespace Elsa.Server.Api.Mapping DisplayName = source.DisplayName, Description = source.Description, Type = source.Type, + ParentId = source.Parent?.Id, PersistWorkflow = source.PersistWorkflow, LoadWorkflowContext = source.LoadWorkflowContext, SaveWorkflowContext = source.SaveWorkflowContext,