tiagobento commented on code in PR #2130:
URL:
https://github.com/apache/incubator-kie-tools/pull/2130#discussion_r1471746177
##########
packages/dmn-editor/dev-webapp/src/AvailableModelsToInclude.ts:
##########
@@ -67,7 +67,7 @@ export const modelsByNamespace =
Object.values(avaiableModels).reduce((acc, v) =
if (v.type === "dmn") {
acc[v.model.definitions["@_namespace"]] = v;
} else if (v.type === "pmml") {
- acc[getPmmlNamespace({ normalizedPathRelativeToTheOpenFile:
v.normalizedPosixPathRelativeToTheOpenFile })] = v;
+ acc[getPmmlNamespace({ normalizedPosixPathRelativeToTheOpenFile:
v.normalizedPosixPathRelativeToTheOpenFile })] = v;
Review Comment:
We missed fixing this on #2112...
##########
packages/dmn-editor/src/dataTypes/DataTypeName.tsx:
##########
@@ -46,17 +47,19 @@ export function DataTypeName({
isActive: boolean;
relativeToNamespace: string;
shouldCommitOnBlur?: boolean;
- allUniqueNames: UniqueNameIndex;
+ onGetAllUniqueNames: (s: State) => UniqueNameIndex;
Review Comment:
This mechanism allows for better caching of node components.
##########
packages/dmn-editor/src/autolayout/AutolayoutButton.tsx:
##########
Review Comment:
This is where the entire autolayout logic is for now.
##########
packages/dmn-editor/src/diagram/Diagram.tsx:
##########
@@ -129,6 +129,8 @@ const FIT_VIEW_OPTIONS: RF.FitViewOptions = { maxZoom: 1,
minZoom: 0.1, duration
const DEFAULT_VIEWPORT = { x: 100, y: 0, zoom: 1 };
+const DELETE_NODE_KEY_CODES = ["Backspace", "Delete"];
Review Comment:
As requested per feedback from the community at
https://github.com/apache/incubator-kie-issues/issues/439#issuecomment-1870193894
##########
packages/dmn-editor/src/mutations/deleteEdge.ts:
##########
@@ -27,52 +27,78 @@ import { addOrGetDrd } from "./addOrGetDrd";
import { DmnDiagramEdgeData } from "../diagram/edges/Edges";
import { repopulateInputDataAndDecisionsOnAllDecisionServices } from
"./repopulateInputDataAndDecisionsOnDecisionService";
+export enum EdgeDeletionMode {
+ FORM_DRG_AND_ALL_DRDS,
+ FROM_CURRENT_DRD_ONLY,
+}
+
export function deleteEdge({
definitions,
drdIndex,
edge,
+ mode,
}: {
definitions: DMN15__tDefinitions;
drdIndex: number;
edge: { id: string; dmnObject: DmnDiagramEdgeData["dmnObject"] };
+ mode: EdgeDeletionMode;
}) {
- const { diagramElements } = addOrGetDrd({ definitions, drdIndex });
+ if (edge.dmnObject.namespace !== definitions["@_namespace"]) {
+ console.debug("DMN MUTATION: Can't delete an edge that's from an external
node.");
+ return { dmnEdge: undefined };
+ }
- const dmnObjects: DMN15__tDefinitions["artifact"] |
DMN15__tDefinitions["drgElement"] =
+ const dmnObjects: DMN15__tDefinitions["drgElement" | "artifact"] =
switchExpression(edge?.dmnObject.type, {
association: definitions.artifact,
+ group: definitions.artifact,
default: definitions.drgElement,
}) ?? [];
const dmnObjectIndex = dmnObjects.findIndex((d) => d["@_id"] ===
edge.dmnObject.id);
if (dmnObjectIndex < 0) {
- throw new Error(`Can't find DMN element with ID ${edge.dmnObject.id}`);
+ throw new Error(`DMN MUTATION: Can't find DMN element with ID
${edge.dmnObject.id}`);
}
- const requirements =
- switchExpression(edge?.dmnObject.requirementType, {
- // Casting to DMN15__tDecision because if has all types of requirement,
but not necessarily that's true.
- informationRequirement: (dmnObjects[dmnObjectIndex] as
DMN15__tDecision).informationRequirement,
- knowledgeRequirement: (dmnObjects[dmnObjectIndex] as
DMN15__tDecision).knowledgeRequirement,
- authorityRequirement: (dmnObjects[dmnObjectIndex] as
DMN15__tDecision).authorityRequirement,
- association: dmnObjects,
- }) ?? [];
+ if (mode === EdgeDeletionMode.FORM_DRG_AND_ALL_DRDS) {
+ const requirements =
+ switchExpression(edge?.dmnObject.requirementType, {
+ // Casting to DMN15__tDecision because if has all types of
requirement, but not necessarily that's true.
+ informationRequirement: (dmnObjects[dmnObjectIndex] as
DMN15__tDecision).informationRequirement,
+ knowledgeRequirement: (dmnObjects[dmnObjectIndex] as
DMN15__tDecision).knowledgeRequirement,
+ authorityRequirement: (dmnObjects[dmnObjectIndex] as
DMN15__tDecision).authorityRequirement,
+ association: dmnObjects,
+ }) ?? [];
- // Deleting the requirement
- const requirementIndex = (requirements ?? []).findIndex((d) => d["@_id"] ===
edge.id);
- if (requirementIndex >= 0) {
- requirements?.splice(requirementIndex, 1);
+ // Deleting the requirement
+ const requirementIndex = (requirements ?? []).findIndex((d) => d["@_id"]
=== edge.id);
+ if (requirementIndex >= 0) {
+ requirements?.splice(requirementIndex, 1);
+ }
}
// Deleting the DMNEdge's
- let dmnEdge: DMNDI15__DMNEdge | undefined;
- const dmnEdgeIndex = (diagramElements ?? []).findIndex((d) =>
d["@_dmnElementRef"] === edge.id);
- if (dmnEdgeIndex >= 0) {
- dmnEdge = diagramElements[dmnEdgeIndex];
- diagramElements?.splice(dmnEdgeIndex, 1);
+ let deletedDmnEdgeOnCurrentDrd: DMNDI15__DMNEdge | undefined;
+
+ const drdCount = (definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"] ??
[]).length;
+ for (let i = 0; i < drdCount; i++) {
+ const { diagramElements } = addOrGetDrd({ definitions, drdIndex: i });
+
+ if (mode === EdgeDeletionMode.FROM_CURRENT_DRD_ONLY && i !== drdIndex) {
+ continue;
+ }
+
+ const dmnEdgeIndex = (diagramElements ?? []).findIndex((d) =>
d["@_dmnElementRef"] === edge.id);
+ if (dmnEdgeIndex >= 0) {
+ if (i === drdIndex) {
+ deletedDmnEdgeOnCurrentDrd = diagramElements[dmnEdgeIndex];
+ }
+
+ diagramElements?.splice(dmnEdgeIndex, 1);
+ }
}
Review Comment:
This fixes a bug of phantom edges on the DRDs. We need to deleting from all
DRDs when deleting from the DRG too!
##########
packages/dmn-editor/src/store/DerivedStore.tsx:
##########
Review Comment:
This was replaced by the `computed` function on the State directly.
##########
packages/dmn-feel-antlr4-parser/src/parser/VariablesRepository.ts:
##########
@@ -513,7 +513,7 @@ export class VariablesRepository {
break;
default:
- throw new Error("Unknown or not supported type for expression.");
+ // throw new Error("Unknown or not supported type for expression.");
Review Comment:
cc @danielzhe. This allows BEE to render even though some expressions are
undefined. Which is not part of the spec, but necessary for good UX.
##########
packages/dmn-editor/src/DmnEditor.css:
##########
@@ -700,14 +735,13 @@ circle.kie-dmn-editor--diagram-edge-waypoint:hover {
}
.kie-dmn-editor--external-nodes-list-item {
border-radius: 999px;
- background: white;
}
.kie-dmn-editor--external-nodes-list-item:hover {
- filter: brightness(95%);
+ background-color: rgba(0, 0, 0, 0.1);
cursor: grab;
}
.kie-dmn-editor--external-nodes-list-item:active {
- filter: brightness(90%);
+ background-color: rgba(0, 0, 0, 0.15);
cursor: grabbing;
}
Review Comment:
Cosmetic changes...
##########
packages/dmn-editor/src/DmnEditor.css:
##########
@@ -903,7 +937,9 @@ th {
fill: rgba(0, 107, 164, 0.1) !important;
}
-.react-flow__node.selected:not(.react-flow__node-node_unknown) >
.kie-dmn-editor--node-shape > * {
+.react-flow__node.selected:not(.react-flow__node-node_unknown):not(.react-flow__node-node_textAnnotation)
+ > .kie-dmn-editor--node-shape
+ > * {
stroke: #006ba4 !important;
}
Review Comment:
Text annotation nodes have a special "selected" style.
##########
packages/dmn-editor/src/diagram/nodes/Nodes.tsx:
##########
@@ -166,17 +174,17 @@ export const InputDataNode = React.memo(
<PositionalNodeHandles isTargeted={isTargeted &&
isValidConnectionTarget} nodeId={id} />
<div
ref={ref}
- className={`kie-dmn-editor--node kie-dmn-editor--input-data-node
${className} ${
- dmnObjectQName.prefix ? "external" : ""
- }`}
+ className={`kie-dmn-editor--node kie-dmn-editor--input-data-node
${additionalClasses}`}
tabIndex={-1}
onDoubleClick={triggerEditing}
onKeyDown={triggerEditingIfEnter}
>
- <InfoNodePanel isVisible={!isTargeted && isHovered} />
+ {/* {`render count: ${renderCount.current}`}
+ <br /> */}
Review Comment:
Let's leave this for now as it can be used in the future for manual checks,
until we have an automatic way of guaranteeing that Node components are
properly cached and not re-rendered for unrelated changes on the model.
##########
packages/dmn-editor/src/diagram/DrdSelectorPanel.tsx:
##########
@@ -59,7 +66,7 @@ export function DrdSelectorPanel() {
<Divider style={{ marginBottom: "8px" }} />
<div className={"kie-dmn-editor--drd-list"}>
{thisDmn.model.definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"]?.map((drd, i)
=> (
- <React.Fragment key={drd["@_id"] ?? i}>
+ <React.Fragment key={drd["@_id"]!}>
Review Comment:
#2131 will introduce a normalization mechanism that will make all xsd:ID
properties have a value, so we can treat all of them as non-nullable.
##########
packages/dmn-editor/src/diagram/DrgNodesPanel.tsx:
##########
@@ -98,6 +98,8 @@ export function DrgNodesPanel() {
</Button>
</Flex>
+ <Divider style={{ marginBottom: "12px" }} />
+
Review Comment:
Making it exactly the same as the External Nodes panel.
##########
packages/dmn-editor/src/boxedExpressions/BoxedExpression.tsx:
##########
@@ -150,13 +179,6 @@ export function BoxedExpression({ container }: {
container: React.RefObject<HTML
};
}, [boxedExpressionEditor.activeDrgElementId,
thisDmn.model.definitions.drgElement, widthsById]);
- const [lastValidExpression, setLastValidExpression] = useState<typeof
expression>(undefined);
- useEffect(() => {
- if (expression) {
- setLastValidExpression(expression);
- }
- }, [expression, setLastValidExpression]);
-
Review Comment:
Removing dead code.
##########
packages/dmn-editor/src/diagram/DrdSelectorPanel.tsx:
##########
@@ -49,7 +52,11 @@ export function DrdSelectorPanel() {
definitions: state.dmn.model.definitions,
drdIndex: newIndex,
});
+
state.diagram.drdIndex = newIndex;
+ state.diagram.drdSelector.isOpen = false;
+ state.diagram.openNodesPanel = DiagramNodesPanel.DRG_NODES;
+ state.focus.consumableId = getDrdId({ drdIndex: newIndex });
Review Comment:
UX improvement. Allows for automatically focusing on the DRD name when
creating a new one.
##########
packages/dmn-editor/src/diagram/DrdSelectorPanel.tsx:
##########
@@ -69,7 +76,7 @@ export function DrdSelectorPanel() {
});
}}
>
- {drd["@_name"] || getDefaultDrdName({ drdIndex: i })}
+ {`${i + 1}. ${drd["@_name"] || getDefaultDrdName({ drdIndex: i
})}`}
Review Comment:
Adding a small UI improvement to list DRDs as an ordered list.
##########
packages/dmn-editor/src/store/useDiagramData.tsx:
##########
Review Comment:
The logic of this hook moved into `computeDiagramData.ts`
##########
packages/dmn-editor/src/mutations/deleteNode.ts:
##########
@@ -17,65 +17,174 @@
* under the License.
*/
-import { DMN15__tDefinitions } from
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
+import { DMN15__tDefinitions, DMNDI15__DMNShape } from
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
import { NodeNature } from "./NodeNature";
import { addOrGetDrd } from "./addOrGetDrd";
import { repopulateInputDataAndDecisionsOnAllDecisionServices } from
"./repopulateInputDataAndDecisionsOnDecisionService";
import { XmlQName, buildXmlQName } from "@kie-tools/xml-parser-ts/dist/qNames";
import { getNewDmnIdRandomizer } from "../idRandomizer/dmnIdRandomizer";
+import { buildXmlHref } from "../xml/xmlHrefs";
+import { Unpacked } from "../tsExt/tsExt";
+import { DrgEdge } from "../diagram/graph/graph";
+import { EdgeDeletionMode, deleteEdge } from "./deleteEdge";
+
+export enum NodeDeletionMode {
+ FORM_DRG_AND_ALL_DRDS,
+ FROM_CURRENT_DRD_ONLY,
+}
export function deleteNode({
definitions,
+ drgEdges,
drdIndex,
nodeNature,
dmnObjectId,
dmnObjectQName,
+ dmnObjectNamespace,
+ mode,
}: {
definitions: DMN15__tDefinitions;
+ drgEdges: DrgEdge[];
drdIndex: number;
nodeNature: NodeNature;
+ dmnObjectNamespace: string | undefined;
dmnObjectId: string | undefined;
dmnObjectQName: XmlQName;
-}) {
- const { diagramElements, widthsExtension } = addOrGetDrd({ definitions,
drdIndex });
+ mode: NodeDeletionMode;
+}): {
+ deletedDmnObject: Unpacked<DMN15__tDefinitions["drgElement" | "artifact"]> |
undefined;
+ deletedDmnShapeOnCurrentDrd: DMNDI15__DMNShape | undefined;
+} {
+ if (
+ mode === NodeDeletionMode.FROM_CURRENT_DRD_ONLY &&
+ !canRemoveNodeFromDrdOnly({
+ definitions,
+ drdIndex,
+ dmnObjectNamespace,
+ dmnObjectId,
+ })
+ ) {
+ console.warn("DMN MUTATION: Cannot hide a Decision that's contained by a
Decision Service from a DRD.");
+ return { deletedDmnObject: undefined, deletedDmnShapeOnCurrentDrd:
undefined };
+ }
- // Edges need to be deleted by a separate call to `deleteEdge` prior to this.
+ // A DRD doesn't necessarily renders all edges of the DRG, so we need to
look for what DRG edges to delete when deleting a node from any DRD.
+ if (mode === NodeDeletionMode.FORM_DRG_AND_ALL_DRDS) {
+ const nodeId = buildXmlHref({ namespace: dmnObjectNamespace, id:
dmnObjectId! });
+ for (let i = 0; i < drgEdges.length; i++) {
+ const drgEdge = drgEdges[i];
+ // Only delete edges that end at or start from the node being deleted.
+ if (drgEdge.sourceId === nodeId || drgEdge.targetId === nodeId) {
+ deleteEdge({
+ definitions,
+ drdIndex,
+ mode: EdgeDeletionMode.FORM_DRG_AND_ALL_DRDS,
+ edge: {
+ id: drgEdge.id,
+ dmnObject: drgEdge.dmnObject,
+ },
+ });
+ }
+ }
+ }
- // delete the DMNShape
- const shapeDmnElementRef = buildXmlQName(dmnObjectQName);
- diagramElements?.splice(
- (diagramElements ?? []).findIndex((d) => d["@_dmnElementRef"] ===
shapeDmnElementRef),
- 1
- );
+ let dmnObject: Unpacked<DMN15__tDefinitions["drgElement" | "artifact"]> |
undefined;
// External or unknown nodes don't have a dmnObject associated with it, just
the shape..
if (!dmnObjectQName.prefix) {
// Delete the dmnObject itself
if (nodeNature === NodeNature.ARTIFACT) {
- definitions.artifact?.splice(
- (definitions.artifact ?? []).findIndex((a) => a["@_id"] ===
dmnObjectId),
- 1
- );
+ if (mode === NodeDeletionMode.FORM_DRG_AND_ALL_DRDS) {
+ const nodeIndex = (definitions.artifact ?? []).findIndex((a) =>
a["@_id"] === dmnObjectId);
+ dmnObject = definitions.artifact?.splice(nodeIndex, 1)?.[0];
+ }
} else if (nodeNature === NodeNature.DRG_ELEMENT) {
- const deleted = definitions.drgElement?.splice(
- (definitions.drgElement ?? []).findIndex((d) => d["@_id"] ===
dmnObjectId),
- 1
- );
-
- const deletedIdsOnDrgElementTree = getNewDmnIdRandomizer()
- .ack({ json: deleted, type: "DMN15__tDefinitions", attr: "drgElement"
})
- .getOriginalIds();
-
- // Delete widths
- widthsExtension["kie:ComponentWidths"] =
widthsExtension["kie:ComponentWidths"]?.filter(
- (w) => !deletedIdsOnDrgElementTree.has(w["@_dmnElementRef"]!)
- );
+ const nodeIndex = (definitions.drgElement ?? []).findIndex((d) =>
d["@_id"] === dmnObjectId);
+ dmnObject =
+ mode === NodeDeletionMode.FORM_DRG_AND_ALL_DRDS
+ ? definitions.drgElement?.splice(nodeIndex, 1)?.[0]
+ : definitions.drgElement?.[nodeIndex];
} else if (nodeNature === NodeNature.UNKNOWN) {
// Ignore. There's no dmnObject here.
} else {
throw new Error(`Unknown node nature '${nodeNature}'.`);
}
+
+ if (!dmnObject) {
+ throw new Error(`DMN MUTATION: Can't delete DMN object that doesn't
exist: ID=${dmnObjectId}`);
+ }
+ }
+
+ const shapeDmnElementRef = buildXmlQName(dmnObjectQName);
+
+ // Deleting the DMNShape's
+ let deletedDmnShapeOnCurrentDrd: DMNDI15__DMNShape | undefined;
+
+ const deletedIdsOnDmnObjectTree = dmnObject
+ ? getNewDmnIdRandomizer()
+ .ack({ json: [dmnObject], type: "DMN15__tDefinitions", attr:
"drgElement" })
+ .getOriginalIds()
+ : new Set<string>();
+
+ const drdCount = (definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"] ??
[]).length;
+ for (let i = 0; i < drdCount; i++) {
+ if (mode === NodeDeletionMode.FROM_CURRENT_DRD_ONLY && i !== drdIndex) {
+ continue;
+ }
+
+ const { diagramElements, widthsExtension } = addOrGetDrd({ definitions,
drdIndex: i });
+ const dmnShapeIndex = (diagramElements ?? []).findIndex((d) =>
d["@_dmnElementRef"] === shapeDmnElementRef);
+ if (dmnShapeIndex >= 0) {
+ if (i === drdIndex) {
+ deletedDmnShapeOnCurrentDrd = diagramElements[dmnShapeIndex];
+ }
+
+ diagramElements?.splice(dmnShapeIndex, 1);
+ }
+
+ // Delete widths
+ widthsExtension["kie:ComponentWidths"] =
widthsExtension["kie:ComponentWidths"]?.filter(
+ (w) => !deletedIdsOnDmnObjectTree.has(w["@_dmnElementRef"]!) // Only
works because xsd:IDs are exactly the same as QNames when the QName is not
prefixed.
+ );
}
Review Comment:
Deleting DMNShape's from all DRDs to avoid unnecessary "unknown" nodes in
DRDs.
##########
packages/dmn-editor/src/store/Store.ts:
##########
@@ -103,26 +117,44 @@ export interface State {
resizingNodes: Array<string>;
draggingWaypoints: Array<string>;
movingDividerLines: Array<string>;
- editingStyle: boolean;
+ isEditingStyle: boolean;
Review Comment:
Simple rename.
##########
packages/dmn-editor/src/store/ComputedStateCache.ts:
##########
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+export type TypeOrReturnType<T> = T extends (...args: any[]) => any ?
ReturnType<T> : T;
+
+export type CacheEntry<T> = {
+ value: TypeOrReturnType<T> | undefined;
+ dependencies: readonly any[];
+};
+
+export type Cache<T> = {
+ [K in keyof T]: CacheEntry<T[K]>;
+};
+
+let r: number = 0;
+// let h: number = 0;
+// let m: number = 0;
+
+export class ComputedStateCache<T extends Record<string, any>> {
+ private readonly cache: Cache<T>;
+
+ constructor(initialValues: Cache<T>) {
+ this.cache = { ...initialValues };
+ }
+
+ public cached<K extends keyof T, D extends readonly any[]>(
+ key: K,
+ delegate: (...dependencies: D) => TypeOrReturnType<T[K]>,
+ dependencies: D
+ ): TypeOrReturnType<T[K]> {
+ r++;
+
+ const cachedDeps = this.cache[key]?.dependencies ?? [];
+
+ let depsAreEqual = cachedDeps.length === dependencies.length;
+ if (depsAreEqual) {
+ for (let i = 0; i < cachedDeps.length; i++) {
+ if (!Object.is(cachedDeps[i], dependencies[i])) {
+ depsAreEqual = false;
+ }
+ }
+ } else {
+ // console.debug(`${r}: COMPUTED STORE CACHE: (Miss) Deps don't have the
same length... (${String(key)})`);
+ }
+
+ if (depsAreEqual) {
+ // console.debug(`${r}: COMPUTED STORE CACHE: Hit ${++h}!
(${String(key)})`);
+ return this.cache[key].value!;
+ } else {
+ // console.debug(`${r}: COMPUTED STORE CACHE: (Miss) Deps have different
values... (${String(key)})`);
+ }
+
+ // console.debug(`${r}: COMPUTED STORE CACHE: Miss (${++m}})...
(${String(key)})`);
+
+ const v = delegate(...dependencies);
+ this.cache[key].dependencies = dependencies;
+ this.cache[key].value = v;
+ return v;
+ }
+}
Review Comment:
This is the caching logic for a Zustand computed state value.
##########
packages/dmn-editor/src/diagram/Diagram.tsx:
##########
@@ -1620,7 +1745,70 @@ export function KeyboardShortcuts(props: {}) {
dmnEditorStoreApi.setState((state) => {
state.diagram.propertiesPanel.isOpen =
!state.diagram.propertiesPanel.isOpen;
});
- }, [dmnEditorStoreApi, i]);
+ }, [i, dmnEditorStoreApi]);
+
+ // Hide from DRD
+ const x = RF.useKeyPress(["x"]);
+ useEffect(() => {
+ if (!x) {
+ return;
+ }
+
+ const nodesById = rf
+ .getNodes()
+ .reduce((acc, s) => acc.set(s.id, s), new Map<string,
RF.Node<DmnDiagramNodeData>>());
+
+ dmnEditorStoreApi.setState((state) => {
+ const selectedNodeIds = new Set(state.diagram._selectedNodes);
+ for (const edge of rf.getEdges()) {
+ if (
+ (selectedNodeIds.has(edge.source) &&
+ canRemoveNodeFromDrdOnly({
+ definitions: state.dmn.model.definitions,
+ drdIndex: state.diagram.drdIndex,
+ dmnObjectNamespace:
nodesById.get(edge.source)!.data.dmnObjectNamespace,
+ dmnObjectId:
nodesById.get(edge.source)!.data.dmnObject?.["@_id"],
+ })) ||
+ (selectedNodeIds.has(edge.target) &&
+ canRemoveNodeFromDrdOnly({
+ definitions: state.dmn.model.definitions,
+ drdIndex: state.diagram.drdIndex,
+ dmnObjectNamespace:
nodesById.get(edge.target)!.data.dmnObjectNamespace,
+ dmnObjectId:
nodesById.get(edge.target)!.data.dmnObject?.["@_id"],
+ }))
+ ) {
+ deleteEdge({
+ definitions: state.dmn.model.definitions,
+ drdIndex: state.diagram.drdIndex,
+ edge: { id: edge.id, dmnObject: edge.data!.dmnObject },
+ mode: EdgeDeletionMode.FROM_CURRENT_DRD_ONLY,
+ });
+ state.dispatch(state).diagram.setEdgeStatus(edge.id, { selected:
false, draggingWaypoint: false });
+ }
+ }
+
+ for (const node of rf.getNodes().filter((s) => s.selected)) {
+ const { deletedDmnShapeOnCurrentDrd: deletedShape } = deleteNode({
+ drgEdges: [], // Deleting from DRD only.
+ definitions: state.dmn.model.definitions,
+ drdIndex: state.diagram.drdIndex,
+ dmnObjectNamespace: node.data.dmnObjectNamespace,
+ dmnObjectQName: node.data.dmnObjectQName,
+ dmnObjectId: node.data.dmnObject?.["@_id"],
+ nodeNature: nodeNatures[node.type as NodeType],
+ mode: NodeDeletionMode.FROM_CURRENT_DRD_ONLY,
+ });
+
+ if (deletedShape) {
+ state.dispatch(state).diagram.setNodeStatus(node.id, {
+ selected: false,
+ dragging: false,
+ resizing: false,
+ });
+ }
+ }
+ });
+ }, [x, dmnEditorStoreApi, rf]);
Review Comment:
This is new. I introduced this as a way to hide a node from a DRD without
deleting it from the DRG.
##########
packages/dmn-editor/src/mutations/deleteNode.ts:
##########
@@ -17,65 +17,174 @@
* under the License.
*/
-import { DMN15__tDefinitions } from
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
+import { DMN15__tDefinitions, DMNDI15__DMNShape } from
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
import { NodeNature } from "./NodeNature";
import { addOrGetDrd } from "./addOrGetDrd";
import { repopulateInputDataAndDecisionsOnAllDecisionServices } from
"./repopulateInputDataAndDecisionsOnDecisionService";
import { XmlQName, buildXmlQName } from "@kie-tools/xml-parser-ts/dist/qNames";
import { getNewDmnIdRandomizer } from "../idRandomizer/dmnIdRandomizer";
+import { buildXmlHref } from "../xml/xmlHrefs";
+import { Unpacked } from "../tsExt/tsExt";
+import { DrgEdge } from "../diagram/graph/graph";
+import { EdgeDeletionMode, deleteEdge } from "./deleteEdge";
+
+export enum NodeDeletionMode {
+ FORM_DRG_AND_ALL_DRDS,
+ FROM_CURRENT_DRD_ONLY,
+}
export function deleteNode({
definitions,
+ drgEdges,
drdIndex,
nodeNature,
dmnObjectId,
dmnObjectQName,
+ dmnObjectNamespace,
+ mode,
}: {
definitions: DMN15__tDefinitions;
+ drgEdges: DrgEdge[];
drdIndex: number;
nodeNature: NodeNature;
+ dmnObjectNamespace: string | undefined;
dmnObjectId: string | undefined;
dmnObjectQName: XmlQName;
-}) {
- const { diagramElements, widthsExtension } = addOrGetDrd({ definitions,
drdIndex });
+ mode: NodeDeletionMode;
+}): {
+ deletedDmnObject: Unpacked<DMN15__tDefinitions["drgElement" | "artifact"]> |
undefined;
+ deletedDmnShapeOnCurrentDrd: DMNDI15__DMNShape | undefined;
+} {
+ if (
+ mode === NodeDeletionMode.FROM_CURRENT_DRD_ONLY &&
+ !canRemoveNodeFromDrdOnly({
+ definitions,
+ drdIndex,
+ dmnObjectNamespace,
+ dmnObjectId,
+ })
+ ) {
+ console.warn("DMN MUTATION: Cannot hide a Decision that's contained by a
Decision Service from a DRD.");
+ return { deletedDmnObject: undefined, deletedDmnShapeOnCurrentDrd:
undefined };
+ }
- // Edges need to be deleted by a separate call to `deleteEdge` prior to this.
+ // A DRD doesn't necessarily renders all edges of the DRG, so we need to
look for what DRG edges to delete when deleting a node from any DRD.
+ if (mode === NodeDeletionMode.FORM_DRG_AND_ALL_DRDS) {
+ const nodeId = buildXmlHref({ namespace: dmnObjectNamespace, id:
dmnObjectId! });
+ for (let i = 0; i < drgEdges.length; i++) {
+ const drgEdge = drgEdges[i];
+ // Only delete edges that end at or start from the node being deleted.
+ if (drgEdge.sourceId === nodeId || drgEdge.targetId === nodeId) {
+ deleteEdge({
+ definitions,
+ drdIndex,
+ mode: EdgeDeletionMode.FORM_DRG_AND_ALL_DRDS,
+ edge: {
+ id: drgEdge.id,
+ dmnObject: drgEdge.dmnObject,
+ },
+ });
+ }
+ }
+ }
Review Comment:
This fixes a bug of phantom edges on DRDs and DRG. We need to delete from
all DRDs and DRG when deleting from any DRD.
##########
packages/dmn-editor/src/store/Store.ts:
##########
@@ -85,16 +97,18 @@ export interface State {
overlaysPanel: {
isOpen: boolean;
};
+ autolayoutPanel: {
+ isOpen: boolean;
+ };
openNodesPanel: DiagramNodesPanel;
drdSelector: {
isOpen: boolean;
};
overlays: {
enableNodeHierarchyHighlight: boolean;
enableExecutionHitsHighlights: boolean;
- enableCustomNodeStyles: boolean;
enableDataTypesToolbarOnNodes: boolean;
- enableStyles: boolean;
Review Comment:
Removing duplication.
##########
packages/dmn-editor/src/store/computed/computeDiagramData.ts:
##########
@@ -0,0 +1,432 @@
+/*
+ * 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 { DMN15__tDefinitions } from
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
+import { XmlQName } from "@kie-tools/xml-parser-ts/dist/qNames";
+import * as RF from "reactflow";
+import { KIE_DMN_UNKNOWN_NAMESPACE } from "../../Dmn15Spec";
+import { snapShapeDimensions, snapShapePosition } from
"../../diagram/SnapGrid";
+import { EdgeType, NodeType } from "../../diagram/connections/graphStructure";
+import { EDGE_TYPES } from "../../diagram/edges/EdgeTypes";
+import { DmnDiagramEdgeData } from "../../diagram/edges/Edges";
+import { DrgEdge, EdgeVisitor, NodeVisitor, getAdjMatrix, traverse } from
"../../diagram/graph/graph";
+import { getNodeTypeFromDmnObject } from "../../diagram/maths/DmnMaths";
+import { DECISION_SERVICE_COLLAPSED_DIMENSIONS, MIN_NODE_SIZES } from
"../../diagram/nodes/DefaultSizes";
+import {
___NASTY_HACK_FOR_SAFARI_to_force_redrawing_svgs_and_avoid_repaint_glitches }
from "../../diagram/nodes/NodeSvgs";
+import { NODE_TYPES } from "../../diagram/nodes/NodeTypes";
+import { DmnDiagramNodeData, NodeDmnObjects } from "../../diagram/nodes/Nodes";
+import { Unpacked } from "../../tsExt/tsExt";
+import { buildXmlHref, parseXmlHref } from "../../xml/xmlHrefs";
+import { TypeOrReturnType } from "../ComputedStateCache";
+import { Computed, State } from "../Store";
+
+export const NODE_LAYERS = {
+ GROUP_NODE: 0,
+ NODES: 1000, // We need a difference > 1000 here, since ReactFlow will add
1000 to the z-index when a node is selected.
+ DECISION_SERVICE_NODE: 2000, // We need a difference > 1000 here, since
ReactFlow will add 1000 to the z-index when a node is selected.
+ NESTED_NODES: 4000,
+};
+
+type AckEdge = (args: {
+ id: string;
+ dmnObject: DmnDiagramEdgeData["dmnObject"];
+ type: EdgeType;
+ source: string;
+ target: string;
+}) => RF.Edge<DmnDiagramEdgeData>;
+
+type AckNode = (
+ dmnObjectQName: XmlQName,
+ dmnObject: NodeDmnObjects,
+ index: number
+) => RF.Node<DmnDiagramNodeData> | undefined;
+
+export function computeDiagramData(
+ diagram: State["diagram"],
+ definitions: State["dmn"]["model"]["definitions"],
+ externalModelTypesByNamespace:
TypeOrReturnType<Computed["getExternalModelTypesByNamespace"]>,
+ indexes: TypeOrReturnType<Computed["indexes"]>
+) {
+ // console.time("nodes");
+
___NASTY_HACK_FOR_SAFARI_to_force_redrawing_svgs_and_avoid_repaint_glitches.flag
=
+
!___NASTY_HACK_FOR_SAFARI_to_force_redrawing_svgs_and_avoid_repaint_glitches.flag;
+
+ const drgElementsWithoutVisualRepresentationOnCurrentDrd: string[] = [];
+
+ const selectedNodesById = new Map<string, RF.Node<DmnDiagramNodeData>>();
+ const selectedEdgesById = new Map<string, RF.Edge<DmnDiagramEdgeData>>();
+ const selectedNodeTypes = new Set<NodeType>();
+ const nodesById = new Map<string, RF.Node<DmnDiagramNodeData>>();
+ const edgesById = new Map<string, RF.Edge<DmnDiagramEdgeData>>();
+ const parentIdsById = new Map<string, DmnDiagramNodeData>();
+
+ const { selectedNodes, draggingNodes, resizingNodes, selectedEdges } = {
+ selectedNodes: new Set(diagram._selectedNodes),
+ draggingNodes: new Set(diagram.draggingNodes),
+ resizingNodes: new Set(diagram.resizingNodes),
+ selectedEdges: new Set(diagram._selectedEdges),
+ };
+
+ // console.time("edges");
+ const edges: RF.Edge<DmnDiagramEdgeData>[] = [];
+
+ const drgEdges: DrgEdge[] = [];
+
+ const ackEdge: AckEdge = ({ id, type, dmnObject, source, target }) => {
+ const data = {
+ dmnObject,
+ dmnEdge: id ? indexes.dmnEdgesByDmnElementRef.get(id) : undefined,
+ dmnShapeSource: indexes.dmnShapesByHref.get(source),
+ dmnShapeTarget: indexes.dmnShapesByHref.get(target),
+ };
+
+ const edge: RF.Edge<DmnDiagramEdgeData> = {
+ data,
+ id,
+ type,
+ source,
+ target,
+ selected: selectedEdges.has(id),
+ };
+
+ edgesById.set(edge.id, edge);
+ if (edge.selected) {
+ selectedEdgesById.set(edge.id, edge);
+ }
+
+ edges.push(edge);
+
+ drgEdges.push({ id, sourceId: source, targetId: target, dmnObject });
+
+ return edge;
+ };
+
+ // requirements
+ ackRequirementEdges(definitions["@_namespace"], definitions["@_namespace"],
definitions.drgElement, ackEdge);
+
+ // associations
+ (definitions.artifact ?? []).forEach((dmnObject, index) => {
+ if (dmnObject.__$$element !== "association") {
+ return;
+ }
+
+ ackEdge({
+ id: dmnObject["@_id"]!,
+ dmnObject: {
+ namespace: definitions["@_namespace"],
+ type: dmnObject.__$$element,
+ id: dmnObject["@_id"]!,
+ requirementType: "association",
+ index,
+ },
+ type: EDGE_TYPES.association,
+ source: dmnObject.sourceRef?.["@_href"],
+ target: dmnObject.targetRef?.["@_href"],
+ });
+ });
+
+ // console.timeEnd("edges");
+ const ackNode: AckNode = (dmnObjectQName, dmnObject, index) => {
+ const type = getNodeTypeFromDmnObject(dmnObject);
+ if (!type) {
+ return undefined;
+ }
+
+ // If the QName is composite, we try and get the namespace from the XML
namespace declarations. If it's not found, we use `UNKNOWN_DMN_NAMESPACE`
+ // If the QName is simple, we simply say that the namespace is undefined,
which is the same as the default namespace.
+ const dmnObjectNamespace = dmnObjectQName.prefix
+ ? definitions[`@_xmlns:${dmnObjectQName.prefix}`] ??
KIE_DMN_UNKNOWN_NAMESPACE
+ : undefined;
+
+ const id = buildXmlHref({ namespace: dmnObjectNamespace, id:
dmnObjectQName.localPart });
+
+ const _shape = indexes.dmnShapesByHref.get(id);
+ if (!_shape) {
+ drgElementsWithoutVisualRepresentationOnCurrentDrd.push(id);
+ return undefined;
+ }
+
+ const { dmnElementRefQName, ...shape } = _shape;
+
+ const data: DmnDiagramNodeData = {
+ dmnObjectNamespace,
+ dmnObjectQName,
+ dmnObject,
+ shape,
+ index,
+ parentRfNode: undefined,
+ };
+
+ const newNode: RF.Node<DmnDiagramNodeData> = {
+ id,
+ type,
+ selected: selectedNodes.has(id),
+ dragging: draggingNodes.has(id),
+ resizing: resizingNodes.has(id),
+ position: snapShapePosition(diagram.snapGrid, shape),
+ data,
+ zIndex: NODE_LAYERS.NODES,
+ style: { ...snapShapeDimensions(diagram.snapGrid, shape,
MIN_NODE_SIZES[type](diagram.snapGrid)) },
+ };
+
+ if (dmnObject?.__$$element === "decisionService") {
+ const containedDecisions = [...(dmnObject.outputDecision ?? []),
...(dmnObject.encapsulatedDecision ?? [])];
+ for (let i = 0; i < containedDecisions.length; i++) {
+ parentIdsById.set(containedDecisions[i]["@_href"], data);
+ }
+ if (shape["@_isCollapsed"] || !!dmnObjectNamespace) {
+ newNode.style = {
+ ...newNode.style,
+ ...DECISION_SERVICE_COLLAPSED_DIMENSIONS,
+ };
+ }
+ }
+
+ nodesById.set(newNode.id, newNode);
+ if (newNode.selected) {
+ selectedNodesById.set(newNode.id, newNode);
+ selectedNodeTypes.add(newNode.type as NodeType);
+ }
+ return newNode;
+ };
+
+ const localNodes: RF.Node<DmnDiagramNodeData>[] = [
+ ...(definitions.drgElement ?? []).flatMap((dmnObject, index) => {
+ const newNode = ackNode({ type: "xml-qname", localPart:
dmnObject["@_id"]! }, dmnObject, index);
+ return newNode ? [newNode] : [];
+ }),
+ ...(definitions.artifact ?? []).flatMap((dmnObject, index) => {
+ if (dmnObject.__$$element === "association") {
+ return [];
+ }
+
+ const newNode = ackNode({ type: "xml-qname", localPart:
dmnObject["@_id"]! }, dmnObject, index);
+ return newNode ? [newNode] : [];
+ }),
+ ];
+
+ // Assign parents & z-index to NODES
+ for (let i = 0; i < localNodes.length; i++) {
+ const parent = parentIdsById.get(localNodes[i].id);
+ if (parent) {
+ localNodes[i].data.parentRfNode = nodesById.get(
+ buildXmlHref({ namespace: parent.dmnObjectNamespace, id:
parent.dmnObjectQName.localPart })
+ );
+ localNodes[i].extent = undefined; // Allows the node to be dragged
freely outside of parent's bounds.
+ localNodes[i].zIndex = NODE_LAYERS.NESTED_NODES;
+ }
+
+ if (localNodes[i].type === NODE_TYPES.group) {
+ localNodes[i].zIndex = NODE_LAYERS.GROUP_NODE;
+ } else if (localNodes[i].type === NODE_TYPES.decisionService) {
+ localNodes[i].zIndex = NODE_LAYERS.DECISION_SERVICE_NODE;
+ }
+ }
+
+ const externalDrgElementsByIdByNamespace =
[...externalModelTypesByNamespace.dmns.entries()].reduce(
+ (acc, [namespace, externalDmn]) => {
+ // Taking advantage of the loop to add the edges here...
+ ackRequirementEdges(
+ definitions["@_namespace"],
+ externalDmn.model.definitions["@_namespace"],
+ externalDmn.model.definitions.drgElement,
+ ackEdge
+ );
+
+ return acc.set(
+ namespace,
+ (externalDmn.model.definitions.drgElement ?? []).reduce(
+ (acc, e, index) => acc.set(e["@_id"]!, { element: e, index }),
+ new Map<string, { index: number; element:
Unpacked<DMN15__tDefinitions["drgElement"]> }>()
+ )
+ );
+ },
+ new Map<string, Map<string, { index: number; element:
Unpacked<DMN15__tDefinitions["drgElement"]> }>>()
+ );
+
+ const externalNodes = [...indexes.dmnShapesByHref.entries()].flatMap(([href,
shape]) => {
+ if (nodesById.get(href)) {
+ return [];
+ }
+
+ if (!nodesById.get(href) &&
!indexes.hrefsOfDmnElementRefsOfShapesPointingToExternalDmnObjects.has(href)) {
+ // Unknown local node.
+ console.warn(`DMN DIAGRAM: Found a shape that references a local DRG
element that doesn't exist.`, shape);
+ const newNode = ackNode(shape.dmnElementRefQName, null, -1);
+ return newNode ? [newNode] : [];
+ }
+
+ const namespace =
definitions[`@_xmlns:${shape.dmnElementRefQName.prefix}`];
+ if (!namespace) {
+ console.warn(
+ `DMN DIAGRAM: Found a shape that references an external node with a
namespace that is not declared at this DMN.`,
+ shape
+ );
+ const newNode = ackNode(shape.dmnElementRefQName, null, -1);
+ return newNode ? [newNode] : [];
+ }
+
+ const externalDrgElementsById =
externalDrgElementsByIdByNamespace.get(namespace);
+ if (!externalDrgElementsById) {
+ console.warn(
+ `DMN DIAGRAM: Found a shape that references an external node from a
namespace that is not provided on this DMN's external DMNs mapping.`,
+ shape
+ );
+ const newNode = ackNode(shape.dmnElementRefQName, null, -1);
+ return newNode ? [newNode] : [];
+ }
+
+ const externalDrgElement =
externalDrgElementsById.get(shape.dmnElementRefQName.localPart);
+ if (!externalDrgElement) {
+ console.warn(`DMN DIAGRAM: Found a shape that references a non-existent
node from an external DMN.`, shape);
+ const newNode = ackNode(shape.dmnElementRefQName, null, -1);
+ return newNode ? [newNode] : [];
+ }
+
+ const newNode = ackNode(shape.dmnElementRefQName,
externalDrgElement.element, externalDrgElement.index);
+ return newNode ? [newNode] : [];
+ });
+
+ // Groups are always at the back. Decision Services after groups, then
everything else.
+ const sortedNodes = [...localNodes, ...externalNodes]
+ .sort((a, b) => Number(b.type === NODE_TYPES.decisionService) -
Number(a.type === NODE_TYPES.decisionService))
+ .sort((a, b) => Number(b.type === NODE_TYPES.group) - Number(a.type ===
NODE_TYPES.group));
+
+ // Selected edges go to the end of the array. This is necessary because
z-index doesn't work on SVGs.
+ const sortedEdges = edges
+ .filter((e) => nodesById.has(e.source) && nodesById.has(e.target))
Review Comment:
Although we now compute all edges on the DRG (and on external models too),
we only include in the diagram edges that have both ends present.
##########
packages/dmn-editor/src/store/Store.ts:
##########
@@ -103,26 +117,44 @@ export interface State {
resizingNodes: Array<string>;
draggingWaypoints: Array<string>;
movingDividerLines: Array<string>;
- editingStyle: boolean;
+ isEditingStyle: boolean;
};
}
+// Read this to understand why we need computed as part of the store.
+// https://github.com/pmndrs/zustand/issues/132#issuecomment-1120467721
+export type Computed = {
+ isDiagramEditingInProgress(): boolean;
+
+ importsByNamespace(): Map<string, DMN15__tImport>;
+
+ indexes(): ReturnType<typeof computeIndexes>;
+
+ getDiagramData(e: ExternalModelsIndex | undefined): ReturnType<typeof
computeDiagramData>;
+
+ isDropTargetNodeValidForSelection(e: ExternalModelsIndex | undefined):
boolean;
+
+ getExternalModelTypesByNamespace: (
+ e: ExternalModelsIndex | undefined
+ ) => ReturnType<typeof computeExternalModelsByType>;
+
+ getDataTypes(e: ExternalModelsIndex | undefined): ReturnType<typeof
computeDataTypes>;
+
+ getAllFeelVariableUniqueNames(): ReturnType<typeof
computeAllFeelVariableUniqueNames>;
+};
+
Review Comment:
These values were on the DerivedStore.
##########
packages/dmn-editor/tsconfig.json:
##########
@@ -1,8 +1,10 @@
{
"extends": "@kie-tools/tsconfig/tsconfig.json",
"compilerOptions": {
- "outDir": "dist",
- "esModuleInterop": true
+ "module": "ES6",
+ "moduleResolution": "Node",
+ "esModuleInterop": true,
+ "outDir": "./dist"
Review Comment:
Changes necessary to make `enableMapAndSet` work.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]