Reference variable with Id instead of Name to prevent issues with updating name (#3814)
* Implemented Id property for variable for memory reference * Removed debugger * Fixed the issue with memory block reference of variable * Fixed infinite recursive calling issue * Handle variables with ID already containing * Update JS variable getters based on declared variables * Prevent Export from including composite roots * Update JS evaluator with support for reading and writing variables in scope * Fix polymorphic converter with support for arrays --------- Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
This commit is contained in:
parent
8289b03f79
commit
e4789cf85b
|
|
@ -248,6 +248,7 @@ export namespace Components {
|
|||
}
|
||||
interface ElsaVariablePickerInput {
|
||||
"inputContext": ActivityInputContext;
|
||||
"workflowDefinition": WorkflowDefinition;
|
||||
}
|
||||
interface ElsaVariablesEditor {
|
||||
"variables"?: Array<Variable>;
|
||||
|
|
@ -1134,6 +1135,7 @@ declare namespace LocalJSX {
|
|||
}
|
||||
interface ElsaVariablePickerInput {
|
||||
"inputContext"?: ActivityInputContext;
|
||||
"workflowDefinition"?: WorkflowDefinition;
|
||||
}
|
||||
interface ElsaVariablesEditor {
|
||||
"onVariablesChanged"?: (event: ElsaVariablesEditorCustomEvent<Array<Variable>>) => void;
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ import {ActivityInputContext} from "../../services/activity-input-driver";
|
|||
import {getPropertyValue} from "../../utils";
|
||||
import {FormEntry} from "../shared/forms/form-entry";
|
||||
import WorkflowDefinitionTunnel from "../../modules/workflow-definitions/state";
|
||||
import {WorkflowDefinition} from "../../modules/workflow-definitions/models/entities";
|
||||
|
||||
@Component({
|
||||
tag: 'elsa-variable-picker-input',
|
||||
shadow: false
|
||||
})
|
||||
export class VariablePickerInput {
|
||||
@Prop() public inputContext: ActivityInputContext;
|
||||
@Prop() inputContext: ActivityInputContext;
|
||||
@Prop() workflowDefinition: WorkflowDefinition; // Injected by WorkflowDefinitionTunnel
|
||||
|
||||
public render() {
|
||||
const inputContext = this.inputContext;
|
||||
|
|
@ -36,9 +38,9 @@ export class VariablePickerInput {
|
|||
<select id={fieldId} name={fieldName} onChange={e => this.onChange(e)}>
|
||||
{variables.map((variable: Variable) => {
|
||||
const variableName = variable?.name;
|
||||
const variableId = variable?.id;
|
||||
const isSelected = variableName == currentValue?.name;
|
||||
const json = variable ? JSON.stringify(variable) : '';
|
||||
return <option value={variableName} selected={isSelected} data-variable={json}>{variableName}</option>;
|
||||
return <option value={variableId} selected={isSelected}>{variableName}</option>;
|
||||
})}
|
||||
</select>
|
||||
</FormEntry>
|
||||
|
|
@ -49,8 +51,10 @@ export class VariablePickerInput {
|
|||
|
||||
private onChange = (e: Event) => {
|
||||
const inputElement = e.target as HTMLSelectElement;
|
||||
const json = inputElement.selectedOptions[0].dataset.variable;
|
||||
const variable = inputElement.selectedIndex <= 0 ? null : JSON.parse(json);
|
||||
const variableId = inputElement.value;
|
||||
const variable = this.workflowDefinition.variables.find(x => x.id == variableId);
|
||||
|
||||
this.inputContext.inputChanged(variable, SyntaxNames.Literal);
|
||||
}
|
||||
}
|
||||
WorkflowDefinitionTunnel.injectProps(VariablePickerInput, ['workflowDefinition']);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export interface Workflow extends Activity {
|
|||
}
|
||||
|
||||
export interface Variable {
|
||||
id: string;
|
||||
name: string;
|
||||
typeName: string;
|
||||
isArray: boolean;
|
||||
|
|
|
|||
|
|
@ -278,8 +278,7 @@ export class ActivityPropertiesEditor {
|
|||
const activity = this.activity;
|
||||
const propertyName = outputDescriptor.name;
|
||||
const camelCasePropertyName = camelCase(propertyName);
|
||||
const outputTargetValuePair = outputTargetValue.split(':');
|
||||
const kind = outputTargetValuePair[0];
|
||||
const outputTargetValuePair = outputTargetValue.split('::');
|
||||
const outputTargetId = outputTargetValuePair[1];
|
||||
|
||||
const property: ActivityOutput = {
|
||||
|
|
@ -303,9 +302,7 @@ export class ActivityPropertiesEditor {
|
|||
newId: activityId,
|
||||
originalId: activityId,
|
||||
activity,
|
||||
activityDescriptor,
|
||||
// propertyName: propertyName,
|
||||
// propertyDescriptor: propertyDescriptor
|
||||
activityDescriptor
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -356,8 +353,9 @@ export class ActivityPropertiesEditor {
|
|||
const key = `${activityId}`;
|
||||
const outputTargetOptions: Array<any> = [null];
|
||||
|
||||
if (variables.length > 0)
|
||||
outputTargetOptions.push({label: 'Variables', items: [...variables.map(x => ({value: x.name, name: x.name}))], kind: 'variable'});
|
||||
if (variables.length > 0) {
|
||||
outputTargetOptions.push({label: 'Variables', items: [...variables.map(x => ({value: x.id, name: x.name}))], kind: 'variable'});
|
||||
}
|
||||
|
||||
if (outputDefinitions.length > 0)
|
||||
outputTargetOptions.push({label: 'Outputs', items: [...outputDefinitions.map(x => ({value: x.name, name: x.name}))], kind: 'output'});
|
||||
|
|
@ -388,7 +386,7 @@ export class ActivityPropertiesEditor {
|
|||
<optgroup label={outputTarget.label}>
|
||||
{items.map(item => {
|
||||
const isSelected = propertyValue?.memoryReference?.id == item.value;
|
||||
return <option value={`${item.kind}:${item.value}`} selected={isSelected}>{item.name}</option>;
|
||||
return <option value={`${outputTarget.kind}::${item.value}`} selected={isSelected}>{item.name}</option>;
|
||||
})}
|
||||
</optgroup>);
|
||||
})}
|
||||
|
|
@ -413,7 +411,6 @@ export class ActivityPropertiesEditor {
|
|||
<CheckboxFormEntry fieldId="RunAsynchronously" label="Execute asynchronously" hint="When enabled, this activity will execute asynchronously and suspend workflow execution until the activity is finished.">
|
||||
<input type="checkbox" name="RunAsynchronously" id="RunAsynchronously" value={"true"} checked={runAsynchronously} onChange={e => this.onRunAsynchronouslyChanged(e)}/>
|
||||
</CheckboxFormEntry>
|
||||
|
||||
</div>
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export class VariableEditorDialogContent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const variable: Variable = this.variable ?? {name: '', typeName: 'Object', isArray: false};
|
||||
const variable: Variable = this.variable ?? {id: '', name: '', typeName: 'Object', isArray: false};
|
||||
const variableTypeName = variable.typeName;
|
||||
const availableTypes: Array<VariableDescriptor> = descriptorsStore.variableDescriptors;
|
||||
const groupedVariableTypes = groupBy(availableTypes, x => x.category);
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export class VariablesEditor {
|
|||
|
||||
private onAddVariableClick = async () => {
|
||||
const newVariableName = this.generateNewVariableName();
|
||||
const variable: Variable = {name: newVariableName, typeName: 'Object', value: null, isArray: false};
|
||||
const variable: Variable = {id: '', name: newVariableName, typeName: 'Object', value: null, isArray: false};
|
||||
|
||||
this.modalDialogInstance = this.modalDialogService.show(() => <elsa-variable-editor-dialog-content variable={variable}/>, {actions: [this.saveAction]})
|
||||
};
|
||||
|
|
|
|||
|
|
@ -59,6 +59,16 @@ public class ExpressionExecutionContext
|
|||
/// </summary>
|
||||
public MemoryBlock GetBlock(MemoryBlockReference blockReference) => GetBlockInternal(blockReference) ?? throw new Exception($"Failed to retrieve memory block with reference {blockReference.Id}");
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="MemoryBlock"/> pointed to by the specified memory block reference.
|
||||
/// </summary>
|
||||
public bool TryGetBlock(MemoryBlockReference blockReference, out MemoryBlock block)
|
||||
{
|
||||
var b = GetBlockInternal(blockReference);
|
||||
block = b ?? default!;
|
||||
return b != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the memory block pointed to by the specified memory block reference.
|
||||
/// </summary>
|
||||
|
|
@ -69,6 +79,21 @@ public class ExpressionExecutionContext
|
|||
/// </summary>
|
||||
public object? Get(MemoryBlockReference blockReference) => GetBlock(blockReference).Value;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the memory block pointed to by the specified memory block reference.
|
||||
/// </summary>
|
||||
public bool TryGet(MemoryBlockReference blockReference, out object? value)
|
||||
{
|
||||
if (TryGetBlock(blockReference, out var block))
|
||||
{
|
||||
value = block.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the memory block pointed to by the specified memory block reference.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,11 @@ public abstract class MemoryBlockReference
|
|||
/// </summary>
|
||||
public T? Get<T>(ExpressionExecutionContext context) => Get(context).ConvertTo<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the memory block.
|
||||
/// </summary>
|
||||
public bool TryGet(ExpressionExecutionContext context, out object? value) => context.TryGet(this, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the memory block.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Elsa.JavaScript.Contracts;
|
|||
using Elsa.JavaScript.Notifications;
|
||||
using Elsa.JavaScript.Options;
|
||||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Humanizer;
|
||||
using Jint;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
@ -60,8 +61,8 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
|
|||
engine.SetValue("setCorrelationId", (Action<string?>)(value => context.GetActivityExecutionContext().WorkflowExecutionContext.CorrelationId = value));
|
||||
engine.SetValue("getCorrelationId", (Func<string?>)(() => context.GetActivityExecutionContext().WorkflowExecutionContext.CorrelationId));
|
||||
engine.SetValue("setCorrelationId", (Action<string?>)(value => context.GetActivityExecutionContext().WorkflowExecutionContext.CorrelationId = value));
|
||||
engine.SetValue("setVariable", (Action<string, object>)((name, value) => context.SetVariable(name, value)));
|
||||
engine.SetValue("getVariable", (Func<string, object?>)(name => context.GetVariable(name)));
|
||||
engine.SetValue("setVariable", (Action<string, object>)((id, value) => context.SetVariable(id, value)));
|
||||
engine.SetValue("getVariable", (Func<string, object?>)(id => context.GetVariable(id)));
|
||||
engine.SetValue("getInput", (Func<string, object?>)(name => context.GetWorkflowExecutionContext().Input.GetValue(name)));
|
||||
|
||||
// Create variable & input setters and getters for each variable.
|
||||
|
|
@ -89,16 +90,58 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
|
|||
|
||||
private static void CreateMemoryBlockAccessors(Engine engine, ExpressionExecutionContext context)
|
||||
{
|
||||
var variablesDictionary = context.ReadAndFlattenMemoryBlocks();
|
||||
var variableNames = GetVariableNamesInScope(context).ToList();
|
||||
|
||||
foreach (var variable in variablesDictionary)
|
||||
foreach (var variableName in variableNames)
|
||||
{
|
||||
var pascalName = variable.Key.Pascalize();
|
||||
engine.SetValue($"get{pascalName}", (Func<object?>)(() => context.GetVariable(variable.Key)));
|
||||
engine.SetValue($"set{pascalName}", (Action<object?>)(value => context.SetVariable(variable.Key, value)));
|
||||
var pascalName = variableName.Pascalize();
|
||||
engine.SetValue($"get{pascalName}", (Func<object?>)(() => GetVariableInScope(context, variableName)));
|
||||
engine.SetValue($"set{pascalName}", (Action<object?>)(value => SetVariableInScope(context, variableName, value)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static IEnumerable<string> GetVariableNamesInScope(ExpressionExecutionContext context) => EnumerateVariablesInScope(context).Select(x => x.Name).Distinct();
|
||||
|
||||
private static object GetVariableInScope(ExpressionExecutionContext context, string variableName)
|
||||
{
|
||||
var q = from variable in EnumerateVariablesInScope(context)
|
||||
where variable.Name == variableName
|
||||
where variable.TryGet(context, out _)
|
||||
select variable.Get(context);
|
||||
|
||||
var value = q.FirstOrDefault();
|
||||
return value!;
|
||||
}
|
||||
|
||||
private static void SetVariableInScope(ExpressionExecutionContext context, string variableName, object? value)
|
||||
{
|
||||
var q = from v in EnumerateVariablesInScope(context)
|
||||
where v.Name == variableName
|
||||
where v.TryGet(context, out _)
|
||||
select v;
|
||||
|
||||
var variable = q.FirstOrDefault();
|
||||
variable?.Set(context, value);
|
||||
}
|
||||
|
||||
private static IEnumerable<Variable> EnumerateVariablesInScope(ExpressionExecutionContext context)
|
||||
{
|
||||
var currentScope = context;
|
||||
|
||||
while (currentScope != null)
|
||||
{
|
||||
if (!currentScope.TryGetActivityExecutionContext(out var activityExecutionContext))
|
||||
break;
|
||||
|
||||
var variables = activityExecutionContext.Variables;
|
||||
|
||||
foreach (var variable in variables)
|
||||
yield return variable;
|
||||
|
||||
currentScope = currentScope.ParentContext;
|
||||
}
|
||||
}
|
||||
|
||||
private static object ExecuteExpressionAndGetResult(Engine engine, string expression)
|
||||
{
|
||||
var result = engine.Evaluate(expression);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ using System.Text.Json;
|
|||
using Elsa.Abstractions;
|
||||
using Elsa.Common.Models;
|
||||
using Elsa.Workflows.Api.Models;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Core.Serialization.Converters;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Mappers;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
|
|
@ -43,7 +45,6 @@ public class Export : ElsaEndpoint<Request>
|
|||
/// <inheritdoc />
|
||||
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
|
||||
{
|
||||
var serializerOptions = _serializerOptionsProvider.CreateApiOptions();
|
||||
var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest;
|
||||
var definition = (await _store.FindManyAsync(new WorkflowDefinitionFilter { DefinitionId = request.DefinitionId, VersionOptions = versionOptions }, cancellationToken: cancellationToken)).FirstOrDefault();
|
||||
|
||||
|
|
@ -73,6 +74,11 @@ public class Export : ElsaEndpoint<Request>
|
|||
definition.IsPublished,
|
||||
workflow.Root);
|
||||
|
||||
var serializerOptions = _serializerOptionsProvider.CreateApiOptions();
|
||||
|
||||
// Exclude composite activities from being serialized.
|
||||
serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory());
|
||||
|
||||
var binaryJson = JsonSerializer.SerializeToUtf8Bytes(model, serializerOptions);
|
||||
var hasWorkflowName = !string.IsNullOrWhiteSpace(definition.Name);
|
||||
var workflowName = hasWorkflowName ? definition.Name!.Trim() : definition.DefinitionId;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ internal class Get : ElsaEndpoint<Request>
|
|||
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
|
||||
{
|
||||
var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest;
|
||||
|
||||
|
||||
var filter = new WorkflowDefinitionFilter
|
||||
{
|
||||
DefinitionId = request.DefinitionId,
|
||||
|
|
@ -49,10 +49,10 @@ internal class Get : ElsaEndpoint<Request>
|
|||
var mapper = new WorkflowDefinitionMapper();
|
||||
var response = await mapper.FromEntityAsync(definition, cancellationToken);
|
||||
var serializerOptions = _serializerOptionsProvider.CreateApiOptions();
|
||||
|
||||
|
||||
// If the root of composite activities is not requested, exclude them from being serialized.
|
||||
if(!request.IncludeCompositeRoot)
|
||||
serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory<IActivity>());
|
||||
if (!request.IncludeCompositeRoot)
|
||||
serializerOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory());
|
||||
|
||||
await HttpContext.Response.WriteAsJsonAsync(response, serializerOptions, cancellationToken);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,12 @@ public class WorkflowBuilder : IWorkflowBuilder
|
|||
/// <inheritdoc />
|
||||
public Variable<T> WithVariable<T>(string name, T value)
|
||||
{
|
||||
var variable = value != null ? new Variable<T>(name, value) : new Variable<T>(name);
|
||||
var variable = new Variable<T>
|
||||
{
|
||||
Name = name,
|
||||
Value = value
|
||||
};
|
||||
|
||||
Variables.Add(variable);
|
||||
return variable;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ public static class ActivityExecutionContextExtensions
|
|||
|
||||
public static Variable SetVariable(this ActivityExecutionContext context, string name, object? value, Type? storageDriverType = default, Action<MemoryBlock>? configure = default) =>
|
||||
context.ExpressionExecutionContext.SetVariable(name, value, storageDriverType, configure);
|
||||
public static T? GetVariable<T>(this ActivityExecutionContext context, string name) => context.ExpressionExecutionContext.GetVariable<T?>(name);
|
||||
public static T? GetVariable<T>(this ActivityExecutionContext context, string id) => context.ExpressionExecutionContext.GetVariable<T?>(id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary of variable keys and their values across scopes.
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ public static class ExpressionExecutionContextExtensions
|
|||
|
||||
public static WorkflowExecutionContext GetWorkflowExecutionContext(this ExpressionExecutionContext context) => (WorkflowExecutionContext)context.TransientProperties[WorkflowExecutionContextKey];
|
||||
public static ActivityExecutionContext GetActivityExecutionContext(this ExpressionExecutionContext context) => (ActivityExecutionContext)context.TransientProperties[ActivityExecutionContextKey];
|
||||
public static bool TryGetActivityExecutionContext(this ExpressionExecutionContext context, out ActivityExecutionContext activityExecutionContext) => context.TransientProperties.TryGetValue(ActivityExecutionContextKey, out activityExecutionContext!);
|
||||
|
||||
public static IDictionary<string, object> GetInput(this ExpressionExecutionContext context) => (IDictionary<string, object>)context.TransientProperties[InputKey];
|
||||
public static T? GetInput<T>(this ExpressionExecutionContext context, string key) => context.GetInput(key).ConvertTo<T>();
|
||||
public static object? GetInput(this ExpressionExecutionContext context, string key) => context.GetInput().TryGetValue(key, out var value) ? value : default;
|
||||
|
|
@ -36,15 +38,13 @@ public static class ExpressionExecutionContextExtensions
|
|||
public static T? Get<T>(this ExpressionExecutionContext context, Input<T>? input) => input != null ? context.GetBlock(input.MemoryBlockReference).Value.ConvertTo<T>() : default;
|
||||
public static T? Get<T>(this ExpressionExecutionContext context, Output output) => context.GetBlock(output.MemoryBlockReference).Value.ConvertTo<T>();
|
||||
public static object? Get(this ExpressionExecutionContext context, Output output) => context.GetBlock(output.MemoryBlockReference).Value;
|
||||
public static T? GetVariable<T>(this ExpressionExecutionContext context, string name) => (T?)context.GetVariable(name);
|
||||
public static T? GetVariable<T>(this ExpressionExecutionContext context) => context.GetVariable(typeof(T).Name).ConvertTo<T>();
|
||||
public static object? GetVariable(this ExpressionExecutionContext context, string name) => new Variable(name).Get(context);
|
||||
public static Variable SetVariable<T>(this ExpressionExecutionContext context, T? value, Type? storageDriverType = default) => context.SetVariable(typeof(T).Name, value, storageDriverType);
|
||||
public static Variable SetVariable<T>(this ExpressionExecutionContext context, string name, T? value, Type? storageDriverType = default) => context.SetVariable(name, (object?)value, storageDriverType);
|
||||
public static T? GetVariable<T>(this ExpressionExecutionContext context, string id) => (T?)context.GetVariable(id);
|
||||
public static object? GetVariable(this ExpressionExecutionContext context, string id) => new Variable(id).Get(context);
|
||||
public static Variable SetVariable<T>(this ExpressionExecutionContext context, string id, T? value, Type? storageDriverType = default) => context.SetVariable(id, (object?)value, storageDriverType, default);
|
||||
|
||||
public static Variable SetVariable(this ExpressionExecutionContext context, string name, object? value, Type? storageDriverType, Action<MemoryBlock>? configure = default)
|
||||
public static Variable SetVariable(this ExpressionExecutionContext context, string id, object? value, Type? storageDriverType, Action<MemoryBlock>? configure = default)
|
||||
{
|
||||
var variable = new Variable(name, value)
|
||||
var variable = new Variable(id, value)
|
||||
{
|
||||
StorageDriverType = storageDriverType
|
||||
};
|
||||
|
|
|
|||
|
|
@ -95,50 +95,4 @@ public static class WorkflowExecutionContextExtensions
|
|||
workflowExecutionContext.Scheduler.Schedule(workItem);
|
||||
workflowExecutionContext.AddCompletionCallback(owner, activityNode, completionCallback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified workflow variable by name.
|
||||
/// </summary>
|
||||
public static T? GetVariable<T>(this WorkflowExecutionContext workflowExecutionContext, string name) => (T?)workflowExecutionContext.GetVariable(name);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified workflow variable by name, where the name is implied by the type name.
|
||||
/// </summary>
|
||||
public static T? GetVariable<T>(this WorkflowExecutionContext workflowExecutionContext) => (T?)workflowExecutionContext.GetVariable(typeof(T).Name);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified workflow variable by name.
|
||||
/// </summary>
|
||||
public static object? GetVariable(this WorkflowExecutionContext workflowExecutionContext, string name)
|
||||
{
|
||||
var variable = workflowExecutionContext.Workflow.Variables.FirstOrDefault(x => x.Name == name);
|
||||
return variable?.Get(workflowExecutionContext.MemoryRegister);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified workflow variable by name, where the name is implied by the type name.
|
||||
/// </summary>
|
||||
public static Variable SetVariable<T>(this WorkflowExecutionContext workflowExecutionContext, T? value) => workflowExecutionContext.SetVariable(typeof(T).Name, value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified workflow variable by name.
|
||||
/// </summary>
|
||||
public static Variable SetVariable<T>(this WorkflowExecutionContext workflowExecutionContext, string name, T? value) => workflowExecutionContext.SetVariable(name, (object?)value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified workflow variable by name.
|
||||
/// </summary>
|
||||
public static Variable SetVariable(this WorkflowExecutionContext workflowExecutionContext, string name, object? value)
|
||||
{
|
||||
var variable = workflowExecutionContext.Workflow.Variables.FirstOrDefault(x => x.Name == name);
|
||||
|
||||
if (variable == null)
|
||||
{
|
||||
variable = new Variable(name, value);
|
||||
workflowExecutionContext.Workflow.Variables.Add(variable);
|
||||
}
|
||||
|
||||
variable.Set(workflowExecutionContext.MemoryRegister, value);
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,23 +8,20 @@ public class Variable : MemoryBlockReference
|
|||
{
|
||||
public Variable()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
public Variable(string name)
|
||||
public Variable(string id)
|
||||
{
|
||||
Id = name;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public Variable(string name, object? value = default) : this(name)
|
||||
public Variable(string id, object? value = default) : this(id)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => Id;
|
||||
set => Id = value;
|
||||
}
|
||||
public string Name { get; set; }
|
||||
|
||||
public object? Value { get; set; }
|
||||
|
||||
|
|
@ -44,14 +41,6 @@ public class Variable<T> : Variable
|
|||
{
|
||||
}
|
||||
|
||||
public Variable(string name) : base(name)
|
||||
{
|
||||
}
|
||||
|
||||
public Variable(string name, T value) : base(name, value ?? default)
|
||||
{
|
||||
}
|
||||
|
||||
public Variable(T value)
|
||||
{
|
||||
Value = value;
|
||||
|
|
|
|||
|
|
@ -2,22 +2,23 @@ using System.Reflection;
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
|
||||
namespace Elsa.Workflows.Core.Serialization.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Ignores properties with the <see cref="JsonIgnoreCompositeRootAttribute"/> attribute.
|
||||
/// </summary>
|
||||
public class JsonIgnoreCompositeRootConverter<T> : JsonConverter<T>
|
||||
public class JsonIgnoreCompositeRootConverter : JsonConverter<IActivity>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
public override IActivity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
||||
public override void Write(Utf8JsonWriter writer, IActivity value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
|
|
@ -39,19 +40,4 @@ public class JsonIgnoreCompositeRootConverter<T> : JsonConverter<T>
|
|||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="JsonConverterFactory"/> that creates <see cref="JsonIgnoreCompositeRootConverter{T}"/> instances.
|
||||
/// </summary>
|
||||
public class JsonIgnoreCompositeRootConverterFactory<T> : JsonConverterFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type typeToConvert) => typeof(T).IsAssignableFrom(typeToConvert);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return new JsonIgnoreCompositeRootConverter<T>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
|
||||
namespace Elsa.Workflows.Core.Serialization.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="JsonConverterFactory"/> that creates <see cref="JsonIgnoreCompositeRootConverter"/> instances.
|
||||
/// </summary>
|
||||
public class JsonIgnoreCompositeRootConverterFactory : JsonConverterFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type typeToConvert) => typeof(IActivity).IsAssignableFrom(typeToConvert);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return new JsonIgnoreCompositeRootConverter();
|
||||
}
|
||||
}
|
||||
|
|
@ -47,12 +47,23 @@ public class PolymorphicObjectConverter : JsonConverter<object>
|
|||
|
||||
if(jsonObject.TryGetProperty(ItemsPropertyName, out var items))
|
||||
{
|
||||
var array = JsonSerializer.Deserialize(items.GetRawText(), targetType, newOptions)!;
|
||||
var elementType = targetType.GetElementType()!;
|
||||
var array = Array.CreateInstance(elementType, items.GetArrayLength());
|
||||
var index = 0;
|
||||
|
||||
newOptions.Converters.Add(this);
|
||||
|
||||
foreach (var element in items.EnumerateArray())
|
||||
{
|
||||
var deserializedElement = JsonSerializer.Deserialize(element.GetRawText(), elementType, newOptions)!;
|
||||
array.SetValue(deserializedElement, index++);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
var json = jsonObject.GetRawText();
|
||||
return JsonSerializer.Deserialize(json, targetType, newOptions)!;
|
||||
var result = JsonSerializer.Deserialize(json, targetType, newOptions)!;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static object ReadPrimitive(ref Utf8JsonReader reader, JsonSerializerOptions options)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ public class VariableConverter : JsonConverter<Variable>
|
|||
var variableGenericType = typeof(Variable<>).MakeGenericType(type);
|
||||
var variable = (Variable)Activator.CreateInstance(variableGenericType)!;
|
||||
|
||||
variable.Id = source.Id ?? Guid.NewGuid().ToString("N"); // Temporarily assign a new ID if the source doesn't have one.
|
||||
variable.Name = source.Name;
|
||||
|
||||
source.Value.TryConvertTo(type)
|
||||
|
|
@ -73,7 +74,7 @@ public class VariableConverter : JsonConverter<Variable>
|
|||
var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName();
|
||||
var serializedValue = value.Format();
|
||||
|
||||
return new VariableModel(source.Name, valueTypeAlias, serializedValue, storageDriverTypeName);
|
||||
return new VariableModel(source.Id, source.Name, valueTypeAlias, serializedValue, storageDriverTypeName);
|
||||
}
|
||||
|
||||
private class VariableModel
|
||||
|
|
@ -83,14 +84,16 @@ public class VariableConverter : JsonConverter<Variable>
|
|||
{
|
||||
}
|
||||
|
||||
public VariableModel(string name, string typeName, string? value, string? storageDriverTypeName)
|
||||
public VariableModel(string id, string name, string typeName, string? value, string? storageDriverTypeName)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
TypeName = typeName;
|
||||
Value = value;
|
||||
StorageDriverTypeName = storageDriverTypeName;
|
||||
}
|
||||
|
||||
public string Id { get; set; } = default!;
|
||||
public string Name { get; set; } = default!;
|
||||
public string TypeName { get; set; } = default!;
|
||||
public string? Value { get; set; }
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public class SerializerOptionsProvider
|
|||
public JsonSerializerOptions CreatePersistenceOptions(ReferenceHandler? referenceHandler = default)
|
||||
{
|
||||
var options = CreateDefaultOptions(referenceHandler ?? ReferenceHandler.IgnoreCycles);
|
||||
options.Converters.Add(Create<JsonIgnoreCompositeRootConverterFactory<IActivity>>());
|
||||
options.Converters.Add(Create<JsonIgnoreCompositeRootConverterFactory>());
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public class IdentityGraphService : IIdentityGraphService
|
|||
var seed = 0;
|
||||
|
||||
foreach (var variable in variables)
|
||||
variable.Id = variable.Name != null! ? variable.Name : $"{activity.Id}:variable-{++seed}";
|
||||
variable.Id = variable.Id != null! ? variable.Id : $"{activity.Id}:variable-{++seed}";
|
||||
}
|
||||
|
||||
private string CreateId(ActivityNode activityNode, IDictionary<string, int> identityCounters, ICollection<ActivityNode> allNodes)
|
||||
|
|
|
|||
|
|
@ -42,8 +42,10 @@ public class WorkflowDefinitionActivity : Composite, IInitializable
|
|||
var evaluatedExpression = input != null ? context.Get(input.MemoryBlockReference()) : default;
|
||||
|
||||
// Create a local scope variable for each input property.
|
||||
var variable = new Variable(inputDescriptor.Name)
|
||||
var variable = new Variable
|
||||
{
|
||||
Id = inputDescriptor.Name,
|
||||
Name = inputDescriptor.Name,
|
||||
StorageDriverType = inputDescriptor.StorageDriverType
|
||||
};
|
||||
|
||||
|
|
@ -62,6 +64,7 @@ public class WorkflowDefinitionActivity : Composite, IInitializable
|
|||
// Create a local scope variable for each input property.
|
||||
var variable = new Variable(inputDescriptor.Name)
|
||||
{
|
||||
Name = inputDescriptor.Name,
|
||||
StorageDriverType = inputDescriptor.StorageDriverType
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ public class VariableDefinitionMapper
|
|||
var variableGenericType = typeof(Variable<>).MakeGenericType(valueType);
|
||||
var variable = (Variable)Activator.CreateInstance(variableGenericType)!;
|
||||
|
||||
if(!string.IsNullOrEmpty(source.Id))
|
||||
variable.Id = source.Id;
|
||||
|
||||
variable.Name = source.Name;
|
||||
variable.Value = source.Value.ConvertTo(valueType);
|
||||
variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : default;
|
||||
|
|
@ -66,7 +69,7 @@ public class VariableDefinitionMapper
|
|||
var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName();
|
||||
var serializedValue = value.Format();
|
||||
|
||||
return new VariableDefinition(source.Name, valueTypeAlias, isArray, serializedValue, storageDriverTypeName);
|
||||
return new VariableDefinition(source.Id, source.Name, valueTypeAlias, isArray, serializedValue, storageDriverTypeName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ namespace Elsa.Workflows.Management.Models;
|
|||
/// <summary>
|
||||
/// Stores information about a workflow variable.
|
||||
/// </summary>
|
||||
public record VariableDefinition(string Name, string TypeName, bool IsArray, string? Value, string? StorageDriverTypeName);
|
||||
public record VariableDefinition(string Id, string Name, string TypeName, bool IsArray, string? Value, string? StorageDriverTypeName);
|
||||
|
|
@ -108,7 +108,12 @@ public class ActivityJsonConverter : JsonConverter<IActivity>
|
|||
if (!memoryReferenceElement.TryGetProperty("id", out var memoryReferenceIdElement))
|
||||
continue;
|
||||
|
||||
var variable = new Variable(memoryReferenceIdElement.GetString()!);
|
||||
var variable = new Variable
|
||||
{
|
||||
Id = memoryReferenceIdElement.GetString()!
|
||||
};
|
||||
variable.Name = variable.Id;
|
||||
|
||||
var output = Activator.CreateInstance(wrappedType, variable)!;
|
||||
|
||||
activity.SyntheticProperties[outputName] = output!;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,12 @@ public class OutputJsonConverter<T> : JsonConverter<Output<T>?>
|
|||
if (!memoryReferenceElement.TryGetProperty("id", out var memoryReferenceIdElement))
|
||||
return default;
|
||||
|
||||
var variable = new Variable(memoryReferenceIdElement.GetString()!);
|
||||
var variable = new Variable
|
||||
{
|
||||
Id = memoryReferenceIdElement.GetString()!
|
||||
};
|
||||
variable.Name = variable.Id;
|
||||
|
||||
return (Output<T>)Activator.CreateInstance(typeof(Output<T>), variable)!;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class AskName : Composite<string>
|
|||
Root = new Sequence
|
||||
{
|
||||
Variables = new List<Variable> { _name },
|
||||
Activities = new List<IActivity>()
|
||||
Activities = new List<IActivity>
|
||||
{
|
||||
new WriteLine(context => Prompt.Get(context)),
|
||||
new ReadLine(_name)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ public class BreakWhileForkWorkflow : WorkflowBase
|
|||
{
|
||||
protected override void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
var currentValue = new Variable<int?>("CurrentValue", 0);
|
||||
var currentValue = new Variable<int?>
|
||||
{
|
||||
Name = "CurrentValue",
|
||||
Value = 0
|
||||
};
|
||||
|
||||
workflow.Root = new Sequence
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ class ForEachWorkflow : WorkflowBase
|
|||
|
||||
protected override void Build(IWorkflowBuilder workflow)
|
||||
{
|
||||
var currentItem = new Variable<string>("CurrentItem");
|
||||
var currentItem = new Variable<string>
|
||||
{
|
||||
Name = "CurrentItem"
|
||||
};
|
||||
|
||||
workflow.Root = new Sequence
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue