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 0959b002371 kie-issues#2220: BPMN Editor: Interface tags configured in 
a service task are missing from the generated XML (#3445)
0959b002371 is described below

commit 0959b002371010fccfe4e8eb74fb047803787a67
Author: Kbowers <[email protected]>
AuthorDate: Tue Mar 10 20:12:48 2026 +0100

    kie-issues#2220: BPMN Editor: Interface tags configured in a service task 
are missing from the generated XML (#3445)
---
 .../nodes/morphing/useTaskNodeMorphingActions.tsx  |   8 ++
 .../{addOrGetMessages.ts => addOrGetInterfaces.ts} |  28 +++---
 .../bpmn-editor/src/mutations/addOrGetMessages.ts  |   2 +-
 .../src/mutations/addOrGetOperations.ts            | 105 +++++++++++++++++++++
 .../src/mutations/deleteInterfaceAndOperation.ts   |  78 +++++++++++++++
 packages/bpmn-editor/src/mutations/deleteNode.ts   |   9 ++
 .../messageSelector/MessageSelector.tsx            |  17 ++--
 .../propertiesManager/PropertiesManager.tsx        |  10 +-
 .../singleNodeProperties/ServiceTaskProperties.tsx |  15 +++
 .../src/store/getServiceTaskMessageIds.ts          |  41 ++++++++
 10 files changed, 287 insertions(+), 26 deletions(-)

diff --git 
a/packages/bpmn-editor/src/diagram/nodes/morphing/useTaskNodeMorphingActions.tsx
 
b/packages/bpmn-editor/src/diagram/nodes/morphing/useTaskNodeMorphingActions.tsx
index d02d71714db..403745dd760 100644
--- 
a/packages/bpmn-editor/src/diagram/nodes/morphing/useTaskNodeMorphingActions.tsx
+++ 
b/packages/bpmn-editor/src/diagram/nodes/morphing/useTaskNodeMorphingActions.tsx
@@ -34,6 +34,7 @@ import { useCustomTasks } from 
"../../../customTasks/BpmnEditorCustomTasksContex
 import { CustomTask } from "../../../BpmnEditor";
 import { WritableDraft } from "immer";
 import { State } from "../../../store/Store";
+import { deleteInterfaceAndOperation } from 
"../../../mutations/deleteInterfaceAndOperation";
 
 export function useTaskNodeMorphingActions(task: Task) {
   const bpmnEditorStoreApi = useBpmnEditorStoreApi();
@@ -96,6 +97,13 @@ export function useTaskNodeMorphingActions(task: Task) {
           return false; // Will stop visiting.
         }
       });
+
+      if (task.__$$element === "serviceTask" && newTaskElement !== 
"serviceTask" && task["@_operationRef"]) {
+        deleteInterfaceAndOperation({
+          definitions: s.bpmn.model.definitions,
+          operationRef: task["@_operationRef"],
+        });
+      }
     },
     [task]
   );
diff --git a/packages/bpmn-editor/src/mutations/addOrGetMessages.ts 
b/packages/bpmn-editor/src/mutations/addOrGetInterfaces.ts
similarity index 66%
copy from packages/bpmn-editor/src/mutations/addOrGetMessages.ts
copy to packages/bpmn-editor/src/mutations/addOrGetInterfaces.ts
index 28fffe4113e..5eea486c63f 100644
--- a/packages/bpmn-editor/src/mutations/addOrGetMessages.ts
+++ b/packages/bpmn-editor/src/mutations/addOrGetInterfaces.ts
@@ -23,30 +23,30 @@ import { Unpacked } from 
"@kie-tools/xyflow-react-kie-diagram/dist/tsExt/tsExt";
 import { Normalized } from "../normalization/normalize";
 import { generateUuid } from 
"@kie-tools/xyflow-react-kie-diagram/dist/uuid/uuid";
 
