Implement storage driver API endpoint
This commit is contained in:
parent
6309c3494f
commit
f93a051a14
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import { createStore } from '@stencil/store';
|
||||
|
||||
const { state, onChange } = createStore({
|
||||
activityDescriptors: [],
|
||||
storageDrivers: []
|
||||
});
|
||||
|
||||
export default state;
|
||||
|
|
@ -125,3 +125,8 @@ export interface IntellisenseContext {
|
|||
activityTypeName: string;
|
||||
propertyName: string;
|
||||
}
|
||||
|
||||
export interface StorageDriverDescriptor {
|
||||
id: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
@ -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!;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace Elsa.Workflows.Core.Services;
|
||||
|
||||
public interface IDataDriveManager
|
||||
{
|
||||
IDataDrive? GetDriveById(string id);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Elsa.Workflows.Core.Services;
|
||||
|
||||
public interface IStorageDriverManager
|
||||
{
|
||||
IStorageDriver? GetDriveById(string id);
|
||||
IEnumerable<IStorageDriver> List();
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
Loading…
Reference in a new issue