Implement storage driver API endpoint

This commit is contained in:
Sipke Schoorstra 2022-06-07 15:48:52 +02:00
parent 6309c3494f
commit f93a051a14
37 changed files with 218 additions and 127 deletions

View file

@ -1,9 +1,8 @@
import {Component, h, Prop, Event, EventEmitter, Method} from "@stencil/core";
import {DeleteIcon, EditIcon, PublishIcon, UnPublishIcon} from "../../icons/tooling";
import {DataDrive, DefaultActions, InputDescriptor, Variable, WorkflowDefinitionSummary} from "../../../models";
import {Filter} from "../../modals/workflow-definition-browser/filter";
import {DefaultActions, StorageDriverDescriptor, Variable} from "../../../models";
import {FormEntry} from "../../shared/forms/form-entry";
import {isNullOrWhitespace} from "../../../utils";
import descriptorsStore from '../../../data/descriptors-store';
@Component({
tag: 'elsa-variable-editor-dialog',
@ -32,7 +31,7 @@ export class VariableEditorDialog {
const saveAction = DefaultActions.Save();
const actions = [cancelAction, saveAction];
const availableTypes: Array<string> = ['Object', 'String', 'Int32', 'Int64', 'Single', 'Double']; // TODO: Fetch these from backend.
const availableDrives: Array<DataDrive> = [null, {id: 'Workflow'}, {id: 'Blob Storage'}, {id: 'FTP'}]; // TODO: Fetch these from backend.
const storageDrivers: Array<StorageDriverDescriptor> = descriptorsStore.storageDrivers;
return (
<div>
@ -57,12 +56,12 @@ export class VariableEditorDialog {
<input type="text" name="variableValue" id="variableValue" value={variable.value}/>
</FormEntry>
<FormEntry fieldId="variableStorage" label="Storage" hint="The storage to use when persisting the variable.">
<select id="variableStorage" name="variableStorage">
{availableDrives.map(drive => {
const value = drive?.id;
const text = drive?.id ?? 'Transient';
const selected = value == variable.driveId;
<FormEntry fieldId="variableStorageDriverId" label="Storage" hint="The storage to use when persisting the variable.">
<select id="variableStorageDriverId" name="variableStorageDriverId">
{storageDrivers.map(driver => {
const value = driver.id;
const text = driver.displayName;
const selected = value == variable.storageDriverId;
return <option value={value} selected={selected}>{text}</option>;
})}
</select>
@ -82,13 +81,13 @@ export class VariableEditorDialog {
const name = formData.get('variableName') as string;
const value = formData.get('variableValue') as string;
const type = formData.get('variableType') as string;
const driveId = formData.get('variableStorage') as string;
const driverId = formData.get('variableStorageDriverId') as string;
const variable = this.variable;
variable.name = name;
variable.type = type;
variable.value = value;
variable.driveId = isNullOrWhitespace(driveId) ? null : driveId;
variable.storageDriverId = isNullOrWhitespace(driverId) ? null : driverId;
this.variableChanged.emit(variable);
await this.hide();

View file

@ -1,7 +1,8 @@
import {Component, h, Prop, State, Event, EventEmitter, Watch} from "@stencil/core";
import {DeleteIcon, EditIcon} from "../../icons/tooling";
import {Variable} from "../../../models";
import {StorageDriverDescriptor, Variable} from "../../../models";
import {isNullOrWhitespace} from "../../../utils";
import descriptorsStore from "../../../data/descriptors-store";
@Component({
tag: 'elsa-variables-editor',
@ -24,6 +25,7 @@ export class VariablesEditor {
render() {
const variables = this.variables;
const storageDrivers: Array<StorageDriverDescriptor> = descriptorsStore.storageDrivers;
return (
<div>
@ -43,7 +45,8 @@ export class VariablesEditor {
</thead>
<tbody>
{variables.map(variable => {
const storageName = isNullOrWhitespace(variable.driveId) ? 'Transient' : variable.driveId;
const storage = storageDrivers.find(x => x.id == variable.storageDriverId);
const storageName = storage?.displayName ?? 'None';
return (
<tr>

View file

@ -8,7 +8,7 @@ import {
ActivityDescriptor,
ActivitySelectedArgs,
ContainerSelectedArgs,
GraphUpdatedArgs,
GraphUpdatedArgs, StorageDriverDescriptor,
WorkflowDefinition
} from '../../../models';
import WorkflowEditorTunnel, {WorkflowDesignerState} from '../state';
@ -50,8 +50,8 @@ export class WorkflowDefinitionEditor {
@Prop() workflowDefinition?: WorkflowDefinition;
@Prop({attribute: 'monaco-lib-path'}) public monacoLibPath: string;
@Prop() public activityDescriptors: Array<ActivityDescriptor> = [];
@Event() public workflowUpdated: EventEmitter<WorkflowDefinitionUpdatedArgs>
@Prop() activityDescriptors: Array<ActivityDescriptor> = [];
@Event() workflowUpdated: EventEmitter<WorkflowDefinitionUpdatedArgs>
@State() private workflowDefinitionState: WorkflowDefinition;
@State() private selectedActivity?: Activity;

View file

@ -128,6 +128,14 @@ export class WorkflowDefinitionPropertiesEditor {
}
}
// const dataDrivesTabModel: TabModel = {
// name: 'dataDrives',
// tab: {
// displayText: 'Data Drives',
// content: () => this.renderDataDrivesTab()
// }
// }
model.tabModels = [propertiesTabModel, variablesTabModel];
const args: WorkflowPropertiesEditorDisplayingArgs = {model};
@ -149,10 +157,18 @@ export class WorkflowDefinitionPropertiesEditor {
const variables: Array<Variable> = this.workflowDefinition?.variables ?? [];
return <div>
<elsa-variables-editor variables={variables} onVariablesChanged={e => this.onVariablesUpdated(e)} />
<elsa-variables-editor variables={variables} onVariablesChanged={e => this.onVariablesUpdated(e)}/>
</div>
};
// private renderDataDrivesTab = () => {
// const dataDrives: Array<DataDriveDefinition> = this.workflowDefinition?.dataDrives ?? [];
//
// return <div>
// <elsa-data-drives-editor dataDrives={dataDrives} onDataDrivesUpdated={e => this.onDataDrivesUpdated(e)}/>
// </div>
// };
private onSelectedTabIndexChanged = (e: CustomEvent<TabChangedArgs>) => this.selectedTabIndex = e.detail.selectedTabIndex;
private onPropertyEditorChanged = (apply: (w: WorkflowDefinition) => void) => {
@ -164,7 +180,7 @@ export class WorkflowDefinitionPropertiesEditor {
private onVariablesUpdated = async (e: CustomEvent<Array<Variable>>) => {
const workflowDefinition = this.workflowDefinition;
if(!workflowDefinition)
if (!workflowDefinition)
return;
const variables = e.detail;
@ -172,6 +188,18 @@ export class WorkflowDefinitionPropertiesEditor {
this.workflowPropsUpdated.emit({workflowDefinition});
await this.createModel();
}
// private onDataDrivesUpdated = async (e: CustomEvent<Array<DataDriveDefinition>>) => {
// const workflowDefinition = this.workflowDefinition;
//
// if (!workflowDefinition)
// return;
//
// const dataDrives = e.detail;
// workflowDefinition.dataDrives = dataDrives;
// this.workflowPropsUpdated.emit({workflowDefinition});
// await this.createModel();
// }
}
WorkflowEditorTunnel.injectProps(WorkflowDefinitionPropertiesEditor, ['activityDescriptors']);

View file

@ -15,6 +15,7 @@ import {ExportWorkflowRequest, ImportWorkflowRequest} from "../../../services/ap
import {WorkflowDefinitionManager} from "../../../services/workflow-definition-manager";
import {WorkflowDefinitionUpdatedArgs} from "../../designer/workflow-definition-editor/models";
import {Flowchart} from "../../activities/flowchart/models";
import descriptorsStore from '../../../data/descriptors-store';
@Component({
tag: 'elsa-studio'
@ -193,6 +194,10 @@ export class Studio {
this.elsaClient = await elsaClientProvider.getElsaClient();
this.activityDescriptors = await this.elsaClient.descriptors.activities.list();
const storageDrivers = await this.elsaClient.descriptors.storageDrivers.list();
descriptorsStore.activityDescriptors = this.activityDescriptors;
descriptorsStore.storageDrivers = storageDrivers;
this.workflowManagerElement = this.el.getElementsByTagName('elsa-workflow-manager')[0] as HTMLElsaWorkflowManagerElement;

View file

@ -0,0 +1,8 @@
import { createStore } from '@stencil/store';
const { state, onChange } = createStore({
activityDescriptors: [],
storageDrivers: []
});
export default state;

View file

@ -125,3 +125,8 @@ export interface IntellisenseContext {
activityTypeName: string;
propertyName: string;
}
export interface StorageDriverDescriptor {
id: string;
displayName: string;
}

View file

@ -25,7 +25,7 @@ export interface Variable {
name: string;
type: string;
value?: any;
driveId?: string;
storageDriverId?: string;
}
export interface ActivityInput {
@ -98,10 +98,6 @@ export interface WorkflowExecutionLogRecord {
eventName: string;
}
export interface DataDrive {
id: string;
}
export enum SyntaxNames {
Literal = 'Literal',
JavaScript = 'JavaScript',

View file

@ -1,17 +1,21 @@
import {ActivityDescriptorsApi, ActivityDescriptorsApiImpl} from "./activity-descriptors-api";
import {AxiosInstance} from "axios";
import {StorageDriversApi, StorageDriversApiImpl} from "./storage-drivers-api";
export interface DescriptorsApi {
activities: ActivityDescriptorsApi;
storageDrivers: StorageDriversApi;
}
export class DescriptorsApiImpl implements DescriptorsApi {
httpClient: AxiosInstance;
activities: ActivityDescriptorsApi;
storageDrivers: StorageDriversApi;
constructor(httpClient: AxiosInstance) {
this.httpClient = httpClient;
this.activities = new ActivityDescriptorsApiImpl(httpClient);
this.storageDrivers = new StorageDriversApiImpl(httpClient);
}
}

View file

@ -0,0 +1,23 @@
import {ActivityDescriptor, StorageDriverDescriptor} from "../../models";
import {AxiosInstance} from "axios";
export interface StorageDriversApi {
list(): Promise<Array<StorageDriverDescriptor>>;
}
export interface StorageDriversResponse {
items: Array<StorageDriverDescriptor>;
}
export class StorageDriversApiImpl implements StorageDriversApi {
private httpClient: AxiosInstance;
constructor(httpClient: AxiosInstance) {
this.httpClient = httpClient;
}
async list(): Promise<Array<StorageDriverDescriptor>> {
const response = await this.httpClient.get<StorageDriversResponse>('descriptors/storage-drivers');
return response.data.items;
}
}

View file

@ -9,4 +9,5 @@ public class ControllerNames
public const string Labels = "Labels";
public const string WorkflowDefinitionLabels = "WorkflowDefinitionLabels";
public const string WorkflowJournal = "WorkflowJournal";
public const string StorageDrivers = "StorageDrivers";
}

View file

@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Linq;
using Elsa.AspNetCore.Attributes;
using Elsa.AspNetCore.Models;
using Elsa.Workflows.Core.Serialization;
using Elsa.Workflows.Core.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
// ReSharper disable NotAccessedPositionalProperty.Global
namespace Elsa.Workflows.Api.Endpoints.StorageDrivers;
[Area(AreaNames.Elsa)]
[ApiEndpoint(ControllerNames.StorageDrivers, "List")]
[ProducesResponseType(typeof(StorageDrivers), StatusCodes.Status200OK, "application/json")]
public class List : Controller
{
private readonly IStorageDriverManager _registry;
private readonly WorkflowSerializerOptionsProvider _serializerOptionsProvider;
public List(IStorageDriverManager registry, WorkflowSerializerOptionsProvider serializerOptionsProvider)
{
_registry = registry;
_serializerOptionsProvider = serializerOptionsProvider;
}
[HttpGet]
public IActionResult Handle()
{
var drivers = _registry.List();
var descriptors = drivers.Select(x => new StorageDriverDescriptor(x.Id, x.DisplayName)).ToList();
var model = new StorageDrivers(descriptors);
var serializerOptions = _serializerOptionsProvider.CreateApiOptions();
return Json(model, serializerOptions);
}
}
public record StorageDrivers(ICollection<StorageDriverDescriptor> Items) : ListModel<StorageDriverDescriptor>(Items);
public record StorageDriverDescriptor(string Id, string DisplayName);

View file

@ -43,6 +43,9 @@ public static class EndpointRouteBuilderExtensions
// Workflow Journal.
Map("WorkflowJournal.Get", "workflow-instances/{id}/journal", new { Controller = ControllerNames.WorkflowJournal, Action = "Get" });
// Storage Drivers.
Map("StorageDrivers.List", "descriptors/storage-drivers", new { Controller = ControllerNames.StorageDrivers, Action = "List" });
return endpoints;
}

View file

@ -28,7 +28,7 @@ public class VariableDefinitionMapper
variable.Name = source.Name;
variable.Value = source.Value.ConvertTo(type);
variable.DriveId = source.DriveId;
variable.StorageDriverId = source.StorageDriverId;
return variable;
}
@ -46,11 +46,11 @@ public class VariableDefinitionMapper
var value = source.Value;
var valueType = source.Value?.GetType() ?? (variableType.IsConstructedGenericType ? variableType.GetGenericArguments().FirstOrDefault() ?? typeof(object) : typeof(object));
var valueTypeAlias = _wellKnownTypeRegistry.GetAliasOrDefault(valueType);
var driveId = source.DriveId;
var driverId = source.StorageDriverId;
var serializedValue = value.Format();
return new VariableDefinition(source.Name, valueTypeAlias, serializedValue, driveId);
return new VariableDefinition(source.Name, valueTypeAlias, serializedValue, driverId);
}
public IEnumerable<VariableDefinition> Map(IEnumerable<Variable>? source) => source?.Select(Map) ?? Enumerable.Empty<VariableDefinition>();

View file

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

View file

@ -39,28 +39,28 @@ public class WorkflowDefinitionBuilder : IWorkflowDefinitionBuilder
return this;
}
public Variable<T> WithVariable<T>(string? driveId = default)
public Variable<T> WithVariable<T>(string? storageDriverId = default)
{
var variable = new Variable<T>()
{
DriveId = driveId
StorageDriverId = storageDriverId
};
Variables.Add(variable);
return variable;
}
public Variable<T> WithVariable<T>(string name, T value, string? driveId = default)
public Variable<T> WithVariable<T>(string name, T value, string? storageDriverId = default)
{
var variable = value != null ? new Variable<T>(name, value) : new Variable<T>(name);
variable.DriveId = driveId;
variable.StorageDriverId = storageDriverId;
Variables.Add(variable);
return variable;
}
public Variable<T> WithVariable<T>(T value, string? driveId = default)
public Variable<T> WithVariable<T>(T value, string? storageDriverId = default)
{
var variable = value != null ? new Variable<T>(value) : new Variable<T>();
variable.DriveId = driveId;
variable.StorageDriverId = storageDriverId;
Variables.Add(variable);
return variable;
}

View file

@ -13,8 +13,8 @@ public static class DependencyInjectionExtensions
return configuration;
}
public static IServiceCollection AddDataDrive<T>(this IServiceCollection services) where T : class, IDataDrive
public static IServiceCollection AddDataDrive<T>(this IServiceCollection services) where T : class, IStorageDriver
{
return services.AddSingleton<IDataDrive, T>();
return services.AddSingleton<IStorageDriver, T>();
}
}

View file

@ -4,12 +4,12 @@ namespace Elsa.Workflows.Core;
public static class VariableExtensions
{
public static Variable WithWorkflowDrive(this Variable variable) => variable.WithDrive(DataDriveNames.Workflow);
public static Variable WithMemoryDrive(this Variable variable) => variable.WithDrive(DataDriveNames.Memory);
public static Variable WithWorkflowDrive(this Variable variable) => variable.WithStorage(StorageDriverNames.Workflow);
public static Variable WithMemoryDrive(this Variable variable) => variable.WithStorage(StorageDriverNames.Memory);
public static Variable WithDrive(this Variable variable, string driveId)
public static Variable WithStorage(this Variable variable, string storageDriverId)
{
variable.DriveId = driveId;
variable.StorageDriverId = storageDriverId;
return variable;
}
}

View file

@ -81,9 +81,9 @@ public class WorkflowsFeature : FeatureBase
.AddSingleton(StandardOutStreamProvider)
// Data drives.
.AddSingleton<IDataDriveManager, DataDriveManager>()
.AddDataDrive<WorkflowStateDataDrive>()
.AddDataDrive<MemoryDataDrive>()
.AddSingleton<IStorageDriverManager, StorageDriverManager>()
.AddDataDrive<WorkflowStateStorageDriver>()
.AddDataDrive<MemoryStorageDriver>()
// Logging
.AddLogging();

View file

@ -1,10 +0,0 @@
using Elsa.Workflows.Core.Services;
namespace Elsa.Workflows.Core.Implementations;
public class DataDriveManager : IDataDriveManager
{
private readonly IEnumerable<IDataDrive> _drives;
public DataDriveManager(IEnumerable<IDataDrive> drives) => _drives = drives;
public IDataDrive? GetDriveById(string id) => _drives.FirstOrDefault(x => x.Id == id);
}

View file

@ -2,10 +2,11 @@ using Elsa.Workflows.Core.Services;
namespace Elsa.Workflows.Core.Implementations;
public class MemoryDataDrive : IDataDrive
public class MemoryStorageDriver : IStorageDriver
{
private readonly IDictionary<string, object> _dictionary = new Dictionary<string, object>();
public string Id => DataDriveNames.Memory;
public string Id => StorageDriverNames.Memory;
public string DisplayName => StorageDriverNames.Memory;
public ValueTask WriteAsync(string id, object value, DataDriveContext context)
{

View file

@ -0,0 +1,11 @@
using Elsa.Workflows.Core.Services;
namespace Elsa.Workflows.Core.Implementations;
public class StorageDriverManager : IStorageDriverManager
{
private readonly IEnumerable<IStorageDriver> _drivers;
public StorageDriverManager(IEnumerable<IStorageDriver> drivers) => _drivers = drivers;
public IStorageDriver? GetDriveById(string id) => _drivers.FirstOrDefault(x => x.Id == id);
public IEnumerable<IStorageDriver> List() => _drivers;
}

View file

@ -168,8 +168,8 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
var workflow = workflowExecutionContext.Workflow;
state.PersistentVariables = workflow.Variables
.Where(x => x.DriveId != null)
.Select(x => new PersistentVariableState(x.Name, x.DriveId!))
.Where(x => x.StorageDriverId != null)
.Select(x => new PersistentVariableState(x.Name, x.StorageDriverId!))
.ToList();
}

View file

@ -5,10 +5,11 @@ namespace Elsa.Workflows.Core.Implementations;
/// <summary>
/// A data drive that stores objects in the workflow state itself.
/// </summary>
public class WorkflowStateDataDrive : IDataDrive
public class WorkflowStateStorageDriver : IStorageDriver
{
public const string VariablesDictionaryStateKey = "PersistentVariablesDictionary";
public string Id => DataDriveNames.Workflow;
public string Id => StorageDriverNames.Workflow;
public string DisplayName => StorageDriverNames.Workflow;
public ValueTask WriteAsync(string id, object value, DataDriveContext context)
{

View file

@ -1,25 +0,0 @@
using System.Text.Json.Serialization;
namespace Elsa.Workflows.Core.Models;
/// <summary>
/// Represents a workflow-specific memory drive definition that is configured with a specific storage driver.
/// </summary>
public class DataDriveDefinition
{
[JsonConstructor]
public DataDriveDefinition()
{
}
public DataDriveDefinition(string id, string driverId, string displayName)
{
Id = id;
DriverId = driverId;
DisplayName = displayName;
}
public string Id { get; set; } = default!;
public string DriverId { get; set; } = default!;
public string DisplayName { get; set; } = default!;
}

View file

@ -29,9 +29,9 @@ public class Variable : MemoryReference
public object? Value { get; set; }
/// <summary>
/// When specified, the variable's value will be stored on the specified data drive.
/// When specified, the variable's value will be stored on the specified <see cref="Elsa.Workflows.Core.Services.IStorageDriver"/>.
/// </summary>
public string? DriveId { get; set; }
public string? StorageDriverId { get; set; }
public override MemoryBlock Declare() => new(Value);
}

View file

@ -47,15 +47,6 @@ public class Workflow : Composite, ICloneable
public WorkflowPublication Publication { get; set; }
public WorkflowMetadata WorkflowMetadata { get; set; }
public ICollection<Variable> Variables { get; init; }
/// <summary>
/// A list of storage drive definitions that can be used by variables to persist their values.
/// </summary>
/// <remarks>
/// Depending on the drive's driver capabilities, only serializable variables can be persisted.
/// </remarks>
public ICollection<DataDriveDefinition> Drives { get; set; } = new List<DataDriveDefinition>();
public Workflow Clone() => (Workflow)((ICloneable)this).Clone();
object ICloneable.Clone() => MemberwiseClone();
}

View file

@ -42,7 +42,7 @@ public class VariableConverter : JsonConverter<Variable>
variable.Name = source.Name;
variable.Value = source.Value.ConvertTo(type);
variable.DriveId = source.DriveId;
variable.StorageDriverId = source.StorageDriverId;
return variable;
}
@ -53,10 +53,10 @@ public class VariableConverter : JsonConverter<Variable>
var value = source.Value;
var valueType = source.Value?.GetType() ?? (variableType.IsConstructedGenericType ? variableType.GetGenericArguments().FirstOrDefault() ?? typeof(object) : typeof(object));
var valueTypeAlias = _wellKnownTypeRegistry.GetAliasOrDefault(valueType);
var driveId = source.DriveId;
var driverId = source.StorageDriverId;
var serializedValue = value.Format();
return new VariableModel(source.Name, valueTypeAlias, serializedValue, driveId);
return new VariableModel(source.Name, valueTypeAlias, serializedValue, driverId);
}
public class VariableModel
@ -66,17 +66,17 @@ public class VariableConverter : JsonConverter<Variable>
{
}
public VariableModel(string name, string typeName, string? value, string? driveId)
public VariableModel(string name, string typeName, string? value, string? storageDriverId)
{
Name = name;
TypeName = typeName;
Value = value;
DriveId = driveId;
StorageDriverId = storageDriverId;
}
public string Name { get; set; } = default!;
public string TypeName { get; set; } = default!;
public string? Value { get; set; }
public string? DriveId { get; set; }
public string? StorageDriverId { get; set; }
}
}

View file

@ -1,6 +0,0 @@
namespace Elsa.Workflows.Core.Services;
public interface IDataDriveManager
{
IDataDrive? GetDriveById(string id);
}

View file

@ -5,9 +5,10 @@ namespace Elsa.Workflows.Core.Services;
/// <summary>
/// Represents a storage driver for workflows to write state to.
/// </summary>
public interface IDataDrive
public interface IStorageDriver
{
string Id { get; }
string DisplayName { get; }
ValueTask WriteAsync(string id, object value, DataDriveContext context);
ValueTask<object?> ReadAsync(string id, DataDriveContext context);
ValueTask DeleteAsync(string id, DataDriveContext context);

View file

@ -0,0 +1,7 @@
namespace Elsa.Workflows.Core.Services;
public interface IStorageDriverManager
{
IStorageDriver? GetDriveById(string id);
IEnumerable<IStorageDriver> List();
}

View file

@ -12,9 +12,9 @@ public interface IWorkflowDefinitionBuilder
IWorkflowDefinitionBuilder WithDefinitionId(string definitionId);
IWorkflowDefinitionBuilder WithVersion(int version);
IWorkflowDefinitionBuilder WithRoot(IActivity root);
Variable<T> WithVariable<T>(string? driveId = default);
Variable<T> WithVariable<T>(string name, T value, string? driveId = default);
Variable<T> WithVariable<T>(T value, string? driveId = default);
Variable<T> WithVariable<T>(string? storageDriverId = default);
Variable<T> WithVariable<T>(string name, T value, string? storageDriverId = default);
Variable<T> WithVariable<T>(T value, string? storageDriverId = default);
IWorkflowDefinitionBuilder WithVariable(Variable variable);
IWorkflowDefinitionBuilder WithVariables(params Variable[] variables);
IWorkflowDefinitionBuilder WithApplicationProperty(string name, object value);

View file

@ -9,12 +9,12 @@ public class PersistentVariableState
{
}
public PersistentVariableState(string name, string driveId)
public PersistentVariableState(string name, string storageDriverId)
{
Name = name;
DriveId = driveId;
StorageDriverId = storageDriverId;
}
public string Name { get; set; } = default!;
public string DriveId { get; set; } = default!;
public string StorageDriverId { get; set; } = default!;
}

View file

@ -1,6 +1,6 @@
namespace Elsa.Workflows.Core;
public static class DataDriveNames
public static class StorageDriverNames
{
public const string Workflow = "Workflow";
public const string Memory = "Memory";

View file

@ -14,7 +14,7 @@ public class WorkflowDefinitionSerializer : IEntitySerializer<WorkflowsDbContext
{
_workflowSerializerOptionsProvider = workflowSerializerOptionsProvider;
}
public void Serialize(WorkflowsDbContext dbContext, WorkflowDefinition entity)
{
var data = new
@ -34,20 +34,20 @@ public class WorkflowDefinitionSerializer : IEntitySerializer<WorkflowsDbContext
public void Deserialize(WorkflowsDbContext dbContext, WorkflowDefinition entity)
{
var data = new WorkflowDefinitionState(entity.Variables, entity.Tags, entity.Metadata, entity.ApplicationProperties);
var json = (string?) dbContext.Entry(entity).Property("Data").CurrentValue;
var json = (string?)dbContext.Entry(entity).Property("Data").CurrentValue;
if (!string.IsNullOrWhiteSpace(json))
{
var options = _workflowSerializerOptionsProvider.CreatePersistenceOptions();
data = JsonSerializer.Deserialize<WorkflowDefinitionState>(json, options)!;
}
entity.Variables = data.Variables;
entity.Tags = data.Tags;
entity.Metadata = data.Metadata;
entity.ApplicationProperties = data.ApplicationProperties;
}
// Can't use records when using System.Text.Json serialization and reference handling. Hence, using a class with default constructor.
private class WorkflowDefinitionState
{
@ -55,14 +55,18 @@ public class WorkflowDefinitionSerializer : IEntitySerializer<WorkflowsDbContext
{
}
public WorkflowDefinitionState(ICollection<Variable> variables, ICollection<string> tags, IDictionary<string, object> metadata, IDictionary<string, object> applicationProperties)
public WorkflowDefinitionState(
ICollection<Variable> variables,
ICollection<string> tags,
IDictionary<string, object> metadata,
IDictionary<string, object> applicationProperties)
{
Variables = variables;
Tags = tags;
Metadata = metadata;
ApplicationProperties = applicationProperties;
}
public ICollection<Variable> Variables { get; set; } = new List<Variable>();
public ICollection<string> Tags { get; set; } = new List<string>();
public IDictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();

View file

@ -25,7 +25,7 @@ public class PersistWorkflowInstanceMiddleware : WorkflowExecutionMiddleware
private readonly IRequestSender _requestSender;
private readonly IEventPublisher _eventPublisher;
private readonly IWorkflowStateSerializer _workflowStateSerializer;
private readonly IDataDriveManager _dataDriveManager;
private readonly IStorageDriverManager _storageDriverManager;
private readonly IBookmarkManager _bookmarkManager;
private readonly IIdentityGenerator _identityGenerator;
private readonly ISystemClock _clock;
@ -38,7 +38,7 @@ public class PersistWorkflowInstanceMiddleware : WorkflowExecutionMiddleware
IEventPublisher eventPublisher,
IBookmarkManager bookmarkManager,
IWorkflowStateSerializer workflowStateSerializer,
IDataDriveManager dataDriveManager,
IStorageDriverManager storageDriverManager,
IIdentityGenerator identityGenerator,
ISystemClock clock) : base(next)
{
@ -48,7 +48,7 @@ public class PersistWorkflowInstanceMiddleware : WorkflowExecutionMiddleware
_eventPublisher = eventPublisher;
_bookmarkManager = bookmarkManager;
_workflowStateSerializer = workflowStateSerializer;
_dataDriveManager = dataDriveManager;
_storageDriverManager = storageDriverManager;
_identityGenerator = identityGenerator;
_clock = clock;
}
@ -104,7 +104,7 @@ public class PersistWorkflowInstanceMiddleware : WorkflowExecutionMiddleware
foreach (var variableState in workflowInstance.WorkflowState.PersistentVariables)
{
var drive = _dataDriveManager.GetDriveById(variableState.DriveId);
var drive = _storageDriverManager.GetDriveById(variableState.StorageDriverId);
if (drive == null) continue;
var id = $"{context.Id}:{variableState.Name}";
var value = await drive.ReadAsync(id, dataDriveContext);
@ -150,7 +150,7 @@ public class PersistWorkflowInstanceMiddleware : WorkflowExecutionMiddleware
foreach (var variableState in workflowState.PersistentVariables)
{
var drive = _dataDriveManager.GetDriveById(variableState.DriveId);
var drive = _storageDriverManager.GetDriveById(variableState.StorageDriverId);
if (drive == null) continue;
if (!context.MemoryRegister.TryGetBlock(variableState.Name, out var block)) continue;
if (block.Value == null) continue;

View file

@ -76,7 +76,7 @@ public class WorkflowInstancePersistenceTests
workflowState = result.WorkflowState;
// Assert that the expected variable is persisted.
var persistentVariablesDictionary = (IDictionary<string, object>)workflowState.Properties[WorkflowStateDataDrive.VariablesDictionaryStateKey];
var persistentVariablesDictionary = (IDictionary<string, object>)workflowState.Properties[WorkflowStateStorageDriver.VariablesDictionaryStateKey];
var stateId = $"{workflowState.Id}:{variable.Name}";
var persistedValue = persistentVariablesDictionary[stateId];
var expectedValue = languages[currentIndex];