This is an automated email from the ASF dual-hosted git repository.
tiagobento pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git
The following commit(s) were added to refs/heads/main by this push:
new 530f8fdcbbb kie-issues#2090: DMN Editor: Does not render edge when
required decision uses namespace in href (#3268)
530f8fdcbbb is described below
commit 530f8fdcbbbc46e27b0eb44b2642104622c15730
Author: Tiago Bento <[email protected]>
AuthorDate: Fri Sep 12 08:48:09 2025 -0400
kie-issues#2090: DMN Editor: Does not render edge when required decision
uses namespace in href (#3268)
---
packages/dmn-editor/src/diagram/graph/graph.ts | 3 +-
packages/dmn-editor/src/mutations/deleteEdge.ts | 4 +-
.../src/store/computed/computeDiagramData.ts | 83 +++++++++++++--------
.../NamespaceInRequiredDecisionHref.mdx | 25 +++++++
.../NamespaceInRequiredDecisionHref.stories.tsx | 78 +++++++++++++++++++
.../dmn-editor/tests-e2e/__fixtures__/editor.ts | 6 ++
.../namespace-in-required-decision-href.png | Bin 0 -> 22780 bytes
.../namespace-in-required-decision-href.png | Bin 0 -> 23419 bytes
.../webkit/namespace-in-required-decision-href.png | Bin 0 -> 23346 bytes
.../tests-e2e/checkDmnWithNamespaceInHref.spec.ts | 33 ++++++++
10 files changed, 199 insertions(+), 33 deletions(-)
diff --git a/packages/dmn-editor/src/diagram/graph/graph.ts
b/packages/dmn-editor/src/diagram/graph/graph.ts
index 720db0226ee..186dd54795f 100644
--- a/packages/dmn-editor/src/diagram/graph/graph.ts
+++ b/packages/dmn-editor/src/diagram/graph/graph.ts
@@ -33,7 +33,8 @@ export type DrgEdge = {
targetId: string;
id: string;
dmnObject: {
- namespace: string;
+ /* Empty or undefined if same as "thisDmn"'s namespace. */
+ normalizedNamespace: undefined | string;
id: string;
type: Unpacked<Normalized<DMN_LATEST__tDefinitions>["artifact" |
"drgElement"]>["__$$element"];
requirementType: "informationRequirement" | "knowledgeRequirement" |
"authorityRequirement" | "association";
diff --git a/packages/dmn-editor/src/mutations/deleteEdge.ts
b/packages/dmn-editor/src/mutations/deleteEdge.ts
index 8302768c60a..52e2e6c041a 100644
--- a/packages/dmn-editor/src/mutations/deleteEdge.ts
+++ b/packages/dmn-editor/src/mutations/deleteEdge.ts
@@ -44,7 +44,7 @@ export function deleteEdge({
mode: EdgeDeletionMode;
externalModelsByNamespace: ExternalModelsIndex | undefined;
}) {
- if (edge.dmnObject.namespace === definitions["@_namespace"]) {
+ if (!edge.dmnObject.normalizedNamespace) {
const dmnObjects: Normalized<DMN_LATEST__tDefinitions>["drgElement" |
"artifact"] =
switchExpression(edge?.dmnObject.type, {
association: definitions.artifact,
@@ -60,7 +60,7 @@ export function deleteEdge({
if (mode === EdgeDeletionMode.FROM_DRG_AND_ALL_DRDS) {
const requirements =
switchExpression(edge?.dmnObject.requirementType, {
- // Casting toDMN_LATEST__tDecision because if has all types of
requirement, but not necessarily that's true.
+ // Casting to DMN_LATEST__tDecision because if has all types of
requirement, but not necessarily that's true.
informationRequirement: (dmnObjects[dmnObjectIndex] as
Normalized<DMN_LATEST__tDecision>)
.informationRequirement,
knowledgeRequirement: (dmnObjects[dmnObjectIndex] as
Normalized<DMN_LATEST__tDecision>).knowledgeRequirement,
diff --git a/packages/dmn-editor/src/store/computed/computeDiagramData.ts
b/packages/dmn-editor/src/store/computed/computeDiagramData.ts
index 3982766f9a6..eb817ea9baf 100644
--- a/packages/dmn-editor/src/store/computed/computeDiagramData.ts
+++ b/packages/dmn-editor/src/store/computed/computeDiagramData.ts
@@ -52,7 +52,8 @@ type AckEdge = (args: {
type: EdgeType;
source: string;
target: string;
- sourceNamespace: string | undefined;
+ /* Empty or undefined if same as "thisDmn"'s namespace. */
+ sourceNormalizedNamespace: string | undefined;
}) => RF.Edge<DmnDiagramEdgeData>;
type AckNode = (
@@ -96,7 +97,7 @@ export function computeDiagramData(
const drgEdges: DrgEdge[] = [];
const drgAdjacencyList: DrgAdjacencyList = new Map();
- const ackEdge: AckEdge = ({ id, type, dmnObject, source, target,
sourceNamespace }) => {
+ const ackEdge: AckEdge = ({ id, type, dmnObject, source, target,
sourceNormalizedNamespace }) => {
const data = {
dmnObject,
dmnEdge: id ? indexedDrd.dmnEdgesByDmnElementRef.get(xmlHrefToQName(id,
definitions)) : undefined,
@@ -113,9 +114,9 @@ export function computeDiagramData(
selected: selectedEdges.has(id),
};
- if (sourceNamespace && sourceNamespace !== KIE_UNKNOWN_NAMESPACE) {
- edgesFromExternalNodesByNamespace.set(sourceNamespace, [
- ...(edgesFromExternalNodesByNamespace.get(sourceNamespace) ?? []),
+ if (sourceNormalizedNamespace && sourceNormalizedNamespace !==
KIE_UNKNOWN_NAMESPACE) {
+ edgesFromExternalNodesByNamespace.set(sourceNormalizedNamespace, [
+ ...(edgesFromExternalNodesByNamespace.get(sourceNormalizedNamespace)
?? []),
edge,
]);
}
@@ -151,7 +152,7 @@ export function computeDiagramData(
ackEdge({
id: dmnObject["@_id"]!,
dmnObject: {
- namespace: definitions["@_namespace"],
+ normalizedNamespace: undefined, // normalized as it's always equal to
`definitions["@_namespace"]`.
type: dmnObject.__$$element,
id: dmnObject["@_id"]!,
requirementType: "association",
@@ -160,7 +161,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
+ sourceNormalizedNamespace: undefined, // association are always from the
current namespace
});
});
@@ -413,30 +414,38 @@ function ackRequirementEdges(
drgElements: Normalized<DMN_LATEST__tDefinitions>["drgElement"],
ackEdge: AckEdge
) {
- const namespace = drgElementsNamespace === thisDmnsNamespace ? "" :
drgElementsNamespace;
+ const normalizedDrgElementsNamespace =
+ !drgElementsNamespace || drgElementsNamespace === thisDmnsNamespace ? "" :
drgElementsNamespace;
for (const dmnObject of drgElements ?? []) {
// information requirements
if (dmnObject.__$$element === "decision") {
(dmnObject.informationRequirement ?? []).forEach((ir, index) => {
const irHref = parseXmlHref((ir.requiredDecision ??
ir.requiredInput)!["@_href"]);
+
+ const normalizedIrSourceNamespace = !irHref.namespace
+ ? normalizedDrgElementsNamespace
+ : irHref.namespace === thisDmnsNamespace
+ ? ""
+ : irHref.namespace;
+
ackEdge({
- // HREF format, used as RF.Edge ID
+ // Plain ID or full HREF format, used as RF.Edge ID
id:
- drgElementsNamespace === thisDmnsNamespace
- ? ir["@_id"]
- : buildXmlHref({ namespace: drgElementsNamespace, id: ir["@_id"]
}),
+ normalizedDrgElementsNamespace === ""
+ ? ir["@_id"] // prevent the id from having a leading `#`
+ : buildXmlHref({ namespace: normalizedDrgElementsNamespace, id:
ir["@_id"] }),
dmnObject: {
- namespace: drgElementsNamespace,
+ normalizedNamespace: normalizedDrgElementsNamespace, // owner of
the requirement (edge)
type: dmnObject.__$$element,
id: dmnObject["@_id"]!,
requirementType: "informationRequirement",
index,
},
type: EDGE_TYPES.informationRequirement,
- source: buildXmlHref({ namespace: irHref.namespace ?? namespace, id:
irHref.id }),
- target: buildXmlHref({ namespace, id: dmnObject["@_id"]! }),
- sourceNamespace: irHref.namespace ?? namespace,
+ source: buildXmlHref({ namespace: normalizedIrSourceNamespace, id:
irHref.id }),
+ target: buildXmlHref({ namespace: normalizedDrgElementsNamespace,
id: dmnObject["@_id"]! }),
+ sourceNormalizedNamespace: normalizedIrSourceNamespace,
});
});
}
@@ -444,23 +453,30 @@ function ackRequirementEdges(
if (dmnObject.__$$element === "decision" || dmnObject.__$$element ===
"businessKnowledgeModel") {
(dmnObject.knowledgeRequirement ?? []).forEach((kr, index) => {
const krHref = parseXmlHref(kr.requiredKnowledge["@_href"]);
+
+ const normalizedKrSourceNamespace = !krHref.namespace
+ ? normalizedDrgElementsNamespace
+ : krHref.namespace === thisDmnsNamespace
+ ? ""
+ : krHref.namespace;
+
ackEdge({
- // HREF format, used as RF.Edge ID
+ // Plain ID or full HREF format, used as RF.Edge ID
id:
- drgElementsNamespace === thisDmnsNamespace
- ? kr["@_id"]
- : buildXmlHref({ namespace: drgElementsNamespace, id: kr["@_id"]
}),
+ normalizedDrgElementsNamespace === ""
+ ? kr["@_id"] // prevent the id from having a leading `#`
+ : buildXmlHref({ namespace: normalizedDrgElementsNamespace, id:
kr["@_id"] }),
dmnObject: {
- namespace: drgElementsNamespace,
+ normalizedNamespace: normalizedDrgElementsNamespace, // owner of
the requirement (edge)
type: dmnObject.__$$element,
id: dmnObject["@_id"]!,
requirementType: "knowledgeRequirement",
index,
},
type: EDGE_TYPES.knowledgeRequirement,
- source: buildXmlHref({ namespace: krHref.namespace ?? namespace, id:
krHref.id }),
- target: buildXmlHref({ namespace, id: dmnObject["@_id"]! }),
- sourceNamespace: krHref.namespace ?? namespace,
+ source: buildXmlHref({ namespace: normalizedKrSourceNamespace, id:
krHref.id }),
+ target: buildXmlHref({ namespace: normalizedDrgElementsNamespace,
id: dmnObject["@_id"]! }),
+ sourceNormalizedNamespace: normalizedKrSourceNamespace,
});
});
}
@@ -472,19 +488,26 @@ function ackRequirementEdges(
) {
(dmnObject.authorityRequirement ?? []).forEach((ar, index) => {
const arHref = parseXmlHref((ar.requiredInput ?? ar.requiredDecision
?? ar.requiredAuthority)!["@_href"]);
+
+ const normalizedArSourceNamespace = !arHref.namespace
+ ? normalizedDrgElementsNamespace
+ : arHref.namespace === thisDmnsNamespace
+ ? ""
+ : arHref.namespace;
+
ackEdge({
- id: ar["@_id"]!,
+ id: ar["@_id"],
dmnObject: {
- namespace: drgElementsNamespace,
+ normalizedNamespace: normalizedDrgElementsNamespace, // owner of
the requirement (edge)
type: dmnObject.__$$element,
- id: dmnObject["@_id"]!,
+ id: dmnObject["@_id"],
requirementType: "authorityRequirement",
index,
},
type: EDGE_TYPES.authorityRequirement,
- source: buildXmlHref({ namespace: arHref.namespace ?? namespace, id:
arHref.id }),
- target: buildXmlHref({ namespace, id: dmnObject["@_id"]! }),
- sourceNamespace: arHref.namespace ?? namespace,
+ source: buildXmlHref({ namespace: normalizedArSourceNamespace, id:
arHref.id }),
+ target: buildXmlHref({ namespace: normalizedDrgElementsNamespace,
id: dmnObject["@_id"]! }),
+ sourceNormalizedNamespace: normalizedArSourceNamespace,
});
});
}
diff --git
a/packages/dmn-editor/stories/useCases/namespaceInRequiredDecisionHref/NamespaceInRequiredDecisionHref.mdx
b/packages/dmn-editor/stories/useCases/namespaceInRequiredDecisionHref/NamespaceInRequiredDecisionHref.mdx
new file mode 100644
index 00000000000..34950c8239b
--- /dev/null
+++
b/packages/dmn-editor/stories/useCases/namespaceInRequiredDecisionHref/NamespaceInRequiredDecisionHref.mdx
@@ -0,0 +1,25 @@
+{/* 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 { Meta, Story } from "@storybook/blocks";
+import * as NamespaceInRequiredDecisionHref from
"./NamespaceInRequiredDecisionHref.stories";
+
+<Meta title="MDX/Use cases/Namespace In Required Decision Href"
of={NamespaceInRequiredDecisionHref} />
+
+## Namespace In Required Decision Href
+
+DMN 1.6 supports referencing required decision with namespace in href.
diff --git
a/packages/dmn-editor/stories/useCases/namespaceInRequiredDecisionHref/NamespaceInRequiredDecisionHref.stories.tsx
b/packages/dmn-editor/stories/useCases/namespaceInRequiredDecisionHref/NamespaceInRequiredDecisionHref.stories.tsx
new file mode 100644
index 00000000000..468b3d3c785
--- /dev/null
+++
b/packages/dmn-editor/stories/useCases/namespaceInRequiredDecisionHref/NamespaceInRequiredDecisionHref.stories.tsx
@@ -0,0 +1,78 @@
+/*
+ * 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 * as React from "react";
+import type { Meta, StoryObj } from "@storybook/react";
+import { getMarshaller } from "@kie-tools/dmn-marshaller";
+import { Empty } from "../../misc/empty/Empty.stories";
+import { DmnEditor, DmnEditorProps } from "../../../src/DmnEditor";
+import { StorybookDmnEditorProps } from "../../dmnEditorStoriesWrapper";
+
+export const namespaceInRequiredDecisionHref = `<?xml version="1.0"
encoding="UTF-8" ?>
+<definitions xmlns="https://www.omg.org/spec/DMN/20240513/MODEL/"
expressionLanguage="https://www.omg.org/spec/DMN/20240513/FEEL/"
namespace="https://kie.org/dmn/_BCA8E83B-E3E6-432C-BFD6-44CD55A76A6C"
id="_2BC7DBF7-90C6-448A-B336-05F26D1FEC8E"
name="DMN_18EBF2E7-B344-47B1-827A-3A2E08B8E9CA"
xmlns:dmndi="https://www.omg.org/spec/DMN/20230324/DMNDI/"
xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/"
xmlns:di="http://www.omg.org/spec/DMN/20180521/DI/"
xmlns:kie="https://kie.org/dmn/extens [...]
+ <decision name="Required Decision A"
id="_7A15C07E-4A30-450D-9796-A2454EC55279">
+ <variable name="Required Decision A"
id="_B156824F-3ABD-4387-9711-08B8A3A5241C" />
+ </decision>
+ <decision name="Decision in Need" id="_A26B267C-96D9-4E35-8564-DC655DC08E39">
+ <variable name="Decision in Need"
id="_AED5DD3A-EC35-457B-A13A-C8CC4614C46F" />
+ <informationRequirement id="_4BA30E29-004F-43E2-9402-77C107CA9339">
+ <requiredDecision
href="https://kie.org/dmn/_BCA8E83B-E3E6-432C-BFD6-44CD55A76A6C#_7A15C07E-4A30-450D-9796-A2454EC55279"
/>
+ </informationRequirement>
+ </decision>
+ <dmndi:DMNDI>
+ <dmndi:DMNDiagram id="_DBBAB955-3E4F-4731-BC13-AD0A71E064D3" name="Default
DRD" useAlternativeInputDataShape="false">
+ <di:extension>
+ <kie:ComponentsWidthsExtension>
+ <kie:ComponentWidths />
+ </kie:ComponentsWidthsExtension>
+ </di:extension>
+ <dmndi:DMNShape id="_68A19ACB-7EA6-4962-A61F-866D08E3D151"
dmnElementRef="_7A15C07E-4A30-450D-9796-A2454EC55279" isCollapsed="false"
isListedInputData="false">
+ <dc:Bounds x="120" y="80" width="160" height="80" />
+ </dmndi:DMNShape>
+ <dmndi:DMNShape id="_B06617DD-EDE9-4D54-8EF9-609545A69743"
dmnElementRef="_A26B267C-96D9-4E35-8564-DC655DC08E39" isCollapsed="false"
isListedInputData="false">
+ <dc:Bounds x="420" y="80" width="160" height="80" />
+ </dmndi:DMNShape>
+ <dmndi:DMNEdge id="_51E984B1-E548-4B7F-918A-802C995C7651"
dmnElementRef="_4BA30E29-004F-43E2-9402-77C107CA9339"
sourceElement="_68A19ACB-7EA6-4962-A61F-866D08E3D151"
targetElement="_B06617DD-EDE9-4D54-8EF9-609545A69743">
+ <di:waypoint x="200" y="120" />
+ <di:waypoint x="500" y="120" />
+ </dmndi:DMNEdge>
+ </dmndi:DMNDiagram>
+ </dmndi:DMNDI>
+</definitions>
+`;
+
+const meta: Meta<DmnEditorProps> = {
+ title: "Use cases/Namespace In Required Decision Href",
+ component: DmnEditor,
+ includeStories: /^[A-Z]/,
+};
+
+export default meta;
+type Story = StoryObj<StorybookDmnEditorProps>;
+
+const marshaller = getMarshaller(namespaceInRequiredDecisionHref, { upgradeTo:
"latest" });
+const model = marshaller.parser.parse();
+
+export const NamespaceInRequiredDecisionHref: Story = {
+ render: Empty.render,
+ args: {
+ model: model,
+ xml: marshaller.builder.build(model),
+ },
+};
diff --git a/packages/dmn-editor/tests-e2e/__fixtures__/editor.ts
b/packages/dmn-editor/tests-e2e/__fixtures__/editor.ts
index 07cfcb17d4c..bc10e1802ff 100644
--- a/packages/dmn-editor/tests-e2e/__fixtures__/editor.ts
+++ b/packages/dmn-editor/tests-e2e/__fixtures__/editor.ts
@@ -62,4 +62,10 @@ export class Editor {
public async changeTab(args: { tab: TabName }) {
await this.page.getByRole("tab", { name: args.tab }).click();
}
+
+ public async openDecisionWithNamespaceInHref() {
+ await this.page.goto(
+
`${this.baseURL}/iframe.html?args=&id=use-cases-namespace-in-required-decision-href--namespace-in-required-decision-href&viewMode=story`
+ );
+ }
}
diff --git
a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/namespace-in-required-decision-href.png
b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/namespace-in-required-decision-href.png
new file mode 100644
index 00000000000..c4be1d0be03
Binary files /dev/null and
b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/namespace-in-required-decision-href.png
differ
diff --git
a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/namespace-in-required-decision-href.png
b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/namespace-in-required-decision-href.png
new file mode 100644
index 00000000000..faf2e8bf101
Binary files /dev/null and
b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/namespace-in-required-decision-href.png
differ
diff --git
a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/namespace-in-required-decision-href.png
b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/namespace-in-required-decision-href.png
new file mode 100644
index 00000000000..3634c666067
Binary files /dev/null and
b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/namespace-in-required-decision-href.png
differ
diff --git a/packages/dmn-editor/tests-e2e/checkDmnWithNamespaceInHref.spec.ts
b/packages/dmn-editor/tests-e2e/checkDmnWithNamespaceInHref.spec.ts
new file mode 100644
index 00000000000..a7df638d5d2
--- /dev/null
+++ b/packages/dmn-editor/tests-e2e/checkDmnWithNamespaceInHref.spec.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 { expect, test } from "./__fixtures__/base";
+
+test.describe("Namespace in Href - Diagram", () => {
+ test.beforeEach(async ({ editor, nodes }) => {
+ await editor.openDecisionWithNamespaceInHref();
+ await expect(nodes.get({ name: "Required Decision A" })).toBeVisible();
+ await editor.setIsReadOnly(true);
+ });
+ test("should show edges if namespace present in hrefs", async ({ page }) => {
+ await expect(page.getByText("Required Decision A")).toBeAttached();
+ await expect(page.getByText("Decision in Need")).toBeAttached();
+ await
expect(page).toHaveScreenshot("namespace-in-required-decision-href.png");
+ });
+});
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]