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> {_import["@_namespace"]}
- </p>
- <p>
- <b>URI:</b> {_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> {_import["@_namespace"]}
+ </p>
+ <p>
+ <b>URI:</b> {_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]