Skip to content

Commit 25fd372

Browse files
authored
Merge pull request #727 from PROCEED-Labs/engine/branch-activation
Engine: Deployment Activation/Deactivation
2 parents 6b14e7d + 35a4224 commit 25fd372

6 files changed

Lines changed: 216 additions & 9 deletions

File tree

src/engine/universal/core/src/engine/engine.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ class Engine {
129129
* @param {string} the version of the process to deploy
130130
*/
131131
async deployProcessVersion(definitionId, versionId) {
132+
const otherVersions = this.versions.filter((version) => version !== versionId);
133+
otherVersions.forEach((version) => {
134+
const process = this._versionProcessMapping[version];
135+
136+
// deactivate other versions so they don't keep spawning new instances automatically
137+
if (process) {
138+
process.undeploy();
139+
}
140+
});
141+
132142
if (!this._versionProcessMapping[versionId]) {
133143
// Fetch the stored BPMN
134144
const bpmn = await distribution.db.getProcessVersion(definitionId, versionId);
@@ -213,6 +223,22 @@ class Engine {
213223
this._versionProcessMapping[versionId] = process;
214224
this._versionBpmnMapping[versionId] = bpmn;
215225
this.versions.push(versionId);
226+
} else if (!this._versionProcessMapping[versionId].isDeployed()) {
227+
// activate the process so auto-start events like timer events are allowed to trigger new
228+
// instances
229+
this._versionProcessMapping[versionId].deploy();
230+
}
231+
}
232+
233+
/**
234+
* Removes the deployed state from the process version in the NeoBPMN Engine preventing it from starting instances
235+
*
236+
* @param {string} the version of the process to undeploy
237+
*/
238+
undeployProcessVersion(versionId) {
239+
const process = this._versionProcessMapping[versionId];
240+
if (process && process.isDeployed()) {
241+
process.undeploy();
216242
}
217243
}
218244

@@ -1014,6 +1040,9 @@ class Engine {
10141040
* Clean up some data when the engine is supposed to be removed
10151041
*/
10161042
destroy() {
1043+
for (const version of this.versions) {
1044+
this._versionProcessMapping[version].undeploy();
1045+
}
10171046
for (const instanceId of this.instanceIDs) {
10181047
this.deleteInstance(instanceId);
10191048
}

src/engine/universal/core/src/management.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,7 @@ const Management = {
5555
const engine = this.ensureProcessEngine(definitionId);
5656

5757
// ensure that the version is deployed
58-
if (!engine.versions.includes(version)) {
59-
await engine.deployProcessVersion(definitionId, version);
60-
}
58+
await engine.deployProcessVersion(definitionId, version);
6159

6260
return engine;
6361
},

src/engine/universal/distribution/src/routes/ProcessInstanceRoutes.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,4 +393,71 @@ module.exports = (path, management) => {
393393
});
394394
},
395395
);
396+
397+
network.put(`${path}/:definitionId/active`, { cors: true }, async (req) => {
398+
const { definitionId } = req.params;
399+
400+
const {
401+
body: { active },
402+
} = req;
403+
404+
if (active === true) {
405+
return {
406+
statusCode: 400,
407+
mimeType: 'text/plain',
408+
response:
409+
'Cannot set active true on a process. Please select a specific version to activate.',
410+
};
411+
} else if (active === false) {
412+
const engine = await management.getEngineWithDefinitionId(definitionId);
413+
414+
if (engine) {
415+
engine.versions.forEach((version) => engine.undeployProcessVersion(version));
416+
}
417+
418+
return {
419+
statusCode: 200,
420+
mimeType: 'application/json',
421+
response: '{}',
422+
};
423+
} else {
424+
return {
425+
statusCode: 400,
426+
mimeType: 'text/plain',
427+
response:
428+
'This endpoint expects the request body to contain an entry called active with a boolean value of "false".',
429+
};
430+
}
431+
});
432+
433+
network.put(`${path}/:definitionId/versions/:version/active`, { cors: true }, async (req) => {
434+
const { definitionId, version } = req.params;
435+
436+
const {
437+
body: { active },
438+
} = req;
439+
440+
if (active === true) {
441+
await management.ensureProcessEngineWithVersion(definitionId, version);
442+
} else if (active === false) {
443+
const engine = await management.getEngineWithDefinitionId(definitionId);
444+
445+
if (engine) {
446+
engine.undeployProcessVersion(version);
447+
}
448+
} else {
449+
return {
450+
statusCode: 400,
451+
mimeType: 'text/plain',
452+
response:
453+
'This endpoint expects the request body to contain an entry called active with a boolean value',
454+
};
455+
}
456+
457+
return {
458+
statusCode: 200,
459+
mimeType: 'application/json',
460+
response: '{}',
461+
};
462+
});
396463
};

