From 326a19b8455bcb7df297883a7b1f37339c2ee204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Jo=C3=A3o=20Motta?= Date: Mon, 27 May 2024 10:30:12 -0300 Subject: [PATCH] NO-ISSUE: Removing an Included Model should remove all references of it on the current model (#2365) --- .../src/includedModels/IncludedModels.tsx | 320 ++++++++---------- .../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 = ""; @@ -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 ? ( - ) : ( @@ -444,25 +447,32 @@ function IncludedModelCard({ isReadonly, }: { _import: Normalized; - 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( (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 ( - } - onSelect={() => setCardActionsOpen(false)} - isOpen={isCardActionsOpen} - menuAppendTo={document.body} - isPlain={true} - position={"right"} - dropdownItems={[ - - {!isReadonly && ( - } - onClick={() => { - if (isReadonly) { - return; - } - - remove(index); - }} - > - Remove - - )} - , - ]} - /> - - - s.computed(s).getAllFeelVariableUniqueNames(), [])} - id={_import["@_id"]!} - name={_import["@_name"]} - isReadonly={false} - shouldCommitOnBlur={true} - onRenamed={rename} - validate={DMN15_SPEC.IMPORT.name.isValid} - /> -
-
- -
-
-
-
- - {`${title}`} -
-
- - + ) + } + 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)} > - {pathDisplayed} - - -
-
- ); -} - -function UnknownIncludedModelCard({ - _import, - index, - isReadonly, -}: { - _import: Normalized; - 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( - (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 ( - - - - } - onSelect={() => setCardActionsOpen(false)} - isOpen={isCardActionsOpen} - menuAppendTo={document.body} - isPlain={true} - position={"right"} - dropdownItems={[ - - {!isReadonly && ( - } - onClick={() => { - if (isReadonly) { - return; - } - - remove(index); - }} - > - Remove - - )} - , - ]} - /> + + - - - + {externalModel ? ( + + {`${title}`} +

-

- Namespace: {_import["@_namespace"]} -

-

- URI: {_import["@_locationURI"] ?? None} -

-
-
+ + + + + ) : ( + // unknown + + + +
+

+ Namespace: {_import["@_namespace"]} +

+

+ URI: {_import["@_locationURI"] ?? None} +

+
+
+ )}
); } 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; index: number }) { +export function deleteImport({ + definitions, + __readonly_index, + __readonly_externalModelTypesByNamespace, +}: { + definitions: Normalized; + __readonly_index: number; + __readonly_externalModelTypesByNamespace: TypeOrReturnType; +}) { 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; type AckNode = ( @@ -78,6 +80,8 @@ export function computeDiagramData( const nodesById = new Map>(); const edgesById = new Map>(); const parentIdsById = new Map(); + const externalNodesByNamespace = new Map>>(); + const edgesFromExternalNodesByNamespace = new Map>>(); 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, }); }); }