This is an automated email from the ASF dual-hosted git repository.

tiagobento 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 4adbb59c98e kie-issues-664: On the DMN Editor, make it possible to 
manually change the order of Decision Service inputs via Properties Panel 
(#2249)
4adbb59c98e is described below

commit 4adbb59c98e8055b48e1d6d8e56eafece496a5e6
Author: Luiz João Motta <[email protected]>
AuthorDate: Mon Apr 22 15:37:10 2024 -0300

    kie-issues-664: On the DMN Editor, make it possible to manually change the 
order of Decision Service inputs via Properties Panel (#2249)
---
 ...pulateInputDataAndDecisionsOnDecisionService.ts |  44 +++--
 .../propertiesPanel/DecisionServiceProperties.tsx  | 198 ++++++++++++++++++++-
 .../DocumentationLinksFormGroup.tsx                |   2 +-
 3 files changed, 220 insertions(+), 24 deletions(-)

diff --git 
a/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts
 
b/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts
index 75db1eb61b1..c3fce874cd1 100644
--- 
a/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts
+++ 
b/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts
@@ -45,6 +45,11 @@ export function 
repopulateInputDataAndDecisionsOnDecisionService({
   definitions: DMN15__tDefinitions;
   decisionService: DMN15__tDecisionService;
 }) {
+  // Save previous values to preserve order
+  const inputDatas = new Set<string>([...(decisionService.inputData ?? 
[])].map((e) => e["@_href"])); // Using Set for uniqueness
+  const inputDecisions = new Set<string>([...(decisionService.inputDecision ?? 
[])].map((e) => e["@_href"])); // Using Set for uniqueness
+
+  // Reset the inputData and inputDecision entries
   decisionService.inputData = [];
   decisionService.inputDecision = [];
 
@@ -53,7 +58,8 @@ export function 
repopulateInputDataAndDecisionsOnDecisionService({
     ...(decisionService.encapsulatedDecision ?? []).map((s) => s["@_href"]),
   ]);
 
-  const requirements = new Array<{ href: string; type: "decisionIr" | 
"inputDataIr" }>();
+  // Map all DS Input Data and Decision requirements to their href
+  const requirements = new Map<string, "decisionIr" | "inputDataIr">();
   for (let i = 0; i < definitions.drgElement!.length; i++) {
     const drgElement = definitions.drgElement![i];
     if (!hrefsToDecisionsInsideDecisionService.has(`#${drgElement["@_id"]}`) 
|| drgElement.__$$element !== "decision") {
@@ -62,27 +68,37 @@ export function 
repopulateInputDataAndDecisionsOnDecisionService({
 
     (drgElement.informationRequirement ?? []).flatMap((ir) => {
       if (ir.requiredDecision) {
-        requirements.push({ href: ir.requiredDecision["@_href"], type: 
"decisionIr" });
+        requirements.set(ir.requiredDecision["@_href"], "decisionIr");
       } else if (ir.requiredInput) {
-        requirements.push({ href: ir.requiredInput["@_href"], type: 
"inputDataIr" });
+        requirements.set(ir.requiredInput["@_href"], "inputDataIr");
       }
     });
   }
 
-  const inputDatas = new Set<string>(); // Using Set for uniqueness
-  const inputDecisions = new Set<string>(); // Using Set for uniqueness
+  // START - Remove outdated requirements
+  [...inputDatas].forEach((inputData) => {
+    if (!requirements.has(inputData)) {
+      inputDatas.delete(inputData);
+    }
+  });
 
-  const requirementsArray = [...requirements];
-  for (let i = 0; i < requirementsArray.length; i++) {
-    const r = requirementsArray[i];
-    if (r.type === "inputDataIr") {
-      inputDatas.add(r.href);
-    } else if (r.type === "decisionIr") {
-      inputDecisions.add(r.href);
+  [...inputDecisions].forEach((inputDecision) => {
+    if (!requirements.has(inputDecision)) {
+      inputDecisions.delete(inputDecision);
+    }
+  });
+  // END
+
+  // Update inputDecisions and inputDatas requirements with possible new hrefs
+  requirements.forEach((type, href) => {
+    if (type === "decisionIr") {
+      inputDecisions.add(href);
+    } else if (type === "inputDataIr") {
+      inputDatas.add(href);
     } else {
-      throw new Error(`DMN MUTATION: Invalid type of element to be referenced 
by DecisionService: '${r.type}'`);
+      throw new Error(`DMN MUTATION: Invalid type of element to be referenced 
by DecisionService: '${type}'`);
     }
-  }
+  });
 
   decisionService.inputData = [...inputDatas].map((iHref) => ({ "@_href": 
iHref }));
   decisionService.inputDecision = [...inputDecisions].flatMap(
diff --git 
a/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx 
b/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx
index 3ae80477c27..0581d7ce441 100644
--- a/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx
+++ b/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx
@@ -19,6 +19,7 @@
 
 import * as React from "react";
 import {
+  DMN15__tDMNElementReference,
   DMN15__tDecision,
   DMN15__tDecisionService,
   DMN15__tInputData,
@@ -38,6 +39,9 @@ import { Divider } from 
"@patternfly/react-core/dist/js/components/Divider";
 import { useDmnEditor } from "../DmnEditorContext";
 import { useResolvedTypeRef } from "../dataTypes/useResolvedTypeRef";
 import { useExternalModels } from 
"../includedModels/DmnEditorDependenciesContext";
+import { DragAndDrop, Draggable } from "../draggable/Draggable";
+import { buildFeelQNameFromNamespace } from "../feel/buildFeelQName";
+import { Alert, AlertVariant } from 
"@patternfly/react-core/dist/js/components/Alert/Alert";
 
 export type AllKnownDrgElementsByHref = Map<
   string,
@@ -169,22 +173,38 @@ export function DecisionServiceProperties({
       </FormGroup>
 
       <Divider />
-
-      <FormGroup label="Input data">
-        <DecisionServiceElementList
+      <FormGroup label="Input decisions">
+        <DraggableDecisionServiceElementList
           decisionServiceNamespace={namespace}
-          elements={decisionService.inputData}
+          elements={decisionService.inputDecision}
           allDrgElementsByHref={allDrgElementsByHref}
+          onChange={(newInputDecisions) => {
+            setState((state) => {
+              (state.dmn.model.definitions.drgElement![index] as 
DMN15__tDecisionService).inputDecision =
+                newInputDecisions;
+            });
+          }}
         />
       </FormGroup>
-      <FormGroup label="Input decisions">
-        <DecisionServiceElementList
+      <FormGroup label="Input data">
+        <DraggableDecisionServiceElementList
           decisionServiceNamespace={namespace}
-          elements={decisionService.inputDecision}
+          elements={decisionService.inputData}
           allDrgElementsByHref={allDrgElementsByHref}
+          onChange={(newInputData) => {
+            setState((state) => {
+              (state.dmn.model.definitions.drgElement![index] as 
DMN15__tDecisionService).inputData = newInputData;
+            });
+          }}
         />
       </FormGroup>
 
+      <DecisionServiceEquivalentFunction
+        decisionService={decisionService}
+        decisionServiceNamespace={namespace}
+        allDrgElementsByHref={allDrgElementsByHref}
+      />
+
       <DocumentationLinksFormGroup
         isReadonly={isReadonly}
         values={decisionService.extensionElements?.["kie:attachment"]}
@@ -214,7 +234,7 @@ export function DecisionServiceElementList({
   return (
     <ul>
       {(elements ?? []).length <= 0 && (
-        <li>
+        <li style={{ paddingLeft: "32px" }}>
           <small>
             <i>(Empty)</i>
           </small>
@@ -237,7 +257,7 @@ export function DecisionServiceElementList({
         });
 
         return (
-          <li key={potentialExternalHref}>
+          <li style={{ paddingLeft: "32px" }} key={potentialExternalHref}>
             <DmnObjectListItem
               dmnObjectHref={potentialExternalHref}
               dmnObject={allDrgElementsByHref.get(potentialExternalHref)}
@@ -250,3 +270,163 @@ export function DecisionServiceElementList({
     </ul>
   );
 }
+
+export function DraggableDecisionServiceElementList({
+  decisionServiceNamespace,
+  elements,
+  allDrgElementsByHref,
+  onChange,
+}: {
+  decisionServiceNamespace: string | undefined;
+  elements: DMN15__tDecisionService["outputDecision"];
+  allDrgElementsByHref: AllKnownDrgElementsByHref;
+  onChange: (hrefs: DMN15__tDMNElementReference[] | undefined) => void;
+}) {
+  const thisDmnsNamespace = useDmnEditorStore((s) => 
s.dmn.model.definitions["@_namespace"]);
+  const [keys, setKeys] = React.useState(() => elements?.map((e) => 
e["@_href"]) ?? []);
+
+  const onDragEnd = useCallback(
+    (source: number, dest: number) => {
+      const reordened = [...(elements ?? [])];
+      const [removed] = reordened.splice(source, 1);
+      reordened.splice(dest, 0, removed);
+      onChange(reordened);
+    },
+    [elements, onChange]
+  );
+
+  const reorder = useCallback((source: number, dest: number) => {
+    setKeys((prev) => {
+      const reordenedUuid = [...prev];
+      const [removedUuid] = reordenedUuid.splice(source, 1);
+      reordenedUuid.splice(dest, 0, removedUuid);
+      return reordenedUuid;
+    });
+  }, []);
+
+  const draggableItem = useCallback(
+    (element: DMN15__tDMNElementReference, index: number) => {
+      const localHref = parseXmlHref(element["@_href"]);
+
+      // If the localHref has a namespace, then that's the one to use, as it 
can be that an external node is pointing to another external node in their 
perspective
+      // E.g., (This DMN) --includes--> (DMN A) --includes--> (DMN B)
+      // In this case, localHref will have DMN B's namespace, and we need to 
respect it. It `DMN B` in included in `This DMN`, then
+      // we can resolve it, otherwise, the dmnObject referenced by localHref 
won't be present on `dmnObjectsByHref`, and we'll only show the href.
+      // Now, if the localHref doesn't have a namespace, then it is local to 
the model where the Decision Service is declared, so we use 
`decisionServiceNamespace`.
+      // If none of that is true, then it means that the Decision Service is 
local to "This DMN", so the namespace is simply "".
+      const resolvedNamespace = localHref.namespace ?? 
decisionServiceNamespace ?? thisDmnsNamespace;
+
+      const potentialExternalHref = buildXmlHref({
+        namespace: resolvedNamespace,
+        id: localHref.id,
+      });
+
+      return (
+        <Draggable
+          key={keys[index]}
+          index={index}
+          handlerStyle={
+            keys[index] ? { paddingLeft: "16px", paddingRight: "16px" } : { 
paddingLeft: "16px", paddingRight: "16px" }
+          }
+        >
+          <li key={potentialExternalHref}>
+            <DmnObjectListItem
+              dmnObjectHref={potentialExternalHref}
+              dmnObject={allDrgElementsByHref.get(potentialExternalHref)}
+              relativeToNamespace={thisDmnsNamespace}
+              namespace={resolvedNamespace}
+            />
+          </li>
+        </Draggable>
+      );
+    },
+    [allDrgElementsByHref, decisionServiceNamespace, keys, thisDmnsNamespace]
+  );
+
+  return (
+    <ul>
+      {(elements ?? []).length <= 0 && (
+        <li style={{ paddingLeft: "32px" }}>
+          <small>
+            <i>(Empty)</i>
+          </small>
+        </li>
+      )}
+      <DragAndDrop
+        reorder={reorder}
+        onDragEnd={onDragEnd}
+        values={elements}
+        draggableItem={draggableItem}
+        isDisabled={false}
+      />
+    </ul>
+  );
+}
+
+function DecisionServiceEquivalentFunction({
+  decisionService,
+  allDrgElementsByHref,
+  decisionServiceNamespace,
+}: {
+  decisionService: DMN15__tDecisionService;
+  allDrgElementsByHref: AllKnownDrgElementsByHref;
+  decisionServiceNamespace: string | undefined;
+}) {
+  const importsByNamespace = useDmnEditorStore((s) => 
s.computed(s).importsByNamespace());
+  const thisDmnsNamespace = useDmnEditorStore((s) => 
s.dmn.model.definitions["@_namespace"]);
+
+  const getNodeNameByHref = useCallback(
+    (href: string) => {
+      const localHref = parseXmlHref(href);
+
+      // If the localHref has a namespace, then that's the one to use, as it 
can be that an external node is pointing to another external node in their 
perspective
+      // E.g., (This DMN) --includes--> (DMN A) --includes--> (DMN B)
+      // In this case, localHref will have DMN B's namespace, and we need to 
respect it. It `DMN B` in included in `This DMN`, then
+      // we can resolve it, otherwise, the dmnObject referenced by localHref 
won't be present on `dmnObjectsByHref`, and we'll only show the href.
+      // Now, if the localHref doesn't have a namespace, then it is local to 
the model where the Decision Service is declared, so we use 
`decisionServiceNamespace`.
+      // If none of that is true, then it means that the Decision Service is 
local to "This DMN", so the namespace is simply "".
+      const resolvedNamespace = localHref.namespace ?? 
decisionServiceNamespace ?? thisDmnsNamespace;
+
+      const potentialExternalHref = buildXmlHref({
+        namespace: resolvedNamespace,
+        id: localHref.id,
+      });
+
+      const dmnObject = allDrgElementsByHref.get(potentialExternalHref);
+
+      return dmnObject
+        ? buildFeelQNameFromNamespace({
+            namedElement: dmnObject,
+            importsByNamespace,
+            namespace: resolvedNamespace,
+            relativeToNamespace: thisDmnsNamespace,
+          }).full
+        : potentialExternalHref;
+    },
+    [allDrgElementsByHref, decisionServiceNamespace, importsByNamespace, 
thisDmnsNamespace]
+  );
+
+  const buildFunctionArgList = useCallback(
+    (inputDecisions?: DMN15__tDMNElementReference[], inputData?: 
DMN15__tDMNElementReference[]) => {
+      const inputDecisionNodeNames = inputDecisions?.map((ide) => 
getNodeNameByHref(ide["@_href"]));
+      const inputDataNodeNames = inputData?.map((ida) => 
getNodeNameByHref(ida["@_href"]));
+
+      return [...(inputDecisionNodeNames ?? []), ...(inputDataNodeNames ?? 
[])].reduce(
+        (acc, name) => (acc ? `${acc}, ${name}` : name),
+        ""
+      );
+    },
+    [getNodeNameByHref]
+  );
+
+  return (
+    <Alert variant={AlertVariant.info} isInline title="Invoking this Decision 
Service in FEEL">
+      <p style={{ fontFamily: "monospace" }}>
+        {`${decisionService["@_name"]}(${buildFunctionArgList(
+          decisionService.inputDecision,
+          decisionService.inputData
+        )})`}
+      </p>
+    </Alert>
+  );
+}
diff --git 
a/packages/dmn-editor/src/propertiesPanel/DocumentationLinksFormGroup.tsx 
b/packages/dmn-editor/src/propertiesPanel/DocumentationLinksFormGroup.tsx
index f39b959716e..59edffc1f2e 100644
--- a/packages/dmn-editor/src/propertiesPanel/DocumentationLinksFormGroup.tsx
+++ b/packages/dmn-editor/src/propertiesPanel/DocumentationLinksFormGroup.tsx
@@ -165,7 +165,7 @@ export function DocumentationLinksFormGroup({
   }, []);
 
   const draggableItem = useCallback(
-    (kieAttachment, index) => {
+    (kieAttachment: Namespaced<"kie", KIE__tAttachment>, index: number) => {
       return (
         <Draggable
           key={valuesUuid?.[index] ?? generateUuid()}


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

Reply via email to