src/management-system-v2/lib/engines/deployment.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,17 @@ async function deployProcessToMachines(
106106
});
107107

108108
await Promise.all(allMachineRequests);
109+
110+
// TODO: if we handle static deployment with machine mapping of process elements in the future
111+
// we might need to make sure that we only activate the process in a way that start events that are not mapped to a machine are not triggered on that machine
112+
// (maybe we need to split the process into multiple sections and deploy them seperately
113+
await asyncForEach(machines, async (machine) => {
114+
await asyncForEach(processesExportData, async (process) => {
115+
await asyncForEach(Object.keys(process.versions), async (version) => {
116+
await changeDeploymentActivation(machine, process.definitionId, version, true);
117+
});
118+
});
119+
});
109120
} catch (error) {
110121
// TODO: don't remove the whole process when deploying a single version fails
111122
await asyncForEach(Object.values(processesExportData), async ({ definitionId }) => {
@@ -153,6 +164,8 @@ async function dynamicDeployment(
153164
}
154165

155166
await deployProcessToMachines([preferredMachine], processesExportData);
167+
168+
return [preferredMachine];
156169
}
157170

158171
async function staticDeployment(
@@ -198,6 +211,8 @@ async function staticDeployment(
198211
// targetedMachines.push(forceMachine);
199212

200213
await deployProcessToMachines(targetedMachines, processesExportData);
214+
215+
return targetedMachines;
201216
}
202217

203218
export async function deployProcess(
@@ -230,9 +245,9 @@ export async function deployProcess(
230245
);
231246

232247
if (method === 'static') {
233-
await staticDeployment(definitionId, version, processesExportData, machines);
248+
return await staticDeployment(definitionId, version, processesExportData, machines);
234249
} else {
235-
await dynamicDeployment(definitionId, version, processesExportData, machines);
250+
return await dynamicDeployment(definitionId, version, processesExportData, machines);
236251
}
237252
}
238253
export type ImportInformation = { definitionId: string; processId: string; version: number };
@@ -338,6 +353,31 @@ export async function getDeployment(engine: Engine, definitionId: string) {
338353
return deployment as DeployedProcessInfo;
339354
}
340355

356+
export async function changeDeploymentActivation(
357+
engine: Engine,
358+
definitionId: string,
359+
version: string | undefined,
360+
value: boolean,
361+
) {
362+
if (version) {
363+
await engineRequest({
364+
method: 'put',
365+
endpoint: '/process/:definitionId/versions/:version/active',
366+
engine,
367+
pathParams: { definitionId, version },
368+
body: { active: value },
369+
});
370+
} else {
371+
await engineRequest({
372+
method: 'put',
373+
endpoint: '/process/:definitionId/active',
374+
engine,
375+
pathParams: { definitionId },
376+
body: { active: value },
377+
});
378+
}
379+
}
380+
341381
export async function getProcessImageFromMachine(
342382
engine: Engine,
343383
definitionId: string,

src/management-system-v2/lib/engines/endpoints/endpoints.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@
8888
"/process/:definitionId/instance/:instanceID/instanceState": {},
8989
"/process/:definitionId/instance/:instanceId/tokens/:tokenId": {},
9090
"/process/:definitionId/instance/:instanceId/tokens/:tokenId/currentFlowNodeState": {},
91+
"/process/:definitionId/active": {},
92+
"/process/:definitionId/versions/:version/active": {},
9193
"/process/:definitionId/versions/:version/start-form": {},
9294
"/process/:definitionId/user-tasks/:fileName": {},
9395
"/process/:definitionId/script-tasks/:fileName": {},

src/management-system-v2/lib/engines/server-actions.ts

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22

33
import { UserFacingError, getErrorMessage, userError } from '../user-error';
44
import {
5-
DeployedProcessInfo,
65
deployProcess as _deployProcess,
76
getDeployments as fetchDeployments,
87
getDeployment as fetchDeployment,
98
getProcessImageFromMachine,
109
removeDeploymentFromMachines,
10+
changeDeploymentActivation as _changeDeploymentActivation,
11+
DeployedProcessInfo,
1112
} from './deployment';
1213
import { Engine, SpaceEngine } from './machines';
1314
import { savedEnginesToEngines } from './saved-engines-helpers';
14-
import { getCurrentEnvironment, getCurrentUser } from '@/components/auth';
15+
import { getCurrentEnvironment } from '@/components/auth';
1516
import { enableUseDB } from 'FeatureFlags';
1617
import { getDbEngines, getDbEngineByAddress } from '@/lib/data/db/engines';
1718
import { asyncFilter, asyncMap, asyncForEach } from '../helpers/javascriptHelpers';
@@ -46,7 +47,6 @@ import {
4647
import { getFileFromMachine, submitFileToMachine, updateVariablesOnMachine } from './instances';
4748
import { getProcessIds, getVariablesFromElementById } from '@proceed/bpmn-helper';
4849
import { Variable } from '@proceed/bpmn-helper/src/getters';
49-
import { getUsersInSpace } from '../data/db/iam/memberships';
5050
import Ability from '../ability/abilityHelper';
5151
import { getUserById } from '../data/db/iam/users';
5252

@@ -107,7 +107,51 @@ export async function deployProcess(
107107

108108
if (engines.length === 0) throw new UserFacingError('No fitting engine found.');
109109

110-
await _deployProcess(definitionId, versionId, spaceId, method, engines);
110+
const processAlreadyDeployedInfo = await asyncMap(engines, async (engine) => {
111+
let deployment;
112+
try {
113+
deployment = await fetchDeployment(engine, definitionId);
114+
// ignore not found errors on engines that don't have a deployment of the process
115+
} catch (err) {
116+
deployment = undefined;
117+
}
118+
return [engine, deployment] as const;
119+
});
120+
121+
function withDeployment(
122+
info: (typeof processAlreadyDeployedInfo)[number],
123+
): info is readonly [Engine, DeployedProcessInfo] {
124+
return !!info[1];
125+
}
126+
const enginesWithDeployment = processAlreadyDeployedInfo.filter(withDeployment);
127+
128+
// check if the version is already deployed to some engine since we don't
129+
// need to redeploy it in that case
130+
if (
131+
!_forceEngine &&
132+
enginesWithDeployment.some(([_, deployment]) =>
133+
deployment.versions.some((version) => version.versionId === versionId),
134+
)
135+
) {
136+
return;
137+
}
138+
139+
if (!_forceEngine && enginesWithDeployment.length) {
140+
// check if an engine already has another version in which case that engine is selected
141+
engines = enginesWithDeployment.map(([engine]) => engine);
142+
}
143+
144+
const deployedTo = await _deployProcess(definitionId, versionId, spaceId, method, engines);
145+
146+
// deactivate the process on all engines that have a deployment but which were not target of the
147+
// new deployment
148+
await Promise.allSettled(
149+
enginesWithDeployment.map(async ([engine]) => {
150+
if (!deployedTo.some((dE) => dE.id === engine.id)) {
151+
await _changeDeploymentActivation(engine, definitionId, undefined, false);
152+
}
153+
}),
154+
);
111155
} catch (e) {
112156
const message = getErrorMessage(e);
113157
return userError(message);
@@ -131,6 +175,33 @@ export async function removeDeployment(definitionId: string, spaceId: string) {
131175
}
132176
}
133177

178+
export async function changeDeploymentActivation(
179+
definitionId: string,
180+
spaceId: string,
181+
version: string,
182+
value: boolean,
183+
) {
184+
try {
185+
const engines = await getCorrectTargetEngines(spaceId, false, async (engine: Engine) => {
186+
const deployments = await fetchDeployments([engine]);
187+
188+
return deployments.some(
189+
(deployment) =>
190+
deployment.definitionId === definitionId &&
191+
deployment.versions.some((v) => v.versionId === version),
192+
);
193+
});
194+
195+
if (!engines.length)
196+
throw new Error('There is no available engine with the requested process version.');
197+
198+
await _changeDeploymentActivation(engines[0], definitionId, version, value);
199+
} catch (e) {
200+
const message = getErrorMessage(e);
201+
return userError(message);
202+
}
203+
}
204+
134205
export async function getAvailableTaskListEntries(spaceId: string, engines: Engine[]) {
135206
try {
136207
if (!enableUseDB)

0 commit comments

Comments
 (0)