Update models and UI to support configurable activity output storage
This commit is contained in:
parent
332741d54f
commit
1db05d3b0b
|
|
@ -102,6 +102,8 @@ namespace Elsa.Activities.Http
|
|||
)]
|
||||
public ICollection<int>? SupportedStatusCodes { get; set; } = new HashSet<int>(new[] { 200 });
|
||||
|
||||
[ActivityOutput] public HttpResponseModel Response { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var request = CreateRequest();
|
||||
|
|
@ -126,12 +128,14 @@ namespace Elsa.Activities.Http
|
|||
|
||||
var statusCode = (int) response.StatusCode;
|
||||
var statusOutcome = statusCode.ToString();
|
||||
var isSupportedStatusCode = SupportedStatusCodes?.Contains(statusCode) == true;
|
||||
var supportedStatusCodes = SupportedStatusCodes;
|
||||
var isSupportedStatusCode = supportedStatusCodes == null || !supportedStatusCodes.Any() || SupportedStatusCodes?.Contains(statusCode) == true;
|
||||
var outcomes = new List<string> { OutcomeNames.Done, statusOutcome };
|
||||
|
||||
if (!isSupportedStatusCode)
|
||||
outcomes.Add("Unsupported Status Code");
|
||||
|
||||
Response = responseModel;
|
||||
return Combine(Output(responseModel), Outcomes(outcomes));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ namespace Elsa.Client.Models
|
|||
[DataMember(Order = 8)] public bool PersistWorkflow { get; set; }
|
||||
[DataMember(Order = 9)] public bool LoadWorkflowContext { get; set; }
|
||||
[DataMember(Order = 10)] public bool SaveWorkflowContext { get; set; }
|
||||
[DataMember(Order = 11)] public ICollection<ActivityDefinitionProperty> Properties { get; set; } = new List<ActivityDefinitionProperty>();
|
||||
[DataMember(Order = 11)] public string? OutputStorageProviderName { get; set; }
|
||||
[DataMember(Order = 12)] public ICollection<ActivityDefinitionProperty> Properties { get; set; } = new List<ActivityDefinitionProperty>();
|
||||
}
|
||||
}
|
||||
|
|
@ -33,18 +33,17 @@ namespace Elsa.Client.Models
|
|||
[DataMember(Order = 14)] public Variables Variables { get; set; }
|
||||
[DataMember(Order = 15)] public object? Output { get; set; }
|
||||
[DataMember(Order = 16)] public IDictionary<string, JObject> ActivityData { get; set; } = new Dictionary<string, JObject>();
|
||||
[DataMember(Order = 17)] public IDictionary<string, object> ActivityOutput { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
[DataMember(Order = 18)]
|
||||
[DataMember(Order = 17)]
|
||||
public HashSet<BlockingActivity> BlockingActivities
|
||||
{
|
||||
get => _blockingActivities;
|
||||
set => _blockingActivities = new HashSet<BlockingActivity>(value, BlockingActivityEqualityComparer.Instance);
|
||||
}
|
||||
|
||||
[DataMember(Order = 19)] public WorkflowFault? Fault { get; set; }
|
||||
[DataMember(Order = 20)] public SimpleStack<ScheduledActivity> ScheduledActivities { get; set; }
|
||||
[DataMember(Order = 21)] public SimpleStack<ActivityScope> Scopes { get; set; }
|
||||
[DataMember(Order = 22)] public ScheduledActivity? CurrentActivity { get; set; }
|
||||
[DataMember(Order = 18)] public WorkflowFault? Fault { get; set; }
|
||||
[DataMember(Order = 19)] public SimpleStack<ScheduledActivity> ScheduledActivities { get; set; }
|
||||
[DataMember(Order = 20)] public SimpleStack<ActivityScope> Scopes { get; set; }
|
||||
[DataMember(Order = 21)] public ScheduledActivity? CurrentActivity { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -21,8 +21,9 @@ namespace Elsa.ActivityResults
|
|||
{
|
||||
var workflowStorageService = activityExecutionContext.GetService<IWorkflowStorageService>();
|
||||
var workflowStorageContext = new WorkflowStorageContext(activityExecutionContext.WorkflowInstance, activityExecutionContext.ActivityId);
|
||||
await workflowStorageService.SaveAsync(StorageProviderName, workflowStorageContext, ActivityOutput.PropertyName, Output, cancellationToken);
|
||||
activityExecutionContext.WorkflowInstance.Output = new WorkflowOutputReference(StorageProviderName, workflowStorageContext.ActivityId);
|
||||
var storageProviderName = StorageProviderName ?? activityExecutionContext.ActivityBlueprint.OutputStorageProviderName;
|
||||
await workflowStorageService.SaveAsync(storageProviderName, workflowStorageContext, ActivityOutput.PropertyName, Output, cancellationToken);
|
||||
activityExecutionContext.WorkflowInstance.Output = new WorkflowOutputReference(storageProviderName, workflowStorageContext.ActivityId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@ namespace Elsa.Models
|
|||
public bool PersistWorkflow { get; set; }
|
||||
public bool LoadWorkflowContext { get; set; }
|
||||
public bool SaveWorkflowContext { get; set; }
|
||||
public string? OutputStorageProviderName { get; set; }
|
||||
public ICollection<ActivityDefinitionProperty> Properties { get; set; } = new List<ActivityDefinitionProperty>();
|
||||
public IDictionary<string, string> PropertyStorageProviders { get; } = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,6 @@ namespace Elsa.Models
|
|||
public Variables Variables { get; set; }
|
||||
public WorkflowOutputReference? Output { get; set; }
|
||||
public IDictionary<string, IDictionary<string, object>> ActivityData { get; set; } = new Dictionary<string, IDictionary<string, object>>();
|
||||
public IDictionary<string, object> ActivityOutput { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
public HashSet<BlockingActivity> BlockingActivities
|
||||
{
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ namespace Elsa.Services.Models
|
|||
public bool PersistWorkflow { get; set; }
|
||||
public bool LoadWorkflowContext { get; set; }
|
||||
public bool SaveWorkflowContext { get; set; }
|
||||
public IDictionary<string, string> PropertyStorageProviders { get; } = new Dictionary<string, string>();
|
||||
public string? OutputStorageProviderName { get; }
|
||||
public IDictionary<string, string> PropertyStorageProviders { get; set; } = new Dictionary<string, string>();
|
||||
public string? OutputStorageProviderName { get; set; }
|
||||
public string? Source { get; set; }
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Providers.WorkflowStorage;
|
||||
|
||||
|
|
@ -7,6 +8,7 @@ namespace Elsa.Services.WorkflowStorage
|
|||
public interface IWorkflowStorageService
|
||||
{
|
||||
IWorkflowStorageProvider GetProviderByNameOrDefault(string? providerName = null);
|
||||
IEnumerable<IWorkflowStorageProvider> ListProviders();
|
||||
ValueTask SaveAsync(string? providerName, WorkflowStorageContext context, string key, object? value, CancellationToken cancellationToken = default);
|
||||
ValueTask<object?> LoadAsync(string? providerName, WorkflowStorageContext context, string key, CancellationToken cancellationToken = default);
|
||||
ValueTask DeleteAsync(string? providerName, WorkflowStorageContext context, string key, CancellationToken cancellationToken = default);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace Elsa.Services.WorkflowStorage
|
|||
}
|
||||
|
||||
public IWorkflowStorageProvider GetProviderByNameOrDefault(string? providerName = default) => providerName != null ? _providersLookup.GetItem(providerName) ?? _defaultStorageProvider : _defaultStorageProvider;
|
||||
public IEnumerable<IWorkflowStorageProvider> ListProviders() => _providersLookup.Values;
|
||||
|
||||
public async ValueTask SaveAsync(string? providerName, WorkflowStorageContext context, string key, object? value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -137,7 +137,9 @@ namespace Elsa.Services.Workflows
|
|||
PersistWorkflow = activityDefinition.PersistWorkflow,
|
||||
LoadWorkflowContext = activityDefinition.LoadWorkflowContext,
|
||||
SaveWorkflowContext = activityDefinition.SaveWorkflowContext,
|
||||
ActivityPropertyProviders = await CreatePropertyProviders(compositeActivityDefinition, cancellationToken)
|
||||
ActivityPropertyProviders = await CreatePropertyProviders(compositeActivityDefinition, cancellationToken),
|
||||
OutputStorageProviderName = activityDefinition.OutputStorageProviderName,
|
||||
PropertyStorageProviders = activityDefinition.PropertyStorageProviders
|
||||
};
|
||||
|
||||
list.Add(compositeActivityBlueprint);
|
||||
|
|
@ -158,6 +160,8 @@ namespace Elsa.Services.Workflows
|
|||
PersistWorkflowEnabled = activityDefinition.PersistWorkflow,
|
||||
LoadWorkflowContextEnabled = activityDefinition.LoadWorkflowContext,
|
||||
SaveWorkflowContextEnabled = activityDefinition.SaveWorkflowContext,
|
||||
OutputStorageProviderName = activityDefinition.OutputStorageProviderName,
|
||||
PropertyStorageProviders = activityDefinition.PropertyStorageProviders
|
||||
};
|
||||
|
||||
compositeActivity.Build(compositeActivityBuilder);
|
||||
|
|
@ -183,6 +187,8 @@ namespace Elsa.Services.Workflows
|
|||
PersistWorkflow = activityDefinition.PersistWorkflow,
|
||||
LoadWorkflowContext = activityDefinition.LoadWorkflowContext,
|
||||
SaveWorkflowContext = activityDefinition.SaveWorkflowContext,
|
||||
OutputStorageProviderName = activityDefinition.OutputStorageProviderName,
|
||||
PropertyStorageProviders = activityDefinition.PropertyStorageProviders
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
|
|||
import { ActivityDefinitionProperty, ActivityDescriptor, ActivityModel, ActivityPropertyDescriptor, SelectListItem, VersionOptions, WorkflowBlueprint, WorkflowDefinition, WorkflowExecutionLogRecord, WorkflowModel } from "./models";
|
||||
import { LocationSegments, MatchResults, RouterHistory } from "@stencil/router";
|
||||
import { MenuItem } from "./components/controls/elsa-context-menu/models";
|
||||
import { ActivityContextMenuState, WorkflowDesignerMode } from "./components/designers/tree/elsa-designer-tree/models";
|
||||
import { ActivityContextMenuState, LayoutDirection, WorkflowDesignerMode } from "./components/designers/tree/elsa-designer-tree/models";
|
||||
import { DropdownButtonItem, DropdownButtonOrigin } from "./components/controls/elsa-dropdown-button/models";
|
||||
import { MonacoValueChangedArgs } from "./components/controls/elsa-monaco/elsa-monaco";
|
||||
import { Map } from "./utils/utils";
|
||||
|
|
@ -39,6 +39,7 @@ export namespace Components {
|
|||
"activityBorderColor"?: (activity: ActivityModel) => string;
|
||||
"activityContextMenu"?: ActivityContextMenuState;
|
||||
"activityContextMenuButton"?: (activity: ActivityModel) => string;
|
||||
"layoutDirection": LayoutDirection;
|
||||
"mode": WorkflowDesignerMode;
|
||||
"model": WorkflowModel;
|
||||
"removeActivity": (activity: ActivityModel) => Promise<void>;
|
||||
|
|
@ -559,6 +560,7 @@ declare namespace LocalJSX {
|
|||
"activityBorderColor"?: (activity: ActivityModel) => string;
|
||||
"activityContextMenu"?: ActivityContextMenuState;
|
||||
"activityContextMenuButton"?: (activity: ActivityModel) => string;
|
||||
"layoutDirection"?: LayoutDirection;
|
||||
"mode"?: WorkflowDesignerMode;
|
||||
"model"?: WorkflowModel;
|
||||
"onActivityContextMenuButtonClicked"?: (event: CustomEvent<ActivityContextMenuState>) => void;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import * as d3 from 'd3';
|
|||
import dagreD3 from 'dagre-d3';
|
||||
import state from '../../../../utils/store';
|
||||
import {ActivityIcon} from '../../../icons/activity-icon';
|
||||
import {ActivityContextMenuState, WorkflowDesignerMode} from "./models";
|
||||
import {ActivityContextMenuState, LayoutDirection, WorkflowDesignerMode} from "./models";
|
||||
|
||||
@Component({
|
||||
tag: 'elsa-designer-tree',
|
||||
|
|
@ -22,6 +22,7 @@ export class ElsaWorkflowDesigner {
|
|||
@Prop() activityBorderColor?: (activity: ActivityModel) => string;
|
||||
@Prop() activityContextMenu?: ActivityContextMenuState;
|
||||
@Prop() mode: WorkflowDesignerMode = WorkflowDesignerMode.Edit;
|
||||
@Prop() layoutDirection: LayoutDirection = LayoutDirection.Vertical;
|
||||
@Event({eventName: 'workflow-changed', bubbles: true, composed: true, cancelable: true}) workflowChanged: EventEmitter<WorkflowModel>;
|
||||
@Event() activitySelected: EventEmitter<ActivityModel>;
|
||||
@Event() activityDeselected: EventEmitter<ActivityModel>;
|
||||
|
|
@ -164,6 +165,7 @@ export class ElsaWorkflowDesigner {
|
|||
outcomes: activityDescriptor.outcomes,
|
||||
displayName: activityDescriptor.displayName,
|
||||
properties: [],
|
||||
propertyStorageProviders: {}
|
||||
};
|
||||
|
||||
for (const property of activityDescriptor.inputProperties) {
|
||||
|
|
@ -316,6 +318,9 @@ export class ElsaWorkflowDesigner {
|
|||
setEntities() {
|
||||
this.graph = new dagreD3.graphlib.Graph().setGraph({});
|
||||
|
||||
const layoutDirection = this.layoutDirection;
|
||||
this.graph.graph().rankdir = layoutDirection == LayoutDirection.Vertical ? 'TB' : 'LR';
|
||||
|
||||
const rootActivities = this.getRootActivities();
|
||||
|
||||
// Start node.
|
||||
|
|
|
|||
|
|
@ -11,4 +11,9 @@ export interface ActivityContextMenuState {
|
|||
x: number;
|
||||
y: number;
|
||||
activity?: ActivityModel | null;
|
||||
}
|
||||
|
||||
export enum LayoutDirection {
|
||||
Horizontal,
|
||||
Vertical
|
||||
}
|
||||
|
|
@ -51,11 +51,11 @@ export class ElsaWorkflowBlueprintViewerScreen {
|
|||
isPublished: false,
|
||||
loadWorkflowContext: false,
|
||||
isSingleton: false,
|
||||
persistOutput: false,
|
||||
saveWorkflowContext: false,
|
||||
variables: {data: {}},
|
||||
type: null,
|
||||
properties: {data: {}}
|
||||
properties: {data: {}},
|
||||
propertyStorageProviders: {}
|
||||
};
|
||||
|
||||
const client = createElsaClient(this.serverUrl);
|
||||
|
|
@ -126,10 +126,11 @@ export class ElsaWorkflowBlueprintViewerScreen {
|
|||
type: source.type,
|
||||
properties: properties,
|
||||
outcomes: [...activityDescriptor.outcomes],
|
||||
persistOutput: source.persistOutput,
|
||||
outputStorageProviderName: source.outputStorageProviderName,
|
||||
persistWorkflow: source.persistWorkflow,
|
||||
saveWorkflowContext: source.saveWorkflowContext,
|
||||
loadWorkflowContext: source.loadWorkflowContext
|
||||
loadWorkflowContext: source.loadWorkflowContext,
|
||||
propertyStorageProviders: source.propertyStorageProviders
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import {Component, Host, h, State, Event} from '@stencil/core';
|
||||
import {Component, Event, h, Host, State} from '@stencil/core';
|
||||
import {eventBus} from '../../../../services/event-bus';
|
||||
import state from '../../../../utils/store';
|
||||
import {ActivityDescriptor, ActivityModel, ActivityPropertyDescriptor, EventTypes} from "../../../../models";
|
||||
import {ActivityDescriptor, ActivityModel, ActivityPropertyDescriptor, EventTypes, WorkflowStorageDescriptor} from "../../../../models";
|
||||
import {propertyDisplayManager} from '../../../../services/property-display-manager';
|
||||
import {checkBox, FormContext, textArea, textInput} from "../../../../utils/forms";
|
||||
import {checkBox, FormContext, section, selectField, SelectOption, textArea, textInput} from "../../../../utils/forms";
|
||||
|
||||
@Component({
|
||||
tag: 'elsa-activity-editor-modal',
|
||||
|
|
@ -11,6 +11,7 @@ import {checkBox, FormContext, textArea, textInput} from "../../../../utils/form
|
|||
})
|
||||
export class ElsaActivityEditorModal {
|
||||
|
||||
@State() workflowStorageDescriptors: Array<WorkflowStorageDescriptor> = [];
|
||||
@State() activityModel: ActivityModel;
|
||||
@State() activityDescriptor: ActivityDescriptor;
|
||||
@State() selectedTab: string = 'Properties';
|
||||
|
|
@ -18,7 +19,7 @@ export class ElsaActivityEditorModal {
|
|||
form: HTMLFormElement;
|
||||
formContext: FormContext;
|
||||
renderProps: any;
|
||||
|
||||
|
||||
// Force a new key every time we show the editor to make sure Stencil creates new components.
|
||||
// This prevents the issue where the designer has e.g. one activity where the user edits the properties, cancels out, then opens the editor again, seeing the entered value still there.
|
||||
timestamp: Date = new Date();
|
||||
|
|
@ -27,8 +28,9 @@ export class ElsaActivityEditorModal {
|
|||
eventBus.on(EventTypes.ShowActivityEditor, async (activity: ActivityModel, animate: boolean) => {
|
||||
this.activityModel = JSON.parse(JSON.stringify(activity));
|
||||
this.activityDescriptor = state.activityDescriptors.find(x => x.type == activity.type);
|
||||
this.workflowStorageDescriptors = state.workflowStorageDescriptors;
|
||||
this.formContext = new FormContext(this.activityModel, newValue => this.activityModel = newValue);
|
||||
this.selectedTab = 'Properties';
|
||||
this.selectedTab = 'Properties';
|
||||
this.timestamp = new Date();
|
||||
await this.dialog.show(animate);
|
||||
});
|
||||
|
|
@ -37,9 +39,9 @@ export class ElsaActivityEditorModal {
|
|||
updateActivity(formData: FormData) {
|
||||
const activity = this.activityModel;
|
||||
const activityDescriptor = this.activityDescriptor;
|
||||
const properties: Array<ActivityPropertyDescriptor> = activityDescriptor.inputProperties;
|
||||
const inputProperties: Array<ActivityPropertyDescriptor> = activityDescriptor.inputProperties;
|
||||
|
||||
for (const property of properties)
|
||||
for (const property of inputProperties)
|
||||
propertyDisplayManager.update(activity, property, formData);
|
||||
}
|
||||
|
||||
|
|
@ -61,25 +63,26 @@ export class ElsaActivityEditorModal {
|
|||
this.selectedTab = tab;
|
||||
}
|
||||
|
||||
componentWillRender(){
|
||||
const activityDescriptor: ActivityDescriptor = this.activityDescriptor || {displayName: '', type: '', outcomes: [], category: '', traits: 0, browsable: false, inputProperties: [], description: ''};
|
||||
componentWillRender() {
|
||||
const activityDescriptor: ActivityDescriptor = this.activityDescriptor || {displayName: '', type: '', outcomes: [], category: '', traits: 0, browsable: false, inputProperties: [], outputProperties: [], description: ''};
|
||||
const propertyCategories = activityDescriptor.inputProperties.filter(x => x.category).map(x => x.category).distinct();
|
||||
const defaultProperties = activityDescriptor.inputProperties.filter(x => !x.category || x.category.length == 0);
|
||||
let tabs: Array<string> = [];
|
||||
|
||||
if(defaultProperties.length > 0) {
|
||||
if (defaultProperties.length > 0) {
|
||||
tabs.push('Properties');
|
||||
}
|
||||
|
||||
tabs.push('Common');
|
||||
tabs.push('Behaviors');
|
||||
tabs = [...tabs, ...propertyCategories];
|
||||
tabs.push('Common');
|
||||
tabs.push('Storage');
|
||||
|
||||
let selectedTab = this.selectedTab;
|
||||
|
||||
if(tabs.findIndex(x => x === selectedTab) < 0)
|
||||
if (tabs.findIndex(x => x === selectedTab) < 0)
|
||||
selectedTab = tabs[0];
|
||||
|
||||
const activityModel: ActivityModel = this.activityModel || {type: '', activityId: '', outcomes: [], properties: []};
|
||||
const activityModel: ActivityModel = this.activityModel || {type: '', activityId: '', outcomes: [], properties: [], propertyStorageProviders: {}};
|
||||
|
||||
this.renderProps = {
|
||||
activityDescriptor,
|
||||
|
|
@ -103,7 +106,7 @@ export class ElsaActivityEditorModal {
|
|||
|
||||
return (
|
||||
<Host class="elsa-block">
|
||||
<elsa-modal-dialog ref={el => this.dialog = el} >
|
||||
<elsa-modal-dialog ref={el => this.dialog = el}>
|
||||
<div slot="content" class="elsa-py-8 elsa-pb-0">
|
||||
<form onSubmit={e => this.onSubmit(e)} ref={el => this.form = el} key={this.timestamp.getTime().toString()}>
|
||||
<div class="elsa-flex elsa-px-8">
|
||||
|
|
@ -159,23 +162,43 @@ export class ElsaActivityEditorModal {
|
|||
|
||||
renderSelectedTab(activityModel: ActivityModel, activityDescriptor: ActivityDescriptor, categories: Array<string>) {
|
||||
return [
|
||||
this.renderWorkflowContextTab(activityModel),
|
||||
this.renderStorageTab(activityModel, activityDescriptor),
|
||||
this.renderCommonTab(activityModel),
|
||||
this.renderPropertiesTab(activityModel, activityDescriptor),
|
||||
this.renderCategoryTabs(activityModel, activityDescriptor, categories)
|
||||
this.renderCategoryTabs(activityModel, activityDescriptor, categories),
|
||||
];
|
||||
}
|
||||
|
||||
renderWorkflowContextTab(activityModel: ActivityModel) {
|
||||
renderStorageTab(activityModel: ActivityModel, activityDescriptor: ActivityDescriptor) {
|
||||
const formContext = this.formContext;
|
||||
let storageDescriptorOptions: Array<SelectOption> = this.workflowStorageDescriptors.map(x => ({value: x.name, text: x.displayName}));
|
||||
let outputProperties = activityDescriptor.outputProperties;
|
||||
|
||||
storageDescriptorOptions = [{value: null, text: 'Default'}, ...storageDescriptorOptions];
|
||||
|
||||
const renderOutputProperty = function (propertyDescriptor: ActivityPropertyDescriptor) {
|
||||
const propertyName = propertyDescriptor.name;
|
||||
const fieldName = `propertyStorageProviders.${propertyName}`;
|
||||
return selectField(formContext, fieldName, propertyName, activityModel.propertyStorageProviders[propertyName], storageDescriptorOptions, 'Select a storage provider.', fieldName);
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={`flex ${this.getHiddenClass('Behaviors')}`}>
|
||||
<div class={`flex ${this.getHiddenClass('Storage')}`}>
|
||||
<div class="elsa-space-y-8 elsa-w-full">
|
||||
|
||||
{section('Workflow Context')}
|
||||
{checkBox(formContext, 'loadWorkflowContext', 'Load Workflow Context', activityModel.loadWorkflowContext, 'When enabled, this will load the workflow context into memory before executing this activity.', 'loadWorkflowContext')}
|
||||
{checkBox(formContext, 'saveWorkflowContext', 'Save Workflow Context', activityModel.saveWorkflowContext, 'When enabled, this will save the workflow context back into storage after executing this activity.', 'saveWorkflowContext')}
|
||||
|
||||
{section('Workflow Instance')}
|
||||
{checkBox(formContext, 'persistWorkflow', 'Save Workflow Instance', activityModel.persistWorkflow, 'When enabled, this will save the workflow instance back into storage right after executing this activity.', 'persistWorkflow')}
|
||||
{checkBox(formContext, 'persistOutput', 'Save Activity Output', activityModel.persistOutput, 'When enabled, this will store this activity\'s output as part of the workflow instance. Enable this when you plan to reference this output from other activities', 'persistOutput')}
|
||||
|
||||
{section('Activity Output', 'Configure what storage to use when persisting activity output.')}
|
||||
{selectField(formContext, 'outputStorageProviderName', 'Output Storage', activityModel.outputStorageProviderName, storageDescriptorOptions, 'Select a storage provider for this activity\'s output.', 'outputStorageProviderName')}
|
||||
|
||||
{Object.keys(outputProperties).length > 0 ? (
|
||||
[section('Activity Output Properties', 'Configure the desired storage for each output property of this activity.'), outputProperties.map(renderOutputProperty)]
|
||||
) : undefined}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -198,7 +221,7 @@ export class ElsaActivityEditorModal {
|
|||
renderPropertiesTab(activityModel: ActivityModel, activityDescriptor: ActivityDescriptor) {
|
||||
const propertyDescriptors: Array<ActivityPropertyDescriptor> = this.renderProps.defaultProperties;
|
||||
|
||||
if(propertyDescriptors.length == 0)
|
||||
if (propertyDescriptors.length == 0)
|
||||
return undefined;
|
||||
|
||||
const key = `activity-settings:${activityModel.activityId}`;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import {Component, Event, h, Host, State} from '@stencil/core';
|
||||
import '../../../utils/utils';
|
||||
import {eventBus} from '../../../services/event-bus';
|
||||
import {ActivityDescriptor, ActivityDescriptorDisplayContext, ActivityTraits, EventTypes} from "../../../models";
|
||||
import state from '../../../utils/store';
|
||||
import {ActivityIcon} from "../../icons/activity-icon";
|
||||
import {eventBus} from '../../../../services/event-bus';
|
||||
import {ActivityDescriptor, ActivityDescriptorDisplayContext, ActivityTraits, EventTypes} from "../../../../models";
|
||||
import state from '../../../../utils/store';
|
||||
import {ActivityIcon} from "../../../icons/activity-icon";
|
||||
|
||||
@Component({
|
||||
tag: 'elsa-activity-picker-modal',
|
||||
|
|
@ -109,8 +109,10 @@ export class ElsaWorkflowDefinitionEditorScreen {
|
|||
|
||||
@Watch("serverUrl")
|
||||
async serverUrlChangedHandler(newValue: string) {
|
||||
if (newValue && newValue.length > 0)
|
||||
if (newValue && newValue.length > 0) {
|
||||
await this.loadActivityDescriptors();
|
||||
await this.loadWorkflowStorageDescriptors();
|
||||
}
|
||||
}
|
||||
|
||||
@Watch("monacoLibPath")
|
||||
|
|
@ -150,6 +152,11 @@ export class ElsaWorkflowDefinitionEditorScreen {
|
|||
state.activityDescriptors = await client.activitiesApi.list();
|
||||
}
|
||||
|
||||
async loadWorkflowStorageDescriptors() {
|
||||
const client = createElsaClient(this.serverUrl);
|
||||
state.workflowStorageDescriptors = await client.workflowStorageProvidersApi.list();
|
||||
}
|
||||
|
||||
updateWorkflowDefinition(value: WorkflowDefinition) {
|
||||
this.workflowDefinition = value;
|
||||
this.workflowModel = this.mapWorkflowModel(value);
|
||||
|
|
@ -203,8 +210,9 @@ export class ElsaWorkflowDefinitionEditorScreen {
|
|||
persistWorkflow: x.persistWorkflow,
|
||||
loadWorkflowContext: x.loadWorkflowContext,
|
||||
saveWorkflowContext: x.saveWorkflowContext,
|
||||
persistOutput: x.persistOutput,
|
||||
properties: x.properties
|
||||
outputStorageProviderName: x.outputStorageProviderName,
|
||||
properties: x.properties,
|
||||
propertyStorageProviders: x.propertyStorageProviders
|
||||
})),
|
||||
connections: workflowModel.connections.map<ConnectionDefinition>(x => ({
|
||||
sourceActivityId: x.sourceId,
|
||||
|
|
@ -275,10 +283,11 @@ export class ElsaWorkflowDefinitionEditorScreen {
|
|||
type: source.type,
|
||||
properties: source.properties,
|
||||
outcomes: [...activityDescriptor.outcomes],
|
||||
persistOutput: source.persistOutput,
|
||||
outputStorageProviderName: source.outputStorageProviderName,
|
||||
persistWorkflow: source.persistWorkflow,
|
||||
saveWorkflowContext: source.saveWorkflowContext,
|
||||
loadWorkflowContext: source.loadWorkflowContext
|
||||
loadWorkflowContext: source.loadWorkflowContext,
|
||||
propertyStorageProviders: source.propertyStorageProviders
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,11 +86,11 @@ export class ElsaWorkflowInstanceViewerScreen {
|
|||
isPublished: false,
|
||||
loadWorkflowContext: false,
|
||||
isSingleton: false,
|
||||
persistOutput: false,
|
||||
saveWorkflowContext: false,
|
||||
variables: {data: {}},
|
||||
type: null,
|
||||
properties: {data: {}}
|
||||
properties: {data: {}},
|
||||
propertyStorageProviders: {}
|
||||
};
|
||||
|
||||
const client = createElsaClient(this.serverUrl);
|
||||
|
|
@ -163,10 +163,11 @@ export class ElsaWorkflowInstanceViewerScreen {
|
|||
type: source.type,
|
||||
properties: properties,
|
||||
outcomes: [...activityDescriptor.outcomes],
|
||||
persistOutput: source.persistOutput,
|
||||
outputStorageProviderName: source.outputStorageProviderName,
|
||||
persistWorkflow: source.persistWorkflow,
|
||||
saveWorkflowContext: source.saveWorkflowContext,
|
||||
loadWorkflowContext: source.loadWorkflowContext
|
||||
loadWorkflowContext: source.loadWorkflowContext,
|
||||
propertyStorageProviders: source.propertyStorageProviders
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,9 +46,10 @@ export interface ActivityBlueprint {
|
|||
persistWorkflow: boolean;
|
||||
loadWorkflowContext: boolean;
|
||||
saveWorkflowContext: boolean;
|
||||
persistOutput: boolean;
|
||||
outputStorageProviderName?: string;
|
||||
source?: string;
|
||||
properties: Variables;
|
||||
propertyStorageProviders: Map<string>;
|
||||
}
|
||||
|
||||
export interface Connection {
|
||||
|
|
@ -139,8 +140,9 @@ export interface ActivityDefinition {
|
|||
persistWorkflow: boolean;
|
||||
loadWorkflowContext: boolean;
|
||||
saveWorkflowContext: boolean;
|
||||
persistOutput: boolean;
|
||||
outputStorageProviderName: string;
|
||||
properties: Array<ActivityDefinitionProperty>;
|
||||
propertyStorageProviders: Map<string>;
|
||||
}
|
||||
|
||||
export interface WorkflowExecutionLogRecord {
|
||||
|
|
@ -260,6 +262,7 @@ export interface ActivityDescriptor {
|
|||
outcomes: Array<string>;
|
||||
browsable: boolean;
|
||||
inputProperties: Array<ActivityPropertyDescriptor>;
|
||||
outputProperties: Array<ActivityPropertyDescriptor>;
|
||||
}
|
||||
|
||||
export interface ActivityPropertyDescriptor {
|
||||
|
|
@ -274,6 +277,11 @@ export interface ActivityPropertyDescriptor {
|
|||
supportedSyntaxes: Array<string>
|
||||
}
|
||||
|
||||
export interface WorkflowStorageDescriptor {
|
||||
name: string;
|
||||
displayName?: string;
|
||||
}
|
||||
|
||||
export interface PagedList<T> {
|
||||
items: Array<T>;
|
||||
page?: number;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {ActivityDefinitionProperty, WorkflowPersistenceBehavior} from "./domain";
|
||||
import {Map} from '../utils/utils';
|
||||
|
||||
export interface WorkflowModel {
|
||||
activities: Array<ActivityModel>;
|
||||
|
|
@ -15,9 +16,10 @@ export interface ActivityModel {
|
|||
outcomes: Array<string>;
|
||||
properties: Array<ActivityDefinitionProperty>;
|
||||
persistWorkflow?: boolean;
|
||||
persistOutput?: boolean;
|
||||
outputStorageProviderName?: string;
|
||||
loadWorkflowContext?: boolean;
|
||||
saveWorkflowContext?: boolean;
|
||||
propertyStorageProviders: Map<string>;
|
||||
}
|
||||
|
||||
export interface ConnectionModel {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
WorkflowContextOptions,
|
||||
WorkflowDefinition,
|
||||
WorkflowDefinitionSummary, WorkflowExecutionLogRecord, WorkflowFault, WorkflowInstance, WorkflowInstanceSummary,
|
||||
WorkflowPersistenceBehavior, WorkflowStatus
|
||||
WorkflowPersistenceBehavior, WorkflowStatus, WorkflowStorageDescriptor
|
||||
} from "../models";
|
||||
|
||||
export const createElsaClient = function (serverUrl: string): ElsaClient {
|
||||
|
|
@ -166,123 +166,134 @@ export const createElsaClient = function (serverUrl: string): ElsaClient {
|
|||
const response = await httpClient.get(`v1/workflow-instances/${workflowInstanceId}/activity-stats/${activityId}`);
|
||||
return response.data;
|
||||
}
|
||||
},
|
||||
workflowStorageProvidersApi: {
|
||||
list: async () => {
|
||||
const response = await httpClient.get<Array<WorkflowStorageDescriptor>>('v1/workflow-storage-providers');
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ElsaClient {
|
||||
activitiesApi: ActivitiesApi;
|
||||
workflowDefinitionsApi: WorkflowDefinitionsApi;
|
||||
workflowRegistryApi: WorkflowRegistryApi;
|
||||
workflowInstancesApi: WorkflowInstancesApi;
|
||||
workflowExecutionLogApi: WorkflowExecutionLogApi;
|
||||
scriptingApi: ScriptingApi;
|
||||
designerApi: DesignerApi;
|
||||
activityStatsApi: ActivityStatsApi;
|
||||
}
|
||||
export interface ElsaClient {
|
||||
activitiesApi: ActivitiesApi;
|
||||
workflowDefinitionsApi: WorkflowDefinitionsApi;
|
||||
workflowRegistryApi: WorkflowRegistryApi;
|
||||
workflowInstancesApi: WorkflowInstancesApi;
|
||||
workflowExecutionLogApi: WorkflowExecutionLogApi;
|
||||
scriptingApi: ScriptingApi;
|
||||
designerApi: DesignerApi;
|
||||
activityStatsApi: ActivityStatsApi;
|
||||
workflowStorageProvidersApi: WorkflowStorageProvidersApi;
|
||||
}
|
||||
|
||||
export interface ActivitiesApi {
|
||||
list(): Promise<Array<ActivityDescriptor>>;
|
||||
}
|
||||
export interface ActivitiesApi {
|
||||
list(): Promise<Array<ActivityDescriptor>>;
|
||||
}
|
||||
|
||||
export interface WorkflowDefinitionsApi {
|
||||
export interface WorkflowDefinitionsApi {
|
||||
|
||||
list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowDefinitionSummary>>;
|
||||
list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowDefinitionSummary>>;
|
||||
|
||||
getByDefinitionAndVersion(definitionId: string, versionOptions: VersionOptions): Promise<WorkflowDefinition>;
|
||||
getByDefinitionAndVersion(definitionId: string, versionOptions: VersionOptions): Promise<WorkflowDefinition>;
|
||||
|
||||
save(request: SaveWorkflowDefinitionRequest): Promise<WorkflowDefinition>;
|
||||
save(request: SaveWorkflowDefinitionRequest): Promise<WorkflowDefinition>;
|
||||
|
||||
delete(definitionId: string): Promise<void>;
|
||||
delete(definitionId: string): Promise<void>;
|
||||
|
||||
retract(workflowDefinitionId: string): Promise<WorkflowDefinition>;
|
||||
retract(workflowDefinitionId: string): Promise<WorkflowDefinition>;
|
||||
|
||||
export(workflowDefinitionId: string, versionOptions: VersionOptions): Promise<ExportWorkflowResponse>;
|
||||
export(workflowDefinitionId: string, versionOptions: VersionOptions): Promise<ExportWorkflowResponse>;
|
||||
|
||||
import(workflowDefinitionId: string, file: File): Promise<WorkflowDefinition>;
|
||||
}
|
||||
import(workflowDefinitionId: string, file: File): Promise<WorkflowDefinition>;
|
||||
}
|
||||
|
||||
export interface WorkflowRegistryApi {
|
||||
list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowBlueprintSummary>>;
|
||||
export interface WorkflowRegistryApi {
|
||||
list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowBlueprintSummary>>;
|
||||
|
||||
get(id: string, versionOptions: VersionOptions): Promise<WorkflowBlueprint>;
|
||||
}
|
||||
get(id: string, versionOptions: VersionOptions): Promise<WorkflowBlueprint>;
|
||||
}
|
||||
|
||||
export interface WorkflowInstancesApi {
|
||||
list(page?: number, pageSize?: number, workflowDefinitionId?: string, workflowStatus?: WorkflowStatus, orderBy?: OrderBy, searchTerm?: string): Promise<PagedList<WorkflowInstanceSummary>>;
|
||||
export interface WorkflowInstancesApi {
|
||||
list(page?: number, pageSize?: number, workflowDefinitionId?: string, workflowStatus?: WorkflowStatus, orderBy?: OrderBy, searchTerm?: string): Promise<PagedList<WorkflowInstanceSummary>>;
|
||||
|
||||
get(id: string): Promise<WorkflowInstance>;
|
||||
get(id: string): Promise<WorkflowInstance>;
|
||||
|
||||
delete(id: string): Promise<void>;
|
||||
delete(id: string): Promise<void>;
|
||||
|
||||
bulkDelete(request: BulkDeleteWorkflowsRequest): Promise<BulkDeleteWorkflowsResponse>;
|
||||
}
|
||||
bulkDelete(request: BulkDeleteWorkflowsRequest): Promise<BulkDeleteWorkflowsResponse>;
|
||||
}
|
||||
|
||||
export interface WorkflowExecutionLogApi {
|
||||
export interface WorkflowExecutionLogApi {
|
||||
|
||||
get(workflowInstanceId: string, page?: number, pageSize?: number): Promise<PagedList<WorkflowExecutionLogRecord>>;
|
||||
get(workflowInstanceId: string, page?: number, pageSize?: number): Promise<PagedList<WorkflowExecutionLogRecord>>;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export interface BulkDeleteWorkflowsRequest {
|
||||
workflowInstanceIds: Array<string>;
|
||||
}
|
||||
export interface BulkDeleteWorkflowsRequest {
|
||||
workflowInstanceIds: Array<string>;
|
||||
}
|
||||
|
||||
export interface BulkDeleteWorkflowsResponse {
|
||||
deletedWorkflowCount: number;
|
||||
}
|
||||
export interface BulkDeleteWorkflowsResponse {
|
||||
deletedWorkflowCount: number;
|
||||
}
|
||||
|
||||
export interface ScriptingApi {
|
||||
getJavaScriptTypeDefinitions(workflowDefinitionId: string, context?: string): Promise<string>
|
||||
}
|
||||
export interface ScriptingApi {
|
||||
getJavaScriptTypeDefinitions(workflowDefinitionId: string, context?: string): Promise<string>
|
||||
}
|
||||
|
||||
export interface DesignerApi {
|
||||
runtimeSelectItemsApi: RuntimeSelectItemsApi;
|
||||
}
|
||||
export interface DesignerApi {
|
||||
runtimeSelectItemsApi: RuntimeSelectItemsApi;
|
||||
}
|
||||
|
||||
export interface RuntimeSelectItemsApi {
|
||||
get(providerTypeName: string, context?: any): Promise<Array<SelectListItem>>
|
||||
}
|
||||
export interface RuntimeSelectItemsApi {
|
||||
get(providerTypeName: string, context?: any): Promise<Array<SelectListItem>>
|
||||
}
|
||||
|
||||
export interface ActivityStatsApi {
|
||||
get(workflowInstanceId: string, activityId: string): Promise<ActivityStats>;
|
||||
}
|
||||
export interface ActivityStatsApi {
|
||||
get(workflowInstanceId: string, activityId: string): Promise<ActivityStats>;
|
||||
}
|
||||
|
||||
export interface SaveWorkflowDefinitionRequest {
|
||||
workflowDefinitionId?: string;
|
||||
name?: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
tag?: string;
|
||||
variables?: Variables;
|
||||
contextOptions?: WorkflowContextOptions;
|
||||
isSingleton?: boolean;
|
||||
persistenceBehavior?: WorkflowPersistenceBehavior;
|
||||
deleteCompletedInstances?: boolean;
|
||||
publish?: boolean;
|
||||
activities: Array<ActivityDefinition>;
|
||||
connections: Array<ConnectionDefinition>;
|
||||
}
|
||||
export interface WorkflowStorageProvidersApi {
|
||||
list(): Promise<Array<WorkflowStorageDescriptor>>;
|
||||
}
|
||||
|
||||
export interface ExportWorkflowResponse {
|
||||
fileName: string;
|
||||
data: Blob;
|
||||
}
|
||||
export interface SaveWorkflowDefinitionRequest {
|
||||
workflowDefinitionId?: string;
|
||||
name?: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
tag?: string;
|
||||
variables?: Variables;
|
||||
contextOptions?: WorkflowContextOptions;
|
||||
isSingleton?: boolean;
|
||||
persistenceBehavior?: WorkflowPersistenceBehavior;
|
||||
deleteCompletedInstances?: boolean;
|
||||
publish?: boolean;
|
||||
activities: Array<ActivityDefinition>;
|
||||
connections: Array<ConnectionDefinition>;
|
||||
}
|
||||
|
||||
export interface ActivityStats {
|
||||
fault?: ActivityFault;
|
||||
averageExecutionTime: string;
|
||||
fastestExecutionTime: string;
|
||||
slowestExecutionTime: string;
|
||||
lastExecutedAt: Date;
|
||||
eventCounts: Array<ActivityEventCount>;
|
||||
}
|
||||
export interface ExportWorkflowResponse {
|
||||
fileName: string;
|
||||
data: Blob;
|
||||
}
|
||||
|
||||
interface ActivityEventCount {
|
||||
eventName: string;
|
||||
count: number;
|
||||
}
|
||||
export interface ActivityStats {
|
||||
fault?: ActivityFault;
|
||||
averageExecutionTime: string;
|
||||
fastestExecutionTime: string;
|
||||
slowestExecutionTime: string;
|
||||
lastExecutedAt: Date;
|
||||
eventCounts: Array<ActivityEventCount>;
|
||||
}
|
||||
|
||||
interface ActivityFault{
|
||||
message: string;
|
||||
}
|
||||
interface ActivityEventCount {
|
||||
eventName: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface ActivityFault {
|
||||
message: string;
|
||||
}
|
||||
|
|
@ -25,7 +25,8 @@ export function textInput(context: FormContext, fieldName: string, label: string
|
|||
{label}
|
||||
</label>
|
||||
<div class="elsa-mt-1">
|
||||
<input type="text" id={fieldId} name={fieldName} value={value} onChange={e => onTextInputChange(e, context)} class="focus:elsa-ring-blue-500 focus:elsa-border-blue-500 block elsa-w-full elsa-min-w-0 elsa-rounded-md sm:elsa-text-sm elsa-border-gray-300"/>
|
||||
<input type="text" id={fieldId} name={fieldName} value={value} onChange={e => onTextInputChange(e, context)}
|
||||
class="focus:elsa-ring-blue-500 focus:elsa-border-blue-500 block elsa-w-full elsa-min-w-0 elsa-rounded-md sm:elsa-text-sm elsa-border-gray-300"/>
|
||||
</div>
|
||||
{hint && hint.length > 0 ? <p class="elsa-mt-2 elsa-text-sm elsa-text-gray-500">{hint}</p> : undefined}
|
||||
</div>);
|
||||
|
|
@ -53,7 +54,8 @@ export function textArea(context: FormContext, fieldName: string, label: string,
|
|||
{label}
|
||||
</label>
|
||||
<div class="elsa-mt-1">
|
||||
<textarea id={fieldId} name={fieldName} value={value} onChange={e => onTextAreaChange(e, context)} rows={3} class="focus:elsa-ring-blue-500 focus:elsa-border-blue-500 block elsa-w-full elsa-min-w-0 elsa-rounded-md sm:elsa-text-sm elsa-border-gray-300"/>
|
||||
<textarea id={fieldId} name={fieldName} value={value} onChange={e => onTextAreaChange(e, context)} rows={3}
|
||||
class="focus:elsa-ring-blue-500 focus:elsa-border-blue-500 block elsa-w-full elsa-min-w-0 elsa-rounded-md sm:elsa-text-sm elsa-border-gray-300"/>
|
||||
</div>
|
||||
{hint && hint.length > 0 ? <p class="elsa-mt-2 elsa-text-sm elsa-text-gray-500">{hint}</p> : undefined}
|
||||
</div>);
|
||||
|
|
@ -78,6 +80,19 @@ export function selectField(context: FormContext, fieldName: string, label: stri
|
|||
</div>);
|
||||
}
|
||||
|
||||
export function section(title: string, subTitle?: string) {
|
||||
return <div>
|
||||
<h3 class="elsa-text-lg elsa-leading-6 elsa-font-medium elsa-text-gray-900">
|
||||
{title}
|
||||
</h3>
|
||||
{!!subTitle ? (
|
||||
<p class="elsa-mt-1 elsa-max-w-2xl elsa-text-sm elsa-text-gray-500">
|
||||
{subTitle}
|
||||
</p>)
|
||||
: undefined}
|
||||
</div>
|
||||
}
|
||||
|
||||
export function updateField<T>(context: FormContext, fieldName: string, value: T) {
|
||||
const fieldNameHierarchy = fieldName.split('.');
|
||||
let clone = {...context.model};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
const { state, onChange } = createStore({
|
||||
activityDescriptors: [],
|
||||
workflowStorageDescriptors: [],
|
||||
monacoLibPath: ''
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ namespace Elsa.Persistence.EntityFramework.Core.Configuration
|
|||
builder.Property(x => x.FaultedAt).HasConversion(ValueConverters.InstantConverter);
|
||||
builder.Ignore(x => x.Output);
|
||||
builder.Ignore(x => x.ActivityData);
|
||||
builder.Ignore(x => x.ActivityOutput);
|
||||
builder.Ignore(x => x.BlockingActivities);
|
||||
builder.Ignore(x => x.Fault);
|
||||
builder.Ignore(x => x.ScheduledActivities);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ namespace Elsa.Persistence.EntityFramework.Core.Stores
|
|||
entity.Output,
|
||||
entity.Variables,
|
||||
entity.ActivityData,
|
||||
entity.ActivityOutput,
|
||||
entity.BlockingActivities,
|
||||
entity.ScheduledActivities,
|
||||
entity.Scopes,
|
||||
|
|
@ -81,7 +80,6 @@ namespace Elsa.Persistence.EntityFramework.Core.Stores
|
|||
entity.Output,
|
||||
entity.Variables,
|
||||
entity.ActivityData,
|
||||
entity.ActivityOutput,
|
||||
entity.BlockingActivities,
|
||||
entity.ScheduledActivities,
|
||||
entity.Scopes,
|
||||
|
|
@ -97,7 +95,6 @@ namespace Elsa.Persistence.EntityFramework.Core.Stores
|
|||
entity.Output = data.Output;
|
||||
entity.Variables = data.Variables;
|
||||
entity.ActivityData = data.ActivityData;
|
||||
entity.ActivityOutput = data.ActivityOutput;
|
||||
entity.BlockingActivities = data.BlockingActivities;
|
||||
entity.ScheduledActivities = data.ScheduledActivities;
|
||||
entity.Scopes = data.Scopes;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ namespace Elsa.Persistence.YesSql.Documents
|
|||
public Variables Variables { get; set; } = new();
|
||||
public object? Output { get; set; }
|
||||
public IDictionary<string, object> ActivityData { get; set; } = new Dictionary<string, object>();
|
||||
public IDictionary<string, object> ActivityOutput { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
public HashSet<BlockingActivity> BlockingActivities
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-1914" />
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2032" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -2,22 +2,19 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper.Internal;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Providers.WorkflowStorage;
|
||||
using Elsa.Scripting.JavaScript.Events;
|
||||
using Elsa.Scripting.JavaScript.Extensions;
|
||||
using Elsa.Scripting.JavaScript.Messages;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Elsa.Services.WorkflowStorage;
|
||||
using Jint;
|
||||
using Jint.Runtime.Interop;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Scripting.JavaScript.Handlers
|
||||
|
|
@ -52,7 +49,9 @@ namespace Elsa.Scripting.JavaScript.Handlers
|
|||
engine.SetValue("getWorkflowDefinitionIdByName", (Func<string, string?>) (name => GetWorkflowDefinitionIdByName(activityExecutionContext, name)));
|
||||
engine.SetValue("getWorkflowDefinitionIdByTag", (Func<string, string?>) (tag => GetWorkflowDefinitionIdByTag(activityExecutionContext, tag)));
|
||||
engine.SetValue("getActivity", (Func<string, object?>) (idOrName => GetActivityModel(activityExecutionContext, idOrName)));
|
||||
engine.SetValue("getInboundActivity", (Func<object?>) (() => GetInboundActivityModel(activityExecutionContext)));
|
||||
|
||||
// Using .Result because Jint doesn't support Task-based functions yet.
|
||||
engine.SetValue("getActivityOutput", (Func<string, string?, object?>) ((activityId, propertyName) => GetActivityOutputAsync(activityId, propertyName, activityExecutionContext).Result));
|
||||
|
||||
// Global variables.
|
||||
engine.SetValue("activityExecutionContext", activityExecutionContext);
|
||||
|
|
@ -83,19 +82,20 @@ namespace Elsa.Scripting.JavaScript.Handlers
|
|||
foreach (var variable in variables.Data)
|
||||
engine.SetValue(variable.Key, variable.Value);
|
||||
|
||||
// DEPRECATED: The following only works when activity state is stored as part of the workflow instance itself.
|
||||
// With the introduction of pluggable workflow state storage providers, which are asynchronous, we don't want to load all states of all activities upfront (e.g. we don't want to download files from blob storage).
|
||||
// Named activities.
|
||||
foreach (var activity in workflowBlueprint.Activities.Where(x => !string.IsNullOrWhiteSpace(x.Name)))
|
||||
{
|
||||
var state = new Dictionary<string, object>(activityExecutionContext.GetActivityData(activity.Id));
|
||||
|
||||
// Output.
|
||||
if (workflowInstance.ActivityOutput.ContainsKey(activity.Id))
|
||||
if (state.ContainsKey(ActivityOutput.PropertyName))
|
||||
{
|
||||
var output = workflowInstance.ActivityOutput[activity.Id];
|
||||
//state["Output"] = output != null ? JObjectExtensions.SerializeState(output) : null;
|
||||
state["Output"] = output;
|
||||
var output = state.GetItem(ActivityOutput.PropertyName);
|
||||
state["Output"] = output ?? new { };
|
||||
}
|
||||
|
||||
|
||||
engine.SetValue(activity.Name, state);
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ namespace Elsa.Scripting.JavaScript.Handlers
|
|||
output.AppendLine("declare function getWorkflowDefinitionIdByName(name: string): string;");
|
||||
output.AppendLine("declare function getWorkflowDefinitionIdByTag(tag: string): string;");
|
||||
output.AppendLine("declare function getActivity(idOrName: string): any;");
|
||||
output.AppendLine("declare function getInboundActivity(): any;");
|
||||
output.AppendLine("declare function getActivityOutput(activityIdOrName: string, propertyName?: string): any;");
|
||||
|
||||
output.AppendLine("declare const activityExecutionContext: ActivityExecutionContext;");
|
||||
output.AppendLine("declare const workflowExecutionContext: WorkflowExecutionContext;");
|
||||
|
|
@ -136,7 +136,6 @@ namespace Elsa.Scripting.JavaScript.Handlers
|
|||
|
||||
if (contextType != null)
|
||||
{
|
||||
|
||||
var workflowContextTypeScriptType = notification.GetTypeScriptType(contextType);
|
||||
output.AppendLine($"declare const workflowContext: {workflowContextTypeScriptType}");
|
||||
}
|
||||
|
|
@ -154,8 +153,8 @@ namespace Elsa.Scripting.JavaScript.Handlers
|
|||
var activityTypeNames = namedActivities.Select(x => x.Type).Distinct().ToList();
|
||||
var activityTypes = await Task.WhenAll(activityTypeNames.Select(async activityTypeName => (activityTypeName, await _activityTypeService.GetActivityTypeAsync(activityTypeName, cancellationToken))));
|
||||
var activityTypeDictionary = activityTypes.ToDictionary(x => x.activityTypeName, x => x.Item2);
|
||||
|
||||
foreach (var activityType in activityTypeDictionary.Values)
|
||||
|
||||
foreach (var activityType in activityTypeDictionary.Values)
|
||||
RenderActivityTypeDeclaration(activityType, output);
|
||||
|
||||
foreach (var activity in namedActivities)
|
||||
|
|
@ -181,7 +180,7 @@ namespace Elsa.Scripting.JavaScript.Handlers
|
|||
var propertyName = property.Name;
|
||||
writer.AppendLine($"{propertyName}: {typeScriptType};");
|
||||
}
|
||||
|
||||
|
||||
foreach (var property in outputProperties)
|
||||
{
|
||||
var typeScriptType = notification.GetTypeScriptType(property.Type);
|
||||
|
|
@ -215,6 +214,17 @@ namespace Elsa.Scripting.JavaScript.Handlers
|
|||
var inboundActivityId = context.WorkflowExecutionContext.GetInboundActivityPath(context.ActivityId).FirstOrDefault();
|
||||
return inboundActivityId == null ? null : GetActivityModel(context, inboundActivityId);
|
||||
}
|
||||
|
||||
private async Task<object?> GetActivityOutputAsync(string activityIdOrName, string? propertyName, ActivityExecutionContext context)
|
||||
{
|
||||
var workflowExecutionContext = context.WorkflowExecutionContext;
|
||||
var activityBlueprint = workflowExecutionContext.GetActivityBlueprintByName(activityIdOrName) ?? workflowExecutionContext.GetActivityBlueprintById(activityIdOrName)!;
|
||||
var storageService = context.GetService<IWorkflowStorageService>();
|
||||
var propName = propertyName ?? ActivityOutput.PropertyName;
|
||||
var providerName = propertyName == null ? activityBlueprint.OutputStorageProviderName : activityBlueprint.PropertyStorageProviders.GetItem(propertyName);
|
||||
var storageContext = new WorkflowStorageContext(context.WorkflowInstance, activityBlueprint.Id);
|
||||
return await storageService.LoadAsync(providerName, storageContext, propName, context.CancellationToken);
|
||||
}
|
||||
|
||||
private void RegisterType<T>(Engine engine) => engine.SetValue(typeof(T).Name, TypeReference.CreateTypeReference(engine, typeof(T)));
|
||||
private void RegisterType(Type type, Engine engine) => engine.SetValue(type.Name, TypeReference.CreateTypeReference(engine, type));
|
||||
|
|
|
|||
|
|
@ -59,8 +59,7 @@ namespace Elsa.Scripting.JavaScript.Services
|
|||
|
||||
private static object? ExecuteExpressionAndGetResult(Engine engine, string expression)
|
||||
{
|
||||
engine.Execute(expression);
|
||||
return engine.GetCompletionValue().ToObject();
|
||||
return engine.Evaluate(expression).ToObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elsa.Server.Api.Services;
|
||||
using Elsa.Services.WorkflowStorage;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Elsa.Server.Api.Endpoints.WorkflowStorageProviders
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1")]
|
||||
[Route("v{version:apiVersion}/workflow-storage-providers")]
|
||||
[Produces("application/json")]
|
||||
public class List : Controller
|
||||
{
|
||||
private readonly IWorkflowStorageService _workflowStorageService;
|
||||
private readonly IEndpointContentSerializerSettingsProvider _serializerSettingsProvider;
|
||||
|
||||
public List(IWorkflowStorageService workflowStorageService, IEndpointContentSerializerSettingsProvider serializerSettingsProvider)
|
||||
{
|
||||
_workflowStorageService = workflowStorageService;
|
||||
_serializerSettingsProvider = serializerSettingsProvider;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<WorkflowStorageDescriptor>))]
|
||||
[SwaggerOperation(
|
||||
Summary = "Returns all available workflow storage providers.",
|
||||
Description = "Returns all available workflow storage providers.",
|
||||
OperationId = "WorkflowStorageProviders.List",
|
||||
Tags = new[] { "WorkflowStorageProviders" })
|
||||
]
|
||||
public IActionResult Handle()
|
||||
{
|
||||
var providers = _workflowStorageService.ListProviders();
|
||||
var descriptors = providers.Select(x => new WorkflowStorageDescriptor(x.Name, x.DisplayName)).ToList();
|
||||
return Json(descriptors, _serializerSettingsProvider.GetSettings());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
namespace Elsa.Server.Api.Endpoints.WorkflowStorageProviders
|
||||
{
|
||||
public record WorkflowStorageDescriptor(string Name, string? DisplayName);
|
||||
}
|
||||
Loading…
Reference in a new issue