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

ljmotta 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 326a19b8455 NO-ISSUE: Removing an Included Model should remove all 
references of it on the current model (#2365)
326a19b8455 is described below

commit 326a19b8455bcb7df297883a7b1f37339c2ee204
Author: Luiz João Motta <[email protected]>
AuthorDate: Mon May 27 10:30:12 2024 -0300

    NO-ISSUE: Removing an Included Model should remove all references of it on 
the current model (#2365)
---
 .../src/includedModels/IncludedModels.tsx          | 320 ++++++++++-----------
 packages/dmn-editor/src/mutations/deleteImport.ts  |  59 +++-
 .../src/store/computed/computeDiagramData.ts       |  26 +-
 3 files changed, 230 insertions(+), 175 deletions(-)

diff --git a/packages/dmn-editor/src/includedModels/IncludedModels.tsx 
b/packages/dmn-editor/src/includedModels/IncludedModels.tsx
index 964fda1a510..3e2d5806ebe 100644
--- a/packages/dmn-editor/src/includedModels/IncludedModels.tsx
+++ b/packages/dmn-editor/src/includedModels/IncludedModels.tsx
@@ -50,13 +50,15 @@ import { allPmmlImportNamespaces, getPmmlNamespace } from 
"../pmml/pmml";
 import { allDmnImportNamespaces } from 
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/Dmn15Spec";
 import { getNamespaceOfDmnImport } from "./importNamespaces";
 import { Alert, AlertVariant } from 
"@patternfly/react-core/dist/js/components/Alert/Alert";
-import { Dropdown, DropdownItem, KebabToggle } from 
"@patternfly/react-core/dist/js/components/Dropdown";
+import { KebabToggle } from 
"@patternfly/react-core/dist/js/components/Dropdown";
 import { TrashIcon } from "@patternfly/react-icons/dist/js/icons/trash-icon";
 import { useInViewSelect } from "../responsiveness/useInViewSelect";
 import { useCancelableEffect } from 
"@kie-tools-core/react-hooks/dist/useCancelableEffect";
 import { State } from "../store/Store";
 import "./IncludedModels.css";
 import { Normalized } from "../normalization/normalize";
+import { Popover, PopoverPosition } from 
"@patternfly/react-core/dist/js/components/Popover";
+import { AlertActionCloseButton, AlertActionLink } from 
"@patternfly/react-core/dist/js/components/Alert";
 
 export const EMPTY_IMPORT_NAME_NAMESPACE_IDENTIFIER = "<Default>";
 
@@ -394,10 +396,11 @@ export function IncludedModels() {
                   (!isModalOpen && index === thisDmnsImports.length - 1 ? 
selectedModel : undefined); // Use the selected model to avoid showing the 
"unknown included model" card.
 
                 return !externalModel ? (
-                  <UnknownIncludedModelCard
+                  <IncludedModelCard
                     key={dmnImport["@_id"]}
                     _import={dmnImport}
                     index={index}
+                    externalModel={undefined}
                     isReadonly={false}
                   />
                 ) : (
@@ -444,25 +447,32 @@ function IncludedModelCard({
   isReadonly,
 }: {
   _import: Normalized<DMN15__tImport>;
-  externalModel: ExternalModel;
+  externalModel: ExternalModel | undefined;
   index: number;
   isReadonly: boolean;
 }) {
+  const { externalModelsByNamespace } = useExternalModels();
   const dmnEditorStoreApi = useDmnEditorStoreApi();
 
   const { onRequestToJumpToPath, onRequestToResolvePath } = useDmnEditor();
 
   const remove = useCallback(
     (index: number) => {
+      setRemovePopoverOpen(false);
       dmnEditorStoreApi.setState((state) => {
-        deleteImport({ definitions: state.dmn.model.definitions, index });
+        const externalModelTypesByNamespace = state
+          .computed(state)
+          .getExternalModelTypesByNamespace(externalModelsByNamespace);
+        deleteImport({
+          definitions: state.dmn.model.definitions,
+          __readonly_index: index,
+          __readonly_externalModelTypesByNamespace: 
externalModelTypesByNamespace,
+        });
       });
     },
-    [dmnEditorStoreApi]
+    [dmnEditorStoreApi, externalModelsByNamespace]
   );
 
-  const { externalModelsByNamespace } = useExternalModels();
-
   const rename = useCallback<OnInlineFeelNameRenamed>(
     (newName) => {
       dmnEditorStoreApi.setState((state) => {
@@ -489,173 +499,121 @@ function IncludedModelCard({
   }, [_import]);
 
   const title = useMemo(() => {
+    if (externalModel === undefined) {
+      return "";
+    }
     if (externalModel.type === "dmn") {
       return externalModel.model.definitions["@_name"];
     } else if (externalModel.type === "pmml") {
       return "";
     }
-  }, [externalModel.model, externalModel.type]);
+  }, [externalModel]);
+
+  const pathDisplayed = useMemo(() => {
+    if (externalModel !== undefined) {
+      return (
+        
onRequestToResolvePath?.(externalModel.normalizedPosixPathRelativeToTheOpenFile)
 ??
+        externalModel.normalizedPosixPathRelativeToTheOpenFile
+      );
+    }
+  }, [onRequestToResolvePath, externalModel]);
 
-  const pathDisplayed = useMemo(
-    () =>
-      
onRequestToResolvePath?.(externalModel.normalizedPosixPathRelativeToTheOpenFile)
 ??
-      externalModel.normalizedPosixPathRelativeToTheOpenFile,
-    [onRequestToResolvePath, 
externalModel.normalizedPosixPathRelativeToTheOpenFile]
+  const [isRemovePopoverOpen, setRemovePopoverOpen] = useState(false);
+  const [isConfirmationPopoverOpen, setConfirmationPopoverOpen] = 
useState(false);
+  const shouldRenderConfirmationMessage = useMemo(
+    () => isRemovePopoverOpen && isConfirmationPopoverOpen,
+    [isConfirmationPopoverOpen, isRemovePopoverOpen]
   );
 
-  const [isCardActionsOpen, setCardActionsOpen] = useState(false);
-
   return (
     <Card isHoverable={true} isCompact={false}>
       <CardHeader>
         <CardActions>
-          <Dropdown
-            toggle={<KebabToggle id={"toggle-kebab-top-level"} 
onToggle={setCardActionsOpen} />}
-            onSelect={() => setCardActionsOpen(false)}
-            isOpen={isCardActionsOpen}
-            menuAppendTo={document.body}
-            isPlain={true}
-            position={"right"}
-            dropdownItems={[
-              <React.Fragment key={"remove-fragment"}>
-                {!isReadonly && (
-                  <DropdownItem
-                    style={{ minWidth: "240px" }}
-                    icon={<TrashIcon />}
-                    onClick={() => {
-                      if (isReadonly) {
-                        return;
-                      }
-
-                      remove(index);
-                    }}
-                  >
-                    Remove
-                  </DropdownItem>
-                )}
-              </React.Fragment>,
-            ]}
-          />
-        </CardActions>
-        <CardTitle>
-          <InlineFeelNameInput
-            placeholder={EMPTY_IMPORT_NAME_NAMESPACE_IDENTIFIER}
-            isPlain={true}
-            allUniqueNames={useCallback((s) => 
s.computed(s).getAllFeelVariableUniqueNames(), [])}
-            id={_import["@_id"]!}
-            name={_import["@_name"]}
-            isReadonly={false}
-            shouldCommitOnBlur={true}
-            onRenamed={rename}
-            validate={DMN15_SPEC.IMPORT.name.isValid}
-          />
-          <br />
-          <br />
-          <ExternalModelLabel extension={extension} />
-          <br />
-          <br />
-        </CardTitle>
-      </CardHeader>
-      <CardBody>
-        {`${title}`}
-        <br />
-        <br />
-        <small>
-          <Button
-            variant={ButtonVariant.link}
-            style={{ paddingLeft: 0, whiteSpace: "break-spaces", textAlign: 
"left" }}
-            onClick={() => {
-              
onRequestToJumpToPath?.(externalModel.normalizedPosixPathRelativeToTheOpenFile);
+          <Popover
+            bodyContent={
+              shouldRenderConfirmationMessage ? (
+                <Alert
+                  isInline
+                  variant={AlertVariant.warning}
+                  title={"This action have major impact to your model"}
+                  actionClose={
+                    <AlertActionCloseButton
+                      onClose={() => {
+                        setRemovePopoverOpen(false);
+                        setConfirmationPopoverOpen(false);
+                      }}
+                    />
+                  }
+                  actionLinks={
+                    <>
+                      <AlertActionLink
+                        onClick={(ev) => {
+                          remove(index);
+                          ev.stopPropagation();
+                          ev.preventDefault();
+                        }}
+                        variant={"link"}
+                        style={{ color: "var(--pf-global--danger-color--200)", 
fontWeight: "bold" }}
+                      >
+                        {`Yes, remove included ${extension.toUpperCase()}`}
+                      </AlertActionLink>
+                    </>
+                  }
+                >
+                  {extension === "dmn" && (
+                    <>
+                      Removing an included DMN will erase all its imported 
nodes and connected edges from your model.
+                      The references to item definitions, Business Knowledge 
Model functions, and Decision expressions
+                      will remain, requiring to be manually removed.
+                    </>
+                  )}
+                  {extension === "pmml" && (
+                    <>
+                      Removing an included PMML will not erase references on 
Boxed Function expressions, requiring it to
+                      be manually removed.
+                    </>
+                  )}
+                </Alert>
+              ) : (
+                <Button
+                  variant={"plain"}
+                  onClick={(ev) => {
+                    ev.stopPropagation();
+                    ev.preventDefault();
+                    if (isReadonly) {
+                      return;
+                    }
+                    setConfirmationPopoverOpen(true);
+                  }}
+                >
+                  <TrashIcon />
+                  {"  "}
+                  Remove
+                </Button>
+              )
+            }
+            hasNoPadding={shouldRenderConfirmationMessage}
+            maxWidth={shouldRenderConfirmationMessage ? "300px" : "150px"}
+            minWidth={shouldRenderConfirmationMessage ? "300px" : "150px"}
+            isVisible={isRemovePopoverOpen}
+            showClose={false}
+            shouldClose={() => {
+              setRemovePopoverOpen(false);
+              setConfirmationPopoverOpen(false);
             }}
+            position={PopoverPosition.bottom}
+            shouldOpen={() => setRemovePopoverOpen(true)}
           >
-            <i>{pathDisplayed}</i>
-          </Button>
-        </small>
-      </CardBody>
-    </Card>
-  );
-}
-
-function UnknownIncludedModelCard({
-  _import,
-  index,
-  isReadonly,
-}: {
-  _import: Normalized<DMN15__tImport>;
-  index: number;
-  isReadonly: boolean;
-}) {
-  const dmnEditorStoreApi = useDmnEditorStoreApi();
-
-  const remove = useCallback(
-    (index: number) => {
-      dmnEditorStoreApi.setState((state) => {
-        deleteImport({ definitions: state.dmn.model.definitions, index });
-      });
-    },
-    [dmnEditorStoreApi]
-  );
-
-  const { externalModelsByNamespace } = useExternalModels();
-
-  const rename = useCallback<OnInlineFeelNameRenamed>(
-    (newName) => {
-      dmnEditorStoreApi.setState((state) => {
-        renameImport({
-          definitions: state.dmn.model.definitions,
-          index,
-          newName,
-          allTopLevelDataTypesByFeelName: 
state.computed(state).getDataTypes(externalModelsByNamespace)
-            .allTopLevelDataTypesByFeelName,
-        });
-      });
-    },
-    [dmnEditorStoreApi, externalModelsByNamespace, index]
-  );
-
-  const extension = useMemo(() => {
-    if (allDmnImportNamespaces.has(_import["@_importType"])) {
-      return "dmn";
-    } else if (allPmmlImportNamespaces.has(_import["@_importType"])) {
-      return "pmml";
-    } else {
-      return "Unknwon";
-    }
-  }, [_import]);
-
-  const [isCardActionsOpen, setCardActionsOpen] = useState(false);
-
-  return (
-    <Card isHoverable={true} isCompact={false}>
-      <CardHeader>
-        <CardActions>
-          <Dropdown
-            toggle={<KebabToggle id={"toggle-kebab-top-level"} 
onToggle={setCardActionsOpen} />}
-            onSelect={() => setCardActionsOpen(false)}
-            isOpen={isCardActionsOpen}
-            menuAppendTo={document.body}
-            isPlain={true}
-            position={"right"}
-            dropdownItems={[
-              <React.Fragment key={"remove-fragment"}>
-                {!isReadonly && (
-                  <DropdownItem
-                    style={{ minWidth: "240px" }}
-                    icon={<TrashIcon />}
-                    onClick={() => {
-                      if (isReadonly) {
-                        return;
-                      }
-
-                      remove(index);
-                    }}
-                  >
-                    Remove
-                  </DropdownItem>
-                )}
-              </React.Fragment>,
-            ]}
-          />
+            <Button
+              variant={"plain"}
+              onClick={(ev) => {
+                ev.stopPropagation();
+                ev.preventDefault();
+              }}
+            >
+              <KebabToggle />
+            </Button>
+          </Popover>
         </CardActions>
         <CardTitle>
           <InlineFeelNameInput
@@ -676,18 +634,38 @@ function UnknownIncludedModelCard({
           <br />
         </CardTitle>
       </CardHeader>
-      <CardBody>
-        <Alert title={"External model not found."} isInline={true} 
variant={AlertVariant.danger}>
-          <Divider style={{ marginTop: "16px" }} />
+      {externalModel ? (
+        <CardBody>
+          {`${title}`}
+          <br />
           <br />
-          <p>
-            <b>Namespace:</b>&nbsp;{_import["@_namespace"]}
-          </p>
-          <p>
-            <b>URI:</b>&nbsp;{_import["@_locationURI"] ?? <i>None</i>}
-          </p>
-        </Alert>
-      </CardBody>
+          <small>
+            <Button
+              variant={ButtonVariant.link}
+              style={{ paddingLeft: 0, whiteSpace: "break-spaces", textAlign: 
"left" }}
+              onClick={() => {
+                
onRequestToJumpToPath?.(externalModel.normalizedPosixPathRelativeToTheOpenFile);
+              }}
+            >
+              <i>{pathDisplayed}</i>
+            </Button>
+          </small>
+        </CardBody>
+      ) : (
+        // unknown
+        <CardBody>
+          <Alert title={"External model not found."} isInline={true} 
variant={AlertVariant.danger}>
+            <Divider style={{ marginTop: "16px" }} />
+            <br />
+            <p>
+              <b>Namespace:</b>&nbsp;{_import["@_namespace"]}
+            </p>
+            <p>
+              <b>URI:</b>&nbsp;{_import["@_locationURI"] ?? <i>None</i>}
+            </p>
+          </Alert>
+        </CardBody>
+      )}
     </Card>
   );
 }
diff --git a/packages/dmn-editor/src/mutations/deleteImport.ts 
b/packages/dmn-editor/src/mutations/deleteImport.ts
index 085896c4588..ca2617e3260 100644
--- a/packages/dmn-editor/src/mutations/deleteImport.ts
+++ b/packages/dmn-editor/src/mutations/deleteImport.ts
@@ -20,15 +20,68 @@
 import { DMN15__tDefinitions } from 
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
 import { getXmlNamespaceDeclarationName } from 
"../xml/xmlNamespaceDeclarations";
 import { Normalized } from "../normalization/normalize";
+import { computeDiagramData } from "../store/computed/computeDiagramData";
+import { deleteNode, NodeDeletionMode } from "./deleteNode";
+import { nodeNatures } from "./NodeNature";
+import { NodeType } from "../diagram/connections/graphStructure";
+import { deleteEdge, EdgeDeletionMode } from "./deleteEdge";
+import { computeIndexedDrd } from "../store/computed/computeIndexes";
+import { Computed, defaultStaticState } from "../store/Store";
+import { TypeOrReturnType } from "../store/ComputedStateCache";
 
-export function deleteImport({ definitions, index }: { definitions: 
Normalized<DMN15__tDefinitions>; index: number }) {
+export function deleteImport({
+  definitions,
+  __readonly_index,
+  __readonly_externalModelTypesByNamespace,
+}: {
+  definitions: Normalized<DMN15__tDefinitions>;
+  __readonly_index: number;
+  __readonly_externalModelTypesByNamespace: 
TypeOrReturnType<Computed["getExternalModelTypesByNamespace"]>;
+}) {
   definitions.import ??= [];
-  const [deleted] = definitions.import.splice(index, 1);
+  const [deletedImport] = definitions.import.splice(__readonly_index, 1);
 
   const namespaceName = getXmlNamespaceDeclarationName({
     rootElement: definitions,
-    namespace: deleted["@_namespace"],
+    namespace: deletedImport["@_namespace"],
   });
+
+  // Delete from all DRDs
+  const defaultDiagram = defaultStaticState().diagram;
+  definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"]?.forEach((_, i) => {
+    const indexedDrd = computeIndexedDrd(definitions["@_namespace"], 
definitions, i);
+    const { externalNodesByNamespace, drgEdges, 
edgesFromExternalNodesByNamespace } = computeDiagramData(
+      defaultDiagram,
+      definitions,
+      __readonly_externalModelTypesByNamespace,
+      indexedDrd,
+      false
+    );
+
+    externalNodesByNamespace.get(deletedImport["@_namespace"])?.forEach((node) 
=> {
+      deleteNode({
+        definitions,
+        drgEdges: drgEdges,
+        drdIndex: 0,
+        nodeNature: nodeNatures[node.type! as NodeType],
+        dmnObjectId: node.data.dmnObject?.["@_id"],
+        dmnObjectQName: node.data.dmnObjectQName,
+        dmnObjectNamespace: node.data.dmnObjectNamespace!,
+        externalDmnsIndex: __readonly_externalModelTypesByNamespace.dmns,
+        mode: NodeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+      });
+    });
+
+    
edgesFromExternalNodesByNamespace.get(deletedImport["@_namespace"])?.forEach((edge)
 => {
+      deleteEdge({
+        definitions,
+        drdIndex: 0,
+        edge: { id: edge.id, dmnObject: edge.data!.dmnObject },
+        mode: EdgeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+      });
+    });
+  });
+
   if (namespaceName) {
     delete definitions[`@_xmlns:${namespaceName}`];
   }
diff --git a/packages/dmn-editor/src/store/computed/computeDiagramData.ts 
b/packages/dmn-editor/src/store/computed/computeDiagramData.ts
index 7c44934b2ca..69552fa0412 100644
--- a/packages/dmn-editor/src/store/computed/computeDiagramData.ts
+++ b/packages/dmn-editor/src/store/computed/computeDiagramData.ts
@@ -37,6 +37,7 @@ import { TypeOrReturnType } from "../ComputedStateCache";
 import { Computed, State } from "../Store";
 import { getDecisionServicePropertiesRelativeToThisDmn } from 
"../../mutations/addExistingDecisionServiceToDrd";
 import { Normalized } from "../../normalization/normalize";
+import { KIE_UNKNOWN_NAMESPACE } from "../../kie/kie";
 
 export const NODE_LAYERS = {
   GROUP_NODE: 0,
@@ -51,6 +52,7 @@ type AckEdge = (args: {
   type: EdgeType;
   source: string;
   target: string;
+  sourceNamespace: string | undefined;
 }) => RF.Edge<DmnDiagramEdgeData>;
 
 type AckNode = (
@@ -78,6 +80,8 @@ export function computeDiagramData(
   const nodesById = new Map<string, RF.Node<DmnDiagramNodeData>>();
   const edgesById = new Map<string, RF.Edge<DmnDiagramEdgeData>>();
   const parentIdsById = new Map<string, DmnDiagramNodeData>();
+  const externalNodesByNamespace = new Map<string, 
Array<RF.Node<DmnDiagramNodeData>>>();
+  const edgesFromExternalNodesByNamespace = new Map<string, 
Array<RF.Edge<DmnDiagramEdgeData>>>();
 
   const { selectedNodes, draggingNodes, resizingNodes, selectedEdges } = {
     selectedNodes: new Set(diagram._selectedNodes),
@@ -92,7 +96,7 @@ export function computeDiagramData(
   const drgEdges: DrgEdge[] = [];
   const drgAdjacencyList: DrgAdjacencyList = new Map();
 
-  const ackEdge: AckEdge = ({ id, type, dmnObject, source, target }) => {
+  const ackEdge: AckEdge = ({ id, type, dmnObject, source, target, 
sourceNamespace }) => {
     const data = {
       dmnObject,
       dmnEdge: id ? indexedDrd.dmnEdgesByDmnElementRef.get(id) : undefined,
@@ -109,6 +113,13 @@ export function computeDiagramData(
       selected: selectedEdges.has(id),
     };
 
+    if (sourceNamespace && sourceNamespace !== KIE_UNKNOWN_NAMESPACE) {
+      edgesFromExternalNodesByNamespace.set(sourceNamespace, [
+        ...(edgesFromExternalNodesByNamespace.get(sourceNamespace) ?? []),
+        edge,
+      ]);
+    }
+
     edgesById.set(edge.id, edge);
     if (edge.selected) {
       selectedEdgesById.set(edge.id, edge);
@@ -149,6 +160,7 @@ export function computeDiagramData(
       type: EDGE_TYPES.association,
       source: dmnObject.sourceRef?.["@_href"],
       target: dmnObject.targetRef?.["@_href"],
+      sourceNamespace: undefined, // association are always from the current 
namespace
     });
   });
 
@@ -224,6 +236,13 @@ export function computeDiagramData(
       }
     }
 
+    if (dmnObjectNamespace && dmnObjectNamespace !== KIE_UNKNOWN_NAMESPACE) {
+      externalNodesByNamespace.set(dmnObjectNamespace, [
+        ...(externalNodesByNamespace.get(dmnObjectNamespace) ?? []),
+        newNode,
+      ]);
+    }
+
     nodesById.set(newNode.id, newNode);
     if (newNode.selected) {
       selectedNodesById.set(newNode.id, newNode);
@@ -360,6 +379,8 @@ export function computeDiagramData(
     nodes: sortedNodes,
     edges: sortedEdges,
     edgesById,
+    externalNodesByNamespace,
+    edgesFromExternalNodesByNamespace,
     nodesById,
     selectedNodeTypes,
     selectedNodesById,
@@ -393,6 +414,7 @@ function ackRequirementEdges(
           type: EDGE_TYPES.informationRequirement,
           source: buildXmlHref({ namespace: irHref.namespace ?? namespace, id: 
irHref.id }),
           target: buildXmlHref({ namespace, id: dmnObject["@_id"]! }),
+          sourceNamespace: irHref.namespace ?? namespace,
         });
       });
     }
@@ -412,6 +434,7 @@ function ackRequirementEdges(
           type: EDGE_TYPES.knowledgeRequirement,
           source: buildXmlHref({ namespace: krHref.namespace ?? namespace, id: 
krHref.id }),
           target: buildXmlHref({ namespace, id: dmnObject["@_id"]! }),
+          sourceNamespace: krHref.namespace ?? namespace,
         });
       });
     }
@@ -435,6 +458,7 @@ function ackRequirementEdges(
           type: EDGE_TYPES.authorityRequirement,
           source: buildXmlHref({ namespace: arHref.namespace ?? namespace, id: 
arHref.id }),
           target: buildXmlHref({ namespace, id: dmnObject["@_id"]! }),
+          sourceNamespace: arHref.namespace ?? namespace,
         });
       });
     }


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

Reply via email to