From d614a0dbe66cd932a8f89e405b992f366ddafde4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=CC=88rkan=20Gu=CC=88ran?= Date: Mon, 29 Aug 2022 18:22:58 +0200 Subject: [PATCH] Implemented version history --- .../src/components.d.ts | 27 +++++ .../src/components/icons/tooling/index.ts | 2 + .../components/icons/tooling/published.tsx | 7 ++ .../src/components/icons/tooling/revert.tsx | 8 ++ .../src/modules/flowchart/flowchart.tsx | 2 +- .../components/editor.tsx | 49 ++++++++- .../components/properties.tsx | 33 +++++- .../components/version-history.tsx | 100 ++++++++++++++++++ .../modules/workflow-definitions/models/ui.ts | 1 + .../modules/workflow-definitions/plugin.tsx | 17 ++- .../workflow-definitions/services/api.ts | 31 +++++- .../workflow-definitions/services/manager.ts | 1 + .../Grains/WorkflowInstanceGrain.cs | 6 +- .../BulkPublish/Endpoint.cs | 3 +- .../BulkRetract/Endpoint.cs | 3 +- .../WorkflowDefinitions/Export/Endpoint.cs | 2 +- .../WorkflowDefinitions/Get/Endpoint.cs | 3 +- .../WorkflowDefinitions/Import/Endpoint.cs | 2 +- .../WorkflowDefinitions/Post/Endpoint.cs | 2 +- .../WorkflowDefinitions/Publish/Endpoint.cs | 3 +- .../WorkflowDefinitions/Retract/Endpoint.cs | 3 +- .../WorkflowDefinitions/Version/Delete.cs | 37 +++++++ .../WorkflowDefinitions/Version/List.cs | 39 +++++++ .../WorkflowDefinitions/Version/Revert.cs | 32 ++++++ .../Models/WorkflowDefinitionRequest.cs | 1 + .../WorkflowDefinitionManager.cs | 65 +++++++++++- .../WorkflowDefinitionPublisher.cs | 10 +- .../WorkflowDefinitionVersionDeleted.cs | 5 + .../Services/IWorkflowDefinitionManager.cs | 4 + .../Services/IWorkflowDefinitionPublisher.cs | 2 +- ...sistence.EntityFrameworkCore.Sqlite.csproj | 4 + ....cs => 20220828110255_Initial.Designer.cs} | 11 +- ...0_Initial.cs => 20220828110255_Initial.cs} | 7 ++ .../WorkflowsDbContextModelSnapshot.cs | 9 ++ ...WorkflowExecutionLogRecordConfiguration.cs | 1 + .../EFCoreWorkflowDefinitionStore.cs | 22 +++- .../Entities/WorkflowBookmark.cs | 2 + .../Entities/WorkflowExecutionLogRecord.cs | 1 + .../MemoryWorkflowDefinitionStore.cs | 18 +++- .../Services/IWorkflowDefinitionStore.cs | 4 +- .../PopulateWorkflowDefinitionStore.cs | 4 +- .../Implementations/DefaultWorkflowInvoker.cs | 4 +- .../WorkflowInstanceFactory.cs | 2 +- .../ResumeWorkflowInstructionInterpreter.cs | 2 +- .../PersistWorkflowExecutionLogMiddleware.cs | 1 + 45 files changed, 554 insertions(+), 38 deletions(-) create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/published.tsx create mode 100644 src/designer/elsa-workflows-designer/src/components/icons/tooling/revert.tsx create mode 100644 src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/version-history.tsx create mode 100644 src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Delete.cs create mode 100644 src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/List.cs create mode 100644 src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs create mode 100644 src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionVersionDeleted.cs rename src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/{20220814124730_Initial.Designer.cs => 20220828110255_Initial.Designer.cs} (96%) rename src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/{20220814124730_Initial.cs => 20220828110255_Initial.cs} (97%) diff --git a/src/designer/elsa-workflows-designer/src/components.d.ts b/src/designer/elsa-workflows-designer/src/components.d.ts index c53a051d8..be8bcfb2c 100644 --- a/src/designer/elsa-workflows-designer/src/components.d.ts +++ b/src/designer/elsa-workflows-designer/src/components.d.ts @@ -257,6 +257,12 @@ export namespace Components { "hide": () => Promise; "show": () => Promise; "workflowDefinition"?: WorkflowDefinition; + "workflowVersions": Array; + } + interface ElsaWorkflowDefinitionVersionHistory { + "selectedVersion": WorkflowDefinition; + "serverUrl": string; + "workflowVersions": Array; } interface ElsaWorkflowInstanceBrowser { } @@ -569,6 +575,12 @@ declare global { prototype: HTMLElsaWorkflowDefinitionPropertiesEditorElement; new (): HTMLElsaWorkflowDefinitionPropertiesEditorElement; }; + interface HTMLElsaWorkflowDefinitionVersionHistoryElement extends Components.ElsaWorkflowDefinitionVersionHistory, HTMLStencilElement { + } + var HTMLElsaWorkflowDefinitionVersionHistoryElement: { + prototype: HTMLElsaWorkflowDefinitionVersionHistoryElement; + new (): HTMLElsaWorkflowDefinitionVersionHistoryElement; + }; interface HTMLElsaWorkflowInstanceBrowserElement extends Components.ElsaWorkflowInstanceBrowser, HTMLStencilElement { } var HTMLElsaWorkflowInstanceBrowserElement: { @@ -664,6 +676,7 @@ declare global { "elsa-workflow-definition-editor-toolbox": HTMLElsaWorkflowDefinitionEditorToolboxElement; "elsa-workflow-definition-editor-toolbox-activities": HTMLElsaWorkflowDefinitionEditorToolboxActivitiesElement; "elsa-workflow-definition-properties-editor": HTMLElsaWorkflowDefinitionPropertiesEditorElement; + "elsa-workflow-definition-version-history": HTMLElsaWorkflowDefinitionVersionHistoryElement; "elsa-workflow-instance-browser": HTMLElsaWorkflowInstanceBrowserElement; "elsa-workflow-instance-properties": HTMLElsaWorkflowInstancePropertiesElement; "elsa-workflow-instance-viewer": HTMLElsaWorkflowInstanceViewerElement; @@ -893,8 +906,20 @@ declare namespace LocalJSX { "graph"?: Graph; } interface ElsaWorkflowDefinitionPropertiesEditor { + "onDeleteVersionClicked"?: (event: CustomEvent) => void; + "onRevertVersionClicked"?: (event: CustomEvent) => void; + "onVersionSelected"?: (event: CustomEvent) => void; "onWorkflowPropsUpdated"?: (event: CustomEvent) => void; "workflowDefinition"?: WorkflowDefinition; + "workflowVersions"?: Array; + } + interface ElsaWorkflowDefinitionVersionHistory { + "onDeleteVersionClicked"?: (event: CustomEvent) => void; + "onRevertVersionClicked"?: (event: CustomEvent) => void; + "onVersionSelected"?: (event: CustomEvent) => void; + "selectedVersion"?: WorkflowDefinition; + "serverUrl"?: string; + "workflowVersions"?: Array; } interface ElsaWorkflowInstanceBrowser { "onWorkflowInstanceSelected"?: (event: CustomEvent) => void; @@ -976,6 +1001,7 @@ declare namespace LocalJSX { "elsa-workflow-definition-editor-toolbox": ElsaWorkflowDefinitionEditorToolbox; "elsa-workflow-definition-editor-toolbox-activities": ElsaWorkflowDefinitionEditorToolboxActivities; "elsa-workflow-definition-properties-editor": ElsaWorkflowDefinitionPropertiesEditor; + "elsa-workflow-definition-version-history": ElsaWorkflowDefinitionVersionHistory; "elsa-workflow-instance-browser": ElsaWorkflowInstanceBrowser; "elsa-workflow-instance-properties": ElsaWorkflowInstanceProperties; "elsa-workflow-instance-viewer": ElsaWorkflowInstanceViewer; @@ -1036,6 +1062,7 @@ declare module "@stencil/core" { "elsa-workflow-definition-editor-toolbox": LocalJSX.ElsaWorkflowDefinitionEditorToolbox & JSXBase.HTMLAttributes; "elsa-workflow-definition-editor-toolbox-activities": LocalJSX.ElsaWorkflowDefinitionEditorToolboxActivities & JSXBase.HTMLAttributes; "elsa-workflow-definition-properties-editor": LocalJSX.ElsaWorkflowDefinitionPropertiesEditor & JSXBase.HTMLAttributes; + "elsa-workflow-definition-version-history": LocalJSX.ElsaWorkflowDefinitionVersionHistory & JSXBase.HTMLAttributes; "elsa-workflow-instance-browser": LocalJSX.ElsaWorkflowInstanceBrowser & JSXBase.HTMLAttributes; "elsa-workflow-instance-properties": LocalJSX.ElsaWorkflowInstanceProperties & JSXBase.HTMLAttributes; "elsa-workflow-instance-viewer": LocalJSX.ElsaWorkflowInstanceViewer & JSXBase.HTMLAttributes; diff --git a/src/designer/elsa-workflows-designer/src/components/icons/tooling/index.ts b/src/designer/elsa-workflows-designer/src/components/icons/tooling/index.ts index b2055fa1d..cf2075956 100644 --- a/src/designer/elsa-workflows-designer/src/components/icons/tooling/index.ts +++ b/src/designer/elsa-workflows-designer/src/components/icons/tooling/index.ts @@ -1,8 +1,10 @@ export {BulkActionsIcon} from "./bulk-actions"; export {DeleteIcon} from "./delete"; export {EditIcon} from "./edit"; +export {RevertIcon} from "./revert"; export {PageSizeIcon} from "./page-size-icon"; export {PublishIcon} from "./publish"; +export {PublishedIcon} from "./published"; export {OrderByIcon} from "./order-by"; export {UnPublishIcon} from "./un-publish"; export {WorkflowIcon} from "./workflow"; diff --git a/src/designer/elsa-workflows-designer/src/components/icons/tooling/published.tsx b/src/designer/elsa-workflows-designer/src/components/icons/tooling/published.tsx new file mode 100644 index 000000000..2e61a04fa --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/icons/tooling/published.tsx @@ -0,0 +1,7 @@ +import {FunctionalComponent, h} from "@stencil/core"; + +export const PublishedIcon: FunctionalComponent = () => + + + ; \ No newline at end of file diff --git a/src/designer/elsa-workflows-designer/src/components/icons/tooling/revert.tsx b/src/designer/elsa-workflows-designer/src/components/icons/tooling/revert.tsx new file mode 100644 index 000000000..26097b610 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/icons/tooling/revert.tsx @@ -0,0 +1,8 @@ +import {FunctionalComponent, h} from "@stencil/core"; + +export const RevertIcon: FunctionalComponent = () => + + + + ; \ No newline at end of file diff --git a/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx b/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx index d3e2f1160..949a73fbe 100644 --- a/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx @@ -202,7 +202,7 @@ export class FlowchartComponent implements ContainerActivityComponent { @Method() async import(root: Activity): Promise { - return this.importInternal(root); + return await this.importInternal(root); } @Method() diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx index d7b181437..87b4f07c9 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx @@ -9,13 +9,15 @@ import { ActivitySelectedArgs, ChildActivitySelectedArgs, ContainerSelectedArgs, - GraphUpdatedArgs, + GraphUpdatedArgs, + VersionedEntity } from '../../../models'; import {ActivityIdUpdatedArgs, ActivityUpdatedArgs} from './activity-properties-editor'; import {ActivityDriverRegistry, ActivityNameFormatter, EventBus, PluginRegistry, PortProviderRegistry} from '../../../services'; import {MonacoEditorSettings} from "../../../services/monaco-editor-settings"; import {ActivityPropertyChangedEventArgs, WorkflowDefinitionPropsUpdatedArgs, WorkflowDefinitionUpdatedArgs, WorkflowEditorEventTypes} from "../models/ui"; import {WorkflowDefinition} from "../models/entities"; +import {WorkflowDefinitionsApi} from "../../workflow-definitions/services/api"; @Component({ tag: 'elsa-workflow-definition-editor', @@ -34,6 +36,7 @@ export class WorkflowDefinitionEditor { private readonly emitActivityChangedDebounced: (e: ActivityPropertyChangedEventArgs) => void; private readonly updateModelDebounced: () => void; private readonly saveChangesDebounced: () => void; + private readonly workflowDefinitionApi: WorkflowDefinitionsApi; constructor() { this.eventBus = Container.get(EventBus); @@ -43,6 +46,7 @@ export class WorkflowDefinitionEditor { this.emitActivityChangedDebounced = debounce(this.emitActivityChanged, 100); this.updateModelDebounced = debounce(this.updateModel, 10); this.saveChangesDebounced = debounce(this.saveChanges, 1000); + this.workflowDefinitionApi = Container.get(WorkflowDefinitionsApi); } @Prop() workflowDefinition?: WorkflowDefinition; @@ -50,6 +54,7 @@ export class WorkflowDefinitionEditor { @Event() workflowUpdated: EventEmitter @State() private workflowDefinitionState: WorkflowDefinition; @State() private selectedActivity?: Activity; + @State() workflowVersions: Array = []; @Watch('monacoLibPath') private handleMonacoLibPath(value: string) { @@ -60,6 +65,7 @@ export class WorkflowDefinitionEditor { @Watch('workflowDefinition') async onWorkflowDefinitionChanged(value: WorkflowDefinition) { await this.importWorkflow(value); + await this.loadWorkflowVersions(); } @Listen('resize', {target: 'window'}) @@ -116,6 +122,7 @@ export class WorkflowDefinitionEditor { @Method() async updateWorkflowDefinition(workflowDefinition: WorkflowDefinition): Promise { this.workflowDefinitionState = workflowDefinition; + await this.loadWorkflowVersions(); } @Method() @@ -138,8 +145,19 @@ export class WorkflowDefinitionEditor { return workflowDefinition; } + loadWorkflowVersions = async () => { + if(this.workflowDefinitionState.definitionId != null && this.workflowDefinitionState.definitionId.length > 0) + { + this.workflowVersions = await this.workflowDefinitionApi.getVersions(this.workflowDefinitionState.definitionId); + } + else { + this.workflowVersions = []; + } + } + async componentWillLoad() { await this.updateWorkflowDefinition(this.workflowDefinition); + await this.loadWorkflowVersions(); } async componentDidLoad() { @@ -176,7 +194,8 @@ export class WorkflowDefinitionEditor { }; private saveChanges = async (): Promise => { - this.workflowUpdated.emit({workflowDefinition: this.workflowDefinitionState}); + const latestVersion = this.workflowVersions.find(v => v.isLatest); + this.workflowUpdated.emit({workflowDefinition: this.workflowDefinitionState, latestVersionNumber: latestVersion?.version}); }; private updateLayout = async () => { @@ -231,6 +250,28 @@ export class WorkflowDefinitionEditor { this.saveChangesDebounced(); } + onVersionSelected = async (e: CustomEvent) => { + const workflowToView = e.detail; + const workflowDefinition = await this.workflowDefinitionApi.get({definitionId: workflowToView.definitionId, versionOptions: {version: workflowToView.version}}); + this.importWorkflow(workflowDefinition); + }; + + onDeleteVersionClicked = async (e: CustomEvent) => { + const workflowToDelete = e.detail; + await this.workflowDefinitionApi.deleteVersion({definitionId: workflowToDelete.definitionId, version: workflowToDelete.version}); + const latestWorkflowDefinition = await this.workflowDefinitionApi.get({definitionId: workflowToDelete.definitionId, versionOptions: {isLatest: true}}); + await this.loadWorkflowVersions(); + this.importWorkflow(latestWorkflowDefinition); + }; + + onRevertVersionClicked = async (e: CustomEvent) => { + const workflowToRevert = e.detail; + await this.workflowDefinitionApi.revertVersion({definitionId: workflowToRevert.definitionId, version: workflowToRevert.version}); + const workflowDefinition = await this.workflowDefinitionApi.get({definitionId: workflowToRevert.definitionId, versionOptions: {isLatest: true}}); + await this.loadWorkflowVersions(); + this.importWorkflow(workflowDefinition); + }; + render() { return ( @@ -254,7 +295,11 @@ export class WorkflowDefinitionEditor {
this.onWorkflowPropsUpdated(e)} + onVersionSelected={e => this.onVersionSelected(e)} + onDeleteVersionClicked={e => this.onDeleteVersionClicked(e)} + onRevertVersionClicked={e => this.onRevertVersionClicked(e)} />
diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/properties.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/properties.tsx index 01299094d..eb0fcd8aa 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/properties.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/properties.tsx @@ -6,7 +6,8 @@ import {PropertiesTabModel, TabModel, WorkflowDefinitionPropsUpdatedArgs, Workfl import {FormEntry} from "../../../components/shared/forms/form-entry"; import {isNullOrWhitespace} from "../../../utils"; import {InfoList} from "../../../components/shared/forms/info-list"; -import {TabChangedArgs, Variable} from "../../../models"; +import {TabChangedArgs, Variable, VersionedEntity} from "../../../models"; +import {WorkflowDefinitionsApi} from "../../workflow-definitions/services/api"; @Component({ tag: 'elsa-workflow-definition-properties-editor', @@ -14,9 +15,11 @@ import {TabChangedArgs, Variable} from "../../../models"; export class WorkflowDefinitionPropertiesEditor { private readonly eventBus: EventBus; private slideOverPanel: HTMLElsaSlideOverPanelElement; + private readonly workflowDefinitionApi: WorkflowDefinitionsApi; constructor() { this.eventBus = Container.get(EventBus); + this.workflowDefinitionApi = Container.get(WorkflowDefinitionsApi); this.model = { tabModels: [], @@ -24,7 +27,11 @@ export class WorkflowDefinitionPropertiesEditor { } @Prop() workflowDefinition?: WorkflowDefinition; + @Prop() workflowVersions: Array; @Event() workflowPropsUpdated: EventEmitter; + @Event() versionSelected: EventEmitter; + @Event() deleteVersionClicked: EventEmitter; + @Event() revertVersionClicked: EventEmitter; @State() private model: WorkflowPropertiesEditorModel; @State() private selectedTabIndex: number = 0; @@ -43,6 +50,11 @@ export class WorkflowDefinitionPropertiesEditor { await this.createModel(); } + @Watch('workflowVersions') + async onWorkflowVersionsChanged() { + await this.createModel(); + } + async componentWillLoad() { await this.createModel(); } @@ -127,7 +139,15 @@ export class WorkflowDefinitionPropertiesEditor { } } - model.tabModels = [propertiesTabModel, variablesTabModel]; + const versionHistoryTabModel: TabModel = { + name: 'versionHistory', + tab: { + displayText: 'Version History', + content: () => this.renderVersionHistoryTab() + } + } + + model.tabModels = [propertiesTabModel, variablesTabModel, versionHistoryTabModel]; const args: WorkflowPropertiesEditorDisplayingArgs = {model}; @@ -152,6 +172,15 @@ export class WorkflowDefinitionPropertiesEditor { }; + private renderVersionHistoryTab = () => { + return
+ +
+ }; + private onSelectedTabIndexChanged = (e: CustomEvent) => this.selectedTabIndex = e.detail.selectedTabIndex; private onPropertyEditorChanged = (apply: (w: WorkflowDefinition) => void) => { diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/version-history.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/version-history.tsx new file mode 100644 index 000000000..70c7175e6 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/version-history.tsx @@ -0,0 +1,100 @@ +import {Component, Event, EventEmitter, h, Host, Method, Prop, State, Watch} from '@stencil/core'; +import {Container} from "typedi"; +import {EventBus} from "../../../services"; +import {WorkflowDefinition} from "../models/entities"; +import {VersionedEntity} from "../../../models"; +import {WorkflowDefinitionsApi} from "../../workflow-definitions/services/api"; +import {DeleteIcon, RevertIcon, PublishedIcon} from "../../../components/icons/tooling"; +import moment from "moment"; + +@Component({ + tag: 'elsa-workflow-definition-version-history', +}) +export class WorkflowDefinitionVersionHistory { + private readonly eventBus: EventBus; + private readonly workflowDefinitionApi: WorkflowDefinitionsApi; + + constructor() { + this.eventBus = Container.get(EventBus); + this.workflowDefinitionApi = Container.get(WorkflowDefinitionsApi); + } + + @Prop() selectedVersion: WorkflowDefinition; + @Prop() workflowVersions: Array; + @Prop() serverUrl: string; + @Event() versionSelected: EventEmitter; + @Event() deleteVersionClicked: EventEmitter; + @Event() revertVersionClicked: EventEmitter; + + @Watch('workflowDefinition') + async onWorkflowDefinitionChanged(value: WorkflowDefinition) { + + } + + async componentWillLoad() { + + } + + onViewVersionClick = (e: Event, version: WorkflowDefinition) => { + e.preventDefault(); + this.versionSelected.emit(version); + }; + + onDeleteVersionClick = async (e: Event, version: WorkflowDefinition) => { + e.preventDefault(); + this.deleteVersionClicked.emit(version); + }; + + onRevertVersionClick = (e: Event, version: WorkflowDefinition) => { + e.preventDefault(); + this.revertVersionClicked.emit(version); + }; + + render() { + return ( +
+ + + + + + + + + {this.workflowVersions.map(v => { + return ( + + + + + + + + ); + } + )} + +
+ VersionCreated + +
{v.isPublished ? : ""}{v.version}{moment(v.createdAt).format('DD-MM-YYYY HH:mm:ss')} + + + {v.isPublished || v.isPublished ? undefined : + this.onDeleteVersionClick(e, v), icon: }, + {text: 'Reverse', clickHandler: e => this.onRevertVersionClick(e, v), icon: }, + ]} + /> + } +
+
+ ); + } +} diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/models/ui.ts b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/models/ui.ts index c7b50d2bc..d7e5ad6b8 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/models/ui.ts +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/models/ui.ts @@ -27,6 +27,7 @@ export interface ActivityPropertyChangedEventArgs { export interface WorkflowDefinitionUpdatedArgs { workflowDefinition: WorkflowDefinition; + latestVersionNumber: number; } export interface WorkflowDefinitionPropsUpdatedArgs { diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/plugin.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/plugin.tsx index b24f1b989..8652d36b1 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/plugin.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/plugin.tsx @@ -19,6 +19,7 @@ import {WorkflowDefinitionUpdatedArgs} from "./models/ui"; import {PublishClickedArgs} from "./components/publish-button"; import {WorkflowDefinitionsApi} from "./services/api"; import {DefaultActions, ModalDialogInstance, ModalDialogService} from "../../components/shared/modal-dialog"; +import {isEqual} from 'lodash' const FlowchartTypeName = 'Elsa.Flowchart'; @@ -106,8 +107,20 @@ export class WorkflowDefinitionsPlugin implements Plugin { }; private onWorkflowUpdated = async (e: CustomEvent) => { - const workflowDefinition = e.detail.workflowDefinition; - await this.saveWorkflowDefinition(workflowDefinition, false); + const updatedWorkflowDefinition = e.detail.workflowDefinition; + + if(e.detail.latestVersionNumber == undefined) + { + await this.saveWorkflowDefinition(updatedWorkflowDefinition, false); + return; + } + + if(updatedWorkflowDefinition.version == e.detail.latestVersionNumber) + { + const currentWorkflowDefinition = await this.api.get({definitionId: updatedWorkflowDefinition.definitionId, versionOptions: {version: updatedWorkflowDefinition.version}}); + if(!isEqual(currentWorkflowDefinition.root.activities, updatedWorkflowDefinition.root.activities)) + await this.saveWorkflowDefinition(updatedWorkflowDefinition, false); + } } private onBrowseWorkflowDefinitions = async () => { diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts index 047f88da6..64fb3da79 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts @@ -1,6 +1,6 @@ import { getVersionOptionsString, serializeQueryString } from '../../../utils'; import {WorkflowDefinition, WorkflowDefinitionSummary} from "../models/entities"; -import {Activity, PagedList, Variable, VersionOptions} from "../../../models"; +import {Activity, PagedList, Variable, VersionedEntity, VersionOptions} from "../../../models"; import {Service} from "typedi"; import {ElsaApiClientProvider} from "../../../services"; @@ -30,6 +30,18 @@ export class WorkflowDefinitionsApi { return response.data; } + async deleteVersion(request: DeleteWorkflowVersionRequest): Promise { + const httpClient = await this.getHttpClient(); + const response = await httpClient.delete(`workflow-definitions/${request.definitionId}/version/${request.version}`); + return response.data; + } + + async revertVersion(request: RevertWorkflowVersionRequest): Promise { + const httpClient = await this.getHttpClient(); + const response = await httpClient.post(`workflow-definitions/${request.definitionId}/revert/${request.version}`); + return response.data; + } + async post(request: SaveWorkflowDefinitionRequest): Promise { const httpClient = await this.getHttpClient(); const response = await httpClient.post('workflow-definitions', request); @@ -47,6 +59,12 @@ export class WorkflowDefinitionsApi { return response.data; } + async getVersions(workflowDefinitionId: string): Promise> { + const httpClient = await this.getHttpClient(); + const response = await httpClient.get>(`workflow-definitions/${workflowDefinitionId}/versions`); + return response.data; + } + async list(request: ListWorkflowDefinitionsRequest): Promise> { const queryString: any = {}; @@ -137,6 +155,7 @@ export class WorkflowDefinitionsApi { export interface SaveWorkflowDefinitionRequest { definitionId: string; + version?: number; name?: string; description?: string; publish: boolean; @@ -155,6 +174,16 @@ export interface DeleteWorkflowDefinitionRequest { definitionId: string; } +export interface DeleteWorkflowVersionRequest { + definitionId: string; + version: number; +} + +export interface RevertWorkflowVersionRequest { + definitionId: string; + version: number; +} + export interface RetractWorkflowDefinitionRequest { definitionId: string; } diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/manager.ts b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/manager.ts index c75d7bef2..9e2859b80 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/manager.ts +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/manager.ts @@ -13,6 +13,7 @@ export class WorkflowDefinitionManager { public saveWorkflow = async (definition: WorkflowDefinition, publish: boolean): Promise => { const request: SaveWorkflowDefinitionRequest = { definitionId: definition.definitionId, + version: definition.version, name: definition.name, description: definition.description, publish: publish, diff --git a/src/modules/Elsa.ProtoActor/Grains/WorkflowInstanceGrain.cs b/src/modules/Elsa.ProtoActor/Grains/WorkflowInstanceGrain.cs index 127718012..3c960b719 100644 --- a/src/modules/Elsa.ProtoActor/Grains/WorkflowInstanceGrain.cs +++ b/src/modules/Elsa.ProtoActor/Grains/WorkflowInstanceGrain.cs @@ -58,7 +58,7 @@ public class WorkflowInstanceGrain : WorkflowInstanceGrainBase throw new Exception($"No workflow instance found with ID {workflowInstanceId}"); var workflowDefinitionId = workflowInstance.DefinitionId; - var workflow = await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken); + var workflow = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken)).FirstOrDefault(); if (workflow == null) throw new Exception($"No workflow definition found with ID {workflowDefinitionId}"); @@ -79,7 +79,7 @@ public class WorkflowInstanceGrain : WorkflowInstanceGrainBase var workflowDefinitionId = request.DefinitionId; var correlationId = request.CorrelationId == "" ? default : request.CorrelationId; var workflowInstance = await _workflowInstanceFactory.CreateAsync(workflowDefinitionId, versionOptions, correlationId, cancellationToken); - var workflow = await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken); + var workflow = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken)).FirstOrDefault(); if (workflow == null) throw new Exception($"No workflow definition found with ID {workflowDefinitionId}"); @@ -100,7 +100,7 @@ public class WorkflowInstanceGrain : WorkflowInstanceGrainBase var workflowDefinitionId = request.DefinitionId; var bookmark = request.Bookmark; var input = request.Input?.Deserialize(); - var workflow = await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, versionOptions, cancellationToken); + var workflow = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, versionOptions, cancellationToken)).FirstOrDefault(); if (workflow == null) throw new Exception($"No workflow definition found with ID {workflowDefinitionId}"); diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs index 7b32282e8..fd5017217 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Elsa.Abstractions; @@ -34,7 +35,7 @@ public class BulkPublish : ElsaEndpoint foreach (var definitionId in request.DefinitionIds) { - var definition = await _store.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken); + var definition = (await _store.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); if (definition == null) { diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs index e5d48c5ac..9f08e7ee3 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Elsa.Abstractions; @@ -33,7 +34,7 @@ public class BulkRetract : ElsaEndpoint foreach (var definitionId in request.DefinitionIds) { - var definition = await _store.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken); + var definition = (await _store.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); if (definition == null) { diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs index 8c4851316..f11f1f8a1 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs @@ -42,7 +42,7 @@ public class Export : ElsaEndpoint { var serializerOptions = _serializerOptionsProvider.CreateApiOptions(); var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest; - var definition = await _store.FindByDefinitionIdAsync(request.DefinitionId, versionOptions, cancellationToken); + var definition = (await _store.FindByDefinitionIdAsync(request.DefinitionId, versionOptions, cancellationToken)).FirstOrDefault(); if (definition == null) { diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs index fbbd0c047..8845a4ce5 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Threading; using System.Threading.Tasks; using Elsa.Abstractions; @@ -26,7 +27,7 @@ public class Get : ElsaEndpoint("definitionId")!; + var version = Route("version"); + + var result = await _workflowDefinitionManager.DeleteVersionAsync(definitionId, version, ct); + if (!result) + { + await SendNotFoundAsync(ct); + return; + } + + await SendOkAsync(ct); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/List.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/List.cs new file mode 100644 index 000000000..de2e88b3c --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/List.cs @@ -0,0 +1,39 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Models; +using Elsa.Workflows.Persistence.Services; +using FastEndpoints; +using Microsoft.AspNetCore.Http; + +namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Version; + +public class ListVersions : EndpointWithoutRequest +{ + private readonly IWorkflowDefinitionStore _store; + + public ListVersions(IWorkflowDefinitionStore store) + { + _store = store; + } + + public override void Configure() + { + Get("workflow-definitions/{definitionId}/versions"); + AllowAnonymous(); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var definitionId = Route("definitionId")!; + + var result = await _store.FindByDefinitionIdAsync(definitionId, VersionOptions.All, ct); + if (!result.Any()) + { + await SendNotFoundAsync(ct); + return; + } + + await SendAsync(result, StatusCodes.Status200OK, ct); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs new file mode 100644 index 000000000..50955e846 --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs @@ -0,0 +1,32 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Workflows.Management.Services; +using FastEndpoints; + +namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Version; + +public class RevertVersion : EndpointWithoutRequest +{ + private readonly IWorkflowDefinitionManager _workflowDefinitionManager; + + public RevertVersion(IWorkflowDefinitionManager workflowDefinitionManager) + { + _workflowDefinitionManager = workflowDefinitionManager; + } + + public override void Configure() + { + Post("workflow-definitions/{definitionId}/revert/{version}"); + AllowAnonymous(); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var definitionId = Route("definitionId")!; + var version = Route("version"); + + await _workflowDefinitionManager.RevertVersionAsync(definitionId, version, ct); + + await SendOkAsync(ct); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionRequest.cs b/src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionRequest.cs index 1bf20f565..7c3145ddf 100644 --- a/src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionRequest.cs +++ b/src/modules/Elsa.Workflows.Api/Models/WorkflowDefinitionRequest.cs @@ -9,6 +9,7 @@ public class WorkflowDefinitionRequest public string? DefinitionId { get; set; } public string? Name { get; set; } public string? Description { get; set; } = default!; + public int? Version { get; set; } public ICollection? Variables { get; set; } = default!; public IDictionary? Metadata { get; set; } = default!; public IDictionary? ApplicationProperties { get; set; } = default!; diff --git a/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionManager.cs b/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionManager.cs index 2a316e9f2..8547e7bc4 100644 --- a/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionManager.cs +++ b/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionManager.cs @@ -1,6 +1,10 @@ using Elsa.Mediator.Services; +using Elsa.Models; +using Elsa.Persistence.Common.Extensions; +using Elsa.Workflows.Core.Services; using Elsa.Workflows.Management.Notifications; using Elsa.Workflows.Management.Services; +using Elsa.Workflows.Persistence.Entities; using Elsa.Workflows.Persistence.Services; namespace Elsa.Workflows.Management.Implementations; @@ -9,11 +13,19 @@ public class WorkflowDefinitionManager : IWorkflowDefinitionManager { private readonly IWorkflowDefinitionStore _store; private readonly IEventPublisher _eventPublisher; + private readonly IWorkflowDefinitionPublisher _workflowPublisher; + private readonly IIdentityGenerator _identityGenerator; - public WorkflowDefinitionManager(IWorkflowDefinitionStore store, IEventPublisher eventPublisher) + public WorkflowDefinitionManager( + IWorkflowDefinitionStore store, + IEventPublisher eventPublisher, + IWorkflowDefinitionPublisher workflowPublisher, + IIdentityGenerator identityGenerator) { _store = store; _eventPublisher = eventPublisher; + _workflowPublisher = workflowPublisher; + _identityGenerator = identityGenerator; } public async Task DeleteByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken = default) @@ -30,4 +42,55 @@ public class WorkflowDefinitionManager : IWorkflowDefinitionManager await _eventPublisher.PublishAsync(new WorkflowDefinitionsDeleted(ids), cancellationToken); return count; } + + public async Task DeleteVersionAsync(string definitionId, int versionToDelete, CancellationToken cancellationToken = default) + { + var workflows = (await _store.FindLatestAndPublishedByDefinitionIdAsync(definitionId, cancellationToken)).ToList(); + var latestVersion = workflows.WithVersion(VersionOptions.Latest).First(); + var publishedVersion = workflows.WithVersion(VersionOptions.Published).FirstOrDefault(); + + if (versionToDelete == publishedVersion?.Version) + { + throw new Exception("Published version cannot be deleted"); + } + + var isDeleted = await _store.DeleteByDefinitionIdAndVersionAsync(definitionId, versionToDelete, cancellationToken) > 0; + + if (isDeleted) + { + await _eventPublisher.PublishAsync(new WorkflowDefinitionVersionDeleted(definitionId, versionToDelete), cancellationToken); + if (latestVersion.Version == versionToDelete) + { + var lastVersion = await _store.FindLastVersionByDefinitionIdAsync(definitionId, cancellationToken); + if (lastVersion is not null) + { + lastVersion.IsLatest = true; + await _store.SaveAsync(lastVersion, cancellationToken); + } + } + } + + return isDeleted; + } + + public async Task RevertVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default) + { + var publishedAndLatestVersions = (await _store.FindLatestAndPublishedByDefinitionIdAsync(definitionId, cancellationToken)).ToList(); + if (publishedAndLatestVersions.Any(v => v.Version == version)) + { + throw new Exception("Latest or published versions cannot be reverted"); + } + + var latestVersion = publishedAndLatestVersions.First(v => v.IsLatest); + latestVersion.IsLatest = false; + await _store.SaveAsync(latestVersion, cancellationToken); + + var draft = await _workflowPublisher.GetDraftAsync(definitionId, version, cancellationToken); + draft!.Id = _identityGenerator.GenerateId(); + draft.Version = latestVersion.Version + 1; + draft.IsLatest = true; + + await _store.SaveAsync(draft, cancellationToken); + return draft; + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionPublisher.cs b/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionPublisher.cs index 34c402452..4a90eb2a5 100644 --- a/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionPublisher.cs +++ b/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionPublisher.cs @@ -56,7 +56,7 @@ namespace Elsa.Workflows.Management.Implementations public async Task PublishAsync(string definitionId, CancellationToken cancellationToken = default) { - var definition = await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken); + var definition = (await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); if (definition == null) return null; @@ -94,7 +94,7 @@ namespace Elsa.Workflows.Management.Implementations public async Task RetractAsync(string definitionId, CancellationToken cancellationToken = default) { - var definition = await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, VersionOptions.Published, cancellationToken); + var definition = (await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, VersionOptions.Published, cancellationToken)).FirstOrDefault(); if (definition == null) return null; @@ -116,9 +116,9 @@ namespace Elsa.Workflows.Management.Implementations return definition; } - public async Task GetDraftAsync(string definitionId, CancellationToken cancellationToken = default) + public async Task GetDraftAsync(string definitionId, int? version, CancellationToken cancellationToken = default) { - var definition = await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken); + var definition = (await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, version.HasValue ? VersionOptions.SpecificVersion(version.Value) : VersionOptions.Latest, cancellationToken)).FirstOrDefault(); if (definition == null) return null; @@ -139,7 +139,7 @@ namespace Elsa.Workflows.Management.Implementations { var draft = definition; var definitionId = definition.DefinitionId; - var latestVersion = await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken); + var latestVersion = (await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); if (latestVersion is { IsPublished: true, IsLatest: true }) { diff --git a/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionVersionDeleted.cs b/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionVersionDeleted.cs new file mode 100644 index 000000000..cb52d64a6 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionVersionDeleted.cs @@ -0,0 +1,5 @@ +using Elsa.Mediator.Services; + +namespace Elsa.Workflows.Management.Notifications; + +public record WorkflowDefinitionVersionDeleted(string DefinitionId, int Version) : INotification; \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionManager.cs b/src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionManager.cs index 7ea2ecdd0..12357cc82 100644 --- a/src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionManager.cs +++ b/src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionManager.cs @@ -1,7 +1,11 @@ +using Elsa.Workflows.Persistence.Entities; + namespace Elsa.Workflows.Management.Services; public interface IWorkflowDefinitionManager { Task DeleteByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken = default); + Task DeleteVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default); Task BulkDeleteByDefinitionIdsAsync(IEnumerable definitionIds, CancellationToken cancellationToken = default); + Task RevertVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionPublisher.cs b/src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionPublisher.cs index 57ceae526..bbbd8a1ef 100644 --- a/src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionPublisher.cs +++ b/src/modules/Elsa.Workflows.Management/Services/IWorkflowDefinitionPublisher.cs @@ -9,7 +9,7 @@ namespace Elsa.Workflows.Management.Services Task PublishAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default); Task RetractAsync(string definitionId, CancellationToken cancellationToken = default); Task RetractAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default); - Task GetDraftAsync(string definitionId, CancellationToken cancellationToken = default); + Task GetDraftAsync(string definitionId, int? version = null, CancellationToken cancellationToken = default); Task SaveDraftAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.csproj b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.csproj index 12907a537..f5243134b 100644 --- a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.csproj +++ b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.csproj @@ -18,4 +18,8 @@ + + + + diff --git a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220814124730_Initial.Designer.cs b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220828110255_Initial.Designer.cs similarity index 96% rename from src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220814124730_Initial.Designer.cs rename to src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220828110255_Initial.Designer.cs index c324a3af3..c4bd19fd4 100644 --- a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220814124730_Initial.Designer.cs +++ b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220828110255_Initial.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.Migrations { [DbContext(typeof(WorkflowsDbContext))] - [Migration("20220814124730_Initial")] + [Migration("20220828110255_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -56,6 +56,9 @@ namespace Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("WorkflowVersion") + .HasColumnType("INTEGER"); + b.HasKey("Id"); b.HasIndex("ActivityId") @@ -188,6 +191,9 @@ namespace Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("WorkflowVersion") + .HasColumnType("INTEGER"); + b.HasKey("Id"); b.HasIndex("ActivityId") @@ -214,6 +220,9 @@ namespace Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.Migrations b.HasIndex("WorkflowInstanceId") .HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowInstanceId"); + b.HasIndex("WorkflowVersion") + .HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowVersion"); + b.ToTable("WorkflowExecutionLogRecords"); }); diff --git a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220814124730_Initial.cs b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220828110255_Initial.cs similarity index 97% rename from src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220814124730_Initial.cs rename to src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220828110255_Initial.cs index e0327ddb0..e0260d15e 100644 --- a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220814124730_Initial.cs +++ b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/20220828110255_Initial.cs @@ -19,6 +19,7 @@ namespace Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.Migrations Data = table.Column(type: "TEXT", nullable: true), WorkflowDefinitionId = table.Column(type: "TEXT", nullable: false), WorkflowInstanceId = table.Column(type: "TEXT", nullable: false), + WorkflowVersion = table.Column(type: "INTEGER", nullable: false), CorrelationId = table.Column(type: "TEXT", nullable: true), ActivityId = table.Column(type: "TEXT", nullable: false), ActivityInstanceId = table.Column(type: "TEXT", nullable: false), @@ -59,6 +60,7 @@ namespace Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.Migrations Id = table.Column(type: "TEXT", nullable: false), WorkflowDefinitionId = table.Column(type: "TEXT", nullable: false), WorkflowInstanceId = table.Column(type: "TEXT", nullable: false), + WorkflowVersion = table.Column(type: "INTEGER", nullable: false), ActivityInstanceId = table.Column(type: "TEXT", nullable: false), ParentActivityInstanceId = table.Column(type: "TEXT", nullable: true), ActivityId = table.Column(type: "TEXT", nullable: false), @@ -209,6 +211,11 @@ namespace Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.Migrations table: "WorkflowExecutionLogRecords", column: "WorkflowInstanceId"); + migrationBuilder.CreateIndex( + name: "IX_WorkflowExecutionLogRecord_WorkflowVersion", + table: "WorkflowExecutionLogRecords", + column: "WorkflowVersion"); + migrationBuilder.CreateIndex( name: "IX_WorkflowInstance_CorrelationId", table: "WorkflowInstances", diff --git a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/WorkflowsDbContextModelSnapshot.cs b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/WorkflowsDbContextModelSnapshot.cs index 8311dbc93..01c5123cc 100644 --- a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/WorkflowsDbContextModelSnapshot.cs +++ b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite/Migrations/WorkflowsDbContextModelSnapshot.cs @@ -54,6 +54,9 @@ namespace Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("WorkflowVersion") + .HasColumnType("INTEGER"); + b.HasKey("Id"); b.HasIndex("ActivityId") @@ -186,6 +189,9 @@ namespace Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("WorkflowVersion") + .HasColumnType("INTEGER"); + b.HasKey("Id"); b.HasIndex("ActivityId") @@ -212,6 +218,9 @@ namespace Elsa.Workflows.Persistence.EntityFrameworkCore.Sqlite.Migrations b.HasIndex("WorkflowInstanceId") .HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowInstanceId"); + b.HasIndex("WorkflowVersion") + .HasDatabaseName("IX_WorkflowExecutionLogRecord_WorkflowVersion"); + b.ToTable("WorkflowExecutionLogRecords"); }); diff --git a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/EntityConfiguration/WorkflowExecutionLogRecordConfiguration.cs b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/EntityConfiguration/WorkflowExecutionLogRecordConfiguration.cs index 739492685..02c101ed4 100644 --- a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/EntityConfiguration/WorkflowExecutionLogRecordConfiguration.cs +++ b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/EntityConfiguration/WorkflowExecutionLogRecordConfiguration.cs @@ -19,6 +19,7 @@ namespace Elsa.Workflows.Persistence.EntityFrameworkCore.EntityConfiguration builder.HasIndex(x => x.EventName).HasDatabaseName($"IX_{nameof(WorkflowExecutionLogRecord)}_{nameof(WorkflowExecutionLogRecord.EventName)}"); builder.HasIndex(x => x.WorkflowDefinitionId).HasDatabaseName($"IX_{nameof(WorkflowExecutionLogRecord)}_{nameof(WorkflowExecutionLogRecord.WorkflowDefinitionId)}"); builder.HasIndex(x => x.WorkflowInstanceId).HasDatabaseName($"IX_{nameof(WorkflowExecutionLogRecord)}_{nameof(WorkflowExecutionLogRecord.WorkflowInstanceId)}"); + builder.HasIndex(x => x.WorkflowVersion).HasDatabaseName($"IX_{nameof(WorkflowExecutionLogRecord)}_{nameof(WorkflowExecutionLogRecord.WorkflowVersion)}"); } } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionStore.cs index 97936da66..d93a725a6 100644 --- a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionStore.cs @@ -6,6 +6,7 @@ using Elsa.Persistence.EntityFrameworkCore.Common.Services; using Elsa.Workflows.Persistence.Entities; using Elsa.Workflows.Persistence.Models; using Elsa.Workflows.Persistence.Services; +using Open.Linq.AsyncExtensions; namespace Elsa.Workflows.Persistence.EntityFrameworkCore.Implementations; @@ -17,11 +18,11 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore public async Task FindByIdAsync(string id, CancellationToken cancellationToken = default) => await _store.FindAsync(x => x.Id == id, cancellationToken); - public async Task FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) + public async Task> FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) { Expression> predicate = x => x.DefinitionId == definitionId; predicate = predicate.WithVersion(versionOptions); - return await _store.FindAsync(predicate, cancellationToken); + return await _store.FindManyAsync(predicate, cancellationToken); } public async Task FindByNameAsync(string name, VersionOptions versionOptions, CancellationToken cancellationToken = default) @@ -48,6 +49,14 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore public async Task> FindLatestAndPublishedByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken = default) => await _store.FindManyAsync(x => x.DefinitionId == definitionId && (x.IsLatest || x.IsPublished), cancellationToken); + public async Task FindLastVersionByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken) + { + await using var dbContext = await _store.CreateDbContextAsync(cancellationToken); + var set = dbContext.WorkflowDefinitions; + var query = set.AsQueryable(); + return query.Where(w => w.DefinitionId == definitionId).OrderByDescending(w => w.Version).FirstOrDefault(); + } + public async Task SaveAsync(WorkflowDefinition record, CancellationToken cancellationToken = default) => await _store.SaveAsync(record, cancellationToken); public async Task SaveManyAsync(IEnumerable records, CancellationToken cancellationToken = default) => await _store.SaveManyAsync(records, cancellationToken); @@ -60,6 +69,15 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore await dbContext.WorkflowBookmarks.DeleteWhereAsync(dbContext, x => x.WorkflowDefinitionId == definitionId, cancellationToken); return await dbContext.WorkflowDefinitions.DeleteWhereAsync(dbContext, x => x.DefinitionId == definitionId, cancellationToken); } + + public async Task DeleteByDefinitionIdAndVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default) + { + await using var dbContext = await _store.CreateDbContextAsync(cancellationToken); + await dbContext.WorkflowExecutionLogRecords.DeleteWhereAsync(dbContext, x => x.WorkflowDefinitionId == definitionId && x.WorkflowVersion == version, cancellationToken); + await dbContext.WorkflowInstances.DeleteWhereAsync(dbContext, x => x.DefinitionId == definitionId && x.Version == version, cancellationToken); + await dbContext.WorkflowBookmarks.DeleteWhereAsync(dbContext, x => x.WorkflowDefinitionId == definitionId && x.WorkflowVersion == version, cancellationToken); + return await dbContext.WorkflowDefinitions.DeleteWhereAsync(dbContext, x => x.DefinitionId == definitionId && x.Version == version, cancellationToken); + } public async Task DeleteByDefinitionIdsAsync(IEnumerable definitionIds, CancellationToken cancellationToken = default) { diff --git a/src/modules/Elsa.Workflows.Persistence/Entities/WorkflowBookmark.cs b/src/modules/Elsa.Workflows.Persistence/Entities/WorkflowBookmark.cs index 5223d1921..75597af0e 100644 --- a/src/modules/Elsa.Workflows.Persistence/Entities/WorkflowBookmark.cs +++ b/src/modules/Elsa.Workflows.Persistence/Entities/WorkflowBookmark.cs @@ -10,6 +10,7 @@ public class WorkflowBookmark : Entity public string? Data { get; set; } public string WorkflowDefinitionId { get; init; } = default!; public string WorkflowInstanceId { get; init; } = default!; + public int WorkflowVersion { get; init; } = default!; public string? CorrelationId { get; init; } public string ActivityId { get; init; } = default!; public string ActivityInstanceId { get; init; } = default!; @@ -23,6 +24,7 @@ public class WorkflowBookmark : Entity Id = bookmark.Id, WorkflowDefinitionId = workflowInstance.DefinitionId, WorkflowInstanceId = workflowInstance.Id, + WorkflowVersion = workflowInstance.Version, CorrelationId = workflowInstance.CorrelationId, Hash = bookmark.Hash, Data = bookmark.Data, diff --git a/src/modules/Elsa.Workflows.Persistence/Entities/WorkflowExecutionLogRecord.cs b/src/modules/Elsa.Workflows.Persistence/Entities/WorkflowExecutionLogRecord.cs index 08bf3b2a5..808401f28 100644 --- a/src/modules/Elsa.Workflows.Persistence/Entities/WorkflowExecutionLogRecord.cs +++ b/src/modules/Elsa.Workflows.Persistence/Entities/WorkflowExecutionLogRecord.cs @@ -6,6 +6,7 @@ namespace Elsa.Workflows.Persistence.Entities { public string WorkflowDefinitionId { get; set; } = default!; public string WorkflowInstanceId { get; set; } = default!; + public int WorkflowVersion { get; init; } public string ActivityInstanceId { get; set; } = default!; public string? ParentActivityInstanceId { get; set; } public string ActivityId { get; set; } = default!; diff --git a/src/modules/Elsa.Workflows.Persistence/Implementations/MemoryWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Persistence/Implementations/MemoryWorkflowDefinitionStore.cs index 6aa5e25e3..c3a4f0a4d 100644 --- a/src/modules/Elsa.Workflows.Persistence/Implementations/MemoryWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Persistence/Implementations/MemoryWorkflowDefinitionStore.cs @@ -32,9 +32,9 @@ public class MemoryWorkflowDefinitionStore : IWorkflowDefinitionStore return Task.FromResult(definition); } - public Task FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) + public Task> FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) { - var definition = _store.Find(x => x.DefinitionId == definitionId && x.WithVersion(versionOptions)); + var definition = _store.FindMany(x => x.DefinitionId == definitionId && x.WithVersion(versionOptions)); return Task.FromResult(definition); } @@ -64,6 +64,12 @@ public class MemoryWorkflowDefinitionStore : IWorkflowDefinitionStore return Task.FromResult(definitions); } + public Task FindLastVersionByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken) + { + var query = _store.List(); + return Task.FromResult(query.Where(w => w.DefinitionId == definitionId).OrderByDescending(w => w.Version).FirstOrDefault()); + } + public Task SaveAsync(WorkflowDefinition record, CancellationToken cancellationToken = default) { _store.Save(record); @@ -84,6 +90,14 @@ public class MemoryWorkflowDefinitionStore : IWorkflowDefinitionStore var result = _store.DeleteWhere(x => x.DefinitionId == definitionId); return Task.FromResult(result); } + + public Task DeleteByDefinitionIdAndVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default) + { + _bookmarkStore.DeleteWhere(x => x.WorkflowDefinitionId == definitionId && x.WorkflowVersion == version); + _instanceStore.DeleteWhere(x => x.DefinitionId == definitionId && x.Version == version); + var result = _store.DeleteWhere(x => x.DefinitionId == definitionId && x.Version == version); + return Task.FromResult(result); + } public Task DeleteByDefinitionIdsAsync(IEnumerable definitionIds, CancellationToken cancellationToken = default) { diff --git a/src/modules/Elsa.Workflows.Persistence/Services/IWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Persistence/Services/IWorkflowDefinitionStore.cs index eaa13f78f..2ea60d972 100644 --- a/src/modules/Elsa.Workflows.Persistence/Services/IWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Persistence/Services/IWorkflowDefinitionStore.cs @@ -14,7 +14,7 @@ public interface IWorkflowDefinitionStore /// /// Finds a workflow definition by its logical definition ID and specified version options. /// - Task FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default); + Task> FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default); /// /// Finds a workflow definition by name and specified version options. @@ -23,9 +23,11 @@ public interface IWorkflowDefinitionStore Task> FindManySummariesAsync(IEnumerable definitionIds, VersionOptions? versionOptions = default, CancellationToken cancellationToken = default); Task> FindLatestAndPublishedByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken = default); + Task FindLastVersionByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken); Task SaveAsync(WorkflowDefinition record, CancellationToken cancellationToken = default); Task SaveManyAsync(IEnumerable records, CancellationToken cancellationToken = default); Task DeleteByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken = default); + Task DeleteByDefinitionIdAndVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default); Task DeleteByDefinitionIdsAsync(IEnumerable definitionIds, CancellationToken cancellationToken = default); Task> ListSummariesAsync( diff --git a/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs index f60a1fe5f..358e5e13c 100644 --- a/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs @@ -44,10 +44,10 @@ public class PopulateWorkflowDefinitionStore : IHostedService private async Task AddOrUpdateAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default) { // Check if there's already a workflow definition by the definition ID and version. - var existingDefinition = await _workflowDefinitionStore.FindByDefinitionIdAsync( + var existingDefinition = (await _workflowDefinitionStore.FindByDefinitionIdAsync( definition.DefinitionId, VersionOptions.SpecificVersion(definition.Version), - cancellationToken); + cancellationToken)).FirstOrDefault(); if (existingDefinition == null) { diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowInvoker.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowInvoker.cs index fff20bc80..4aae8199e 100644 --- a/src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowInvoker.cs +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowInvoker.cs @@ -51,7 +51,7 @@ public class DefaultWorkflowInvoker : IWorkflowInvoker public async Task InvokeAsync(WorkflowInstance workflowInstance, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default) { - var workflow = await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowInstance.DefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken); + var workflow = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowInstance.DefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken)).FirstOrDefault(); if (workflow == null) throw new Exception($"Workflow instance references a workflow that does not exist"); @@ -73,7 +73,7 @@ public class DefaultWorkflowInvoker : IWorkflowInvoker private async Task GetWorkflowDefinitionAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) { - var definition = await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, versionOptions, cancellationToken); + var definition = (await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, versionOptions, cancellationToken)).FirstOrDefault(); if (definition == null) throw new Exception($"Workflow instance references a workflow that does not exist"); diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstanceFactory.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstanceFactory.cs index 2ba965380..9b004d3cf 100644 --- a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstanceFactory.cs +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstanceFactory.cs @@ -33,7 +33,7 @@ public class WorkflowInstanceFactory : IWorkflowInstanceFactory public async Task CreateAsync(string workflowDefinitionId, VersionOptions versionOptions, string? correlationId, CancellationToken cancellationToken = default) { - var workflow = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, versionOptions, cancellationToken))!; + var workflow = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, versionOptions, cancellationToken)).FirstOrDefault()!; return await CreateAsync(workflow, correlationId, cancellationToken); } diff --git a/src/modules/Elsa.Workflows.Runtime/Interpreters/ResumeWorkflowInstructionInterpreter.cs b/src/modules/Elsa.Workflows.Runtime/Interpreters/ResumeWorkflowInstructionInterpreter.cs index c66786dfa..377499413 100644 --- a/src/modules/Elsa.Workflows.Runtime/Interpreters/ResumeWorkflowInstructionInterpreter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Interpreters/ResumeWorkflowInstructionInterpreter.cs @@ -56,7 +56,7 @@ public class ResumeWorkflowInstructionInterpreter : WorkflowInstructionInterpret return null; } - var definition = await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken); + var definition = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken)).FirstOrDefault(); if (definition == null) { diff --git a/src/modules/Elsa.Workflows.Runtime/Middleware/PersistWorkflowExecutionLogMiddleware.cs b/src/modules/Elsa.Workflows.Runtime/Middleware/PersistWorkflowExecutionLogMiddleware.cs index e069deb8d..6c84f40ac 100644 --- a/src/modules/Elsa.Workflows.Runtime/Middleware/PersistWorkflowExecutionLogMiddleware.cs +++ b/src/modules/Elsa.Workflows.Runtime/Middleware/PersistWorkflowExecutionLogMiddleware.cs @@ -38,6 +38,7 @@ public class PersistWorkflowExecutionLogMiddleware : WorkflowExecutionMiddleware EventName = x.EventName, WorkflowDefinitionId = context.Workflow.Identity.DefinitionId, WorkflowInstanceId = context.Id, + WorkflowVersion = context.Workflow.Version, Source = x.Source, Payload = x.Payload, Timestamp = x.Timestamp