From d71d89cda081fe49b4c07628f6b49ed92dd782a9 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 30 Apr 2021 22:11:52 +0200 Subject: [PATCH] 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