-export function addOrGetMessages({
+export function addOrGetInterfaces({
   definitions,
-  messageName,
+  interfaceName,
 }: {
   definitions: Normalized<BPMN20__tDefinitions>;
-  messageName: string;
+  interfaceName: string;
 }): {
-  messageRef: string;
+  interface: 
ElementFilter<Unpacked<Normalized<BPMN20__tDefinitions["rootElement"]>>, 
"interface">;
 } {
   definitions.rootElement ??= [];
-  const messages = definitions.rootElement.filter((s) => s.__$$element === 
"message");
-  const existingMessage = messages.find((s) => s["@_id"] === messageName);
+  const interfaces = definitions.rootElement.filter((s) => s.__$$element === 
"interface");
 
-  if (existingMessage) {
-    return { messageRef: existingMessage["@_id"] };
+  const existingInterface = interfaces.find((s) => s["@_name"] === 
interfaceName);
+  if (existingInterface) {
+    return { interface: existingInterface };
   }
 
-  const newMessage: 
ElementFilter<Unpacked<Normalized<BPMN20__tDefinitions["rootElement"]>>, 
"message"> = {
-    __$$element: "message",
+  const newInterface: 
ElementFilter<Unpacked<Normalized<BPMN20__tDefinitions["rootElement"]>>, 
"interface"> = {
+    __$$element: "interface",
     "@_id": generateUuid(),
-    "@_itemRef": `${messageName}Type`,
-    "@_name": messageName,
+    "@_name": interfaceName,
+    operation: [],
   };
 
-  definitions.rootElement.push(newMessage);
-  return { messageRef: newMessage["@_id"] };
+  definitions.rootElement.push(newInterface);
+  return { interface: newInterface };
 }
diff --git a/packages/bpmn-editor/src/mutations/addOrGetMessages.ts 
b/packages/bpmn-editor/src/mutations/addOrGetMessages.ts
index 28fffe4113e..0d9fd990abf 100644
--- a/packages/bpmn-editor/src/mutations/addOrGetMessages.ts
+++ b/packages/bpmn-editor/src/mutations/addOrGetMessages.ts
@@ -43,7 +43,7 @@ export function addOrGetMessages({
   const newMessage: 
ElementFilter<Unpacked<Normalized<BPMN20__tDefinitions["rootElement"]>>, 
"message"> = {
     __$$element: "message",
     "@_id": generateUuid(),
-    "@_itemRef": `${messageName}Type`,
+    "@_itemRef": `${messageName}Type`, // broken reference to a placeholder 
type that has no meaning to the jBPM Workflow Engine
     "@_name": messageName,
   };
 
diff --git a/packages/bpmn-editor/src/mutations/addOrGetOperations.ts 
b/packages/bpmn-editor/src/mutations/addOrGetOperations.ts
new file mode 100644
index 00000000000..53e860baef0
--- /dev/null
+++ b/packages/bpmn-editor/src/mutations/addOrGetOperations.ts
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { BPMN20__tDefinitions } from 
"@kie-tools/bpmn-marshaller/dist/schemas/bpmn-2_0/ts-gen/types";
+import { ElementFilter } from "@kie-tools/xml-parser-ts/dist/elementFilter";
+import { Unpacked } from 
"@kie-tools/xyflow-react-kie-diagram/dist/tsExt/tsExt";
+import { Normalized } from "../normalization/normalize";
+import { addOrGetInterfaces } from "./addOrGetInterfaces";
+import { addOrGetMessages } from "./addOrGetMessages";
+import { generateUuid } from 
"@kie-tools/xyflow-react-kie-diagram/dist/uuid/uuid";
+
+export function addOrGetOperations({
+  definitions,
+  interfaceName,
+  operationRef,
+  operationName,
+}: {
+  definitions: Normalized<BPMN20__tDefinitions>;
+  interfaceName: string;
+  operationRef?: string;
+  operationName: string;
+}): {
+  interface: 
ElementFilter<Unpacked<Normalized<BPMN20__tDefinitions["rootElement"]>>, 
"interface">;
+  operation: 
ElementFilter<Unpacked<Normalized<BPMN20__tDefinitions["rootElement"]>>, 
"interface">["operation"][number];
+} {
+  const { interface: serviceTaskInterface } = addOrGetInterfaces({
+    definitions,
+    interfaceName,
+  });
+
+  serviceTaskInterface.operation ??= [];
+
+  let operation:
+    | ElementFilter<Unpacked<Normalized<BPMN20__tDefinitions["rootElement"]>>, 
"interface">["operation"][number]
+    | undefined;
+
+  if (operationRef) {
+    const interfaces = definitions.rootElement?.filter((s) => s.__$$element 
=== "interface") ?? [];
+
+    for (const iface of interfaces) {
+      if (iface.__$$element === "interface") {
+        iface.operation ??= [];
+        const operationIndex = iface.operation.findIndex((op) => op["@_id"] 
=== operationRef);
+
+        if (operationIndex >= 0) {
+          operation = iface.operation[operationIndex];
+
+          if (iface["@_id"] !== serviceTaskInterface["@_id"]) {
+            iface.operation.splice(operationIndex, 1);
+            serviceTaskInterface.operation.push(operation);
+
+            if (iface.operation.length === 0) {
+              const interfaceIndex = definitions.rootElement?.findIndex(
+                (s) => s.__$$element === "interface" && s["@_id"] === 
iface["@_id"]
+              );
+              if (interfaceIndex !== undefined && interfaceIndex >= 0) {
+                definitions.rootElement?.splice(interfaceIndex, 1);
+              }
+            }
+          }
+          break;
+        }
+      }
+    }
+  }
+
+  if (!operation) {
+    const { messageRef: inMessageId } = addOrGetMessages({
+      definitions,
+      messageName: "",
+    });
+    const { messageRef: outMessageId } = addOrGetMessages({
+      definitions,
+      messageName: "",
+    });
+
+    operation = {
+      "@_id": generateUuid(),
+      "@_name": operationName,
+      inMessageRef: { __$$text: inMessageId },
+      outMessageRef: { __$$text: outMessageId },
+    };
+    serviceTaskInterface.operation.push(operation);
+  } else if (operation["@_name"] !== operationName) {
+    operation["@_name"] = operationName;
+  }
+
+  return { interface: serviceTaskInterface, operation: operation };
+}
diff --git a/packages/bpmn-editor/src/mutations/deleteInterfaceAndOperation.ts 
b/packages/bpmn-editor/src/mutations/deleteInterfaceAndOperation.ts
new file mode 100644
index 00000000000..fed6e23fff4
--- /dev/null
+++ b/packages/bpmn-editor/src/mutations/deleteInterfaceAndOperation.ts
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { BPMN20__tDefinitions } from 
"@kie-tools/bpmn-marshaller/dist/schemas/bpmn-2_0/ts-gen/types";
+import { Normalized } from "../normalization/normalize";
+
+export function deleteInterfaceAndOperation({
+  definitions,
+  operationRef,
+}: {
+  definitions: Normalized<BPMN20__tDefinitions>;
+  operationRef: string;
+}) {
+  if (!definitions.rootElement) {
+    return;
+  }
+
+  const serviceTaskInterface = definitions.rootElement.find(
+    (s) => s.__$$element === "interface" && s.operation?.some((op) => 
op["@_id"] === operationRef)
+  );
+
+  if (!serviceTaskInterface || serviceTaskInterface.__$$element !== 
"interface") {
+    return;
+  }
+
+  serviceTaskInterface.operation ??= [];
+
+  const operationIndex = serviceTaskInterface.operation.findIndex((op) => 
op["@_id"] === operationRef);
+  if (operationIndex < 0) {
+    return;
+  }
+
+  const operation = serviceTaskInterface.operation[operationIndex];
+
+  const inMessageId = operation.inMessageRef.__$$text;
+  const outMessageId = operation.outMessageRef?.__$$text;
+
+  const existingInMessageIndex = definitions.rootElement.findIndex(
+    (s) => s.__$$element === "message" && s["@_id"] === inMessageId
+  );
+  const existingOutMessageIndex = outMessageId
+    ? definitions.rootElement.findIndex((s) => s.__$$element === "message" && 
s["@_id"] === outMessageId)
+    : -1;
+
+  [existingInMessageIndex, existingOutMessageIndex]
+    .filter((index): index is number => index >= 0)
+    .sort((a, b) => b - a)
+    .forEach((index) => {
+      definitions.rootElement?.splice(index, 1);
+    });
+
+  serviceTaskInterface.operation.splice(operationIndex, 1);
+
+  if (serviceTaskInterface.operation.length === 0) {
+    const interfaceIndex = definitions.rootElement.findIndex(
+      (s) => s.__$$element === "interface" && s["@_id"] === 
serviceTaskInterface["@_id"]
+    );
+    if (interfaceIndex >= 0) {
+      definitions.rootElement.splice(interfaceIndex, 1);
+    }
+  }
+}
diff --git a/packages/bpmn-editor/src/mutations/deleteNode.ts 
b/packages/bpmn-editor/src/mutations/deleteNode.ts
index dfe60478596..16c20fc38c3 100644
--- a/packages/bpmn-editor/src/mutations/deleteNode.ts
+++ b/packages/bpmn-editor/src/mutations/deleteNode.ts
@@ -29,6 +29,7 @@ import { deleteEdge } from "./deleteEdge";
 import { FoundElement, visitFlowElementsAndArtifacts } from 
"./_elementVisitor";
 import { Unpacked } from 
"@kie-tools/xyflow-react-kie-diagram/dist/tsExt/tsExt";
 import { ElementExclusion } from "@kie-tools/xml-parser-ts/dist/elementFilter";
+import { deleteInterfaceAndOperation } from "./deleteInterfaceAndOperation";
 
 export function deleteNode({
   definitions,
@@ -84,6 +85,14 @@ export function deleteNode({
     deletedBpmnElement = foundElement.array.splice(foundElement.index, 1)?.[0] 
as BpmnNodeElement | undefined;
   }
 
+  // if Service Task
+  if (deletedBpmnElement?.__$$element === "serviceTask" && 
deletedBpmnElement["@_operationRef"]) {
+    deleteInterfaceAndOperation({
+      definitions,
+      operationRef: deletedBpmnElement["@_operationRef"],
+    });
+  }
+
   // if lane
   else if (
     (laneIndex = (process.laneSet?.[0].lane ?? []).findIndex((d) => d["@_id"] 
=== __readonly_bpmnElementId)) >= 0
diff --git 
a/packages/bpmn-editor/src/propertiesPanel/messageSelector/MessageSelector.tsx 
b/packages/bpmn-editor/src/propertiesPanel/messageSelector/MessageSelector.tsx
index ff21be74749..753301cd180 100644
--- 
a/packages/bpmn-editor/src/propertiesPanel/messageSelector/MessageSelector.tsx
+++ 
b/packages/bpmn-editor/src/propertiesPanel/messageSelector/MessageSelector.tsx
@@ -31,6 +31,7 @@ import { useCallback, useMemo } from "react";
 import { addOrGetMessages } from "../../mutations/addOrGetMessages";
 import "./MessageSelector.css";
 import { useBpmnEditorI18n } from "../../i18n";
+import { getServiceTaskMessageIds } from 
"../../store/getServiceTaskMessageIds";
 
 export type EventWithMessage =
   | undefined
@@ -57,14 +58,14 @@ export function MessageSelector({
 
   const bpmnEditorStoreApi = useBpmnEditorStoreApi();
 
-  const messagesById = useBpmnEditorStore(
-    (s) =>
-      new Map(
-        s.bpmn.model.definitions.rootElement
-          ?.filter((e) => e.__$$element === "message")
-          .map((m) => [m["@_id"], m] as [string, BPMN20__tMessage])
-      )
-  );
+  const messagesById = useBpmnEditorStore((s) => {
+    const allMessages = s.bpmn.model.definitions.rootElement?.filter((e) => 
e.__$$element === "message") ?? [];
+    const serviceTaskMessageIds = 
getServiceTaskMessageIds(s.bpmn.model.definitions);
+
+    const filteredMessages = allMessages.filter((msg) => 
!serviceTaskMessageIds.has(msg["@_id"]));
+
+    return new Map(filteredMessages.map((m) => [m["@_id"], m] as [string, 
BPMN20__tMessage]));
+  });
 
   const disableIdsSet = useMemo(() => new Set<string | 
undefined>(disableValues), [disableValues]);
 
diff --git 
a/packages/bpmn-editor/src/propertiesPanel/propertiesManager/PropertiesManager.tsx
 
b/packages/bpmn-editor/src/propertiesPanel/propertiesManager/PropertiesManager.tsx
index f0fd4cd0c33..4110b935cc0 100644
--- 
a/packages/bpmn-editor/src/propertiesPanel/propertiesManager/PropertiesManager.tsx
+++ 
b/packages/bpmn-editor/src/propertiesPanel/propertiesManager/PropertiesManager.tsx
@@ -62,6 +62,7 @@ import { renameEscalation } from 
"../../mutations/renameEscalation";
 import { deleteError } from "../../mutations/deleteError";
 import { renameError } from "../../mutations/renameError";
 import { useBpmnEditorI18n } from "../../i18n";
+import { getServiceTaskMessageIds } from 
"../../store/getServiceTaskMessageIds";
 
 export type WithVariables = Normalized<
   | ElementFilter<Unpacked<NonNullable<BPMN20__tDefinitions["rootElement"]>>, 
"process">
@@ -87,9 +88,12 @@ export function PropertiesManager({ p }: { p: undefined | 
WithVariables }) {
   )?.filter(
     (s) => Object.values(DEFAULT_DATA_TYPES).findIndex((defaultDataType) => 
defaultDataType === s["@_structureRef"]) < 0
   );
-  const messages = useBpmnEditorStore((s) =>
-    s.bpmn.model.definitions.rootElement?.filter((s) => s.__$$element === 
"message")
-  );
+  const messages = useBpmnEditorStore((s) => {
+    const allMessages = s.bpmn.model.definitions.rootElement?.filter((s) => 
s.__$$element === "message") ?? [];
+    const serviceTaskMessageIds = 
getServiceTaskMessageIds(s.bpmn.model.definitions);
+
+    return allMessages.filter((msg) => 
!serviceTaskMessageIds.has(msg["@_id"]));
+  });
   const signals = useBpmnEditorStore((s) =>
     s.bpmn.model.definitions.rootElement?.filter((s) => s.__$$element === 
"signal")
   );
diff --git 
a/packages/bpmn-editor/src/propertiesPanel/singleNodeProperties/ServiceTaskProperties.tsx
 
b/packages/bpmn-editor/src/propertiesPanel/singleNodeProperties/ServiceTaskProperties.tsx
index 3f3c14577e0..6c64760aa65 100644
--- 
a/packages/bpmn-editor/src/propertiesPanel/singleNodeProperties/ServiceTaskProperties.tsx
+++ 
b/packages/bpmn-editor/src/propertiesPanel/singleNodeProperties/ServiceTaskProperties.tsx
@@ -42,6 +42,7 @@ import { addOrGetProcessAndDiagramElements } from 
"../../mutations/addOrGetProce
 import { useBpmnEditorStore, useBpmnEditorStoreApi } from 
"../../store/StoreContext";
 import { TextInput } from 
"@patternfly/react-core/dist/js/components/TextInput";
 import { useBpmnEditorI18n } from "../../i18n";
+import { addOrGetOperations } from "../../mutations/addOrGetOperations";
 
 export function ServiceTaskProperties({
   serviceTask,
@@ -128,7 +129,14 @@ export function ServiceTaskProperties({
                 });
                 visitFlowElementsAndArtifacts(process, ({ element: e }) => {
                   if (e["@_id"] === serviceTask["@_id"] && e.__$$element === 
serviceTask.__$$element) {
+                    const { operation } = addOrGetOperations({
+                      definitions: s.bpmn.model.definitions,
+                      interfaceName: newInterface,
+                      operationRef: e["@_operationRef"],
+                      operationName: e["@_drools:serviceoperation"] || "",
+                    });
                     e["@_drools:serviceinterface"] = newInterface;
+                    e["@_operationRef"] = operation["@_id"];
                   }
                 });
               })
@@ -150,7 +158,14 @@ export function ServiceTaskProperties({
                 });
                 visitFlowElementsAndArtifacts(process, ({ element: e }) => {
                   if (e["@_id"] === serviceTask["@_id"] && e.__$$element === 
serviceTask.__$$element) {
+                    const { operation } = addOrGetOperations({
+                      definitions: s.bpmn.model.definitions,
+                      interfaceName: e["@_drools:serviceinterface"] || "",
+                      operationRef: e["@_operationRef"],
+                      operationName: newOperation,
+                    });
                     e["@_drools:serviceoperation"] = newOperation;
+                    e["@_operationRef"] = operation["@_id"];
                   }
                 });
               })
diff --git a/packages/bpmn-editor/src/store/getServiceTaskMessageIds.ts 
b/packages/bpmn-editor/src/store/getServiceTaskMessageIds.ts
new file mode 100644
index 00000000000..4de4da99d9e
--- /dev/null
+++ b/packages/bpmn-editor/src/store/getServiceTaskMessageIds.ts
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { BPMN20__tDefinitions } from 
"@kie-tools/bpmn-marshaller/dist/schemas/bpmn-2_0/ts-gen/types";
+import { Normalized } from "../normalization/normalize";
+
+export function getServiceTaskMessageIds(definitions: 
Normalized<BPMN20__tDefinitions>): Set<string> {
+  const interfaces = definitions.rootElement?.filter((e) => e.__$$element === 
"interface") ?? [];
+  const operationMessageIds = new Set<string>();
+
+  interfaces.forEach((iface) => {
+    if (iface.__$$element === "interface") {
+      iface.operation?.forEach((op) => {
+        if (op.inMessageRef?.__$$text) {
+          operationMessageIds.add(op.inMessageRef.__$$text);
+        }
+        if (op.outMessageRef?.__$$text) {
+          operationMessageIds.add(op.outMessageRef.__$$text);
+        }
+      });
+    }
+  });
+
+  return operationMessageIds;
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to