Implement variable picker

This commit is contained in:
Sipke Schoorstra 2022-11-23 22:36:43 +01:00
parent c3a3d85f47
commit 2567eb6535
26 changed files with 199 additions and 95 deletions

View file

@ -23,7 +23,7 @@ var services = builder.Services;
services
.AddElsa(elsa => elsa
.UseWorkflows()
.UseRuntime()
.UseWorkflowRuntime()
.UseWorkflowManagement(management => management
.AddActivity<Sequence>()
.AddActivity<WriteLine>()

View file

@ -78,7 +78,7 @@ services
identity.CreateDefaultUser = true;
identity.IdentityOptions = options => identitySection.Bind(options);
})
.UseRuntime(runtime =>
.UseWorkflowRuntime(runtime =>
{
runtime.UseProtoActor(proto => proto.PersistenceProvider = _ => new SqliteProvider(new SqliteConnectionStringBuilder(sqliteConnectionString)));
runtime.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString));

View file

@ -244,6 +244,9 @@ export namespace Components {
"getVariable": () => Promise<Variable>;
"variable": Variable;
}
interface ElsaVariablePickerInput {
"inputContext": ActivityInputContext;
}
interface ElsaVariablesEditor {
"variables"?: Array<Variable>;
}
@ -562,6 +565,12 @@ declare global {
prototype: HTMLElsaVariableEditorDialogContentElement;
new (): HTMLElsaVariableEditorDialogContentElement;
};
interface HTMLElsaVariablePickerInputElement extends Components.ElsaVariablePickerInput, HTMLStencilElement {
}
var HTMLElsaVariablePickerInputElement: {
prototype: HTMLElsaVariablePickerInputElement;
new (): HTMLElsaVariablePickerInputElement;
};
interface HTMLElsaVariablesEditorElement extends Components.ElsaVariablesEditor, HTMLStencilElement {
}
var HTMLElsaVariablesEditorElement: {
@ -700,6 +709,7 @@ declare global {
"elsa-switch-editor": HTMLElsaSwitchEditorElement;
"elsa-tooltip": HTMLElsaTooltipElement;
"elsa-variable-editor-dialog-content": HTMLElsaVariableEditorDialogContentElement;
"elsa-variable-picker-input": HTMLElsaVariablePickerInputElement;
"elsa-variables-editor": HTMLElsaVariablesEditorElement;
"elsa-workflow-definition-browser": HTMLElsaWorkflowDefinitionBrowserElement;
"elsa-workflow-definition-editor": HTMLElsaWorkflowDefinitionEditorElement;
@ -922,6 +932,9 @@ declare namespace LocalJSX {
"onVariableChanged"?: (event: CustomEvent<Variable>) => void;
"variable"?: Variable;
}
interface ElsaVariablePickerInput {
"inputContext"?: ActivityInputContext;
}
interface ElsaVariablesEditor {
"onVariablesChanged"?: (event: CustomEvent<Array<Variable>>) => void;
"variables"?: Array<Variable>;
@ -1036,6 +1049,7 @@ declare namespace LocalJSX {
"elsa-switch-editor": ElsaSwitchEditor;
"elsa-tooltip": ElsaTooltip;
"elsa-variable-editor-dialog-content": ElsaVariableEditorDialogContent;
"elsa-variable-picker-input": ElsaVariablePickerInput;
"elsa-variables-editor": ElsaVariablesEditor;
"elsa-workflow-definition-browser": ElsaWorkflowDefinitionBrowser;
"elsa-workflow-definition-editor": ElsaWorkflowDefinitionEditor;
@ -1099,6 +1113,7 @@ declare module "@stencil/core" {
"elsa-switch-editor": LocalJSX.ElsaSwitchEditor & JSXBase.HTMLAttributes<HTMLElsaSwitchEditorElement>;
"elsa-tooltip": LocalJSX.ElsaTooltip & JSXBase.HTMLAttributes<HTMLElsaTooltipElement>;
"elsa-variable-editor-dialog-content": LocalJSX.ElsaVariableEditorDialogContent & JSXBase.HTMLAttributes<HTMLElsaVariableEditorDialogContentElement>;
"elsa-variable-picker-input": LocalJSX.ElsaVariablePickerInput & JSXBase.HTMLAttributes<HTMLElsaVariablePickerInputElement>;
"elsa-variables-editor": LocalJSX.ElsaVariablesEditor & JSXBase.HTMLAttributes<HTMLElsaVariablesEditorElement>;
"elsa-workflow-definition-browser": LocalJSX.ElsaWorkflowDefinitionBrowser & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionBrowserElement>;
"elsa-workflow-definition-editor": LocalJSX.ElsaWorkflowDefinitionEditor & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionEditorElement>;

View file

@ -20,9 +20,9 @@ export class VariableEditorDialogContent {
}
render() {
const variable: Variable = this.variable ?? {name: '', type: 'Object'};
const variableType = variable.type;
const availableTypes: Array<string> = ['Object', 'String', 'Boolean', 'Int32', 'Int64', 'Single', 'Double']; // TODO: Fetch these from backend.
const variable: Variable = this.variable ?? {name: '', typeName: 'Object'};
const variableTypeName = variable.typeName;
const availableTypeNames: Array<string> = ['Object', 'String', 'Boolean', 'Int32', 'Int64', 'Single', 'Double']; // TODO: Fetch these from backend.
const storageDrivers: Array<StorageDriverDescriptor> = [{id: null, displayName: '-'}, ...descriptorsStore.storageDrivers];
return (
@ -36,9 +36,9 @@ export class VariableEditorDialogContent {
<input type="text" name="variableName" id="variableName" value={variable.name}/>
</FormEntry>
<FormEntry fieldId="variableType" label="Type" hint="The type of the variable.">
<select id="variableType" name="variableType">
{availableTypes.map(type => <option value={type} selected={type == variableType}>{type}</option>)}
<FormEntry fieldId="variableTypeName" label="Type" hint="The type of the variable.">
<select id="variableTypeName" name="variableTypeName">
{availableTypeNames.map(typeName => <option value={typeName} selected={typeName == variableTypeName}>{typeName}</option>)}
</select>
</FormEntry>
@ -65,7 +65,6 @@ export class VariableEditorDialogContent {
}
private onSubmit = async (e: Event) => {
debugger;
e.preventDefault();
const form = e.target as HTMLFormElement;
const variable = this.getVariableInternal(form);
@ -76,12 +75,12 @@ export class VariableEditorDialogContent {
const formData = new FormData(form as HTMLFormElement);
const name = formData.get('variableName') as string;
const value = formData.get('variableValue') as string;
const type = formData.get('variableType') as string;
const type = formData.get('variableTypeName') as string;
const driverId = formData.get('variableStorageDriverId') as string;
const variable = this.variable;
variable.name = name;
variable.type = type;
variable.typeName = type;
variable.value = value;
variable.storageDriverId = isNullOrWhitespace(driverId) ? null : driverId;

View file

@ -67,7 +67,7 @@ export class VariablesEditor {
return (
<tr>
<td class="whitespace-nowrap">{variable.name}</td>
<td class="whitespace-nowrap">{variable.type}</td>
<td class="whitespace-nowrap">{variable.typeName}</td>
<td>{variable.value}</td>
<td>{storageName}</td>
<td class="pr-6">
@ -109,7 +109,7 @@ export class VariablesEditor {
private onAddVariableClick = async () => {
const newVariableName = this.generateNewVariableName();
const variable = {name: newVariableName, type: 'Object', value: null};
const variable = {name: newVariableName, typeName: 'Object', value: null};
this.modalDialogInstance = this.modalDialogService.show(() => <elsa-variable-editor-dialog-content variable={variable}/>, {actions: [this.saveAction]})
};

View file

@ -8,7 +8,7 @@ import {ExpressionChangedArs} from "../designer/input-control-switch/input-contr
tag: 'elsa-dropdown-input',
shadow: false
})
export class SingleLineInput {
export class DropdownInput {
@Prop() public inputContext: ActivityInputContext;
private selectList: SelectList = {items: [], isFlagsEnum: false};

View file

@ -0,0 +1,57 @@
import {Component, Prop, h} from '@stencil/core';
import {LiteralExpression, SelectList, SyntaxNames, Variable} from "../../models";
import {ActivityInputContext} from "../../services/node-input-driver";
import {getInputPropertyValue, getPropertyValue, getSelectListItems} from "../../utils";
import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch";
import WorkflowDefinitionEditorTunnel from "../../modules/workflow-definitions/state";
import {FormEntry} from "../shared/forms/form-entry";
@Component({
tag: 'elsa-variable-picker-input',
shadow: false
})
export class VariablePickerInput {
@Prop() public inputContext: ActivityInputContext;
public render() {
const inputContext = this.inputContext;
const inputDescriptor = inputContext.inputDescriptor;
const fieldName = inputDescriptor.name;
const fieldId = inputDescriptor.name;
const displayName = inputDescriptor.displayName;
const description = inputDescriptor.description;
const value = getPropertyValue(inputContext) as Variable;
let currentValue = value;
if (currentValue == undefined) {
const defaultValue = inputDescriptor.defaultValue;
currentValue = defaultValue ? defaultValue.toString() : undefined;
}
debugger;
return (
<WorkflowDefinitionEditorTunnel.Consumer>
{({workflowDefinition}) => {
const variables = workflowDefinition?.variables ?? [];
return<FormEntry fieldId={fieldId} label={displayName} hint={description}>
<select id={fieldId} name={fieldName} onChange={e => this.onChange(e)}>
{variables.map((variable: Variable) => {
const isSelected = variable.name == currentValue?.name;
const json = JSON.stringify(variable);
return <option value={variable.name} selected={isSelected} data-variable={json}>{variable.name}</option>;
})}
</select>
</FormEntry>
}}
</WorkflowDefinitionEditorTunnel.Consumer>
);
}
private onChange = (e: Event) => {
debugger;
const inputElement = e.target as HTMLSelectElement;
const variable = inputElement.selectedOptions.length == 0 ? null : JSON.parse(inputElement.selectedOptions[0].dataset.variable);
this.inputContext.inputChanged(variable, SyntaxNames.Literal);
}
}

View file

@ -32,7 +32,7 @@ export interface Container extends Activity {
export interface Variable {
name: string;
type: string;
typeName: string;
value?: any;
storageDriverId?: string;
}
@ -91,6 +91,7 @@ export interface PropertyDescriptor {
}
export interface InputDescriptor extends PropertyDescriptor {
isWrapped?: boolean;
uiHint?: string;
options?: any;
category?: string;

View file

@ -19,9 +19,9 @@ import {Hash} from "../../utils";
import PositionEventArgs = NodeView.PositionEventArgs;
import FromJSONData = Model.FromJSONData;
import PointLike = Point.PointLike;
import FlowchartTunnel, {FlowchartState} from "./state";
import {generateUniqueActivityName} from "../../utils/generate-activity-name";
import { DagreLayout, OutNode} from '@antv/layout';
import FlowchartTunnel, {FlowchartState} from "./state";
const FlowchartTypeName = 'Elsa.Flowchart';
@ -131,7 +131,7 @@ export class FlowchartComponent implements ContainerActivityComponent {
nodesep: 15,
controlPoints: true,
});
let flowchartModel = this.getFlowchartModel();
let nodes = [];
@ -550,7 +550,6 @@ export class FlowchartComponent implements ContainerActivityComponent {
};
private onNodeContextMenu = async (e: PositionEventArgs<JQuery.ContextMenuEvent>) => {
debugger;
const node = e.node as ActivityNodeShape;
const activity = e.node.data as Activity;

View file

@ -7,6 +7,7 @@ import {ActivityInputContext} from "../../../services/node-input-driver";
import {CheckboxFormEntry, FormEntry} from "../../../components/shared/forms/form-entry";
import {isNullOrWhitespace} from "../../../utils";
import descriptorsStore from "../../../data/descriptors-store";
import {Input} from "postcss";
export interface ActivityUpdatedArgs {
originalId: string;
@ -127,11 +128,10 @@ export class ActivityPropertiesEditor {
tabs.push(outputTab);
}
if(isTask)
{
if (isTask) {
const taskTab: TabDefinition = {
displayText: 'Task',
order:12,
order: 12,
content: () => this.renderTaskTab()
};
@ -222,15 +222,20 @@ export class ActivityPropertiesEditor {
private onInputPropertyEditorChanged = (inputDescriptor: InputDescriptor, propertyValue: any, syntax: string) => {
const activity = this.activity;
const propertyName = inputDescriptor.name;
const isWrapped = inputDescriptor.isWrapped;
const camelCasePropertyName = camelCase(propertyName);
activity[camelCasePropertyName] = {
type: inputDescriptor.type,
expression: {
type: syntax,
value: propertyValue // TODO: The "value" field is currently hardcoded, but we should be able to be more flexible and potentially have different fields for a given syntax.
}
};
if (isWrapped) {
activity[camelCasePropertyName] = {
type: inputDescriptor.type,
expression: {
type: syntax,
value: propertyValue // TODO: The "value" field is currently hardcoded, but we should be able to be more flexible and potentially have different fields for a given syntax.
}
};
} else {
activity[camelCasePropertyName] = propertyValue;
}
this.updateActivity(propertyName);
}

View file

@ -4,20 +4,19 @@ import {Container} from "typedi";
import {PanelPosition, PanelStateChangedArgs} from '../../../components/panel/models';
import {
Activity,
ActivityDeletedArgs,
ActivityDescriptor,
ActivitySelectedArgs,
ChildActivitySelectedArgs,
ContainerSelectedArgs,
GraphUpdatedArgs,
VersionedEntity
GraphUpdatedArgs
} from '../../../models';
import {ActivityIdUpdatedArgs, ActivityUpdatedArgs} from './activity-properties-editor';
import {ActivityUpdatedArgs} from './activity-properties-editor';
import {ActivityDriverRegistry, ActivityNameFormatter, EventBus, PluginRegistry, PortProviderRegistry} from '../../../services';
import {MonacoEditorSettings} from "../../../services/monaco-editor-settings";
import {ActivityPropertyChangedEventArgs, WorkflowDefinitionPropsUpdatedArgs, WorkflowDefinitionUpdatedArgs, WorkflowEditorEventTypes} from "../models/ui";
import {WorkflowDefinition} from "../models/entities";
import {WorkflowDefinitionsApi} from "../../workflow-definitions/services/api";
import {WorkflowDefinitionsApi} from "../services/api"
import WorkflowDefinitionEditorTunnel, {WorkflowDefinitionEditorState} from "../state";
@Component({
tag: 'elsa-workflow-definition-editor',
@ -146,12 +145,10 @@ export class WorkflowDefinitionEditor {
@Method()
async loadWorkflowVersions(): Promise<void> {
if(this.workflowDefinitionState.definitionId != null && this.workflowDefinitionState.definitionId.length > 0)
{
if (this.workflowDefinitionState.definitionId != null && this.workflowDefinitionState.definitionId.length > 0) {
const workflowVersions = await this.workflowDefinitionApi.getVersions(this.workflowDefinitionState.definitionId);
this.workflowVersions = workflowVersions.sort(x => x.version).reverse();
}
else {
} else {
this.workflowVersions = [];
}
}
@ -256,7 +253,7 @@ export class WorkflowDefinitionEditor {
onVersionSelected = async (e: CustomEvent<WorkflowDefinition>) => {
const workflowToView = e.detail;
const workflowDefinition = await this.workflowDefinitionApi.get({definitionId: workflowToView.definitionId, versionOptions: {version: workflowToView.version}});
this.importWorkflow(workflowDefinition);
await this.importWorkflow(workflowDefinition);
};
onDeleteVersionClicked = async (e: CustomEvent<WorkflowDefinition>) => {
@ -264,7 +261,7 @@ export class WorkflowDefinitionEditor {
await this.workflowDefinitionApi.deleteVersion({definitionId: workflowToDelete.definitionId, version: workflowToDelete.version});
const latestWorkflowDefinition = await this.workflowDefinitionApi.get({definitionId: workflowToDelete.definitionId, versionOptions: {isLatest: true}});
await this.loadWorkflowVersions();
this.importWorkflow(latestWorkflowDefinition);
await this.importWorkflow(latestWorkflowDefinition);
};
onRevertVersionClicked = async (e: CustomEvent<WorkflowDefinition>) => {
@ -272,49 +269,54 @@ export class WorkflowDefinitionEditor {
await this.workflowDefinitionApi.revertVersion({definitionId: workflowToRevert.definitionId, version: workflowToRevert.version});
const workflowDefinition = await this.workflowDefinitionApi.get({definitionId: workflowToRevert.definitionId, versionOptions: {isLatest: true}});
await this.loadWorkflowVersions();
this.importWorkflow(workflowDefinition);
await this.importWorkflow(workflowDefinition);
};
render() {
const state: WorkflowDefinitionEditorState = {
workflowDefinition: this.workflowDefinition
};
return (
<div class="absolute inset-0" ref={el => this.container = el}>
<elsa-workflow-definition-editor-toolbar zoomToFit={this.onZoomToFit} autoLayout={this.onAutoLayout}/>
<elsa-panel
class="elsa-activity-picker-container z-30"
position={PanelPosition.Left}
onExpandedStateChanged={e => this.onActivityPickerPanelStateChanged(e.detail)}>
<elsa-workflow-definition-editor-toolbox ref={el => this.toolbox = el}/>
</elsa-panel>
<elsa-canvas
class="absolute" ref={el => this.canvas = el}
interactiveMode={true}
onDragOver={this.onDragOver}
onDrop={this.onDrop}/>
<elsa-panel
class="elsa-workflow-editor-container z-30"
position={PanelPosition.Right}
onExpandedStateChanged={e => this.onWorkflowEditorPanelStateChanged(e.detail)}>
<div class="object-editor-container">
<elsa-workflow-definition-properties-editor
workflowDefinition={this.workflowDefinitionState}
workflowVersions={this.workflowVersions}
onWorkflowPropsUpdated={e => this.onWorkflowPropsUpdated(e)}
onVersionSelected={e => this.onVersionSelected(e)}
onDeleteVersionClicked={e => this.onDeleteVersionClicked(e)}
onRevertVersionClicked={e => this.onRevertVersionClicked(e)}
/>
</div>
</elsa-panel>
<elsa-panel
class="elsa-activity-editor-container"
position={PanelPosition.Bottom}
onExpandedStateChanged={e => this.onActivityEditorPanelStateChanged(e.detail)}>
<div class="activity-editor-container">
{this.renderSelectedObject()}
</div>
</elsa-panel>
</div>
<WorkflowDefinitionEditorTunnel.Provider state={state}>
<div class="absolute inset-0" ref={el => this.container = el}>
<elsa-workflow-definition-editor-toolbar zoomToFit={this.onZoomToFit} autoLayout={this.onAutoLayout}/>
<elsa-panel
class="elsa-activity-picker-container z-30"
position={PanelPosition.Left}
onExpandedStateChanged={e => this.onActivityPickerPanelStateChanged(e.detail)}>
<elsa-workflow-definition-editor-toolbox ref={el => this.toolbox = el}/>
</elsa-panel>
<elsa-canvas
class="absolute" ref={el => this.canvas = el}
interactiveMode={true}
onDragOver={this.onDragOver}
onDrop={this.onDrop}/>
<elsa-panel
class="elsa-workflow-editor-container z-30"
position={PanelPosition.Right}
onExpandedStateChanged={e => this.onWorkflowEditorPanelStateChanged(e.detail)}>
<div class="object-editor-container">
<elsa-workflow-definition-properties-editor
workflowDefinition={this.workflowDefinitionState}
workflowVersions={this.workflowVersions}
onWorkflowPropsUpdated={e => this.onWorkflowPropsUpdated(e)}
onVersionSelected={e => this.onVersionSelected(e)}
onDeleteVersionClicked={e => this.onDeleteVersionClicked(e)}
onRevertVersionClicked={e => this.onRevertVersionClicked(e)}
/>
</div>
</elsa-panel>
<elsa-panel
class="elsa-activity-editor-container"
position={PanelPosition.Bottom}
onExpandedStateChanged={e => this.onActivityEditorPanelStateChanged(e.detail)}>
<div class="activity-editor-container">
{this.renderSelectedObject()}
</div>
</elsa-panel>
</div>
</WorkflowDefinitionEditorTunnel.Provider>
);
}
}

View file

@ -110,18 +110,15 @@ export class WorkflowDefinitionsPlugin implements Plugin {
private onWorkflowUpdated = async (e: CustomEvent<WorkflowDefinitionUpdatedArgs>) => {
const updatedWorkflowDefinition = e.detail.workflowDefinition;
if(e.detail.latestVersionNumber == undefined)
{
if (e.detail.latestVersionNumber == undefined) {
await this.saveWorkflowDefinition(updatedWorkflowDefinition, false);
return;
}
if(updatedWorkflowDefinition.version == e.detail.latestVersionNumber || updatedWorkflowDefinition.isPublished)
{
if (updatedWorkflowDefinition.version == e.detail.latestVersionNumber || updatedWorkflowDefinition.isPublished) {
const currentWorkflowDefinition = await this.api.get({definitionId: updatedWorkflowDefinition.definitionId, versionOptions: {version: updatedWorkflowDefinition.version}});
if(!isEqual(currentWorkflowDefinition.root.activities, updatedWorkflowDefinition.root.activities))
{
if(updatedWorkflowDefinition.isPublished)
if (!isEqual(currentWorkflowDefinition.root.activities, updatedWorkflowDefinition.root.activities) || !isEqual(currentWorkflowDefinition.variables, updatedWorkflowDefinition.variables)) {
if (updatedWorkflowDefinition.isPublished)
updatedWorkflowDefinition.version = e.detail.latestVersionNumber;
await this.saveWorkflowDefinition(updatedWorkflowDefinition, false);
@ -135,7 +132,7 @@ export class WorkflowDefinitionsPlugin implements Plugin {
const actions = [closeAction, newAction];
this.workflowDefinitionBrowserInstance = this.modalDialogService.show(() =>
<elsa-workflow-definition-browser onWorkflowDefinitionSelected={this.onWorkflowDefinitionSelected} onNewWorkflowDefinitionSelected={this.onNewWorkflowDefinitionClick}/>,
<elsa-workflow-definition-browser onWorkflowDefinitionSelected={this.onWorkflowDefinitionSelected} onNewWorkflowDefinitionSelected={this.onNewWorkflowDefinitionClick}/>,
{actions})
}

View file

@ -0,0 +1,14 @@
import {createProviderConsumer} from "@stencil/state-tunnel";
import {h} from "@stencil/core";
import {WorkflowDefinition} from "./models/entities";
export interface WorkflowDefinitionEditorState {
workflowDefinition: WorkflowDefinition;
}
export default createProviderConsumer<WorkflowDefinitionEditorState>(
{
workflowDefinition: null,
},
(subscribe, child) => (<context-consumer subscribe={subscribe} renderer={child}/>)
);

View file

@ -62,7 +62,6 @@ export class WorkflowInstancesPlugin implements Plugin {
const version = e.detail.version;
const workflowDefinition = await this.workflowDefinitionsApi.get({definitionId, versionOptions: {version}});
const workflowInstance = await this.workflowInstancesApi.get({id: instanceId});
debugger;
this.showWorkflowInstanceViewer(workflowDefinition, workflowInstance);
this.modalDialogService.hide(this.workflowInstanceBrowserInstance);
}

View file

@ -18,6 +18,7 @@ export class InputControlRegistry {
this.add('multi-text', c => <elsa-multi-text-input inputContext={c}/>);
this.add('code-editor', c => <elsa-code-editor-input inputContext={c}/>);
this.add('checkbox', c => <elsa-checkbox-input inputContext={c}/>);
this.add('variable-picker', c => <elsa-variable-picker-input inputContext={c}/>);
}
public add(uiHint: UIHint, control: RenderActivityPropInputControl) {

View file

@ -74,6 +74,11 @@ export const getInputPropertyValue = (inputContext: ActivityInputContext): Activ
return inputContext.node[propName] as ActivityInput;
};
export const getPropertyValue = (inputContext: ActivityInputContext): any => {
const propName = getInputPropertyName(inputContext);
return inputContext.node[propName] as any
};
export const stripActivityNameSpace = (name: string): string => {
const lastDotIndex = name.lastIndexOf('.');
return lastDotIndex < 0 ? name : name.substr(lastDotIndex + 1);

View file

@ -8,7 +8,7 @@ public class List : ElsaEndpointWithoutRequest<Response>
{
private readonly IActivityRegistry _registry;
public List(IActivityRegistry registry, IWellKnownTypeRegistry wellKnownTypeRegistry)
public List(IActivityRegistry registry)
{
_registry = registry;
}

View file

@ -48,8 +48,8 @@ public class SetVariable<T> : Activity
[Activity("Elsa", "Primitives", "Set a workflow variable to a given value.")]
public class SetVariable : Activity
{
[Input] public Variable Variable { get; set; } = default!;
[Input] public Input<object?> Value { get; set; } = new(default(object));
public Variable Variable { get; set; } = default!;
protected override void Execute(ActivityExecutionContext context)
{

View file

@ -9,6 +9,7 @@ public class InputDescriptor : PropertyDescriptor
public InputDescriptor(
string name,
Type type,
bool isWrapped,
string uiHint,
string displayName,
string? description = default,
@ -23,6 +24,7 @@ public class InputDescriptor : PropertyDescriptor
{
Name = name;
Type = type;
IsWrapped = isWrapped;
UIHint = uiHint;
DisplayName = displayName;
Description = description;
@ -36,6 +38,7 @@ public class InputDescriptor : PropertyDescriptor
IsBrowsable = isBrowsable;
}
public bool IsWrapped { get; set; }
public string UIHint { get; set; } = default!;
public object? Options { get; set; }
public string? Category { get; set; }

View file

@ -34,6 +34,9 @@ public class VariableConverter : JsonConverter<Variable>
public Variable? Map(VariableModel source)
{
if (string.IsNullOrWhiteSpace(source.TypeName))
return null;
if (!_wellKnownTypeRegistry.TryGetTypeOrDefault(source.TypeName, out var type))
return null;

View file

@ -102,12 +102,14 @@ public class ActivityDescriber : IActivityDescriber
var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>();
var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
var propertyType = propertyInfo.PropertyType;
var wrappedPropertyType = !typeof(Input).IsAssignableFrom(propertyType) ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0];
var isWrappedProperty = typeof(Input).IsAssignableFrom(propertyType);
var wrappedPropertyType = !isWrappedProperty ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0];
yield return new InputDescriptor
(
inputAttribute?.Name ?? propertyInfo.Name,
wrappedPropertyType,
isWrappedProperty,
GetUIHint(wrappedPropertyType, inputAttribute),
inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
descriptionAttribute?.Description ?? inputAttribute?.Description,
@ -159,6 +161,9 @@ public class ActivityDescriber : IActivityDescriber
if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable().IsEnum)
return InputUIHints.Dropdown;
if (wrappedPropertyType == typeof(Variable))
return InputUIHints.VariablePicker;
return InputUIHints.SingleLine;
}

View file

@ -16,7 +16,7 @@ public class VariableDefinitionMapper
public Variable? Map(VariableDefinition source)
{
if (!_wellKnownTypeRegistry.TryGetTypeOrDefault(source.Type, out var type))
if (!_wellKnownTypeRegistry.TryGetTypeOrDefault(source.TypeName, out var type))
return null;
var variableGenericType = typeof(Variable<>).MakeGenericType(type);

View file

@ -14,6 +14,7 @@ public static class InputUIHints
public const string Dropdown = "dropdown";
public const string MultiText = "multi-text";
public const string CodeEditor = "code-editor";
public const string VariablePicker = "variable-picker";
/// <summary>
/// An editor that allows the user to write a blob of JSON.

View file

@ -1,3 +1,3 @@
namespace Elsa.Workflows.Management.Models;
public record VariableDefinition(string Name, string Type, string? Value, string? StorageDriverId);
public record VariableDefinition(string Name, string TypeName, string? Value, string? StorageDriverId);

View file

@ -7,7 +7,7 @@ namespace Elsa.Workflows.Runtime.Extensions;
public static class DependencyInjectionExtensions
{
public static IModule UseRuntime(this IModule module, Action<WorkflowRuntimeFeature>? configure = default)
public static IModule UseWorkflowRuntime(this IModule module, Action<WorkflowRuntimeFeature>? configure = default)
{
module.Configure(configure);
return module;

View file

@ -1,8 +1,6 @@
using Elsa.Extensions;
using Elsa.Features.Extensions;
using Elsa.Identity;
using Elsa.Identity.Extensions;
using Elsa.Identity.Features;
using Elsa.Identity.Options;
using Elsa.JavaScript.Activities;
using Elsa.JavaScript.Extensions;
@ -66,7 +64,7 @@ services
identity.CreateDefaultUser = true;
identity.IdentityOptions = options => identitySection.Bind(options);
})
.UseRuntime(runtime =>
.UseWorkflowRuntime(runtime =>
{
runtime.UseProtoActor(proto =>
{