This is an automated email from the ASF dual-hosted git repository.
thiagoelg pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git
The following commit(s) were added to refs/heads/main by this push:
new 9d43f3cc24d kie-issues#2312: BPMN Editor: Cannot nest Sub-processes
within Sub-processes (#3580)
9d43f3cc24d is described below
commit 9d43f3cc24d58cdbdbc59b9891a6799f0f936df2
Author: Rajalakshmy S <[email protected]>
AuthorDate: Sat May 30 08:02:05 2026 +0530
kie-issues#2312: BPMN Editor: Cannot nest Sub-processes within
Sub-processes (#3580)
Co-authored-by: Rajalakshmy S <[email protected]>
---
packages/bpmn-editor/src/diagram/BpmnDiagram.tsx | 94 +++++++---
.../bpmn-editor/src/diagram/BpmnDiagramDomain.tsx | 1 +
.../bpmn-editor/src/mutations/addConnectedNode.ts | 22 ++-
packages/bpmn-editor/src/mutations/addEdge.ts | 22 ++-
.../src/mutations/moveNodesInsideSubProcess.ts | 72 +++----
.../src/mutations/moveNodesOutOfSubProcess.ts | 206 +++++++++++++++++----
.../bpmn-editor/src/store/computeDiagramData.ts | 115 ++++++++----
7 files changed, 384 insertions(+), 148 deletions(-)
diff --git a/packages/bpmn-editor/src/diagram/BpmnDiagram.tsx
b/packages/bpmn-editor/src/diagram/BpmnDiagram.tsx
index a4bcd38248d..d3dc93f6d55 100644
--- a/packages/bpmn-editor/src/diagram/BpmnDiagram.tsx
+++ b/packages/bpmn-editor/src/diagram/BpmnDiagram.tsx
@@ -54,6 +54,7 @@ import { addEdge } from "../mutations/addEdge";
import { addEdgeWaypoint } from "../mutations/addEdgeWaypoint";
import { moveNodesInsideLane } from "../mutations/moveNodesInsideLane";
import { moveNodesInsideSubProcess } from
"../mutations/moveNodesInsideSubProcess";
+import { findParentFlowElements, findParentSubProcess } from
"../mutations/moveNodesOutOfSubProcess";
import { addStandaloneNode } from "../mutations/addStandaloneNode";
import { deleteEdge } from "../mutations/deleteEdge";
import { deleteEdgeWaypoint } from "../mutations/deleteEdgeWaypoint";
@@ -276,10 +277,15 @@ export function BpmnDiagram({
console.log("BPMN EDITOR DIAGRAM: onNodeUnparented");
if (exParentNode.type === NODE_TYPES.subProcess) {
// ContainmentMode was INSIDE
+
+ const targetSubProcessId =
+ dropTarget?.node?.type === NODE_TYPES.subProcess ?
dropTarget.node.data.bpmnElement?.["@_id"] : null;
+
moveNodesOutOfSubProcess({
definitions: state.bpmn.model.definitions,
__readonly_subProcessId: exParentNode.data.bpmnElement?.["@_id"],
__readonly_nodeIds: selectedNodes.flatMap((s) =>
s.data.bpmnElement?.["@_id"] ?? []),
+ __readonly_targetSubProcessId: targetSubProcessId,
});
}
@@ -382,40 +388,72 @@ export function BpmnDiagram({
// edges
+ const findCommonParentContainer = useCallback((state: State, sourceNodeId:
string, targetNodeId: string) => {
+ const process = state.bpmn.model.definitions.rootElement?.find((e) =>
e.__$$element === "process");
+ if (!process) {
+ return { parentFlowElements: undefined, parentArtifacts: undefined };
+ }
+
+ const sourceParentFlowElements =
findParentFlowElements(process.flowElement ?? [], sourceNodeId);
+ const targetParentFlowElements =
findParentFlowElements(process.flowElement ?? [], targetNodeId);
+
+ const parentFlowElements =
+ sourceParentFlowElements === targetParentFlowElements ?
sourceParentFlowElements : undefined;
+
+ const sourceParentSubProcess = findParentSubProcess(process.flowElement ??
[], sourceNodeId);
+ const targetParentSubProcess = findParentSubProcess(process.flowElement ??
[], targetNodeId);
+
+ const parentArtifacts =
+ sourceParentSubProcess === targetParentSubProcess ?
sourceParentSubProcess?.artifact : undefined;
+
+ return { parentFlowElements, parentArtifacts };
+ }, []);
+
const onEdgeAdded = useCallback<
OnEdgeAdded<State, BpmnNodeType, BpmnEdgeType, BpmnDiagramNodeData,
BpmnDiagramEdgeData>
- >(({ state, edgeType, sourceNode, targetNode, targetHandle }) => {
- console.log("BPMN EDITOR DIAGRAM: onEdgeAdded");
- addEdge({
- definitions: state.bpmn.model.definitions,
- __readonly_edge: {
- type: edgeType as BpmnEdgeType,
- targetHandle: targetHandle,
- sourceHandle: PositionalNodeHandleId.Center,
- autoPositionedEdgeMarker: undefined,
- name: undefined,
- documentation: undefined,
- },
- __readonly_sourceNode: {
- type: sourceNode.type as BpmnNodeType,
- href: sourceNode.id,
- bounds: sourceNode.data.shape["dc:Bounds"],
- shapeId: sourceNode.data.shape["@_id"],
- },
- __readonly_targetNode: {
- type: targetNode.type as BpmnNodeType,
- href: targetNode.id,
- bounds: targetNode.data.shape["dc:Bounds"],
- shapeId: targetNode.data.shape["@_id"],
- },
- __readonly_keepWaypoints: false,
- });
- }, []);
+ >(
+ ({ state, edgeType, sourceNode, targetNode, targetHandle }) => {
+ console.log("BPMN EDITOR DIAGRAM: onEdgeAdded");
+
+ const { parentFlowElements, parentArtifacts } =
findCommonParentContainer(state, sourceNode.id, targetNode.id);
+
+ addEdge({
+ definitions: state.bpmn.model.definitions,
+ __readonly_edge: {
+ type: edgeType as BpmnEdgeType,
+ targetHandle: targetHandle,
+ sourceHandle: PositionalNodeHandleId.Center,
+ autoPositionedEdgeMarker: undefined,
+ name: undefined,
+ documentation: undefined,
+ },
+ __readonly_sourceNode: {
+ type: sourceNode.type as BpmnNodeType,
+ href: sourceNode.id,
+ bounds: sourceNode.data.shape["dc:Bounds"],
+ shapeId: sourceNode.data.shape["@_id"],
+ },
+ __readonly_targetNode: {
+ type: targetNode.type as BpmnNodeType,
+ href: targetNode.id,
+ bounds: targetNode.data.shape["dc:Bounds"],
+ shapeId: targetNode.data.shape["@_id"],
+ },
+ __readonly_keepWaypoints: false,
+ __readonly_parentFlowElements: parentFlowElements,
+ __readonly_parentArtifacts: parentArtifacts,
+ });
+ },
+ [findCommonParentContainer]
+ );
const onEdgeUpdated = useCallback<
OnEdgeUpdated<State, BpmnNodeType, BpmnEdgeType, BpmnDiagramNodeData,
BpmnDiagramEdgeData>
>(({ state, edge, targetNode, sourceNode, targetHandle, sourceHandle,
firstWaypoint, lastWaypoint }) => {
console.log("BPMN EDITOR DIAGRAM: onEdgeUpdated");
+
+ const { parentFlowElements, parentArtifacts } =
findCommonParentContainer(state, sourceNode.id, targetNode.id);
+
const { newBpmnEdge } = addEdge({
definitions: state.bpmn.model.definitions,
__readonly_edge: {
@@ -444,6 +482,8 @@ export function BpmnDiagram({
shapeId: targetNode.data.shape["@_id"],
},
__readonly_keepWaypoints: true,
+ __readonly_parentFlowElements: parentFlowElements,
+ __readonly_parentArtifacts: parentArtifacts,
});
// The BPMN Edge changed nodes, so we need to delete the old one, but keep
the waypoints.
diff --git a/packages/bpmn-editor/src/diagram/BpmnDiagramDomain.tsx
b/packages/bpmn-editor/src/diagram/BpmnDiagramDomain.tsx
index 7a76d2d1be9..0ab98cd8443 100644
--- a/packages/bpmn-editor/src/diagram/BpmnDiagramDomain.tsx
+++ b/packages/bpmn-editor/src/diagram/BpmnDiagramDomain.tsx
@@ -294,6 +294,7 @@ export const BPMN_CONTAINMENT_MAP:
ContainmentMap<BpmnNodeType> = new Map<
NODE_TYPES.intermediateCatchEvent,
NODE_TYPES.intermediateThrowEvent,
NODE_TYPES.gateway,
+ NODE_TYPES.subProcess,
NODE_TYPES.endEvent,
NODE_TYPES.textAnnotation,
NODE_TYPES.group,
diff --git a/packages/bpmn-editor/src/mutations/addConnectedNode.ts
b/packages/bpmn-editor/src/mutations/addConnectedNode.ts
index d8f4db3e40e..8a1ddd9ece8 100644
--- a/packages/bpmn-editor/src/mutations/addConnectedNode.ts
+++ b/packages/bpmn-editor/src/mutations/addConnectedNode.ts
@@ -29,6 +29,7 @@ import { addOrGetProcessAndDiagramElements } from
"./addOrGetProcessAndDiagramEl
import { NodeNature, nodeNatures } from "./_NodeNature";
import { addEdge } from "./addEdge";
import { PositionalNodeHandleId } from
"@kie-tools/xyflow-react-kie-diagram/dist/nodes/PositionalNodeHandles";
+import { findParentFlowElements, findParentSubProcess } from
"./moveNodesOutOfSubProcess";
export function addConnectedNode({
definitions,
@@ -51,10 +52,15 @@ export function addConnectedNode({
const { process, diagramElements } = addOrGetProcessAndDiagramElements({
definitions });
- if (newNodeNature === NodeNature.PROCESS_FLOW_ELEMENT || newNodeNature ===
NodeNature.CONTAINER) {
- process.flowElement ??= [];
+ const parentFlowElements = findParentFlowElements(
+ process.flowElement ?? [],
+ __readonly_sourceNode.bpmnElement["@_id"]
+ );
+
+ const targetFlowElements = parentFlowElements ?? process.flowElement ?? [];
- process.flowElement?.push(
+ if (newNodeNature === NodeNature.PROCESS_FLOW_ELEMENT || newNodeNature ===
NodeNature.CONTAINER) {
+ targetFlowElements.push(
switchExpression(
__readonly_newNode.type as Exclude<
BpmnNodeType,
@@ -107,8 +113,9 @@ export function addConnectedNode({
)
);
} else if (newNodeNature === NodeNature.ARTIFACT) {
- process.artifact ??= [];
- process.artifact.push(
+ const parentSubProcess = findParentSubProcess(process.flowElement ?? [],
__readonly_sourceNode.bpmnElement["@_id"]);
+ const targetArtifacts = parentSubProcess?.artifact ?? process.artifact ??
[];
+ targetArtifacts.push(
...switchExpression(__readonly_newNode.type as Extract<BpmnNodeType,
"node_group" | "node_textAnnotation">, {
[NODE_TYPES.group]: [
{
@@ -175,6 +182,11 @@ export function addConnectedNode({
href: newBpmnElementId,
shapeId: newShapeId,
},
+ __readonly_parentFlowElements: parentFlowElements,
+ __readonly_parentArtifacts:
+ newNodeNature === NodeNature.ARTIFACT
+ ? findParentSubProcess(process.flowElement ?? [],
__readonly_sourceNode.bpmnElement["@_id"])?.artifact
+ : undefined,
});
return { id: newBpmnElementId, href: newBpmnElementId };
diff --git a/packages/bpmn-editor/src/mutations/addEdge.ts
b/packages/bpmn-editor/src/mutations/addEdge.ts
index 2cf7b6b41e4..213ccfad102 100644
--- a/packages/bpmn-editor/src/mutations/addEdge.ts
+++ b/packages/bpmn-editor/src/mutations/addEdge.ts
@@ -48,6 +48,8 @@ export function addEdge({
__readonly_targetNode,
__readonly_edge,
__readonly_keepWaypoints,
+ __readonly_parentFlowElements,
+ __readonly_parentArtifacts,
}: {
definitions: Normalized<BPMN20__tDefinitions>;
__readonly_sourceNode: {
@@ -71,6 +73,8 @@ export function addEdge({
autoPositionedEdgeMarker: AutoPositionedEdgeMarker | undefined;
};
__readonly_keepWaypoints: boolean;
+ __readonly_parentFlowElements?: Normalized<BPMN20__tProcess>["flowElement"];
+ __readonly_parentArtifacts?: Normalized<BPMN20__tProcess>["artifact"];
}) {
if (
!_checkIsValidConnection(BPMN_GRAPH_STRUCTURE, __readonly_sourceNode,
__readonly_targetNode, __readonly_edge.type)
@@ -88,7 +92,10 @@ export function addEdge({
// Associations (incl. Compensation Associations)
if (__readonly_edge.type === EDGE_TYPES.association || __readonly_edge.type
=== EDGE_TYPES.compensationAssociation) {
- process.artifact ??= [];
+ const targetArtifacts = __readonly_parentArtifacts ?? process.artifact ??
[];
+ if (!process.artifact) {
+ process.artifact = targetArtifacts;
+ }
const newAssociation: Normalized<BPMN20__tAssociation> = {
"@_id": newEdgeId,
@@ -99,13 +106,13 @@ export function addEdge({
// Remove previously existing association
const removed = removeFirstMatchIfPresent(
- process.artifact,
+ targetArtifacts,
(a) => a.__$$element === "association" && areEdgesEquivalent(a,
newAssociation)
);
existingEdgeId = removed?.["@_id"];
// Replace with the new one.
- process.artifact?.push({
+ targetArtifacts.push({
__$$element: "association",
...newAssociation,
"@_id": tryKeepingEdgeId(existingEdgeId, newEdgeId),
@@ -114,7 +121,10 @@ export function addEdge({
// Sequence Flows
else {
- process.flowElement ??= [];
+ const targetFlowElements = __readonly_parentFlowElements ??
process.flowElement ?? [];
+ if (!process.flowElement) {
+ process.flowElement = targetFlowElements;
+ }
const newSequenceFlow: Normalized<BPMN20__tSequenceFlow> = {
"@_id": newEdgeId,
@@ -126,14 +136,14 @@ export function addEdge({
// Remove previously existing association
const removed = removeFirstMatchIfPresent(
- process.flowElement,
+ targetFlowElements,
(a) => a.__$$element === "sequenceFlow" && areEdgesEquivalent(a,
newSequenceFlow)
);
existingEdgeId = removed?.["@_id"];
newEdgeId = tryKeepingEdgeId(existingEdgeId, newEdgeId);
// Replace with the new one.
- process.flowElement?.push({
+ targetFlowElements.push({
__$$element: "sequenceFlow",
...newSequenceFlow,
"@_id": newEdgeId,
diff --git a/packages/bpmn-editor/src/mutations/moveNodesInsideSubProcess.ts
b/packages/bpmn-editor/src/mutations/moveNodesInsideSubProcess.ts
index 63974bcfa35..8cd564b6426 100644
--- a/packages/bpmn-editor/src/mutations/moveNodesInsideSubProcess.ts
+++ b/packages/bpmn-editor/src/mutations/moveNodesInsideSubProcess.ts
@@ -23,6 +23,7 @@ import { State } from "../store/Store";
import { addOrGetProcessAndDiagramElements } from
"./addOrGetProcessAndDiagramElements";
import { ElementExclusion } from "@kie-tools/xml-parser-ts/dist/elementFilter";
import { BPMN20__tProcess } from
"@kie-tools/bpmn-marshaller/dist/schemas/bpmn-2_0/ts-gen/types";
+import { isSubProcessElement, findSubProcessRecursively,
shouldMoveSequenceFlow } from "./moveNodesOutOfSubProcess";
export function moveNodesInsideSubProcess({
definitions,
@@ -34,55 +35,58 @@ export function moveNodesInsideSubProcess({
__readonly_nodeIds: string[];
}) {
const { process } = addOrGetProcessAndDiagramElements({ definitions });
- const subProcess = process.flowElement?.find((s) => s["@_id"] ===
__readonly_subProcessId);
- if (
- !(
- subProcess?.__$$element === "subProcess" ||
- subProcess?.__$$element === "adHocSubProcess" ||
- subProcess?.__$$element === "transaction"
- )
- ) {
- throw new Error(`BPMN Element with id ${__readonly_subProcessId} is not a
subProcess.`);
+
+ const subProcess = findSubProcessRecursively(process.flowElement ?? [],
__readonly_subProcessId ?? "");
+ if (!subProcess) {
+ throw new Error(`Cannot find subprocess with ID:
${__readonly_subProcessId}`);
}
- const flowElementsToMove:
Normalized<Unpacked<NonNullable<BPMN20__tProcess["flowElement"]>>>[] = [];
+ const flowElementsToMove:
Normalized<Unpacked<Normalized<BPMN20__tProcess>["flowElement"]>>[] = [];
const artifactsToMove: Normalized<
- ElementExclusion<Unpacked<NonNullable<BPMN20__tProcess["artifact"]>>,
"association">
+ ElementExclusion<Unpacked<Normalized<BPMN20__tProcess>["artifact"]>,
"association">
>[] = [];
const nodeIdsToMoveInside = new Set(__readonly_nodeIds);
- const subProcessNodes = new Set();
- subProcess.flowElement?.forEach((flowElement) => {
+ const subProcessNodes = new Set<string>();
+ subProcess.flowElement?.forEach((flowElement: Normalized<Unpacked<typeof
subProcess.flowElement>>) => {
if (flowElement.__$$element !== "sequenceFlow") {
subProcessNodes.add(flowElement["@_id"]);
}
});
- for (let i = 0; i < (process.flowElement ?? []).length; i++) {
- const flowElement = (process.flowElement ?? [])[i];
- if (
- nodeIdsToMoveInside.has(flowElement["@_id"]) ||
- (flowElement.__$$element === "boundaryEvent" &&
nodeIdsToMoveInside.has(flowElement["@_attachedToRef"]))
- ) {
- flowElementsToMove.push(...((process.flowElement?.splice(i, 1) ?? []) as
typeof flowElementsToMove));
- i--; // repeat one index because we just altered the array we're
iterating over.
- } else if (
- flowElement.__$$element === "sequenceFlow" &&
- ((nodeIdsToMoveInside.has(flowElement["@_sourceRef"]) &&
nodeIdsToMoveInside.has(flowElement["@_targetRef"])) ||
- (subProcessNodes.has(flowElement["@_sourceRef"]) &&
nodeIdsToMoveInside.has(flowElement["@_targetRef"])) ||
- (nodeIdsToMoveInside.has(flowElement["@_sourceRef"]) &&
subProcessNodes.has(flowElement["@_targetRef"])))
- ) {
- // If the source and target are both outside of the sub-process
- // or if the source and target is already in the sub process the
sequenceFlow must be copied
- flowElementsToMove.push(...((process.flowElement?.splice(i, 1) ?? []) as
typeof flowElementsToMove));
- i--; // repeat one index because we just altered the array we're
iterating over.
+ const toMove:
Normalized<Unpacked<Normalized<BPMN20__tProcess>["flowElement"]>>[] = [];
+
+ const collectElements = (flowElements:
Normalized<BPMN20__tProcess>["flowElement"]): void => {
+ if (!flowElements) {
+ return;
}
- }
+ for (let i = flowElements.length - 1; i >= 0; i--) {
+ const flowElement = flowElements[i];
+ if (
+ nodeIdsToMoveInside.has(flowElement["@_id"]) ||
+ (flowElement.__$$element === "boundaryEvent" &&
+ flowElement["@_attachedToRef"] &&
+ nodeIdsToMoveInside.has(flowElement["@_attachedToRef"]))
+ ) {
+ toMove.push(...flowElements.splice(i, 1));
+ } else if (shouldMoveSequenceFlow(flowElement, nodeIdsToMoveInside,
subProcessNodes)) {
+ // If the source and target are both outside of the sub-process
+ // or if the source and target is already in the sub process the
sequenceFlow must be copied
+ toMove.push(...flowElements.splice(i, 1));
+ } else if (isSubProcessElement(flowElement) && flowElement.flowElement) {
+ collectElements(flowElement.flowElement);
+ }
+ }
+ };
+
+ collectElements(process.flowElement ?? []);
+ flowElementsToMove.push(...toMove);
for (let i = 0; i < (process.artifact ?? []).length; i++) {
const artifact = (process.artifact ?? [])[i];
- if (nodeIdsToMoveInside.has(artifact["@_id"])) {
- artifactsToMove.push(...((process.artifact?.splice(i, 1) ?? []) as
typeof artifactsToMove));
+ if (artifact.__$$element !== "association" &&
nodeIdsToMoveInside.has(artifact["@_id"])) {
+ const spliced = process.artifact?.splice(i, 1) ?? [];
+ artifactsToMove.push(...spliced.filter((a) => a.__$$element !==
"association"));
i--; // repeat one index because we just altered the array we're
iterating over.
}
}
diff --git a/packages/bpmn-editor/src/mutations/moveNodesOutOfSubProcess.ts
b/packages/bpmn-editor/src/mutations/moveNodesOutOfSubProcess.ts
index a22d61326f8..e7576d888fd 100644
--- a/packages/bpmn-editor/src/mutations/moveNodesOutOfSubProcess.ts
+++ b/packages/bpmn-editor/src/mutations/moveNodesOutOfSubProcess.ts
@@ -24,61 +24,178 @@ import { Normalized } from "../normalization/normalize";
import { State } from "../store/Store";
import { addOrGetProcessAndDiagramElements } from
"./addOrGetProcessAndDiagramElements";
+export type SubProcessElement = Normalized<
+ Extract<
+ Unpacked<Normalized<BPMN20__tProcess>["flowElement"]>,
+ { __$$element: "subProcess" | "adHocSubProcess" | "transaction" }
+ >
+>;
+
+export function isSubProcessElement(element: { __$$element?: string }):
element is SubProcessElement {
+ return (
+ element.__$$element === "subProcess" ||
+ element.__$$element === "adHocSubProcess" ||
+ element.__$$element === "transaction"
+ );
+}
+
+export function findSubProcessRecursively(
+ flowElements: Normalized<BPMN20__tProcess>["flowElement"],
+ subProcessId: string
+): SubProcessElement | undefined {
+ if (!flowElements) {
+ return undefined;
+ }
+ for (const element of flowElements) {
+ if (element["@_id"] === subProcessId && isSubProcessElement(element)) {
+ return element;
+ }
+ if (isSubProcessElement(element) && element.flowElement) {
+ const found = findSubProcessRecursively(element.flowElement,
subProcessId);
+ if (found) {
+ return found;
+ }
+ }
+ }
+ return undefined;
+}
+
+export function findParentFlowElements(
+ flowElements: Normalized<BPMN20__tProcess>["flowElement"],
+ elementId: string
+): Normalized<BPMN20__tProcess>["flowElement"] | undefined {
+ if (!flowElements) {
+ return undefined;
+ }
+ for (const element of flowElements) {
+ if (element["@_id"] === elementId) {
+ return flowElements;
+ }
+ if (isSubProcessElement(element) && element.flowElement) {
+ const found = findParentFlowElements(element.flowElement, elementId);
+ if (found) {
+ return found;
+ }
+ }
+ }
+ return undefined;
+}
+
+export function findParentSubProcess(
+ flowElements: Normalized<BPMN20__tProcess>["flowElement"],
+ elementId: string
+): SubProcessElement | undefined {
+ if (!flowElements) {
+ return undefined;
+ }
+ for (const element of flowElements) {
+ if (isSubProcessElement(element) && element.flowElement) {
+ // Check if this subprocess directly contains the target
+ for (const child of element.flowElement) {
+ if (child["@_id"] === elementId) {
+ return element;
+ }
+ }
+ // Recurse into nested subprocesses
+ const found = findParentSubProcess(element.flowElement, elementId);
+ if (found) {
+ return found;
+ }
+ }
+ }
+ return undefined;
+}
+
+export function shouldMoveSequenceFlow(
+ flowElement: Unpacked<Normalized<BPMN20__tProcess>["flowElement"]>,
+ nodeIds: Set<string>,
+ existingNodesIds: Set<string>
+): boolean {
+ return (
+ flowElement.__$$element === "sequenceFlow" &&
+ !!flowElement["@_sourceRef"] &&
+ !!flowElement["@_targetRef"] &&
+ ((nodeIds.has(flowElement["@_sourceRef"]) &&
nodeIds.has(flowElement["@_targetRef"])) ||
+ (existingNodesIds.has(flowElement["@_sourceRef"]) &&
nodeIds.has(flowElement["@_targetRef"])) ||
+ (nodeIds.has(flowElement["@_sourceRef"]) &&
existingNodesIds.has(flowElement["@_targetRef"])))
+ );
+}
+
export function moveNodesOutOfSubProcess({
definitions,
__readonly_subProcessId,
__readonly_nodeIds,
+ __readonly_targetSubProcessId,
}: {
definitions: State["bpmn"]["model"]["definitions"];
__readonly_subProcessId: string | undefined;
__readonly_nodeIds: string[];
+ __readonly_targetSubProcessId?: string | null;
}) {
const { process } = addOrGetProcessAndDiagramElements({ definitions });
- const subProcess = process.flowElement?.find((s) => s["@_id"] ===
__readonly_subProcessId);
- if (
- !(
- subProcess?.__$$element === "subProcess" ||
- subProcess?.__$$element === "adHocSubProcess" ||
- subProcess?.__$$element === "transaction"
- )
- ) {
- throw new Error(`Can't find subProcess with ID
${__readonly_subProcessId}`);
+
+ const subProcess = findSubProcessRecursively(process.flowElement ?? [],
__readonly_subProcessId ?? "");
+ if (!subProcess) {
+ throw new Error(`Cannot find subprocess with ID:
${__readonly_subProcessId}`);
}
- const flowElementsToMove:
Normalized<Unpacked<NonNullable<BPMN20__tProcess["flowElement"]>>>[] = [];
+ let parentFlowElements: Normalized<BPMN20__tProcess>["flowElement"];
+ let targetSubProcess: SubProcessElement | undefined;
+
+ if (__readonly_targetSubProcessId) {
+ targetSubProcess = findSubProcessRecursively(process.flowElement ?? [],
__readonly_targetSubProcessId);
+ if (!targetSubProcess) {
+ throw new Error(`Cannot find target subprocess with ID:
${__readonly_targetSubProcessId}`);
+ }
+ targetSubProcess.flowElement ??= [];
+ parentFlowElements = targetSubProcess.flowElement;
+ } else if (__readonly_targetSubProcessId === null) {
+ process.flowElement ??= [];
+ parentFlowElements = process.flowElement;
+ } else {
+ parentFlowElements =
+ findParentFlowElements(process.flowElement ?? [],
__readonly_subProcessId ?? "") ?? process.flowElement ?? [];
+ }
+
+ const flowElementsToMove:
Normalized<Unpacked<Normalized<BPMN20__tProcess>["flowElement"]>>[] = [];
const artifactsToMove: Normalized<
- ElementExclusion<Unpacked<NonNullable<BPMN20__tProcess["artifact"]>>,
"association">
+ ElementExclusion<Unpacked<Normalized<BPMN20__tProcess>["artifact"]>,
"association">
>[] = [];
const nodeIdsToMoveOut = new Set(__readonly_nodeIds);
- const subProcessNodes = new Set();
- subProcess.flowElement?.forEach((flowElement) => {
- if (flowElement.__$$element !== "sequenceFlow") {
- subProcessNodes.add(flowElement["@_id"]);
+
+ const subProcessNodes = new Set<string>();
+ const collectSubProcessNodeIds = (flowElements:
Normalized<BPMN20__tProcess>["flowElement"]): void => {
+ if (!flowElements) {
+ return;
}
- });
+ for (const flowElement of flowElements) {
+ if (flowElement.__$$element !== "sequenceFlow" && flowElement["@_id"]) {
+ subProcessNodes.add(flowElement["@_id"]);
+ }
+ if (isSubProcessElement(flowElement) && flowElement.flowElement) {
+ collectSubProcessNodeIds(flowElement.flowElement);
+ }
+ }
+ };
+ collectSubProcessNodeIds(subProcess.flowElement ?? []);
- // Check if we're moving out of an Event Sub-Process
const isEventSubProcess = subProcess.__$$element === "subProcess" &&
(subProcess["@_triggeredByEvent"] ?? false);
- for (let i = 0; i < (subProcess.flowElement ?? []).length; i++) {
- const flowElement = (subProcess.flowElement ?? [])[i];
+ for (let i = (subProcess.flowElement ?? []).length - 1; i >= 0; i--) {
+ const flowElement = subProcess.flowElement![i];
if (
- nodeIdsToMoveOut.has(flowElement["@_id"]) ||
- (flowElement.__$$element === "boundaryEvent" &&
nodeIdsToMoveOut.has(flowElement["@_attachedToRef"]))
- ) {
- flowElementsToMove.push(...((subProcess.flowElement?.splice(i, 1) ?? [])
as typeof flowElementsToMove));
- i--; // repeat one index because we just altered the array we're
iterating over.
- } else if (
- flowElement.__$$element === "sequenceFlow" &&
- ((nodeIdsToMoveOut.has(flowElement["@_sourceRef"]) &&
nodeIdsToMoveOut.has(flowElement["@_targetRef"])) ||
- (subProcessNodes.has(flowElement["@_sourceRef"]) &&
nodeIdsToMoveOut.has(flowElement["@_targetRef"])) ||
- (nodeIdsToMoveOut.has(flowElement["@_sourceRef"]) &&
subProcessNodes.has(flowElement["@_targetRef"])))
+ (flowElement["@_id"] && nodeIdsToMoveOut.has(flowElement["@_id"])) ||
+ (flowElement.__$$element === "boundaryEvent" &&
+ flowElement["@_attachedToRef"] &&
+ nodeIdsToMoveOut.has(flowElement["@_attachedToRef"]))
) {
+ flowElementsToMove.push(...subProcess.flowElement!.splice(i, 1));
+ } else if (shouldMoveSequenceFlow(flowElement, nodeIdsToMoveOut,
subProcessNodes)) {
// If the source and target are both outside of the sub-process
// or if the source and target is already in the sub process the
sequenceFlow must be copied
- flowElementsToMove.push(...((subProcess.flowElement?.splice(i, 1) ?? [])
as typeof flowElementsToMove));
- i--; // repeat one index because we just altered the array we're
iterating over.
+
+ flowElementsToMove.push(...subProcess.flowElement!.splice(i, 1));
}
}
@@ -112,14 +229,29 @@ export function moveNodesOutOfSubProcess({
for (let i = 0; i < (subProcess.artifact ?? []).length; i++) {
const artifact = (subProcess.artifact ?? [])[i];
- if (nodeIdsToMoveOut.has(artifact["@_id"])) {
- artifactsToMove.push(...((subProcess.artifact?.splice(i, 1) ?? []) as
typeof artifactsToMove));
+ if (artifact.__$$element !== "association" &&
nodeIdsToMoveOut.has(artifact["@_id"])) {
+ const spliced = subProcess.artifact?.splice(i, 1) ?? [];
+ artifactsToMove.push(...spliced.filter((a) => a.__$$element !==
"association"));
i--; // repeat one index because we just altered the array we're
iterating over.
}
}
- process.flowElement ??= [];
- process.flowElement.push(...flowElementsToMove);
- process.artifact ??= [];
- process.artifact.push(...artifactsToMove);
+ parentFlowElements.push(...flowElementsToMove);
+
+ if (__readonly_targetSubProcessId && targetSubProcess) {
+ targetSubProcess.artifact ??= [];
+ targetSubProcess.artifact.push(...artifactsToMove);
+ } else if (__readonly_targetSubProcessId === null) {
+ process.artifact ??= [];
+ process.artifact.push(...artifactsToMove);
+ } else {
+ const parentSubProcess = findParentSubProcess(process.flowElement ?? [],
__readonly_subProcessId ?? "");
+ if (parentSubProcess) {
+ parentSubProcess.artifact ??= [];
+ parentSubProcess.artifact.push(...artifactsToMove);
+ } else {
+ process.artifact ??= [];
+ process.artifact.push(...artifactsToMove);
+ }
+ }
}
diff --git a/packages/bpmn-editor/src/store/computeDiagramData.ts
b/packages/bpmn-editor/src/store/computeDiagramData.ts
index bf670c3687b..d9729f2293d 100644
--- a/packages/bpmn-editor/src/store/computeDiagramData.ts
+++ b/packages/bpmn-editor/src/store/computeDiagramData.ts
@@ -37,6 +37,7 @@ import { BpmnXyFlowDiagramState, State } from "./Store";
import { NODE_LAYERS } from
"@kie-tools/xyflow-react-kie-diagram/dist/nodes/Hooks";
import { ContainmentMode } from
"@kie-tools/xyflow-react-kie-diagram/dist/graph/graphStructure";
import { BPMN20__tLane } from
"@kie-tools/bpmn-marshaller/dist/schemas/bpmn-2_0/ts-gen/types";
+import { isSubProcessElement } from "../mutations/moveNodesOutOfSubProcess";
export function computeDiagramData(
definitions: State["bpmn"]["model"]["definitions"],
@@ -102,46 +103,48 @@ export function computeDiagramData(
nodeBpmnElementsById.set(bpmnElement["@_id"], bpmnElement);
// sub-processes
- if (
- bpmnElement?.__$$element === "subProcess" ||
- bpmnElement?.__$$element === "adHocSubProcess" ||
- bpmnElement?.__$$element === "transaction"
- ) {
- for (const flowElement of bpmnElement.flowElement ?? []) {
- if (flowElement.__$$element === "boundaryEvent") {
- parentIdsById.set(flowElement["@_id"],
flowElement["@_attachedToRef"]);
- } else {
- parentIdsById.set(flowElement["@_id"], bpmnElement["@_id"]);
+ if (isSubProcessElement(bpmnElement)) {
+ const processSubProcessElements = (subProcess: typeof bpmnElement,
parentId: string): void => {
+ for (const flowElement of subProcess.flowElement ?? []) {
+ if (flowElement.__$$element === "boundaryEvent") {
+ parentIdsById.set(flowElement["@_id"],
flowElement["@_attachedToRef"]);
+ } else {
+ parentIdsById.set(flowElement["@_id"], parentId);
+ }
+ if (flowElement.__$$element !== "sequenceFlow") {
+ if (
+ flowElement.__$$element !== "callChoreography" &&
+ flowElement.__$$element !== "choreographyTask" &&
+ flowElement.__$$element !== "dataObjectReference" &&
+ flowElement.__$$element !== "dataStoreReference" &&
+ flowElement.__$$element !== "implicitThrowEvent" &&
+ flowElement.__$$element !== "manualTask" &&
+ flowElement.__$$element !== "receiveTask" &&
+ flowElement.__$$element !== "sendTask" &&
+ flowElement.__$$element !== "subChoreography"
+ ) {
+ nodeBpmnElementsById.set(flowElement["@_id"], flowElement);
+ if (isSubProcessElement(flowElement)) {
+ processSubProcessElements(flowElement,
flowElement["@_id"]);
+ }
+ } else {
+ // ignore on purpose. those flowElements are not nodes.
+ }
+ } else {
+ edgeBpmnElementsById.set(flowElement["@_id"], flowElement);
+ }
}
- if (flowElement.__$$element !== "sequenceFlow") {
- if (
- flowElement.__$$element !== "callChoreography" &&
- flowElement.__$$element !== "choreographyTask" &&
- flowElement.__$$element !== "dataObjectReference" &&
- flowElement.__$$element !== "dataStoreReference" &&
- flowElement.__$$element !== "implicitThrowEvent" &&
- flowElement.__$$element !== "manualTask" &&
- flowElement.__$$element !== "receiveTask" &&
- flowElement.__$$element !== "sendTask" &&
- flowElement.__$$element !== "subChoreography"
- ) {
+
+ for (const flowElement of subProcess.artifact ?? []) {
+ parentIdsById.set(flowElement["@_id"], parentId);
+ if (flowElement.__$$element !== "association") {
nodeBpmnElementsById.set(flowElement["@_id"], flowElement);
} else {
- // ignore on purpose. those flowElements are not nodes.
+ edgeBpmnElementsById.set(flowElement["@_id"], flowElement);
}
- } else {
- edgeBpmnElementsById.set(flowElement["@_id"], flowElement);
}
- }
-
- for (const flowElement of bpmnElement.artifact ?? []) {
- parentIdsById.set(flowElement["@_id"], bpmnElement["@_id"]);
- if (flowElement.__$$element !== "association") {
- nodeBpmnElementsById.set(flowElement["@_id"], flowElement);
- } else {
- edgeBpmnElementsById.set(flowElement["@_id"], flowElement);
- }
- }
+ };
+ processSubProcessElements(bpmnElement, bpmnElement["@_id"]);
}
// lanes
@@ -336,10 +339,44 @@ export function computeDiagramData(
new Map<string, RF.Edge<BpmnDiagramEdgeData>>()
);
- const sortedNodes = [...nodes]
- .sort((a, b) => Number(b.type === NODE_TYPES.subProcess) - Number(a.type
=== NODE_TYPES.subProcess))
- .sort((a, b) => Number(b.type === NODE_TYPES.lane) - Number(a.type ===
NODE_TYPES.lane))
- .sort((a, b) => Number(b.type === NODE_TYPES.group) - Number(a.type ===
NODE_TYPES.group));
+ const depthCache = new Map<string, number>();
+
+ const getDepth = (nodeId: string, visiting = new Set<string>()): number => {
+ if (depthCache.has(nodeId)) {
+ return depthCache.get(nodeId)!;
+ }
+
+ if (visiting.has(nodeId)) {
+ console.warn(`Cycle detected in BPMN containment hierarchy at node:
${nodeId}`);
+ return 0;
+ }
+
+ visiting.add(nodeId);
+ const parentId = parentIdsById.get(nodeId);
+ const depth = parentId ? getDepth(parentId, visiting) + 1 : 0;
+ visiting.delete(nodeId);
+ depthCache.set(nodeId, depth);
+ return depth;
+ };
+
+ const typePriority = (type: BpmnNodeType): number => {
+ switch (type) {
+ case NODE_TYPES.group:
+ return 0;
+ case NODE_TYPES.lane:
+ return 1;
+ case NODE_TYPES.subProcess:
+ return 2;
+ default:
+ return 3;
+ }
+ };
+
+ const sortedNodes = [...nodes].sort((a, b) => {
+ const depthDiff = getDepth(a.id) - getDepth(b.id);
+ if (depthDiff !== 0) return depthDiff;
+ return typePriority(a.type!) - typePriority(b.type!);
+ });
const finalNodes = newNodeProjection ? [...sortedNodes, newNodeProjection] :
sortedNodes;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]