You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kie.apache.org by ti...@apache.org on 2024/09/30 17:15:08 UTC
(incubator-kie-tools) branch main updated: kie-issues#1298: Decision Services & multiple DRDs: make it possible o add external Decisions to a Decision Service (#2508)
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 e53f6b56e9b kie-issues#1298: Decision Services & multiple DRDs: make it possible o add external Decisions to a Decision Service (#2508)
e53f6b56e9b is described below
commit e53f6b56e9b78aa942951c0ee231579ed526f118
Author: Daniel José dos Santos <da...@users.noreply.github.com>
AuthorDate: Mon Sep 30 14:15:03 2024 -0300
kie-issues#1298: Decision Services & multiple DRDs: make it possible o add external Decisions to a Decision Service (#2508)
Co-authored-by: Tiago Bento <15...@users.noreply.github.com>
Co-authored-by: Tiago Bento <ti...@gmail.com>
Co-authored-by: Luiz João Motta <lm...@ibm.com>
---
packages/dmn-editor-envelope/src/DmnEditorRoot.tsx | 230 ++++++++++++++++-----
packages/dmn-editor/src/DmnEditor.tsx | 4 +-
.../dmn-editor/src/autolayout/AutolayoutButton.tsx | 5 +-
.../src/boxedExpressions/BoxedExpressionScreen.tsx | 4 +-
packages/dmn-editor/src/diagram/Diagram.tsx | 42 ++--
.../dmn-editor/src/diagram/DiagramCommands.tsx | 49 +++--
packages/dmn-editor/src/diagram/DrgNodesPanel.tsx | 3 +-
packages/dmn-editor/src/diagram/Palette.tsx | 11 +-
packages/dmn-editor/src/diagram/nodes/Nodes.tsx | 2 +-
.../src/externalNodes/DmnObjectListItem.tsx | 136 +++++++++---
.../src/externalNodes/ExternalNodesPanel.tsx | 5 +-
.../src/includedModels/IncludedModels.tsx | 4 +-
.../dmn-editor/src/mutations/addConnectedNode.ts | 5 +-
.../src/mutations/addDecisionToDecisionService.ts | 49 ++++-
packages/dmn-editor/src/mutations/addEdge.ts | 9 +-
.../mutations/addExistingDecisionServiceToDrd.ts | 19 +-
.../dmn-editor/src/mutations/addStandaloneNode.ts | 5 +-
.../src/mutations/applyAutoLayoutToDrd.ts | 5 +-
.../mutations/deleteDecisionFromDecisionService.ts | 44 +++-
packages/dmn-editor/src/mutations/deleteEdge.ts | 5 +-
packages/dmn-editor/src/mutations/deleteImport.ts | 11 +-
packages/dmn-editor/src/mutations/deleteNode.ts | 103 +++++++--
...pulateInputDataAndDecisionsOnDecisionService.ts | 83 +++++++-
.../src/normalization/autoGenerateDrd.ts | 1 +
.../propertiesPanel/DecisionServiceProperties.tsx | 37 +++-
.../src/propertiesPanel/UnknownProperties.tsx | 2 +-
packages/dmn-editor/src/store/Store.ts | 21 +-
...ainingDecisionServiceHrefsByDecisionHrefs.ts.ts | 59 +++---
.../src/store/computed/computeDataTypes.ts | 2 +-
.../src/store/computed/computeDiagramData.ts | 46 +++--
...teDirectlyIncludedExternalModelsByNamespace.ts} | 2 +-
packages/dmn-editor/src/store/computed/initial.ts | 2 +-
.../dmn-editor/stories/dev/DevWebApp.stories.tsx | 7 +-
.../EmptyWithAvailableExternalModels.mdx | 10 +
.../EmptyWithAvailableExternalModels.stories.tsx | 134 ++++++++++++
.../availableModelsToInclude.ts | 76 +++++++
.../externalModels.ts | 217 +++++++++++++++++++
packages/dmn-editor/tests-e2e/__fixtures__/base.ts | 10 +
.../tests-e2e/__fixtures__/includedModels.ts | 54 +++++
.../dmn-editor/tests-e2e/__fixtures__/nodes.ts | 16 +-
.../dmn-editor/tests-e2e/__fixtures__/palette.ts | 15 ++
.../dmn-editor/tests-e2e/__fixtures__/stories.ts | 38 ++++
.../add-external-node-inside-decision-service.png | Bin 0 -> 45507 bytes
...l-node-inside-decision-service-after-delete.png | Bin 0 -> 45816 bytes
...-node-inside-decision-service-before-delete.png | Bin 0 -> 43833 bytes
...l-decision-from-ds-to-another-ds-after-move.png | Bin 0 -> 48290 bytes
...-decision-from-ds-to-another-ds-before-move.png | Bin 0 -> 50580 bytes
...n-inside-decision-service-after-move-to-out.png | Bin 0 -> 48391 bytes
...-inside-decision-service-before-move-to-out.png | Bin 0 -> 45828 bytes
...al-decision-out-from-decision-service-after.png | Bin 0 -> 34971 bytes
...l-decision-out-from-decision-service-before.png | Bin 0 -> 45828 bytes
...node-inside-decision-service-after-resizing.png | Bin 0 -> 48367 bytes
...ode-inside-decision-service-before-resizing.png | Bin 0 -> 43833 bytes
.../add-external-node-inside-decision-service.png | Bin 0 -> 45507 bytes
...l-node-inside-decision-service-after-delete.png | Bin 0 -> 45816 bytes
...-node-inside-decision-service-before-delete.png | Bin 0 -> 43833 bytes
...l-decision-from-ds-to-another-ds-after-move.png | Bin 0 -> 48290 bytes
...-decision-from-ds-to-another-ds-before-move.png | Bin 0 -> 50580 bytes
...n-inside-decision-service-after-move-to-out.png | Bin 0 -> 48391 bytes
...-inside-decision-service-before-move-to-out.png | Bin 0 -> 45828 bytes
...al-decision-out-from-decision-service-after.png | Bin 0 -> 34971 bytes
...l-decision-out-from-decision-service-before.png | Bin 0 -> 45828 bytes
...node-inside-decision-service-after-resizing.png | Bin 0 -> 48367 bytes
...ode-inside-decision-service-before-resizing.png | Bin 0 -> 43833 bytes
.../add-external-node-inside-decision-service.png | Bin 0 -> 49315 bytes
...l-node-inside-decision-service-after-delete.png | Bin 0 -> 46737 bytes
...-node-inside-decision-service-before-delete.png | Bin 0 -> 46307 bytes
...l-decision-from-ds-to-another-ds-after-move.png | Bin 0 -> 53698 bytes
...-decision-from-ds-to-another-ds-before-move.png | Bin 0 -> 55964 bytes
...n-inside-decision-service-after-move-to-out.png | Bin 0 -> 49226 bytes
...-inside-decision-service-before-move-to-out.png | Bin 0 -> 46921 bytes
...al-decision-out-from-decision-service-after.png | Bin 0 -> 53315 bytes
...l-decision-out-from-decision-service-before.png | Bin 0 -> 46921 bytes
...node-inside-decision-service-after-resizing.png | Bin 0 -> 49381 bytes
...ode-inside-decision-service-before-resizing.png | Bin 0 -> 46307 bytes
.../tests-e2e/drgElements/addExternalNode.spec.ts | 76 +++++++
.../drgElements/deleteExternalNode.spec.ts | 80 +++++++
.../tests-e2e/drgElements/moveExternalNode.spec.ts | 227 ++++++++++++++++++++
.../drgElements/resizeExternalNode.spec.ts | 87 ++++++++
79 files changed, 1789 insertions(+), 263 deletions(-)
diff --git a/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx b/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx
index 608d6d77d42..d1c5bc75c47 100644
--- a/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx
+++ b/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx
@@ -19,7 +19,7 @@
import * as __path from "path";
import * as React from "react";
-import { useEffect, useMemo, useState } from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
import * as DmnEditor from "@kie-tools/dmn-editor/dist/DmnEditor";
import { normalize, Normalized } from "@kie-tools/dmn-editor/dist/normalization/normalize";
import { DMN_LATEST_VERSION, DmnLatestModel, DmnMarshaller, getMarshaller } from "@kie-tools/dmn-marshaller";
@@ -72,12 +72,12 @@ export type DmnEditorRootState = {
marshaller: DmnMarshaller<typeof DMN_LATEST_VERSION> | undefined;
stack: Normalized<DmnLatestModel>[];
pointer: number;
- openFilenormalizedPosixPathRelativeToTheWorkspaceRoot: string | undefined;
+ openFileNormalizedPosixPathRelativeToTheWorkspaceRoot: string | undefined;
externalModelsByNamespace: DmnEditor.ExternalModelsIndex;
isReadOnly: boolean;
externalModelsManagerDoneBootstraping: boolean;
keyboardShortcutsRegisterIds: number[];
- keyboardShortcutsRegistred: boolean;
+ keyboardShortcutsRegistered: boolean;
error: Error | undefined;
};
@@ -95,11 +95,11 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
marshaller: undefined,
stack: [],
pointer: -1,
- openFilenormalizedPosixPathRelativeToTheWorkspaceRoot: undefined,
+ openFileNormalizedPosixPathRelativeToTheWorkspaceRoot: undefined,
isReadOnly: props.isReadOnly,
externalModelsManagerDoneBootstraping: false,
keyboardShortcutsRegisterIds: [],
- keyboardShortcutsRegistred: false,
+ keyboardShortcutsRegistered: false,
error: undefined,
};
}
@@ -129,7 +129,7 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
}
public async setContent(
- openFilenormalizedPosixPathRelativeToTheWorkspaceRoot: string,
+ openFileNormalizedPosixPathRelativeToTheWorkspaceRoot: string,
content: string
): Promise<void> {
const marshaller = this.getMarshaller(content);
@@ -142,7 +142,7 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
savedStackPointer = [...prev.stack];
return {
stack: [normalize(marshaller.parser.parse())],
- openFilenormalizedPosixPathRelativeToTheWorkspaceRoot,
+ openFileNormalizedPosixPathRelativeToTheWorkspaceRoot,
pointer: 0,
};
});
@@ -150,17 +150,17 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
// Wait the external manager models to load.
await this.externalModelsManagerDoneBootstraping.promise;
- // Set the valeus to render the DMN Editor.
+ // Set the values to render the DMN Editor.
this.setState((prev) => {
// External change to the same file.
if (
- prev.openFilenormalizedPosixPathRelativeToTheWorkspaceRoot ===
- openFilenormalizedPosixPathRelativeToTheWorkspaceRoot
+ prev.openFileNormalizedPosixPathRelativeToTheWorkspaceRoot ===
+ openFileNormalizedPosixPathRelativeToTheWorkspaceRoot
) {
const newStack = savedStackPointer.slice(0, prev.pointer + 1);
return {
marshaller,
- openFilenormalizedPosixPathRelativeToTheWorkspaceRoot,
+ openFileNormalizedPosixPathRelativeToTheWorkspaceRoot,
stack: [...newStack, normalize(marshaller.parser.parse())],
isReadOnly: prev.isReadOnly,
pointer: newStack.length,
@@ -172,7 +172,7 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
else {
return {
marshaller,
- openFilenormalizedPosixPathRelativeToTheWorkspaceRoot,
+ openFileNormalizedPosixPathRelativeToTheWorkspaceRoot,
stack: [normalize(marshaller.parser.parse())],
isReadOnly: prev.isReadOnly,
pointer: 0,
@@ -216,13 +216,13 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
},
() =>
this.props.onNewEdit({
- id: `${this.state.openFilenormalizedPosixPathRelativeToTheWorkspaceRoot}__${generateUuid()}`,
+ id: `${this.state.openFileNormalizedPosixPathRelativeToTheWorkspaceRoot}__${generateUuid()}`,
})
);
};
private onRequestExternalModelsAvailableToInclude: DmnEditor.OnRequestExternalModelsAvailableToInclude = async () => {
- if (!this.state.openFilenormalizedPosixPathRelativeToTheWorkspaceRoot) {
+ if (!this.state.openFileNormalizedPosixPathRelativeToTheWorkspaceRoot) {
return [];
}
@@ -233,9 +233,9 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
return list.normalizedPosixPathsRelativeToTheWorkspaceRoot.flatMap((p) =>
// Do not show this DMN on the list
- p === this.state.openFilenormalizedPosixPathRelativeToTheWorkspaceRoot
+ p === this.state.openFileNormalizedPosixPathRelativeToTheWorkspaceRoot
? []
- : __path.relative(__path.dirname(this.state.openFilenormalizedPosixPathRelativeToTheWorkspaceRoot!), p)
+ : __path.relative(__path.dirname(this.state.openFileNormalizedPosixPathRelativeToTheWorkspaceRoot!), p)
);
};
@@ -244,7 +244,7 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
) => {
const normalizedPosixPathRelativeToTheWorkspaceRoot = __path
.resolve(
- __path.dirname(this.state.openFilenormalizedPosixPathRelativeToTheWorkspaceRoot!),
+ __path.dirname(this.state.openFileNormalizedPosixPathRelativeToTheWorkspaceRoot!),
normalizedPosixPathRelativeToTheOpenFile
)
.substring(1); // Remove leading slash.
@@ -290,7 +290,7 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
};
private onOpenFileFromPathRelativeToTheOpenFile = (normalizedPosixPathRelativeToTheOpenFile: string) => {
- if (!this.state.openFilenormalizedPosixPathRelativeToTheWorkspaceRoot) {
+ if (!this.state.openFileNormalizedPosixPathRelativeToTheWorkspaceRoot) {
return;
}
@@ -304,7 +304,7 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
prevState: Readonly<DmnEditorRootState>,
snapshot?: any
): void {
- if (this.props.keyboardShortcutsService === undefined || this.state.keyboardShortcutsRegistred === true) {
+ if (this.props.keyboardShortcutsService === undefined || this.state.keyboardShortcutsRegistered === true) {
return;
}
@@ -428,7 +428,7 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
this.setState((prev) => ({
...prev,
- keyboardShortcutsRegistred: true,
+ keyboardShortcutsRegistered: true,
keyboardShortcutsRegisterIds: [
bigMoveDown,
bigMoveLeft,
@@ -497,7 +497,7 @@ export class DmnEditorRoot extends React.Component<DmnEditorRootProps, DmnEditor
<ExternalModelsManager
workspaceRootAbsolutePosixPath={this.props.workspaceRootAbsolutePosixPath}
thisDmnsNormalizedPosixPathRelativeToTheWorkspaceRoot={
- this.state.openFilenormalizedPosixPathRelativeToTheWorkspaceRoot
+ this.state.openFileNormalizedPosixPathRelativeToTheWorkspaceRoot
}
model={this.model}
onChange={this.setExternalModelsByNamespace}
@@ -532,10 +532,7 @@ function ExternalModelsManager({
externalModelsManagerDoneBootstraping: PromiseImperativeHandle<void>;
}) {
const namespaces = useMemo(
- () =>
- (model.definitions.import ?? [])
- .map((i) => getNamespaceOfDmnImport({ dmnImport: i }))
- .join(NAMESPACES_EFFECT_SEPARATOR),
+ () => getIncludedNamespacesFromModel(model.definitions.import),
[model.definitions.import]
);
@@ -563,6 +560,38 @@ function ExternalModelsManager({
};
}, [thisDmnsNormalizedPosixPathRelativeToTheWorkspaceRoot]);
+ const getDmnsByNamespace = useCallback((resources: (ResourceContent | undefined)[]) => {
+ const ret = new Map<string, ResourceContent>();
+ for (let i = 0; i < resources.length; i++) {
+ const resource = resources[i];
+ if (!resource) {
+ continue;
+ }
+
+ const content = resource.content ?? "";
+ const ext = __path.extname(resource.normalizedPosixPathRelativeToTheWorkspaceRoot);
+ if (ext === ".dmn") {
+ const namespace = domParser.getDomDocument(content).documentElement.getAttribute("namespace");
+ if (namespace) {
+ // Check for multiplicity of namespaces on DMN models
+ if (ret.has(namespace)) {
+ console.warn(
+ `DMN EDITOR ROOT: Multiple DMN models encountered with the same namespace '${namespace}': '${
+ resource.normalizedPosixPathRelativeToTheWorkspaceRoot
+ }' and '${
+ ret.get(namespace)!.normalizedPosixPathRelativeToTheWorkspaceRoot
+ }'. The latter will be considered.`
+ );
+ }
+
+ ret.set(namespace, resource);
+ }
+ }
+ }
+
+ return ret;
+ }, []);
+
// This effect actually populates `externalModelsByNamespace` through the `onChange` call.
useEffect(() => {
let canceled = false;
@@ -594,6 +623,8 @@ function ExternalModelsManager({
const externalModelsIndex: DmnEditor.ExternalModelsIndex = {};
const namespacesSet = new Set(namespaces.split(NAMESPACES_EFFECT_SEPARATOR));
+ const loadedDmnsByPathRelativeToTheWorkspaceRoot = new Set<string>();
+ const dmnsByNamespace = getDmnsByNamespace(resources);
for (let i = 0; i < resources.length; i++) {
const resource = resources[i];
@@ -601,46 +632,54 @@ function ExternalModelsManager({
continue;
}
- const content = resource.content ?? "";
-
+ const ext = __path.extname(resource.normalizedPosixPathRelativeToTheWorkspaceRoot);
const normalizedPosixPathRelativeToTheOpenFile = __path.relative(
__path.dirname(thisDmnsNormalizedPosixPathRelativeToTheWorkspaceRoot),
resource.normalizedPosixPathRelativeToTheWorkspaceRoot
);
- const ext = __path.extname(resource.normalizedPosixPathRelativeToTheWorkspaceRoot);
- if (ext === ".dmn") {
- const namespace = domParser.getDomDocument(content).documentElement.getAttribute("namespace");
- if (namespace && namespacesSet.has(namespace)) {
- // Check for multiplicity of namespaces on DMN models
- if (externalModelsIndex[namespace]) {
- console.warn(
- `DMN EDITOR ROOT: Multiple DMN models encountered with the same namespace '${namespace}': '${
- resource.normalizedPosixPathRelativeToTheWorkspaceRoot
- }' and '${
- externalModelsIndex[namespace]!.normalizedPosixPathRelativeToTheOpenFile
- }'. The latter will be considered.`
- );
- }
+ const resourceContent = resource.content ?? "";
- externalModelsIndex[namespace] = {
- normalizedPosixPathRelativeToTheOpenFile,
- model: normalize(getMarshaller(content, { upgradeTo: "latest" }).parser.parse()),
- type: "dmn",
- svg: "",
- };
+ // DMN Files
+ if (ext === ".dmn") {
+ const namespaceOfTheResourceFile = domParser
+ .getDomDocument(resourceContent)
+ .documentElement.getAttribute("namespace");
+
+ if (namespaceOfTheResourceFile && namespacesSet.has(namespaceOfTheResourceFile)) {
+ checkIfNamespaceIsAlreadyLoaded({
+ externalModelsIndex,
+ namespaceOfTheResourceFile,
+ normalizedPosixPathRelativeToTheWorkspaceRoot: resource.normalizedPosixPathRelativeToTheWorkspaceRoot,
+ });
+
+ loadModel({
+ includedModelContent: resourceContent,
+ includedModelNamespace: namespaceOfTheResourceFile,
+ externalModelsIndex,
+ thisDmnsNormalizedPosixPathRelativeToTheWorkspaceRoot,
+ loadedDmnsByPathRelativeToTheWorkspaceRoot,
+ normalizedPosixPathRelativeToTheWorkspaceRoot: resource.normalizedPosixPathRelativeToTheWorkspaceRoot,
+ resourcesByNamespace: dmnsByNamespace,
+ });
}
- } else if (ext === ".pmml") {
+ }
+
+ // PMML Files
+ else if (ext === ".pmml") {
const namespace = getPmmlNamespace({ normalizedPosixPathRelativeToTheOpenFile });
if (namespace && namespacesSet.has(namespace)) {
// No need to check for namespaces being equal becuase there can't be two files with the same relativePath.
externalModelsIndex[namespace] = {
normalizedPosixPathRelativeToTheOpenFile,
- model: XML2PMML(content),
+ model: XML2PMML(resourceContent),
type: "pmml",
};
}
- } else {
+ }
+
+ // Unknown files
+ else {
throw new Error(`Unknown extension '${ext}'.`);
}
}
@@ -663,6 +702,7 @@ function ExternalModelsManager({
externalUpdatesCount,
workspaceRootAbsolutePosixPath,
externalModelsManagerDoneBootstraping,
+ getDmnsByNamespace,
]);
return <></>;
@@ -682,3 +722,93 @@ function DmnMarshallerFallbackError({ error }: { error: Error }) {
</Flex>
);
}
+
+function getIncludedNamespacesFromModel(imports: Normalized<DmnLatestModel["definitions"]["import"]>) {
+ return (imports ?? []).map((i) => getNamespaceOfDmnImport({ dmnImport: i })).join(NAMESPACES_EFFECT_SEPARATOR);
+}
+
+function loadModel(args: {
+ thisDmnsNormalizedPosixPathRelativeToTheWorkspaceRoot: string;
+ resourcesByNamespace: Map<string, ResourceContent>;
+ normalizedPosixPathRelativeToTheWorkspaceRoot: string;
+ includedModelNamespace: string;
+ loadedDmnsByPathRelativeToTheWorkspaceRoot: Set<string>;
+ includedModelContent: string;
+ externalModelsIndex: DmnEditor.ExternalModelsIndex;
+}) {
+ const normalizedPosixPathRelativeToTheOpenFile = __path.relative(
+ __path.dirname(args.thisDmnsNormalizedPosixPathRelativeToTheWorkspaceRoot),
+ args.normalizedPosixPathRelativeToTheWorkspaceRoot
+ );
+
+ const includedModel = normalize(getMarshaller(args.includedModelContent, { upgradeTo: "latest" }).parser.parse());
+ args.externalModelsIndex[args.includedModelNamespace] = {
+ normalizedPosixPathRelativeToTheOpenFile,
+ model: includedModel,
+ type: "dmn",
+ svg: "",
+ };
+
+ args.loadedDmnsByPathRelativeToTheWorkspaceRoot.add(args.normalizedPosixPathRelativeToTheWorkspaceRoot);
+
+ loadDependentModels({
+ ...args,
+ model: includedModel,
+ });
+}
+
+// Load all included models from the model and the included models of those models, recursively.
+function loadDependentModels(args: {
+ model: Normalized<DmnLatestModel>;
+ externalModelsIndex: DmnEditor.ExternalModelsIndex;
+ resourcesByNamespace: Map<string, ResourceContent>;
+ loadedDmnsByPathRelativeToTheWorkspaceRoot: Set<string>;
+ thisDmnsNormalizedPosixPathRelativeToTheWorkspaceRoot: string;
+}) {
+ const includedNamespaces = new Set(
+ getIncludedNamespacesFromModel(args.model.definitions.import).split(NAMESPACES_EFFECT_SEPARATOR)
+ );
+
+ for (const includedNamespace of includedNamespaces) {
+ if (!args.resourcesByNamespace.has(includedNamespace)) {
+ console.warn(
+ `DMN EDITOR ROOT: The included namespace '${includedNamespace}' for the model '${args.model.definitions["@_id"]}' can not be found.`
+ );
+ continue;
+ }
+
+ const resource = args.resourcesByNamespace.get(includedNamespace)!;
+ if (args.loadedDmnsByPathRelativeToTheWorkspaceRoot.has(resource.normalizedPosixPathRelativeToTheWorkspaceRoot)) {
+ continue;
+ }
+
+ checkIfNamespaceIsAlreadyLoaded({
+ externalModelsIndex: args.externalModelsIndex,
+ namespaceOfTheResourceFile: includedNamespace,
+ normalizedPosixPathRelativeToTheWorkspaceRoot: resource.normalizedPosixPathRelativeToTheWorkspaceRoot,
+ });
+
+ loadModel({
+ ...args,
+ includedModelContent: resource.content ?? "",
+ normalizedPosixPathRelativeToTheWorkspaceRoot: resource.normalizedPosixPathRelativeToTheWorkspaceRoot,
+ includedModelNamespace: includedNamespace,
+ });
+ }
+}
+
+function checkIfNamespaceIsAlreadyLoaded(args: {
+ externalModelsIndex: DmnEditor.ExternalModelsIndex;
+ namespaceOfTheResourceFile: string;
+ normalizedPosixPathRelativeToTheWorkspaceRoot: string;
+}) {
+ if (args.externalModelsIndex[args.namespaceOfTheResourceFile]) {
+ console.warn(
+ `DMN EDITOR ROOT: Multiple DMN models encountered with the same namespace '${args.namespaceOfTheResourceFile}': '${
+ args.normalizedPosixPathRelativeToTheWorkspaceRoot
+ }' and '${
+ args.externalModelsIndex[args.namespaceOfTheResourceFile]!.normalizedPosixPathRelativeToTheOpenFile
+ }'. The latter will be considered.`
+ );
+ }
+}
diff --git a/packages/dmn-editor/src/DmnEditor.tsx b/packages/dmn-editor/src/DmnEditor.tsx
index 7739a0cbf7c..766c08146cb 100644
--- a/packages/dmn-editor/src/DmnEditor.tsx
+++ b/packages/dmn-editor/src/DmnEditor.tsx
@@ -399,7 +399,9 @@ export const DmnEditorInternal = ({
</Tab>
<Tab eventKey={DmnEditorTab.INCLUDED_MODELS} title={tabTitle.includedModels}>
- {navigationTab === DmnEditorTab.INCLUDED_MODELS && <IncludedModels />}
+ <div data-testid={"kie-tools--dmn-editor--included-models-container"}>
+ {navigationTab === DmnEditorTab.INCLUDED_MODELS && <IncludedModels />}
+ </div>
</Tab>
</Tabs>
</div>
diff --git a/packages/dmn-editor/src/autolayout/AutolayoutButton.tsx b/packages/dmn-editor/src/autolayout/AutolayoutButton.tsx
index c1a09f169eb..0b1be461482 100644
--- a/packages/dmn-editor/src/autolayout/AutolayoutButton.tsx
+++ b/packages/dmn-editor/src/autolayout/AutolayoutButton.tsx
@@ -57,7 +57,10 @@ export function AutolayoutButton() {
__readonly_parentNodesById,
__readonly_drdIndex: s.computed(s).getDrdIndex(),
__readonly_dmnObjectNamespace: s.dmn.model.definitions["@_namespace"],
- __readonly_externalDmnsIndex: s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
+ __readonly_externalDmnsIndex: s
+ .computed(s)
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns,
+ __readonly_externalModelsByNamespace: externalModelsByNamespace,
});
});
}, [dmnEditorStoreApi, externalModelsByNamespace]);
diff --git a/packages/dmn-editor/src/boxedExpressions/BoxedExpressionScreen.tsx b/packages/dmn-editor/src/boxedExpressions/BoxedExpressionScreen.tsx
index 3a0c4e08fa8..5e4fb2ab5e9 100644
--- a/packages/dmn-editor/src/boxedExpressions/BoxedExpressionScreen.tsx
+++ b/packages/dmn-editor/src/boxedExpressions/BoxedExpressionScreen.tsx
@@ -94,12 +94,12 @@ export function BoxedExpressionScreen({ container }: { container: React.RefObjec
const isPropertiesPanelOpen = useDmnEditorStore((s) => s.boxedExpressionEditor.propertiesPanel.isOpen);
const externalDmnsByNamespace = useDmnEditorStore(
- (s) => s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace).dmns
+ (s) => s.computed(s).getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns
);
const dataTypesTree = useDmnEditorStore((s) => s.computed(s).getDataTypes(externalModelsByNamespace).dataTypesTree);
const importsByNamespace = useDmnEditorStore((s) => s.computed(s).importsByNamespace());
const externalPmmlsByNamespace = useDmnEditorStore(
- (s) => s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace).pmmls
+ (s) => s.computed(s).getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).pmmls
);
const isAlternativeInputDataShape = useDmnEditorStore((s) => s.computed(s).isAlternativeInputDataShape());
const drdIndex = useDmnEditorStore((s) => s.computed(s).getDrdIndex());
diff --git a/packages/dmn-editor/src/diagram/Diagram.tsx b/packages/dmn-editor/src/diagram/Diagram.tsx
index 123aba83e25..876c22ba4bb 100644
--- a/packages/dmn-editor/src/diagram/Diagram.tsx
+++ b/packages/dmn-editor/src/diagram/Diagram.tsx
@@ -246,6 +246,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
shapeId: targetNode.data.shape["@_id"],
},
keepWaypoints: false,
+ externalModelsByNamespace,
});
});
},
@@ -337,6 +338,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
})["@_height"],
},
},
+ externalModelsByNamespace,
});
state.diagram._selectedNodes = [newNodeId];
state.focus.consumableId = newNodeId;
@@ -352,7 +354,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
const state = dmnEditorStoreApi.getState();
const externalDmnsIndex = state
.computed(state)
- .getExternalModelTypesByNamespace(externalModelsByNamespace).dmns;
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns;
const externalNodeDmn = externalDmnsIndex.get(externalNode.externalDrgElementNamespace);
const externalDrgElement = (externalNodeDmn?.model.definitions.drgElement ?? []).find(
@@ -429,7 +431,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
__readonly_drdIndex: drdIndex,
__readonly_externalDmnsIndex: dereferencedState
.computed(dereferencedState)
- .getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns,
__readonly_indexedDrd: dereferencedState.computed(dereferencedState).indexedDrd(),
__readonly_indexedDrdContainingDecisionServiceDepiction: indexedDrdContainingDecisionServiceDepiction!,
__readonly_containedDecisionHrefsRelativeToThisDmn: containedDecisionHrefsRelativeToThisDmn,
@@ -481,7 +483,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
const drdIndex = dereferencedState.computed(dereferencedState).getDrdIndex();
const externalDmnsIndex = dereferencedState
.computed(dereferencedState)
- .getExternalModelTypesByNamespace(externalModelsByNamespace).dmns;
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns;
const {
strategyForAddingDecisionServiceToDrd,
@@ -528,7 +530,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
__readonly_drdIndex: drdIndex,
__readonly_externalDmnsIndex: dereferencedState
.computed(dereferencedState)
- .getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns,
__readonly_indexedDrd: dereferencedState.computed(dereferencedState).indexedDrd(),
__readonly_indexedDrdContainingDecisionServiceDepiction: indexedDrdContainingDecisionServiceDepiction!,
__readonly_containedDecisionHrefsRelativeToThisDmn: containedDecisionHrefsRelativeToThisDmn,
@@ -670,6 +672,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
})["@_height"],
},
},
+ externalModelsByNamespace,
});
state.diagram._selectedNodes = [newDmnObejctHref];
@@ -754,7 +757,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
__readonly_dmnObjectNamespace: node.data.dmnObjectNamespace,
__readonly_externalDmnsIndex: state
.computed(state)
- .getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns,
change: {
isExternal: !!node.data.dmnObjectQName.prefix,
nodeType: node.type as NodeType,
@@ -872,10 +875,11 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
__readonly_dmnObjectQName: node.data.dmnObjectQName,
__readonly_dmnObjectId: node.data.dmnObject?.["@_id"],
__readonly_nodeNature: nodeNatures[node.type as NodeType],
- mode: NodeDeletionMode.FROM_DRG_AND_ALL_DRDS,
- __readonly_externalModelTypesByNamespace: state
+ __readonly_mode: NodeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+ __readonly_externalDmnsIndex: state
.computed(state)
- .getExternalModelTypesByNamespace(externalModelsByNamespace),
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns,
+ __readonly_externalModelsByNamespace: externalModelsByNamespace,
});
state.dispatch(state).diagram.setNodeStatus(node.id, {
selected: false,
@@ -897,7 +901,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
}
});
},
- [reactFlowInstance, dmnEditorStoreApi, externalModelsByNamespace]
+ [dmnEditorStoreApi, externalModelsByNamespace]
);
const resetToBeforeEditingBegan = useCallback(() => {
@@ -990,8 +994,9 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
for (let i = 0; i < selectedNodes.length; i++) {
deleteDecisionFromDecisionService({
definitions: state.dmn.model.definitions,
- decisionId: selectedNodes[i].data.dmnObject!["@_id"]!, // We can assume that all selected nodes are Decisions because the contaiment was validated above.
+ decisionHref: selectedNodes[i].id, // We can assume that all selected nodes are Decisions because the contaiment was validated above.
decisionServiceId: p.data.dmnObject!["@_id"]!,
+ externalModelsByNamespace,
});
}
} else {
@@ -1007,12 +1012,13 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
addDecisionToDecisionService({
definitions: state.dmn.model.definitions,
drdIndex: state.computed(state).getDrdIndex(),
- decisionId: selectedNodes[i].data.dmnObject!["@_id"]!, // We can assume that all selected nodes are Decisions because the contaiment was validated above.
+ decisionHref: selectedNodes[i].id, // We can assume that all selected nodes are Decisions because the contaiment was validated above.
decisionServiceId: state
.computed(state)
.getDiagramData(externalModelsByNamespace)
.nodesById.get(dropTargetNode.id)!.data.dmnObject!["@_id"]!,
snapGrid: state.diagram.snapGrid,
+ externalModelsByNamespace,
});
}
} else {
@@ -1026,7 +1032,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
resetToBeforeEditingBegan();
}
},
- [dmnEditorStoreApi, externalModelsByNamespace, resetToBeforeEditingBegan]
+ [dmnEditorStoreApi, externalModelsByNamespace, resetToBeforeEditingBegan, thisDmn.model.definitions]
);
const onEdgesChange = useCallback<RF.OnEdgesChange>(
@@ -1047,6 +1053,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
drdIndex: state.computed(state).getDrdIndex(),
edge: { id: change.id, dmnObject: edge.data.dmnObject },
mode: EdgeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+ externalModelsByNamespace,
});
state.dispatch(state).diagram.setEdgeStatus(change.id, {
selected: false,
@@ -1125,6 +1132,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
shapeId: targetNode.data.shape["@_id"],
},
keepWaypoints: true,
+ externalModelsByNamespace,
});
// The DMN Edge changed nodes, so we need to delete the old one, but keep the waypoints on the same DRD.
@@ -1134,6 +1142,7 @@ export const Diagram = React.forwardRef<DiagramRef, { container: React.RefObject
drdIndex: state.computed(state).getDrdIndex(),
edge: { id: oldEdge.id, dmnObject: oldEdge.data!.dmnObject },
mode: EdgeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+ externalModelsByNamespace,
});
const deletedWaypoints = deletedDmnEdgeOnCurrentDrd?.["di:waypoint"];
@@ -1380,7 +1389,7 @@ function DmnDiagramWithoutDrd() {
const externalModelTypesByNamespace = dereferencedState
.computed(dereferencedState)
- .getExternalModelTypesByNamespace(externalModelsByNamespace);
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace);
autoGenerateDrd({
model: dereferencedState.dmn.model,
@@ -1434,7 +1443,8 @@ function DmnDiagramWithoutDrd() {
__readonly_dmnObjectNamespace: dereferencedState.dmn.model.definitions["@_namespace"],
__readonly_externalDmnsIndex: dereferencedState
.computed(dereferencedState)
- .getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns,
+ __readonly_externalModelsByNamespace: externalModelsByNamespace,
});
s.dmn.model = dereferencedState.dmn.model;
});
@@ -1463,6 +1473,7 @@ function DmnDiagramEmptyState({
isReadOnly?: boolean;
}) {
const dmnEditorStoreApi = useDmnEditorStoreApi();
+ const { externalModelsByNamespace } = useExternalModels();
return (
<Bullseye
@@ -1524,6 +1535,7 @@ function DmnDiagramEmptyState({
})["@_height"],
},
},
+ externalModelsByNamespace,
});
const drgElementIndex = (state.dmn.model.definitions.drgElement ?? []).length - 1;
@@ -1584,6 +1596,7 @@ function DmnDiagramEmptyState({
type: NODE_TYPES.inputData,
bounds: inputDataNodeBounds,
},
+ externalModelsByNamespace,
});
const { href: decisionNodeHref } = addConnectedNode({
@@ -1609,6 +1622,7 @@ function DmnDiagramEmptyState({
})["@_height"],
},
},
+ externalModelsByNamespace,
});
state.diagram._selectedNodes = [decisionNodeHref];
diff --git a/packages/dmn-editor/src/diagram/DiagramCommands.tsx b/packages/dmn-editor/src/diagram/DiagramCommands.tsx
index af73112a7e5..e7354f80d9f 100644
--- a/packages/dmn-editor/src/diagram/DiagramCommands.tsx
+++ b/packages/dmn-editor/src/diagram/DiagramCommands.tsx
@@ -134,6 +134,7 @@ export function DiagramCommands(props: {}) {
drdIndex: state.computed(state).getDrdIndex(),
edge: { id: edge.id, dmnObject: edge.data!.dmnObject },
mode: EdgeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+ externalModelsByNamespace,
});
state.dispatch(state).diagram.setEdgeStatus(edge.id, {
selected: false,
@@ -156,10 +157,11 @@ export function DiagramCommands(props: {}) {
__readonly_dmnObjectQName: node.data.dmnObjectQName,
__readonly_dmnObjectId: node.data.dmnObject?.["@_id"],
__readonly_nodeNature: nodeNatures[node.type as NodeType],
- mode: NodeDeletionMode.FROM_DRG_AND_ALL_DRDS,
- __readonly_externalModelTypesByNamespace: state
+ __readonly_mode: NodeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+ __readonly_externalDmnsIndex: state
.computed(state)
- .getExternalModelTypesByNamespace(externalModelsByNamespace),
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns,
+ __readonly_externalModelsByNamespace: externalModelsByNamespace,
});
state.dispatch(state).diagram.setNodeStatus(node.id, {
selected: false,
@@ -239,12 +241,25 @@ export function DiagramCommands(props: {}) {
definitions: state.dmn.model.definitions,
drdIndex: state.computed(state).getDrdIndex(),
});
- diagramElements.push(...clipboard.shapes.map((s) => ({ ...s, __$$element: "dmndi:DMNShape" as const })));
- diagramElements.push(...clipboard.edges.map((s) => ({ ...s, __$$element: "dmndi:DMNEdge" as const })));
+ diagramElements.push(
+ ...clipboard.shapes.map((s) => ({
+ ...s,
+ __$$element: "dmndi:DMNShape" as const,
+ }))
+ );
+ diagramElements.push(
+ ...clipboard.edges.map((s) => ({
+ ...s,
+ __$$element: "dmndi:DMNEdge" as const,
+ }))
+ );
widths.push(...clipboard.widths);
- repopulateInputDataAndDecisionsOnAllDecisionServices({ definitions: state.dmn.model.definitions });
+ repopulateInputDataAndDecisionsOnAllDecisionServices({
+ definitions: state.dmn.model.definitions,
+ externalModelsByNamespace,
+ });
state.diagram._selectedNodes = [...clipboard.drgElements, ...clipboard.artifacts].map((s) =>
buildXmlHref({ id: s["@_id"]! })
@@ -256,7 +271,7 @@ export function DiagramCommands(props: {}) {
});
});
};
- }, [dmnEditorStoreApi, commandsRef]);
+ }, [dmnEditorStoreApi, commandsRef, externalModelsByNamespace]);
// Select/deselect all nodes
useEffect(() => {
@@ -317,12 +332,13 @@ export function DiagramCommands(props: {}) {
padding: CONTAINER_NODES_DESIRABLE_PADDING,
}),
},
+ externalModelsByNamespace,
});
state.dispatch(state).diagram.setNodeStatus(newNodeId, { selected: true });
});
};
- }, [dmnEditorStoreApi, commandsRef, rf]);
+ }, [dmnEditorStoreApi, commandsRef, rf, externalModelsByNamespace]);
// Toggle hierarchy highlights
useEffect(() => {
@@ -369,7 +385,7 @@ export function DiagramCommands(props: {}) {
canRemoveNodeFromDrdOnly({
__readonly_externalDmnsIndex: state
.computed(state)
- .getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns,
definitions: state.dmn.model.definitions,
__readonly_drdIndex: state.computed(state).getDrdIndex(),
__readonly_dmnObjectNamespace:
@@ -380,7 +396,7 @@ export function DiagramCommands(props: {}) {
canRemoveNodeFromDrdOnly({
__readonly_externalDmnsIndex: state
.computed(state)
- .getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns,
definitions: state.dmn.model.definitions,
__readonly_drdIndex: state.computed(state).getDrdIndex(),
__readonly_dmnObjectNamespace:
@@ -393,8 +409,12 @@ export function DiagramCommands(props: {}) {
drdIndex: state.computed(state).getDrdIndex(),
edge: { id: edge.id, dmnObject: edge.data!.dmnObject },
mode: EdgeDeletionMode.FROM_CURRENT_DRD_ONLY,
+ externalModelsByNamespace,
+ });
+ state.dispatch(state).diagram.setEdgeStatus(edge.id, {
+ selected: false,
+ draggingWaypoint: false,
});
- state.dispatch(state).diagram.setEdgeStatus(edge.id, { selected: false, draggingWaypoint: false });
}
}
@@ -406,15 +426,16 @@ export function DiagramCommands(props: {}) {
const { deletedDmnShapeOnCurrentDrd: deletedShape } = deleteNode({
definitions: state.dmn.model.definitions,
__readonly_drgEdges: [], // Deleting from DRD only.
- __readonly_externalModelTypesByNamespace: state
+ __readonly_externalDmnsIndex: state
.computed(state)
- .getExternalModelTypesByNamespace(externalModelsByNamespace),
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns,
__readonly_drdIndex: state.computed(state).getDrdIndex(),
__readonly_dmnObjectNamespace: node.data.dmnObjectNamespace ?? state.dmn.model.definitions["@_namespace"],
__readonly_dmnObjectQName: node.data.dmnObjectQName,
__readonly_dmnObjectId: node.data.dmnObject?.["@_id"],
__readonly_nodeNature: nodeNatures[node.type as NodeType],
- mode: NodeDeletionMode.FROM_CURRENT_DRD_ONLY,
+ __readonly_mode: NodeDeletionMode.FROM_CURRENT_DRD_ONLY,
+ __readonly_externalModelsByNamespace: externalModelsByNamespace,
});
if (deletedShape) {
diff --git a/packages/dmn-editor/src/diagram/DrgNodesPanel.tsx b/packages/dmn-editor/src/diagram/DrgNodesPanel.tsx
index 66b9903f8a4..2391eda1e09 100644
--- a/packages/dmn-editor/src/diagram/DrgNodesPanel.tsx
+++ b/packages/dmn-editor/src/diagram/DrgNodesPanel.tsx
@@ -61,8 +61,7 @@ export function DrgNodesPanel() {
const containingDecisionServiceHrefsByDecisionHrefsRelativeToThisDmn = useMemo(
() =>
computeContainingDecisionServiceHrefsByDecisionHrefs({
- drgElements: thisDmnsDrgElements,
- drgElementsNamespace: thisDmnsNamespace,
+ drgElementsByNamespace: new Map([[thisDmnsNamespace, thisDmnsDrgElements]]),
thisDmnsNamespace: thisDmnsNamespace,
}),
[thisDmnsDrgElements, thisDmnsNamespace]
diff --git a/packages/dmn-editor/src/diagram/Palette.tsx b/packages/dmn-editor/src/diagram/Palette.tsx
index 05fe4938571..1bfed0c65d0 100644
--- a/packages/dmn-editor/src/diagram/Palette.tsx
+++ b/packages/dmn-editor/src/diagram/Palette.tsx
@@ -49,6 +49,7 @@ import { useInViewSelect } from "../responsiveness/useInViewSelect";
import { useDmnEditor } from "../DmnEditorContext";
import { getDrdId } from "./drd/drdId";
import { useSettings } from "../settings/DmnEditorSettingsContext";
+import { useExternalModels } from "../includedModels/DmnEditorDependenciesContext";
export const MIME_TYPE_FOR_DMN_EDITOR_NEW_NODE_FROM_PALETTE = "application/kie-dmn-editor--new-node-from-palette";
@@ -65,6 +66,7 @@ export function Palette({ pulse }: { pulse: boolean }) {
const rfStoreApi = RF.useStoreApi();
const isAlternativeInputDataShape = useDmnEditorStore((s) => s.computed(s).isAlternativeInputDataShape());
const drdIndex = useDmnEditorStore((s) => s.computed(s).getDrdIndex());
+ const { externalModelsByNamespace } = useExternalModels();
const settings = useSettings();
const groupNodes = useCallback(() => {
@@ -88,11 +90,12 @@ export function Palette({ pulse }: { pulse: boolean }) {
padding: CONTAINER_NODES_DESIRABLE_PADDING,
}),
},
+ externalModelsByNamespace,
});
state.dispatch(state).diagram.setNodeStatus(newNodeId, { selected: true });
});
- }, [dmnEditorStoreApi, rfStoreApi]);
+ }, [dmnEditorStoreApi, externalModelsByNamespace, rfStoreApi]);
const drd = thisDmn.definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"]?.[drdIndex];
@@ -265,7 +268,11 @@ export function Palette({ pulse }: { pulse: boolean }) {
<br />
<aside className={"kie-dmn-editor--external-nodes-panel-toggle"}>
{diagram.openLhsPanel === DiagramLhsPanel.EXTERNAL_NODES && (
- <div className={"kie-dmn-editor--palette-nodes-popover"} style={{ maxHeight }}>
+ <div
+ className={"kie-dmn-editor--palette-nodes-popover"}
+ style={{ maxHeight }}
+ data-testid={"kie-tools--dmn-editor--external-nodes-popover"}
+ >
<ExternalNodesPanel />
</div>
)}
diff --git a/packages/dmn-editor/src/diagram/nodes/Nodes.tsx b/packages/dmn-editor/src/diagram/nodes/Nodes.tsx
index 3c0e362f9bd..9da807bfcc8 100644
--- a/packages/dmn-editor/src/diagram/nodes/Nodes.tsx
+++ b/packages/dmn-editor/src/diagram/nodes/Nodes.tsx
@@ -939,7 +939,7 @@ export const DecisionServiceNode = React.memo(
__readonly_dmnObjectNamespace: dmnObjectNamespace,
__readonly_externalDmnsIndex: state
.computed(state)
- .getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns,
shapeIndex: shape.index,
localYPosition: e.y,
snapGrid: state.diagram.snapGrid,
diff --git a/packages/dmn-editor/src/externalNodes/DmnObjectListItem.tsx b/packages/dmn-editor/src/externalNodes/DmnObjectListItem.tsx
index 85423717aba..b21e543428b 100644
--- a/packages/dmn-editor/src/externalNodes/DmnObjectListItem.tsx
+++ b/packages/dmn-editor/src/externalNodes/DmnObjectListItem.tsx
@@ -31,6 +31,8 @@ import { useDmnEditorStore } from "../store/StoreContext";
import { useExternalModels } from "../includedModels/DmnEditorDependenciesContext";
import { DMN15_SPEC } from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/Dmn15Spec";
import { Normalized } from "../normalization/normalize";
+import { Tooltip } from "@patternfly/react-core/dist/js/components/Tooltip";
+import { NODE_TYPES } from "../diagram/nodes/NodeTypes";
export function DmnObjectListItem({
dmnObject,
@@ -50,14 +52,70 @@ export function DmnObjectListItem({
);
const isAlternativeInputDataShape = useDmnEditorStore((s) => s.computed(s).isAlternativeInputDataShape());
- const displayName = dmnObject
- ? buildFeelQNameFromNamespace({
- namedElement: dmnObject,
- importsByNamespace,
- namespace,
- relativeToNamespace,
- }).full
- : dmnObjectHref;
+ // The dmnObject represented here can be a node from a 3rd model that is not included in this model.
+ // For example, consider a "Local Decision Service" with an encapsulated "Decision-A" from "Model A",
+ // but that "Decision-A" have an "Input-B" that is from "Model B", which is not included in local model.
+ //
+ // Model Name: Local Model.dmn
+ // Nodes: Local Decision Service
+ // Included Models: Model-A.dmn
+ //
+ // Model Name: Model-A.dmn
+ // Nodes: Decision-A
+ // Included Models: Model-B.dmn
+ //
+ // Model Name: Model-B.dmn
+ // Nodes: Input-B
+ // Included Models: [none]
+ //
+ // So, the "Local Model" only "knows" the nodes from "Model-A" and NOT from "Model-B".
+ // That's why we have different logic to build description for "known dmnObjects" and "unknown dmnObjects".
+ const isNamespaceDirectlyIncluded = useMemo(
+ () => namespace === relativeToNamespace || importsByNamespace.has(namespace),
+ [importsByNamespace, namespace, relativeToNamespace]
+ );
+
+ const transitivelyIncludedNamespaceDescription = useMemo(() => {
+ return `${namespace.substring(0, 11)}...${namespace.substring(namespace.length - 4)}`;
+ }, [namespace]);
+
+ const displayName = useMemo(
+ () =>
+ dmnObject && isNamespaceDirectlyIncluded
+ ? buildFeelQNameFromNamespace({
+ namedElement: dmnObject,
+ importsByNamespace,
+ namespace,
+ relativeToNamespace,
+ }).full
+ : dmnObject?.["@_name"],
+ [dmnObject, importsByNamespace, isNamespaceDirectlyIncluded, namespace, relativeToNamespace]
+ );
+
+ const nodeTypeTooltipDescription = useMemo(() => {
+ if (dmnObject === undefined) {
+ throw new Error("nodeTypeDescription can't be defined without a DMN object");
+ }
+ const nodeType = getNodeTypeFromDmnObject(dmnObject);
+ if (nodeType === undefined) {
+ throw new Error("Can't determine nodeTypeDescription with undefined node type");
+ }
+ if (nodeType === NODE_TYPES.decision) {
+ return "Decision";
+ } else if (nodeType === NODE_TYPES.inputData) {
+ return "Input Data";
+ } else {
+ return "Unknown";
+ }
+ }, [dmnObject]);
+
+ const toolTip = useMemo(() => {
+ return dmnObject && isNamespaceDirectlyIncluded ? (
+ <p>{displayName}</p>
+ ) : (
+ <div>{`This ${nodeTypeTooltipDescription} node is from an external model that is not included in this one. Namespace: ${namespace}`}</div>
+ );
+ }, [displayName, dmnObject, isNamespaceDirectlyIncluded, namespace, nodeTypeTooltipDescription]);
const isValid = useDmnEditorStore((s) =>
DMN15_SPEC.namedElement.isValidName(
@@ -81,32 +139,42 @@ export function DmnObjectListItem({
return !dmnObject ? (
<>{dmnObjectHref}</>
) : (
- <Flex
- alignItems={{ default: "alignItemsCenter" }}
- justifyContent={{ default: "justifyContentFlexStart" }}
- spaceItems={{ default: "spaceItemsNone" }}
- >
- <div style={{ width: "40px", height: "40px", marginRight: 0 }}>
- <Icon />
- </div>
- <div style={{ color: isValid ? undefined : "red" }}>{`${displayName}`}</div>
- <div>
- {dmnObject.__$$element !== "knowledgeSource" ? (
- <>
-
- <TypeRefLabel
- typeRef={dmnObject.variable?.["@_typeRef"]}
- relativeToNamespace={namespace}
- isCollection={
- allTopLevelDataTypesByFeelName.get(dmnObject.variable?.["@_typeRef"] ?? DmnBuiltInDataType.Undefined)
- ?.itemDefinition["@_isCollection"]
- }
- />
- </>
- ) : (
- <></>
+ <Tooltip content={toolTip} isContentLeftAligned={true}>
+ <Flex
+ alignItems={{ default: "alignItemsCenter" }}
+ justifyContent={{ default: "justifyContentFlexStart" }}
+ spaceItems={{ default: "spaceItemsNone" }}
+ >
+ <div style={{ width: "40px", height: "40px", marginRight: 0 }}>
+ <Icon />
+ </div>
+ {!isNamespaceDirectlyIncluded && (
+ <div
+ style={{
+ backgroundColor: "#f0f0f0",
+ color: "#6a6e72",
+ }}
+ >{`${transitivelyIncludedNamespaceDescription}.`}</div>
)}
- </div>
- </Flex>
+ <div style={{ color: isValid ? undefined : "red" }}>{`${displayName}`}</div>
+ <div>
+ {dmnObject.__$$element !== "knowledgeSource" ? (
+ <>
+
+ <TypeRefLabel
+ typeRef={dmnObject.variable?.["@_typeRef"]}
+ relativeToNamespace={namespace}
+ isCollection={
+ allTopLevelDataTypesByFeelName.get(dmnObject.variable?.["@_typeRef"] ?? DmnBuiltInDataType.Undefined)
+ ?.itemDefinition["@_isCollection"]
+ }
+ />
+ </>
+ ) : (
+ <></>
+ )}
+ </div>
+ </Flex>
+ </Tooltip>
);
}
diff --git a/packages/dmn-editor/src/externalNodes/ExternalNodesPanel.tsx b/packages/dmn-editor/src/externalNodes/ExternalNodesPanel.tsx
index f30a5c7b437..2cb1ceb5dbf 100644
--- a/packages/dmn-editor/src/externalNodes/ExternalNodesPanel.tsx
+++ b/packages/dmn-editor/src/externalNodes/ExternalNodesPanel.tsx
@@ -54,7 +54,7 @@ export function ExternalNodesPanel() {
const importsByNamespace = useDmnEditorStore((s) => s.computed(s).importsByNamespace());
const { externalModelsByNamespace } = useExternalModels();
const externalDmnsByNamespace = useDmnEditorStore(
- (s) => s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace).dmns
+ (s) => s.computed(s).getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns
);
const dmnShapesByHref = useDmnEditorStore((s) => s.computed(s).indexedDrd().dmnShapesByHref);
const { onRequestToResolvePath } = useDmnEditor();
@@ -136,7 +136,7 @@ export function ExternalNodesPanel() {
const _import = importsByNamespace.get(namespace);
if (!_import) {
console.debug(
- `DMN EDITOR: Couldn't find import for namespace '${namespace}', although there's an external DMN referncing it.`
+ `DMN EDITOR: Couldn't find import for namespace '${namespace}', although there's an external DMN referencing it.`
);
return [];
}
@@ -159,6 +159,7 @@ export function ExternalNodesPanel() {
externalDrgElementId: drgElement["@_id"]!,
})
}
+ data-testid={`kie-tools--dmn-editor--external-node-${_import["@_name"] === "" ? _import["@_id"] : _import["@_name"]}-${drgElement["@_name"]}`}
>
<Flex
alignItems={{ default: "alignItemsCenter" }}
diff --git a/packages/dmn-editor/src/includedModels/IncludedModels.tsx b/packages/dmn-editor/src/includedModels/IncludedModels.tsx
index 6f18ab766d2..cb929054328 100644
--- a/packages/dmn-editor/src/includedModels/IncludedModels.tsx
+++ b/packages/dmn-editor/src/includedModels/IncludedModels.tsx
@@ -251,6 +251,7 @@ export function IncludedModels() {
return (
<>
<Modal
+ data-testid={"kie-tools--dmn-editor--included-models-modal"}
isOpen={isModalOpen}
onClose={() => cancel()}
title={"Include model"}
@@ -466,11 +467,12 @@ function IncludedModelCard({
dmnEditorStoreApi.setState((state) => {
const externalModelTypesByNamespace = state
.computed(state)
- .getExternalModelTypesByNamespace(externalModelsByNamespace);
+ .getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace);
deleteImport({
definitions: state.dmn.model.definitions,
__readonly_index: index,
__readonly_externalModelTypesByNamespace: externalModelTypesByNamespace,
+ __readonly_externalModelsByNamespace: externalModelsByNamespace,
});
});
},
diff --git a/packages/dmn-editor/src/mutations/addConnectedNode.ts b/packages/dmn-editor/src/mutations/addConnectedNode.ts
index 75868d7f355..6bd26ba3173 100644
--- a/packages/dmn-editor/src/mutations/addConnectedNode.ts
+++ b/packages/dmn-editor/src/mutations/addConnectedNode.ts
@@ -38,6 +38,7 @@ import { getCentralizedDecisionServiceDividerLine } from "./updateDecisionServic
import { repopulateInputDataAndDecisionsOnAllDecisionServices } from "./repopulateInputDataAndDecisionsOnDecisionService";
import { buildXmlHref } from "../xml/xmlHrefs";
import { Normalized } from "../normalization/normalize";
+import { ExternalModelsIndex } from "../DmnEditor";
export function addConnectedNode({
definitions,
@@ -45,12 +46,14 @@ export function addConnectedNode({
sourceNode,
newNode,
edgeType,
+ externalModelsByNamespace,
}: {
definitions: Normalized<DMN15__tDefinitions>;
drdIndex: number;
sourceNode: { type: NodeType; href: string; bounds: DC__Bounds; shapeId: string | undefined };
newNode: { type: NodeType; bounds: DC__Bounds };
edgeType: EdgeType;
+ externalModelsByNamespace: ExternalModelsIndex | undefined;
}) {
const newDmnObjectId = generateUuid();
const newDmnObjectHref = buildXmlHref({ id: newDmnObjectId });
@@ -171,7 +174,7 @@ export function addConnectedNode({
"di:waypoint": [getDmnBoundsCenterPoint(sourceNode.bounds), getDmnBoundsCenterPoint(newNode.bounds)],
});
- repopulateInputDataAndDecisionsOnAllDecisionServices({ definitions });
+ repopulateInputDataAndDecisionsOnAllDecisionServices({ definitions, externalModelsByNamespace });
return { id: newDmnObjectId, href: newDmnObjectHref };
}
diff --git a/packages/dmn-editor/src/mutations/addDecisionToDecisionService.ts b/packages/dmn-editor/src/mutations/addDecisionToDecisionService.ts
index 6bd3055581f..18c45243d80 100644
--- a/packages/dmn-editor/src/mutations/addDecisionToDecisionService.ts
+++ b/packages/dmn-editor/src/mutations/addDecisionToDecisionService.ts
@@ -25,25 +25,52 @@ import { SnapGrid } from "../store/Store";
import { MIN_NODE_SIZES } from "../diagram/nodes/DefaultSizes";
import { NODE_TYPES } from "../diagram/nodes/NodeTypes";
import { Normalized } from "../normalization/normalize";
+import { ExternalModelsIndex } from "../DmnEditor";
+import { buildXmlHref, parseXmlHref } from "../xml/xmlHrefs";
+import { DmnLatestModel } from "@kie-tools/dmn-marshaller/dist";
+import { xmlHrefToQName } from "../xml/xmlHrefToQName";
export function addDecisionToDecisionService({
definitions,
- decisionId,
+ decisionHref,
decisionServiceId,
drdIndex,
snapGrid,
+ externalModelsByNamespace,
}: {
definitions: Normalized<DMN15__tDefinitions>;
- decisionId: string;
+ decisionHref: string;
decisionServiceId: string;
drdIndex: number;
snapGrid: SnapGrid;
+ externalModelsByNamespace: ExternalModelsIndex | undefined;
}) {
- console.debug(`DMN MUTATION: Adding Decision '${decisionId}' to Decision Service '${decisionServiceId}'`);
+ console.debug(`DMN MUTATION: Adding Decision '${decisionHref}' to Decision Service '${decisionServiceId}'`);
- const decision = definitions.drgElement?.find((s) => s["@_id"] === decisionId);
- if (decision?.__$$element !== "decision") {
- throw new Error(`DMN MUTATION: DRG Element with id '${decisionId}' is either not a Decision or doesn't exist.`);
+ const href = parseXmlHref(decisionHref);
+
+ if (href.namespace) {
+ const externalModel = externalModelsByNamespace?.[href.namespace];
+ if (!externalModel) {
+ throw new Error(`DMN MUTATION: Namespace '${href.namespace}' not found.`);
+ }
+
+ if (externalModel?.type !== "dmn") {
+ throw new Error(`DMN MUTATION: External model with namespace ${href.namespace} is not a DMN.`);
+ }
+
+ const externalDrgs = externalModel.model.definitions.drgElement;
+ const decision = externalDrgs?.find((drgElement) => drgElement["@_id"] === href.id);
+ if (decision?.__$$element !== "decision") {
+ throw new Error(
+ `DMN MUTATION: DRG Element with id '${href.id}' is either not a Decision or doesn't exist in the external model '${href.namespace}'`
+ );
+ }
+ } else {
+ const decision = definitions.drgElement?.find((s) => s["@_id"] === href.id);
+ if (decision?.__$$element !== "decision") {
+ throw new Error(`DMN MUTATION: DRG Element with id '${href.id}' is either not a Decision or doesn't exist.`);
+ }
}
const decisionService = definitions.drgElement?.find((s) => s["@_id"] === decisionServiceId);
@@ -54,8 +81,10 @@ export function addDecisionToDecisionService({
}
const diagram = addOrGetDrd({ definitions, drdIndex });
+ const dmnElementRef = xmlHrefToQName(decisionHref, definitions);
+
const decisionShape = diagram.diagramElements.find(
- (s) => s["@_dmnElementRef"] === decisionId && s.__$$element === "dmndi:DMNShape"
+ (s) => s["@_dmnElementRef"] === dmnElementRef && s.__$$element === "dmndi:DMNShape"
) as Normalized<DMNDI15__DMNShape>;
const decisionServiceShape = diagram.diagramElements.find(
@@ -65,15 +94,15 @@ export function addDecisionToDecisionService({
const section = getSectionForDecisionInsideDecisionService({ decisionShape, decisionServiceShape, snapGrid });
if (section === "encapsulated") {
decisionService.encapsulatedDecision ??= [];
- decisionService.encapsulatedDecision.push({ "@_href": `#${decisionId}` });
+ decisionService.encapsulatedDecision.push({ "@_href": `${decisionHref}` });
} else if (section === "output") {
decisionService.outputDecision ??= [];
- decisionService.outputDecision.push({ "@_href": `#${decisionId}` });
+ decisionService.outputDecision.push({ "@_href": `${decisionHref}` });
} else {
throw new Error(`DMN MUTATION: Invalid section to add decision to: '${section}' `);
}
- repopulateInputDataAndDecisionsOnDecisionService({ definitions, decisionService });
+ repopulateInputDataAndDecisionsOnDecisionService({ definitions, decisionService, externalModelsByNamespace });
}
export function getSectionForDecisionInsideDecisionService({
diff --git a/packages/dmn-editor/src/mutations/addEdge.ts b/packages/dmn-editor/src/mutations/addEdge.ts
index 14e549433f9..7be83b0f2ce 100644
--- a/packages/dmn-editor/src/mutations/addEdge.ts
+++ b/packages/dmn-editor/src/mutations/addEdge.ts
@@ -41,6 +41,7 @@ import { repopulateInputDataAndDecisionsOnAllDecisionServices } from "./repopula
import { DmnDiagramNodeData } from "../diagram/nodes/Nodes";
import { AutoPositionedEdgeMarker } from "../diagram/edges/AutoPositionedEdgeMarker";
import { Normalized } from "../normalization/normalize";
+import { ExternalModelsIndex } from "../DmnEditor";
export function addEdge({
definitions,
@@ -49,6 +50,7 @@ export function addEdge({
targetNode,
edge,
keepWaypoints,
+ externalModelsByNamespace,
}: {
definitions: Normalized<DMN15__tDefinitions>;
drdIndex: number;
@@ -74,6 +76,7 @@ export function addEdge({
autoPositionedEdgeMarker: AutoPositionedEdgeMarker | undefined;
};
keepWaypoints: boolean;
+ externalModelsByNamespace: ExternalModelsIndex | undefined;
}) {
if (!_checkIsValidConnection(sourceNode, targetNode, edge.type)) {
throw new Error(`DMN MUTATION: Invalid structure: (${sourceNode.type}) --${edge.type}--> (${targetNode.type}) `);
@@ -188,7 +191,10 @@ export function addEdge({
// Replace with the new one.
diagramElements.push(newDmnEdge);
- repopulateInputDataAndDecisionsOnAllDecisionServices({ definitions });
+ repopulateInputDataAndDecisionsOnAllDecisionServices({
+ definitions,
+ externalModelsByNamespace,
+ });
return { newDmnEdge };
}
@@ -229,6 +235,7 @@ function removeFirstMatchIfPresent<T>(arr: T[], predicate: Parameters<Array<T>["
function tryKeepingEdgeId(existingEdgeId: string | undefined, newEdgeId: string) {
return existingEdgeId ?? newEdgeId;
}
+
function withoutDiscreteAutoPosinitioningMarker(edgeId: string) {
const marker = getDiscreteAutoPositioningEdgeIdMarker(edgeId);
return marker ? edgeId.replace(`${marker}`, "") : edgeId;
diff --git a/packages/dmn-editor/src/mutations/addExistingDecisionServiceToDrd.ts b/packages/dmn-editor/src/mutations/addExistingDecisionServiceToDrd.ts
index 84817732264..dd7ab5281a9 100644
--- a/packages/dmn-editor/src/mutations/addExistingDecisionServiceToDrd.ts
+++ b/packages/dmn-editor/src/mutations/addExistingDecisionServiceToDrd.ts
@@ -30,7 +30,7 @@ import { Normalized } from "../normalization/normalize";
import { Computed, SnapGrid, State } from "../store/Store";
import { computeContainingDecisionServiceHrefsByDecisionHrefs } from "../store/computed/computeContainingDecisionServiceHrefsByDecisionHrefs.ts";
import { computeDiagramData } from "../store/computed/computeDiagramData";
-import { computeExternalModelsByType } from "../store/computed/computeExternalModelsByType";
+import { computeDirectlyIncludedExternalModelsByNamespace } from "../store/computed/computeDirectlyIncludedExternalModelsByNamespace";
import { computeIndexedDrd } from "../store/computed/computeIndexes";
import { xmlHrefToQName } from "../xml/xmlHrefToQName";
import { buildXmlHref, parseXmlHref } from "../xml/xmlHrefs";
@@ -66,7 +66,7 @@ export function getStrategyToAddExistingDecisionServiceToDrd({
__readonly_definitions: Normalized<DMN15__tDefinitions>;
__readonly_decisionServiceNamespace: string;
__readonly_drgElement: Normalized<DMN15__tDecisionService>;
- __readonly_externalDmnsIndex: ReturnType<Computed["getExternalModelTypesByNamespace"]>["dmns"];
+ __readonly_externalDmnsIndex: ReturnType<Computed["getDirectlyIncludedExternalModelsByNamespace"]>["dmns"];
__readonly_namespace: string;
__readonly_indexedDrd: ReturnType<Computed["indexedDrd"]>;
__readonly_drdIndex: number;
@@ -92,11 +92,15 @@ export function getStrategyToAddExistingDecisionServiceToDrd({
id: __readonly_drgElement["@_id"]!,
});
+ const drgElementsByNamespace = new Map([[__readonly_namespace, __readonly_definitions.drgElement]]);
+ __readonly_externalDmnsIndex.forEach((value, key) => {
+ drgElementsByNamespace.set(key, value.model.definitions.drgElement);
+ });
+
const containingDecisionServiceHrefsByDecisionHrefsRelativeToThisDmn =
computeContainingDecisionServiceHrefsByDecisionHrefs({
thisDmnsNamespace: __readonly_namespace,
- drgElementsNamespace: __readonly_decisionServiceNamespace,
- drgElements: decisionServiceDmnDefinitions.drgElement,
+ drgElementsByNamespace,
});
const doesThisDrdHaveConflictingDecisionService = containedDecisionHrefsRelativeToThisDmn.some((decisionHref) =>
@@ -195,7 +199,7 @@ export async function addAutoGeneratedDecisionServiceToDrd({
}: {
state: State;
__readonly_decisionServiceNamespace: string;
- __readonly_externalDmnsIndex: ReturnType<Computed["getExternalModelTypesByNamespace"]>["dmns"];
+ __readonly_externalDmnsIndex: ReturnType<Computed["getDirectlyIncludedExternalModelsByNamespace"]>["dmns"];
__readonly_drdIndex: number;
__readonly_snapGrid: SnapGrid;
__readonly_decisionServiceHrefRelativeToThisDmn: string;
@@ -265,7 +269,7 @@ export async function addAutoGeneratedDecisionServiceToDrd({
}
// Compute the external model types by namespace after autogenerating the Decision Service
- const externalModelTypesByNamespace = computeExternalModelsByType(
+ const externalModelTypesByNamespace = computeDirectlyIncludedExternalModelsByNamespace(
state.dmn.model.definitions.import,
__readonly_externalModelsByNamespace
);
@@ -308,6 +312,7 @@ export async function addAutoGeneratedDecisionServiceToDrd({
__readonly_drdIndex: dummyDrdIndex,
__readonly_dmnObjectNamespace: __readonly_decisionServiceNamespace,
__readonly_externalDmnsIndex: externalModelTypesByNamespace.dmns,
+ __readonly_externalModelsByNamespace,
});
// Save DS shape before applying the autolayout
@@ -390,7 +395,7 @@ export function addExistingDecisionServiceToDrd({
}: {
definitions: Normalized<DMN15__tDefinitions>;
__readonly_decisionServiceNamespace: string;
- __readonly_externalDmnsIndex: ReturnType<Computed["getExternalModelTypesByNamespace"]>["dmns"];
+ __readonly_externalDmnsIndex: ReturnType<Computed["getDirectlyIncludedExternalModelsByNamespace"]>["dmns"];
__readonly_namespace: string;
__readonly_indexedDrd: ReturnType<Computed["indexedDrd"]>;
__readonly_indexedDrdContainingDecisionServiceDepiction: ReturnType<Computed["indexedDrd"]>;
diff --git a/packages/dmn-editor/src/mutations/addStandaloneNode.ts b/packages/dmn-editor/src/mutations/addStandaloneNode.ts
index 2b348f07def..aad8ac660df 100644
--- a/packages/dmn-editor/src/mutations/addStandaloneNode.ts
+++ b/packages/dmn-editor/src/mutations/addStandaloneNode.ts
@@ -28,15 +28,18 @@ import { getCentralizedDecisionServiceDividerLine } from "./updateDecisionServic
import { repopulateInputDataAndDecisionsOnAllDecisionServices } from "./repopulateInputDataAndDecisionsOnDecisionService";
import { buildXmlHref } from "../xml/xmlHrefs";
import { Normalized } from "../normalization/normalize";
+import { ExternalModelsIndex } from "../DmnEditor";
export function addStandaloneNode({
definitions,
drdIndex,
newNode,
+ externalModelsByNamespace,
}: {
definitions: Normalized<DMN15__tDefinitions>;
drdIndex: number;
newNode: { type: NodeType; bounds: DC__Bounds };
+ externalModelsByNamespace: ExternalModelsIndex | undefined;
}) {
const newNodeId = generateUuid();
const nature = nodeNatures[newNode.type];
@@ -133,7 +136,7 @@ export function addStandaloneNode({
: {}),
});
- repopulateInputDataAndDecisionsOnAllDecisionServices({ definitions });
+ repopulateInputDataAndDecisionsOnAllDecisionServices({ definitions, externalModelsByNamespace });
return { id: newNodeId, href: buildXmlHref({ id: newNodeId }), shapeId };
}
diff --git a/packages/dmn-editor/src/mutations/applyAutoLayoutToDrd.ts b/packages/dmn-editor/src/mutations/applyAutoLayoutToDrd.ts
index 0f3786a1b83..baf78e2994e 100644
--- a/packages/dmn-editor/src/mutations/applyAutoLayoutToDrd.ts
+++ b/packages/dmn-editor/src/mutations/applyAutoLayoutToDrd.ts
@@ -33,7 +33,7 @@ import { updateDecisionServiceDividerLine } from "../mutations/updateDecisionSer
import { Normalized } from "../normalization/normalize";
import { State } from "../store/Store";
import { AutolayoutParentNode, FAKE_MARKER, visitNodeAndNested } from "../autolayout/autoLayoutInfo";
-import { ExternalDmnsIndex } from "../DmnEditor";
+import { ExternalDmnsIndex, ExternalModelsIndex } from "../DmnEditor";
export function applyAutoLayoutToDrd({
state,
@@ -46,6 +46,7 @@ export function applyAutoLayoutToDrd({
__readonly_drdIndex,
__readonly_dmnObjectNamespace,
__readonly_externalDmnsIndex,
+ __readonly_externalModelsByNamespace,
}: {
state: State;
__readonly_autoLayoutedInfo: {
@@ -67,6 +68,7 @@ export function applyAutoLayoutToDrd({
__readonly_drdIndex: number;
__readonly_dmnObjectNamespace: string | undefined;
__readonly_externalDmnsIndex: ExternalDmnsIndex;
+ __readonly_externalModelsByNamespace: ExternalModelsIndex | undefined;
}) {
// 7. Update all nodes positions skipping empty groups, which will be positioned manually after all nodes are done being repositioned.
const autolayoutedElkNodesById = new Map<string, Elk.ElkNode>();
@@ -220,6 +222,7 @@ export function applyAutoLayoutToDrd({
shapeId: targetNode.data.shape["@_id"],
},
keepWaypoints: false,
+ externalModelsByNamespace: __readonly_externalModelsByNamespace,
});
}
}
diff --git a/packages/dmn-editor/src/mutations/deleteDecisionFromDecisionService.ts b/packages/dmn-editor/src/mutations/deleteDecisionFromDecisionService.ts
index cb9e1273299..81f09c3c518 100644
--- a/packages/dmn-editor/src/mutations/deleteDecisionFromDecisionService.ts
+++ b/packages/dmn-editor/src/mutations/deleteDecisionFromDecisionService.ts
@@ -20,21 +20,45 @@
import { DMN15__tDefinitions } from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
import { repopulateInputDataAndDecisionsOnDecisionService } from "./repopulateInputDataAndDecisionsOnDecisionService";
import { Normalized } from "../normalization/normalize";
+import { buildXmlHref, parseXmlHref } from "../xml/xmlHrefs";
+import { ExternalModelsIndex } from "../DmnEditor";
export function deleteDecisionFromDecisionService({
definitions,
- decisionId,
+ decisionHref,
decisionServiceId,
+ externalModelsByNamespace,
}: {
definitions: Normalized<DMN15__tDefinitions>;
- decisionId: string;
+ decisionHref: string;
decisionServiceId: string;
+ externalModelsByNamespace: ExternalModelsIndex | undefined;
}) {
- console.debug(`DMN MUTATION: Deleting Decision '${decisionId}' from Decision Service '${decisionServiceId}'`);
+ console.debug(`DMN MUTATION: Deleting Decision '${decisionHref}' from Decision Service '${decisionServiceId}'`);
- const decision = definitions.drgElement?.find((s) => s["@_id"] === decisionId);
- if (decision?.__$$element !== "decision") {
- throw new Error(`DMN MUTATION: DRG Element with id '${decisionId}' is either not a Decision or doesn't exist.`);
+ const href = parseXmlHref(decisionHref);
+ if (href.namespace) {
+ const externalModel = externalModelsByNamespace?.[href.namespace];
+ if (!externalModel) {
+ throw new Error(`DMN MUTATION: Namespace '${href.namespace}' not found.`);
+ }
+
+ if (externalModel?.type !== "dmn") {
+ throw new Error(`DMN MUTATION: External model with namespace ${href.namespace} is not a DMN.`);
+ }
+
+ const externalDrgs = externalModel.model.definitions.drgElement;
+ const decision = externalDrgs?.find((drgElement) => drgElement["@_id"] === href.id);
+ if (decision?.__$$element !== "decision") {
+ throw new Error(
+ `DMN MUTATION: DRG Element with id '${href.id}' is either not a Decision or doesn't exist in the external model '${href.namespace}'`
+ );
+ }
+ } else {
+ const decision = definitions.drgElement?.find((s) => s["@_id"] === href.id);
+ if (decision?.__$$element !== "decision") {
+ throw new Error(`DMN MUTATION: DRG Element with id '${href.id}' is either not a Decision or doesn't exist.`);
+ }
}
const decisionService = definitions.drgElement?.find((s) => s["@_id"] === decisionServiceId);
@@ -44,12 +68,10 @@ export function deleteDecisionFromDecisionService({
);
}
- decisionService.outputDecision = (decisionService.outputDecision ?? []).filter(
- (s) => s["@_href"] !== `#${decisionId}`
- );
+ decisionService.outputDecision = (decisionService.outputDecision ?? []).filter((od) => od["@_href"] !== decisionHref);
decisionService.encapsulatedDecision = (decisionService.encapsulatedDecision ?? []).filter(
- (s) => s["@_href"] !== `#${decisionId}`
+ (ed) => ed["@_href"] !== decisionHref
);
- repopulateInputDataAndDecisionsOnDecisionService({ definitions, decisionService });
+ repopulateInputDataAndDecisionsOnDecisionService({ definitions, decisionService, externalModelsByNamespace });
}
diff --git a/packages/dmn-editor/src/mutations/deleteEdge.ts b/packages/dmn-editor/src/mutations/deleteEdge.ts
index 3cc43c8126c..58a3af9e627 100644
--- a/packages/dmn-editor/src/mutations/deleteEdge.ts
+++ b/packages/dmn-editor/src/mutations/deleteEdge.ts
@@ -27,6 +27,7 @@ import { addOrGetDrd } from "./addOrGetDrd";
import { DmnDiagramEdgeData } from "../diagram/edges/Edges";
import { repopulateInputDataAndDecisionsOnAllDecisionServices } from "./repopulateInputDataAndDecisionsOnDecisionService";
import { Normalized } from "../normalization/normalize";
+import { ExternalModelsIndex } from "../DmnEditor";
export enum EdgeDeletionMode {
FROM_DRG_AND_ALL_DRDS,
@@ -38,11 +39,13 @@ export function deleteEdge({
drdIndex,
edge,
mode,
+ externalModelsByNamespace,
}: {
definitions: Normalized<DMN15__tDefinitions>;
drdIndex: number;
edge: { id: string; dmnObject: DmnDiagramEdgeData["dmnObject"] };
mode: EdgeDeletionMode;
+ externalModelsByNamespace: ExternalModelsIndex | undefined;
}) {
if (edge.dmnObject.namespace !== definitions["@_namespace"]) {
console.debug("DMN MUTATION: Can't delete an edge that's from an external node.");
@@ -99,7 +102,7 @@ export function deleteEdge({
}
}
- repopulateInputDataAndDecisionsOnAllDecisionServices({ definitions });
+ repopulateInputDataAndDecisionsOnAllDecisionServices({ definitions, externalModelsByNamespace });
return { deletedDmnEdgeOnCurrentDrd };
}
diff --git a/packages/dmn-editor/src/mutations/deleteImport.ts b/packages/dmn-editor/src/mutations/deleteImport.ts
index 34aa40bcc7a..4195c39afa2 100644
--- a/packages/dmn-editor/src/mutations/deleteImport.ts
+++ b/packages/dmn-editor/src/mutations/deleteImport.ts
@@ -28,15 +28,18 @@ import { deleteEdge, EdgeDeletionMode } from "./deleteEdge";
import { computeIndexedDrd } from "../store/computed/computeIndexes";
import { Computed, defaultStaticState } from "../store/Store";
import { TypeOrReturnType } from "../store/ComputedStateCache";
+import { ExternalModelsIndex } from "../DmnEditor";
export function deleteImport({
definitions,
__readonly_index,
__readonly_externalModelTypesByNamespace,
+ __readonly_externalModelsByNamespace,
}: {
definitions: Normalized<DMN15__tDefinitions>;
__readonly_index: number;
- __readonly_externalModelTypesByNamespace: TypeOrReturnType<Computed["getExternalModelTypesByNamespace"]>;
+ __readonly_externalModelTypesByNamespace: TypeOrReturnType<Computed["getDirectlyIncludedExternalModelsByNamespace"]>;
+ __readonly_externalModelsByNamespace: ExternalModelsIndex | undefined;
}) {
definitions.import ??= [];
const [deletedImport] = definitions.import.splice(__readonly_index, 1);
@@ -67,8 +70,9 @@ export function deleteImport({
__readonly_dmnObjectId: node.data.dmnObject?.["@_id"],
__readonly_dmnObjectQName: node.data.dmnObjectQName,
__readonly_dmnObjectNamespace: node.data.dmnObjectNamespace!,
- __readonly_externalModelTypesByNamespace,
- mode: NodeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+ __readonly_externalDmnsIndex: __readonly_externalModelTypesByNamespace.dmns,
+ __readonly_mode: NodeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+ __readonly_externalModelsByNamespace,
});
});
@@ -78,6 +82,7 @@ export function deleteImport({
drdIndex: 0,
edge: { id: edge.id, dmnObject: edge.data!.dmnObject },
mode: EdgeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+ externalModelsByNamespace: __readonly_externalModelsByNamespace,
});
});
});
diff --git a/packages/dmn-editor/src/mutations/deleteNode.ts b/packages/dmn-editor/src/mutations/deleteNode.ts
index 5f4affe9fa4..5782ec13b86 100644
--- a/packages/dmn-editor/src/mutations/deleteNode.ts
+++ b/packages/dmn-editor/src/mutations/deleteNode.ts
@@ -31,7 +31,7 @@ import { Computed } from "../store/Store";
import { computeContainingDecisionServiceHrefsByDecisionHrefs } from "../store/computed/computeContainingDecisionServiceHrefsByDecisionHrefs.ts";
import { xmlHrefToQName } from "../xml/xmlHrefToQName";
import { Normalized } from "../normalization/normalize";
-import { NodeDmnObjects } from "../diagram/nodes/Nodes";
+import { ExternalDmnsIndex, ExternalModelsIndex } from "../DmnEditor";
export enum NodeDeletionMode {
FROM_DRG_AND_ALL_DRDS,
@@ -46,37 +46,52 @@ export function deleteNode({
__readonly_dmnObjectId,
__readonly_dmnObjectNamespace,
__readonly_dmnObjectQName,
- __readonly_externalModelTypesByNamespace,
- mode,
+ __readonly_externalDmnsIndex,
+ __readonly_mode,
+ __readonly_externalModelsByNamespace,
}: {
definitions: Normalized<DMN15__tDefinitions>;
__readonly_drgEdges: DrgEdge[];
__readonly_drdIndex: number;
__readonly_nodeNature: NodeNature;
- __readonly_externalModelTypesByNamespace: ReturnType<Computed["getExternalModelTypesByNamespace"]>;
+ __readonly_externalDmnsIndex: ExternalDmnsIndex;
__readonly_dmnObjectId: string | undefined;
__readonly_dmnObjectNamespace: string;
__readonly_dmnObjectQName: XmlQName;
- mode: NodeDeletionMode;
+ __readonly_mode: NodeDeletionMode;
+ __readonly_externalModelsByNamespace: ExternalModelsIndex | undefined;
}): {
deletedDmnObject: Unpacked<Normalized<DMN15__tDefinitions>["drgElement" | "artifact"]> | undefined;
deletedDmnShapeOnCurrentDrd: Normalized<DMNDI15__DMNShape> | undefined;
} {
if (
- mode === NodeDeletionMode.FROM_CURRENT_DRD_ONLY &&
+ __readonly_mode === NodeDeletionMode.FROM_CURRENT_DRD_ONLY &&
!canRemoveNodeFromDrdOnly({
definitions,
__readonly_drdIndex,
__readonly_dmnObjectNamespace,
__readonly_dmnObjectId,
- __readonly_externalDmnsIndex: __readonly_externalModelTypesByNamespace.dmns,
+ __readonly_externalDmnsIndex,
})
) {
console.warn("DMN MUTATION: Cannot hide a Decision that's contained by a Decision Service from a DRD.");
return { deletedDmnObject: undefined, deletedDmnShapeOnCurrentDrd: undefined };
}
- if (mode === NodeDeletionMode.FROM_DRG_AND_ALL_DRDS) {
+ if (
+ !isContainedInAnExternalExpandedDecisionService({
+ definitions,
+ __readonly_drdIndex,
+ __readonly_dmnObjectNamespace,
+ __readonly_dmnObjectId,
+ __readonly_externalDmnsIndex,
+ })
+ ) {
+ console.warn("DMN MUTATION: Cannot delete a Decision that's contained by a expanded external Decision Service.");
+ return { deletedDmnObject: undefined, deletedDmnShapeOnCurrentDrd: undefined };
+ }
+
+ if (__readonly_mode === NodeDeletionMode.FROM_DRG_AND_ALL_DRDS) {
// Delete Edges
// 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.
const nodeId = buildXmlHref({
@@ -96,6 +111,7 @@ export function deleteNode({
id: drgEdge.id,
dmnObject: drgEdge.dmnObject,
},
+ externalModelsByNamespace: __readonly_externalModelsByNamespace,
});
}
}
@@ -119,7 +135,7 @@ export function deleteNode({
if (!__readonly_dmnObjectQName.prefix) {
// Delete the dmnObject itself
if (__readonly_nodeNature === NodeNature.ARTIFACT) {
- if (mode === NodeDeletionMode.FROM_DRG_AND_ALL_DRDS) {
+ if (__readonly_mode === NodeDeletionMode.FROM_DRG_AND_ALL_DRDS) {
const nodeIndex = (definitions.artifact ?? []).findIndex((a) => a["@_id"] === __readonly_dmnObjectId);
deletedDmnObject = definitions.artifact?.splice(nodeIndex, 1)?.[0];
} else {
@@ -128,7 +144,7 @@ export function deleteNode({
} else if (__readonly_nodeNature === NodeNature.DRG_ELEMENT) {
const nodeIndex = (definitions.drgElement ?? []).findIndex((d) => d["@_id"] === __readonly_dmnObjectId);
deletedDmnObject =
- mode === NodeDeletionMode.FROM_DRG_AND_ALL_DRDS
+ __readonly_mode === NodeDeletionMode.FROM_DRG_AND_ALL_DRDS
? definitions.drgElement?.splice(nodeIndex, 1)?.[0]
: definitions.drgElement?.[nodeIndex];
} else if (__readonly_nodeNature === NodeNature.UNKNOWN) {
@@ -160,7 +176,7 @@ export function deleteNode({
const drdCount = (definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"] ?? []).length;
for (let i = 0; i < drdCount; i++) {
- if (mode === NodeDeletionMode.FROM_CURRENT_DRD_ONLY && i !== __readonly_drdIndex) {
+ if (__readonly_mode === NodeDeletionMode.FROM_CURRENT_DRD_ONLY && i !== __readonly_drdIndex) {
continue;
}
@@ -180,14 +196,62 @@ export function deleteNode({
);
}
- repopulateInputDataAndDecisionsOnAllDecisionServices({ definitions });
+ repopulateInputDataAndDecisionsOnAllDecisionServices({
+ definitions,
+ externalModelsByNamespace: __readonly_externalModelsByNamespace,
+ });
return {
- deletedDmnObject: mode === NodeDeletionMode.FROM_DRG_AND_ALL_DRDS ? deletedDmnObject : undefined,
+ deletedDmnObject: __readonly_mode === NodeDeletionMode.FROM_DRG_AND_ALL_DRDS ? deletedDmnObject : undefined,
deletedDmnShapeOnCurrentDrd,
};
}
+export function isContainedInAnExternalExpandedDecisionService({
+ definitions,
+ __readonly_drdIndex,
+ __readonly_dmnObjectNamespace,
+ __readonly_dmnObjectId,
+ __readonly_externalDmnsIndex,
+}: {
+ definitions: Normalized<DMN15__tDefinitions>;
+ __readonly_dmnObjectNamespace: string;
+ __readonly_dmnObjectId: string | undefined;
+ __readonly_drdIndex: number;
+ __readonly_externalDmnsIndex: ReturnType<Computed["getDirectlyIncludedExternalModelsByNamespace"]>["dmns"];
+}) {
+ const { diagramElements } = addOrGetDrd({ definitions, drdIndex: __readonly_drdIndex });
+
+ const dmnObjectHref = buildXmlHref({
+ namespace: __readonly_dmnObjectNamespace === definitions["@_namespace"] ? undefined : __readonly_dmnObjectNamespace,
+ id: __readonly_dmnObjectId!,
+ });
+
+ const drgElementsByNamespace = new Map();
+ __readonly_externalDmnsIndex.forEach((value, key) => {
+ drgElementsByNamespace.set(key, value.model.definitions.drgElement);
+ });
+
+ const containingDecisionServiceHrefsByDecisionHrefsRelativeToThisDmn =
+ computeContainingDecisionServiceHrefsByDecisionHrefs({
+ thisDmnsNamespace: definitions["@_namespace"],
+ drgElementsByNamespace,
+ });
+
+ const isContainingDecisionServiceInExpandedFormPresentInTheDrd = (
+ containingDecisionServiceHrefsByDecisionHrefsRelativeToThisDmn.get(dmnObjectHref) ?? []
+ ).some((dsHref) =>
+ diagramElements.some(
+ (e) =>
+ e.__$$element === "dmndi:DMNShape" &&
+ e["@_dmnElementRef"] === xmlHrefToQName(dsHref, definitions) &&
+ !(e["@_isCollapsed"] ?? false)
+ )
+ );
+
+ return !isContainingDecisionServiceInExpandedFormPresentInTheDrd;
+}
+
export function canRemoveNodeFromDrdOnly({
definitions,
__readonly_drdIndex,
@@ -199,7 +263,7 @@ export function canRemoveNodeFromDrdOnly({
__readonly_dmnObjectNamespace: string;
__readonly_dmnObjectId: string | undefined;
__readonly_drdIndex: number;
- __readonly_externalDmnsIndex: ReturnType<Computed["getExternalModelTypesByNamespace"]>["dmns"];
+ __readonly_externalDmnsIndex: ReturnType<Computed["getDirectlyIncludedExternalModelsByNamespace"]>["dmns"];
}) {
const { diagramElements } = addOrGetDrd({ definitions, drdIndex: __readonly_drdIndex });
@@ -208,16 +272,15 @@ export function canRemoveNodeFromDrdOnly({
id: __readonly_dmnObjectId!,
});
- const drgElements =
- definitions["@_namespace"] === __readonly_dmnObjectNamespace
- ? definitions.drgElement ?? []
- : __readonly_externalDmnsIndex.get(__readonly_dmnObjectNamespace)?.model.definitions.drgElement ?? [];
+ const drgElementsByNamespace = new Map([[definitions["@_namespace"], definitions.drgElement]]);
+ __readonly_externalDmnsIndex.forEach((value, key) => {
+ drgElementsByNamespace.set(key, value.model.definitions.drgElement);
+ });
const containingDecisionServiceHrefsByDecisionHrefsRelativeToThisDmn =
computeContainingDecisionServiceHrefsByDecisionHrefs({
thisDmnsNamespace: definitions["@_namespace"],
- drgElementsNamespace: __readonly_dmnObjectNamespace,
- drgElements,
+ drgElementsByNamespace,
});
const containingDecisionServiceHrefs =
diff --git a/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts b/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts
index 3b5bb781e1b..35f4e6c2102 100644
--- a/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts
+++ b/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts
@@ -22,11 +22,15 @@ import {
DMN15__tDefinitions,
} from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
import { Normalized } from "../normalization/normalize";
+import { ExternalModelsIndex } from "../DmnEditor";
+import { buildXmlHref, parseXmlHref } from "../xml/xmlHrefs";
export function repopulateInputDataAndDecisionsOnAllDecisionServices({
definitions,
+ externalModelsByNamespace,
}: {
definitions: Normalized<DMN15__tDefinitions>;
+ externalModelsByNamespace: ExternalModelsIndex | undefined;
}) {
for (let i = 0; i < (definitions.drgElement ?? []).length; i++) {
const drgElement = definitions.drgElement![i];
@@ -34,6 +38,7 @@ export function repopulateInputDataAndDecisionsOnAllDecisionServices({
repopulateInputDataAndDecisionsOnDecisionService({
definitions,
decisionService: drgElement,
+ externalModelsByNamespace,
});
}
}
@@ -42,9 +47,11 @@ export function repopulateInputDataAndDecisionsOnAllDecisionServices({
export function repopulateInputDataAndDecisionsOnDecisionService({
definitions,
decisionService,
+ externalModelsByNamespace,
}: {
definitions: Normalized<DMN15__tDefinitions>;
decisionService: Normalized<DMN15__tDecisionService>;
+ externalModelsByNamespace: ExternalModelsIndex | undefined;
}) {
// Save previous values to preserve order
const inputDatas = new Set<string>([...(decisionService.inputData ?? [])].map((e) => e["@_href"])); // Using Set for uniqueness
@@ -59,21 +66,75 @@ export function repopulateInputDataAndDecisionsOnDecisionService({
...(decisionService.encapsulatedDecision ?? []).map((s) => s["@_href"]),
]);
- // Map all DS Input Data and Decision requirements to their href
+ /** Map all DS Input Data and Decision requirements to their href */
const requirements = new Map<string, "decisionIr" | "inputDataIr">();
- for (let i = 0; i < definitions.drgElement!.length; i++) {
- const drgElement = definitions.drgElement![i];
- if (!hrefsToDecisionsInsideDecisionService.has(`#${drgElement["@_id"]}`) || drgElement.__$$element !== "decision") {
- continue;
+
+ for (const decisionHrefString of hrefsToDecisionsInsideDecisionService) {
+ const decisionHref = parseXmlHref(decisionHrefString);
+
+ // local decision
+ if (!decisionHref.namespace || decisionHref.namespace === definitions["@_namespace"]) {
+ const localDecision = definitions.drgElement?.find((drgElement) => drgElement["@_id"] === decisionHref.id);
+ if (localDecision?.__$$element !== "decision") {
+ throw new Error(`DMN MUTATION: Node inside Decision Service is not a Decision. ID: ${localDecision?.["@_id"]}`);
+ }
+
+ (localDecision.informationRequirement ?? []).forEach((ir) => {
+ if (ir.requiredDecision) {
+ requirements.set(ir.requiredDecision["@_href"], "decisionIr");
+ } else if (ir.requiredInput) {
+ requirements.set(ir.requiredInput["@_href"], "inputDataIr");
+ }
+ });
}
+ // external decision
+ else {
+ const externalModel = externalModelsByNamespace?.[decisionHref.namespace];
+ if (externalModel?.type !== "dmn") {
+ throw new Error(`DMN MUTATION: External model with namespace ${decisionHref.namespace} is not a DMN.`);
+ }
- (drgElement.informationRequirement ?? []).flatMap((ir) => {
- if (ir.requiredDecision) {
- requirements.set(ir.requiredDecision["@_href"], "decisionIr");
- } else if (ir.requiredInput) {
- requirements.set(ir.requiredInput["@_href"], "inputDataIr");
+ const externalDecision = externalModel.model.definitions.drgElement?.find(
+ (drgElement) => drgElement["@_id"] === decisionHref.id
+ );
+ if (externalDecision?.__$$element !== "decision") {
+ throw new Error(
+ `DMN MUTATION: Node inside Decision Service is not a Decision. ID: ${externalDecision?.["@_id"]}`
+ );
}
- });
+
+ (externalDecision.informationRequirement ?? []).forEach((ir) => {
+ if (ir.requiredDecision) {
+ const requirementHref = parseXmlHref(ir.requiredDecision["@_href"]);
+ // If the requiredDecision has namespace, it means that it is pointing to a node in a 3rd model,
+ // not this one (the local model) neither the model in the `href.namespace`.
+ if (requirementHref.namespace) {
+ requirements.set(ir.requiredDecision["@_href"], "decisionIr");
+ } else {
+ requirements.set(
+ buildXmlHref({ namespace: externalModel.model.definitions["@_namespace"], id: requirementHref.id }),
+ "decisionIr"
+ );
+ }
+ } else if (ir.requiredInput) {
+ // If the requiredInput has namespace, it means that it is pointing to a node in a 3rd model,
+ // not this one (the local model) neither the model in the `href.namespace`.
+ const requirementHref = parseXmlHref(ir.requiredInput["@_href"]);
+ if (requirementHref.namespace) {
+ requirements.set(ir.requiredInput["@_href"], "inputDataIr");
+ } else {
+ requirements.set(
+ buildXmlHref({ namespace: externalModel.model.definitions["@_namespace"], id: requirementHref.id }),
+ "inputDataIr"
+ );
+ }
+ } else {
+ throw new Error(
+ `DMN MUTATION: Invalid information requirement referenced by external DecisionService: '${externalDecision["@_id"]}'`
+ );
+ }
+ });
+ }
}
// START - Remove outdated requirements
diff --git a/packages/dmn-editor/src/normalization/autoGenerateDrd.ts b/packages/dmn-editor/src/normalization/autoGenerateDrd.ts
index 2053b0c7f9f..5b2ef11a65e 100644
--- a/packages/dmn-editor/src/normalization/autoGenerateDrd.ts
+++ b/packages/dmn-editor/src/normalization/autoGenerateDrd.ts
@@ -206,6 +206,7 @@ export async function autoGenerateDrd(args: {
index: targetNode.data.index,
shapeId: targetNode.data.shape["@_id"],
},
+ externalModelsByNamespace: args.externalModelsByNamespace,
});
}
}
diff --git a/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx b/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx
index 21f29fed530..39822a2f402 100644
--- a/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx
+++ b/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx
@@ -23,6 +23,7 @@ import {
DMN15__tDecision,
DMN15__tDecisionService,
DMN15__tInputData,
+ DMN15__tDefinitions,
} from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
import { ClipboardCopy } from "@patternfly/react-core/dist/js/components/ClipboardCopy";
import { FormGroup } from "@patternfly/react-core/dist/js/components/Form";
@@ -44,6 +45,8 @@ import { buildFeelQNameFromNamespace } from "../feel/buildFeelQName";
import { Alert, AlertVariant } from "@patternfly/react-core/dist/js/components/Alert/Alert";
import { Normalized } from "../normalization/normalize";
import { generateUuid } from "@kie-tools/boxed-expression-component/dist/api";
+import { ExternalDmn } from "../DmnEditor";
+import { Unpacked } from "../tsExt/tsExt";
import { useSettings } from "../settings/DmnEditorSettingsContext";
export type AllKnownDrgElementsByHref = Map<
@@ -66,14 +69,24 @@ export function DecisionServiceProperties({
const thisDmn = useDmnEditorStore((s) => s.dmn);
const { externalModelsByNamespace } = useExternalModels();
- const externalDmnsByNamespace = useDmnEditorStore(
- (s) => s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace).dmns
- );
+
+ const allExternalDmns = Object.entries(externalModelsByNamespace ?? {}).reduce((acc, [namespace, externalModel]) => {
+ if (!externalModel) {
+ console.warn(`DMN EDITOR: Could not find model with namespace '${namespace}'. Ignoring.`);
+ return acc;
+ }
+
+ if (externalModel.type === "dmn") {
+ acc.push(externalModel);
+ }
+
+ return acc;
+ }, new Array<Normalized<ExternalDmn>>());
const allDrgElementsByHref = useMemo(() => {
const ret: AllKnownDrgElementsByHref = new Map();
- const allDmns = [{ model: thisDmn.model }, ...externalDmnsByNamespace.values()];
+ const allDmns = [{ model: thisDmn.model }, ...allExternalDmns.values()];
for (let i = 0; i < allDmns.length; i++) {
const anyDmn = allDmns[i]!;
@@ -90,7 +103,7 @@ export function DecisionServiceProperties({
}
return ret;
- }, [externalDmnsByNamespace, thisDmn]);
+ }, [allExternalDmns, thisDmn]);
const thisDmnsNamespace = useDmnEditorStore((s) => s.dmn.model.definitions["@_namespace"]);
const isReadOnly = settings.isReadOnly || (!!namespace && namespace !== thisDmnsNamespace);
@@ -410,14 +423,17 @@ function DecisionServiceEquivalentFunction({
const dmnObject = allDrgElementsByHref.get(potentialExternalHref);
- return dmnObject
+ const isNamespaceDirectlyIncluded =
+ importsByNamespace.has(resolvedNamespace) || resolvedNamespace === thisDmnsNamespace;
+
+ return dmnObject && isNamespaceDirectlyIncluded
? buildFeelQNameFromNamespace({
namedElement: dmnObject,
importsByNamespace,
namespace: resolvedNamespace,
relativeToNamespace: thisDmnsNamespace,
}).full
- : potentialExternalHref;
+ : buildDisplayNameForDmnObject(dmnObject, resolvedNamespace);
},
[allDrgElementsByHref, decisionServiceNamespace, importsByNamespace, thisDmnsNamespace]
);
@@ -449,3 +465,10 @@ function DecisionServiceEquivalentFunction({
</Alert>
);
}
+
+function buildDisplayNameForDmnObject(
+ dmnObject: Unpacked<Normalized<DMN15__tDefinitions>["drgElement"]> | undefined,
+ namespace: string
+) {
+ return `${namespace.substring(0, 11)}...${namespace.substring(namespace.length - 4)}.${dmnObject?.["@_name"]}`;
+}
diff --git a/packages/dmn-editor/src/propertiesPanel/UnknownProperties.tsx b/packages/dmn-editor/src/propertiesPanel/UnknownProperties.tsx
index 83dfedb4ab0..32782b41c2e 100644
--- a/packages/dmn-editor/src/propertiesPanel/UnknownProperties.tsx
+++ b/packages/dmn-editor/src/propertiesPanel/UnknownProperties.tsx
@@ -34,7 +34,7 @@ export function UnknownProperties(props: { shape: Normalized<DMNDI15__DMNShape>;
const thisDmn = useDmnEditorStore((s) => s.dmn);
const { externalModelsByNamespace } = useExternalModels();
const externalDmnsByNamespace = useDmnEditorStore(
- (s) => s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace).dmns
+ (s) => s.computed(s).getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns
);
const { onRequestToJumpToPath } = useDmnEditor();
diff --git a/packages/dmn-editor/src/store/Store.ts b/packages/dmn-editor/src/store/Store.ts
index 4b89906cc84..702fb19b3ec 100644
--- a/packages/dmn-editor/src/store/Store.ts
+++ b/packages/dmn-editor/src/store/Store.ts
@@ -30,7 +30,7 @@ import { ComputedStateCache } from "./ComputedStateCache";
import { computeAllFeelVariableUniqueNames } from "./computed/computeAllFeelVariableUniqueNames";
import { computeDataTypes } from "./computed/computeDataTypes";
import { computeDiagramData } from "./computed/computeDiagramData";
-import { computeExternalModelsByType } from "./computed/computeExternalModelsByType";
+import { computeDirectlyIncludedExternalModelsByNamespace } from "./computed/computeDirectlyIncludedExternalModelsByNamespace";
import { computeImportsByNamespace } from "./computed/computeImportsByNamespace";
import { computeIndexedDrd } from "./computed/computeIndexes";
import { computeIsDropTargetNodeValidForSelection } from "./computed/computeIsDropTargetNodeValidForSelection";
@@ -142,9 +142,9 @@ export type Computed = {
isDropTargetNodeValidForSelection(e: ExternalModelsIndex | undefined): boolean;
- getExternalModelTypesByNamespace: (
+ getDirectlyIncludedExternalModelsByNamespace: (
e: ExternalModelsIndex | undefined
- ) => ReturnType<typeof computeExternalModelsByType>;
+ ) => ReturnType<typeof computeDirectlyIncludedExternalModelsByNamespace>;
/**
* Get a valid DRD index.
@@ -401,7 +401,7 @@ export function createDmnEditorStore(model: DmnLatestModel, computedCache: Compu
computedCache.cached("getDataTypes", computeDataTypes, [
s.dmn.model.definitions["@_namespace"],
s.dmn.model.definitions.itemDefinition,
- s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace),
+ s.computed(s).getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace),
s.computed(s).importsByNamespace(),
]),
@@ -411,17 +411,18 @@ export function createDmnEditorStore(model: DmnLatestModel, computedCache: Compu
s.dmn.model.definitions.import,
]),
- getExternalModelTypesByNamespace: (externalModelsByNamespace: ExternalModelsIndex | undefined) =>
- computedCache.cached("getExternalModelTypesByNamespace", computeExternalModelsByType, [
- s.dmn.model.definitions.import,
- externalModelsByNamespace,
- ]),
+ getDirectlyIncludedExternalModelsByNamespace: (externalModelsByNamespace: ExternalModelsIndex | undefined) =>
+ computedCache.cached(
+ "getDirectlyIncludedExternalModelsByNamespace",
+ computeDirectlyIncludedExternalModelsByNamespace,
+ [s.dmn.model.definitions.import, externalModelsByNamespace]
+ ),
getDiagramData: (externalModelsByNamespace: ExternalModelsIndex | undefined) =>
computedCache.cached("getDiagramData", computeDiagramData, [
s.diagram,
s.dmn.model.definitions,
- s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace),
+ s.computed(s).getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace),
s.computed(s).indexedDrd(),
s.computed(s).isAlternativeInputDataShape(),
]),
diff --git a/packages/dmn-editor/src/store/computed/computeContainingDecisionServiceHrefsByDecisionHrefs.ts.ts b/packages/dmn-editor/src/store/computed/computeContainingDecisionServiceHrefsByDecisionHrefs.ts.ts
index 6365fa7fb88..3ab2a584842 100644
--- a/packages/dmn-editor/src/store/computed/computeContainingDecisionServiceHrefsByDecisionHrefs.ts.ts
+++ b/packages/dmn-editor/src/store/computed/computeContainingDecisionServiceHrefsByDecisionHrefs.ts.ts
@@ -23,45 +23,44 @@ import { State } from "../Store";
export function computeContainingDecisionServiceHrefsByDecisionHrefs({
thisDmnsNamespace,
- drgElementsNamespace,
- drgElements,
+ drgElementsByNamespace,
}: {
thisDmnsNamespace: string;
- drgElementsNamespace: string;
- drgElements: State["dmn"]["model"]["definitions"]["drgElement"];
+ drgElementsByNamespace: Map<string, State["dmn"]["model"]["definitions"]["drgElement"]>;
}) {
- drgElements ??= [];
const decisionServiceHrefsByDecisionHrefs = new Map<string, string[]>();
- for (const drgElement of drgElements) {
- const drgElementHref = buildXmlHref({
- namespace: drgElementsNamespace === thisDmnsNamespace ? "" : drgElementsNamespace,
- id: drgElement["@_id"]!,
- });
-
- // Decision
- if (drgElement.__$$element === "decision") {
- decisionServiceHrefsByDecisionHrefs.set(
- drgElementHref,
- decisionServiceHrefsByDecisionHrefs.get(drgElementHref) ?? []
- );
- }
- // DS
- else if (drgElement.__$$element === "decisionService") {
- const { containedDecisionHrefsRelativeToThisDmn } = getDecisionServicePropertiesRelativeToThisDmn({
- thisDmnsNamespace,
- decisionServiceNamespace: drgElementsNamespace,
- decisionService: drgElement,
+ for (const [drgElementsNamespace, drgElements] of drgElementsByNamespace) {
+ for (const drgElement of drgElements ?? []) {
+ const drgElementHref = buildXmlHref({
+ namespace: drgElementsNamespace === thisDmnsNamespace ? "" : drgElementsNamespace,
+ id: drgElement["@_id"]!,
});
- for (const containedDecisionHref of containedDecisionHrefsRelativeToThisDmn) {
- decisionServiceHrefsByDecisionHrefs.set(containedDecisionHref, [
- ...(decisionServiceHrefsByDecisionHrefs.get(containedDecisionHref) ?? []),
+ // Decision
+ if (drgElement.__$$element === "decision") {
+ decisionServiceHrefsByDecisionHrefs.set(
drgElementHref,
- ]);
+ decisionServiceHrefsByDecisionHrefs.get(drgElementHref) ?? []
+ );
+ }
+ // DS
+ else if (drgElement.__$$element === "decisionService") {
+ const { containedDecisionHrefsRelativeToThisDmn } = getDecisionServicePropertiesRelativeToThisDmn({
+ thisDmnsNamespace,
+ decisionServiceNamespace: drgElementsNamespace,
+ decisionService: drgElement,
+ });
+
+ for (const containedDecisionHref of containedDecisionHrefsRelativeToThisDmn) {
+ decisionServiceHrefsByDecisionHrefs.set(containedDecisionHref, [
+ ...(decisionServiceHrefsByDecisionHrefs.get(containedDecisionHref) ?? []),
+ drgElementHref,
+ ]);
+ }
+ } else {
+ // Ignore other elements
}
- } else {
- // Ignore other elements
}
}
diff --git a/packages/dmn-editor/src/store/computed/computeDataTypes.ts b/packages/dmn-editor/src/store/computed/computeDataTypes.ts
index baa3c388179..4670400ad60 100644
--- a/packages/dmn-editor/src/store/computed/computeDataTypes.ts
+++ b/packages/dmn-editor/src/store/computed/computeDataTypes.ts
@@ -29,7 +29,7 @@ import { Normalized } from "../../normalization/normalize";
export function computeDataTypes(
namespace: State["dmn"]["model"]["definitions"]["@_namespace"],
itemDefinitions: State["dmn"]["model"]["definitions"]["itemDefinition"],
- externalModelTypesByNamespace: TypeOrReturnType<Computed["getExternalModelTypesByNamespace"]>,
+ externalModelTypesByNamespace: TypeOrReturnType<Computed["getDirectlyIncludedExternalModelsByNamespace"]>,
thisDmnsImportsByNamespace: TypeOrReturnType<Computed["importsByNamespace"]>
) {
const allDataTypesById: DataTypeIndex = new Map();
diff --git a/packages/dmn-editor/src/store/computed/computeDiagramData.ts b/packages/dmn-editor/src/store/computed/computeDiagramData.ts
index 69552fa0412..0c15d4839cd 100644
--- a/packages/dmn-editor/src/store/computed/computeDiagramData.ts
+++ b/packages/dmn-editor/src/store/computed/computeDiagramData.ts
@@ -64,7 +64,7 @@ type AckNode = (
export function computeDiagramData(
diagram: State["diagram"],
definitions: State["dmn"]["model"]["definitions"],
- externalModelTypesByNamespace: TypeOrReturnType<Computed["getExternalModelTypesByNamespace"]>,
+ externalModelTypesByNamespace: TypeOrReturnType<Computed["getDirectlyIncludedExternalModelsByNamespace"]>,
indexedDrd: TypeOrReturnType<Computed["indexedDrd"]>,
isAlternativeInputDataShape: boolean
) {
@@ -218,17 +218,17 @@ export function computeDiagramData(
};
if (dmnObject?.__$$element === "decisionService") {
- const { containedDecisionHrefsRelativeToThisDmn } = getDecisionServicePropertiesRelativeToThisDmn({
- thisDmnsNamespace: definitions["@_namespace"],
- decisionServiceNamespace: dmnObjectNamespace ?? definitions["@_namespace"],
- decisionService: dmnObject,
- });
-
- for (let i = 0; i < containedDecisionHrefsRelativeToThisDmn.length; i++) {
- parentIdsById.set(containedDecisionHrefsRelativeToThisDmn[i], data);
- }
+ if (!shape["@_isCollapsed"]) {
+ const { containedDecisionHrefsRelativeToThisDmn } = getDecisionServicePropertiesRelativeToThisDmn({
+ thisDmnsNamespace: definitions["@_namespace"],
+ decisionServiceNamespace: dmnObjectNamespace ?? definitions["@_namespace"],
+ decisionService: dmnObject,
+ });
- if (shape["@_isCollapsed"]) {
+ for (let i = 0; i < containedDecisionHrefsRelativeToThisDmn.length; i++) {
+ parentIdsById.set(containedDecisionHrefsRelativeToThisDmn[i], data);
+ }
+ } else {
newNode.style = {
...newNode.style,
...DECISION_SERVICE_COLLAPSED_DIMENSIONS,
@@ -280,11 +280,26 @@ export function computeDiagramData(
namespace,
(externalDmn.model.definitions.drgElement ?? []).reduce(
(acc, e, index) => acc.set(e["@_id"]!, { element: e, index }),
- new Map<string, { index: number; element: Unpacked<Normalized<DMN15__tDefinitions>["drgElement"]> }>()
+ new Map<
+ string,
+ {
+ index: number;
+ element: Unpacked<Normalized<DMN15__tDefinitions>["drgElement"]>;
+ }
+ >()
)
);
},
- new Map<string, Map<string, { index: number; element: Unpacked<Normalized<DMN15__tDefinitions>["drgElement"]> }>>()
+ new Map<
+ string,
+ Map<
+ string,
+ {
+ index: number;
+ element: Unpacked<Normalized<DMN15__tDefinitions>["drgElement"]>;
+ }
+ >
+ >()
);
const externalNodes = [...indexedDrd.dmnShapesByHref.entries()].flatMap(([href, shape]) => {
@@ -360,7 +375,10 @@ export function computeDiagramData(
const parentNodeData = parentIdsById.get(sortedNodes[i].id);
if (parentNodeData) {
sortedNodes[i].data.parentRfNode = nodesById.get(
- buildXmlHref({ namespace: parentNodeData.dmnObjectNamespace, id: parentNodeData.dmnObjectQName.localPart })
+ buildXmlHref({
+ namespace: parentNodeData.dmnObjectNamespace,
+ id: parentNodeData.dmnObjectQName.localPart,
+ })
);
sortedNodes[i].extent = undefined; // Allows the node to be dragged freely outside of parent's bounds.
sortedNodes[i].zIndex = NODE_LAYERS.NESTED_NODES;
diff --git a/packages/dmn-editor/src/store/computed/computeExternalModelsByType.ts b/packages/dmn-editor/src/store/computed/computeDirectlyIncludedExternalModelsByNamespace.ts
similarity index 96%
rename from packages/dmn-editor/src/store/computed/computeExternalModelsByType.ts
rename to packages/dmn-editor/src/store/computed/computeDirectlyIncludedExternalModelsByNamespace.ts
index b62fec76e2b..eb38842ab14 100644
--- a/packages/dmn-editor/src/store/computed/computeExternalModelsByType.ts
+++ b/packages/dmn-editor/src/store/computed/computeDirectlyIncludedExternalModelsByNamespace.ts
@@ -21,7 +21,7 @@ import { ExternalDmnsIndex, ExternalModelsIndex, ExternalPmmlsIndex } from "../.
import { getNamespaceOfDmnImport } from "../../includedModels/importNamespaces";
import { State } from "../Store";
-export function computeExternalModelsByType(
+export function computeDirectlyIncludedExternalModelsByNamespace(
imports: State["dmn"]["model"]["definitions"]["import"],
externalModelsByNamespace: ExternalModelsIndex | undefined
) {
diff --git a/packages/dmn-editor/src/store/computed/initial.ts b/packages/dmn-editor/src/store/computed/initial.ts
index 7efe970d57e..63d2ad32cb6 100644
--- a/packages/dmn-editor/src/store/computed/initial.ts
+++ b/packages/dmn-editor/src/store/computed/initial.ts
@@ -45,7 +45,7 @@ export const INITIAL_COMPUTED_CACHE: Cache<Computed> = {
value: undefined,
dependencies: [],
},
- getExternalModelTypesByNamespace: {
+ getDirectlyIncludedExternalModelsByNamespace: {
value: undefined,
dependencies: [],
},
diff --git a/packages/dmn-editor/stories/dev/DevWebApp.stories.tsx b/packages/dmn-editor/stories/dev/DevWebApp.stories.tsx
index bec763d7f81..56d74dee53c 100644
--- a/packages/dmn-editor/stories/dev/DevWebApp.stories.tsx
+++ b/packages/dmn-editor/stories/dev/DevWebApp.stories.tsx
@@ -18,20 +18,19 @@
*/
import * as React from "react";
-import type { Meta, StoryObj } from "@storybook/react";
import { useCallback, useMemo, useRef, useState } from "react";
+import type { Meta, StoryObj } from "@storybook/react";
import "@patternfly/react-core/dist/styles/base.css";
import { Flex, FlexItem } from "@patternfly/react-core/dist/js/layouts/Flex";
import { Page, PageSection } from "@patternfly/react-core/dist/js/components/Page";
-import { DmnLatestModel, getMarshaller, DmnMarshaller } from "@kie-tools/dmn-marshaller";
-import { Normalized, normalize } from "@kie-tools/dmn-editor/dist/normalization/normalize";
+import { DmnLatestModel, DmnMarshaller, getMarshaller } from "@kie-tools/dmn-marshaller";
+import { normalize, Normalized } from "@kie-tools/dmn-editor/dist/normalization/normalize";
import { availableModelsByPath, modelsByNamespace } from "./availableModelsToInclude";
import { generateEmptyDmn15 } from "../misc/empty/Empty.stories";
import { loanPreQualificationDmn } from "../useCases/loanPreQualification/LoanPreQualification.stories";
import { DmnEditorWrapper } from "../dmnEditorStoriesWrapper";
import {
DmnEditorProps,
- DmnEditorRef,
ExternalModelsIndex,
OnDmnModelChange,
OnRequestExternalModelByPath,
diff --git a/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/EmptyWithAvailableExternalModels.mdx b/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/EmptyWithAvailableExternalModels.mdx
new file mode 100644
index 00000000000..f7e21c174ae
--- /dev/null
+++ b/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/EmptyWithAvailableExternalModels.mdx
@@ -0,0 +1,10 @@
+import EmptyWithAvailableExternalModelsStories from "./EmptyWithAvailableExternalModels.stories";
+import { Meta } from "@storybook/blocks";
+
+<Meta title="MDX/Misc/EmptyWithAvailableExternalModelsStories" of={EmptyWithAvailableExternalModelsStories} />
+
+## Empty With Available External Models
+
+When user starts with an empty diagram, a wizard for quick content initialization is displayed.
+The wizard enables to create simple diagram with one input and one output or a diagram with one decision table expression.
+This model also have some external models available to be included.
diff --git a/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/EmptyWithAvailableExternalModels.stories.tsx b/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/EmptyWithAvailableExternalModels.stories.tsx
new file mode 100644
index 00000000000..388de3a4ff7
--- /dev/null
+++ b/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/EmptyWithAvailableExternalModels.stories.tsx
@@ -0,0 +1,134 @@
+/*
+ * 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 { useCallback, useMemo, useState } from "react";
+import type { Meta, StoryObj } from "@storybook/react";
+import { DmnLatestModel, DmnMarshaller, getMarshaller } from "@kie-tools/dmn-marshaller";
+import { ns as dmn15ns } from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/meta";
+import { generateUuid } from "@kie-tools/boxed-expression-component/dist/api";
+import { DMN15_SPEC } from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/Dmn15Spec";
+import {
+ DmnEditor,
+ DmnEditorProps,
+ ExternalModelsIndex,
+ OnRequestExternalModelByPath,
+ OnRequestExternalModelsAvailableToInclude,
+ OnDmnModelChange,
+} from "@kie-tools/dmn-editor/dist/DmnEditor";
+import { normalize, Normalized } from "@kie-tools/dmn-editor/dist/normalization/normalize";
+
+import { DmnEditorWrapper, StorybookDmnEditorProps } from "../../dmnEditorStoriesWrapper";
+
+import { availableModelsByPath, modelsByNamespace } from "./availableModelsToInclude";
+
+export const generateEmptyDmn15 = () => `<?xml version="1.0" encoding="UTF-8"?>
+<definitions
+ xmlns="${dmn15ns.get("")}"
+ expressionLanguage="${DMN15_SPEC.expressionLanguage.default}"
+ namespace="https://kie.apache.org/dmn/${generateUuid()}"
+ id="${generateUuid()}"
+ name="DMN${generateUuid()}">
+</definitions>`;
+
+const initialModel = generateEmptyDmn15();
+
+function EmptyStoryWithIncludedModels(args: DmnEditorProps) {
+ const [state, setState] = useState<{
+ marshaller: DmnMarshaller;
+ stack: Normalized<DmnLatestModel>[];
+ pointer: number;
+ }>(() => {
+ const initialDmnMarshaller = getMarshaller(initialModel, { upgradeTo: "latest" });
+ return {
+ marshaller: initialDmnMarshaller,
+ stack: [normalize(initialDmnMarshaller.parser.parse())],
+ pointer: 0,
+ };
+ });
+
+ const currentModel = state.stack[state.pointer];
+
+ const externalModelsByNamespace = useMemo<ExternalModelsIndex>(() => {
+ return (currentModel.definitions.import ?? []).reduce((acc, i) => {
+ acc[i["@_namespace"]] = modelsByNamespace[i["@_namespace"]];
+ return acc;
+ }, {} as ExternalModelsIndex);
+ }, [currentModel.definitions.import]);
+
+ const onRequestExternalModelByPath = useCallback<OnRequestExternalModelByPath>(async (path) => {
+ return availableModelsByPath[path] ?? null;
+ }, []);
+
+ const onRequestExternalModelsAvailableToInclude = useCallback<OnRequestExternalModelsAvailableToInclude>(async () => {
+ return Object.keys(availableModelsByPath);
+ }, []);
+
+ const onModelChange = useCallback<OnDmnModelChange>((model) => {
+ setState((prev) => {
+ const newStack = prev.stack.slice(0, prev.pointer + 1);
+ return {
+ ...prev,
+ stack: [...newStack, model],
+ pointer: newStack.length,
+ };
+ });
+ }, []);
+
+ return (
+ <>
+ {DmnEditorWrapper({
+ model: currentModel,
+ originalVersion: args.originalVersion,
+ onModelChange,
+ onRequestExternalModelByPath,
+ onRequestExternalModelsAvailableToInclude,
+ externalModelsByNamespace: externalModelsByNamespace,
+ externalContextName: args.externalContextName,
+ externalContextDescription: args.externalContextDescription,
+ validationMessages: args.validationMessages,
+ evaluationResults: args.evaluationResults,
+ issueTrackerHref: args.issueTrackerHref,
+ })}
+ </>
+ );
+}
+
+const meta: Meta<DmnEditorProps> = {
+ title: "Misc/EmptyWithAvailableExternalModels",
+ component: DmnEditor,
+ includeStories: /^[A-Z]/,
+};
+
+export default meta;
+type Story = StoryObj<typeof EmptyStoryWithIncludedModels>;
+
+export const EmptyWithAvailableExternalModels: Story = {
+ render: (args) => EmptyStoryWithIncludedModels(args),
+ args: {
+ model: getMarshaller(initialModel, { upgradeTo: "latest" }).parser.parse(),
+ originalVersion: "1.5",
+ evaluationResults: {},
+ externalContextDescription: "External context description",
+ externalContextName: "Storybook - DMN Editor",
+ externalModelsByNamespace: {},
+ issueTrackerHref: "",
+ validationMessages: {},
+ },
+};
diff --git a/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/availableModelsToInclude.ts b/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/availableModelsToInclude.ts
new file mode 100644
index 00000000000..46bb7291be0
--- /dev/null
+++ b/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/availableModelsToInclude.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.
+ */
+
+import { getMarshaller } from "@kie-tools/dmn-marshaller";
+import { normalize } from "@kie-tools/dmn-editor/dist/normalization/normalize";
+import { XML2PMML } from "@kie-tools/pmml-editor-marshaller";
+import * as DmnEditor from "@kie-tools/dmn-editor/dist/DmnEditor";
+import { getPmmlNamespace } from "@kie-tools/dmn-editor/dist/pmml/pmml";
+import { sumBkm, sumDiffDs, testTreePmml } from "./externalModels";
+
+export const sumBkmModel = normalize(getMarshaller(sumBkm, { upgradeTo: "latest" }).parser.parse());
+export const sumDiffDsModel = normalize(getMarshaller(sumDiffDs, { upgradeTo: "latest" }).parser.parse());
+export const testTreePmmlModel = XML2PMML(testTreePmml);
+
+export const availableModels: DmnEditor.ExternalModel[] = [
+ {
+ type: "dmn",
+ model: sumBkmModel,
+ svg: "",
+ normalizedPosixPathRelativeToTheOpenFile: "dev-webapp/available-models-to-include/sumBkm.dmn",
+ },
+ {
+ type: "dmn",
+ model: sumDiffDsModel,
+ svg: "",
+ normalizedPosixPathRelativeToTheOpenFile: "dev-webapp/available-models-to-include/sumDiffDs.dmn",
+ },
+ {
+ type: "dmn",
+ model: normalize(
+ getMarshaller(`<definitions xmlns="https://www.omg.org/spec/DMN/20230324/MODEL/" />`, {
+ upgradeTo: "latest",
+ }).parser.parse()
+ ),
+ svg: "",
+ normalizedPosixPathRelativeToTheOpenFile: "dev-webapp/available-models-to-include/empty.dmn",
+ },
+ {
+ type: "pmml",
+ model: testTreePmmlModel,
+ normalizedPosixPathRelativeToTheOpenFile: "dev-webapp/available-models-to-include/testTree.pmml",
+ },
+];
+
+export const availableModelsByPath: Record<string, DmnEditor.ExternalModel> = Object.values(availableModels).reduce(
+ (acc, v) => {
+ acc[v.normalizedPosixPathRelativeToTheOpenFile] = v;
+ return acc;
+ },
+ {} as Record<string, DmnEditor.ExternalModel>
+);
+
+export const modelsByNamespace = Object.values(availableModels).reduce((acc, v) => {
+ if (v.type === "dmn") {
+ acc[v.model.definitions["@_namespace"]] = v;
+ } else if (v.type === "pmml") {
+ acc[getPmmlNamespace({ normalizedPosixPathRelativeToTheOpenFile: v.normalizedPosixPathRelativeToTheOpenFile })] = v;
+ }
+ return acc;
+}, {} as DmnEditor.ExternalModelsIndex);
diff --git a/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/externalModels.ts b/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/externalModels.ts
new file mode 100644
index 00000000000..0c293ed34b9
--- /dev/null
+++ b/packages/dmn-editor/stories/misc/emptyWithAvailableExternalModels/externalModels.ts
@@ -0,0 +1,217 @@
+/*
+ * 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 const sumBkm = `<definitions
+ xmlns="https://www.omg.org/spec/DMN/20230324/MODEL/"
+ 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.apache.org/dmn/extensions/1.0"
+ expressionLanguage="https://www.omg.org/spec/DMN/20211108/FEEL/"
+ typeLanguage="http://www.omg.org/spec/DMN/20211108/FEEL/"
+ namespace="https://kie.apache.org/dmn/_923784BD-CD31-488A-9C31-C1A83C5483C0"
+ id="_0E6F4D88-B955-404B-A31A-72BB7E1E6A77"
+ name="Sum BKM">
+<businessKnowledgeModel name="Sum BKM" id="_3646600D-035F-47B9-8DC6-3FABE844234F">
+ <variable name="New BKM" id="_94BAC9FD-8519-49F9-855A-F4FCBACD2A04" typeRef="number" />
+ <functionDefinition label="Sum BKM" kind="FEEL" typeRef="number">
+ <formalParameter id="_F3A70A69-09FF-417C-8F6C-BD9129231377" name="a" typeRef="number" />
+ <formalParameter id="_E51B1C6E-896B-41C0-B03E-85B3CD7B6655" name="b" typeRef="number" />
+ <literalExpression id="_FA1EE22B-CC6B-499F-95EE-68158A2F233B" label="Expression Name" typeRef="<Undefined>">
+ <text>a + b</text>
+ </literalExpression>
+ </functionDefinition>
+</businessKnowledgeModel>
+<dmndi:DMNDI>
+ <dmndi:DMNDiagram>
+ <dmndi:DMNShape id="_3506A087-AB60-428D-9C75-1E5F5D6F0947" dmnElementRef="_3646600D-035F-47B9-8DC6-3FABE844234F" isCollapsed="false" isListedInputData="false">
+ <dc:Bounds x="40" y="60" width="160" height="80" />
+ </dmndi:DMNShape>
+ <di:extension>
+ <kie:ComponentsWidthsExtension>
+ <kie:ComponentWidths dmnElementRef="_FA1EE22B-CC6B-499F-95EE-68158A2F233B">
+ <kie:width>190</kie:width>
+ </kie:ComponentWidths>
+ </kie:ComponentsWidthsExtension>
+ </di:extension>
+ </dmndi:DMNDiagram>
+</dmndi:DMNDI>
+</definitions>
+`;
+
+export const sumDiffDs = `<definitions
+ xmlns="https://www.omg.org/spec/DMN/20230324/MODEL/"
+ 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.apache.org/dmn/extensions/1.0"
+ expressionLanguage="https://www.omg.org/spec/DMN/20211108/FEEL/"
+ typeLanguage="http://www.omg.org/spec/DMN/20211108/FEEL/"
+ namespace="https://kie.apache.org/dmn/_D19B0015-2CBD-4BA8-84A9-5F554D84A9E1"
+ id="_F360CECB-5DF2-4546-A7A9-E5ECF0F5A872"
+ name="Sum and Diff DS">
+<decisionService name="Sum and Diff DS" id="_721B7634-7227-42B4-AAA4-17DE60A7A967">
+ <variable name="New Decision Service" id="_A996D4BD-CF69-4CC5-89C0-37CF2BD6BA6E" typeRef="context" />
+ <inputData href="#_18FAE913-2B2A-479D-A53A-14A2D57C46E4" />
+ <inputData href="#_0D6E83A0-171F-4D7C-B0DF-8464DDE05FD1" />
+ <outputDecision href="#_1991FB34-1253-4A54-AD3D-89697938DDFA" />
+ <outputDecision href="#_05621ED4-9236-47F1-B93A-164A4527B136" />
+</decisionService>
+<decision name="Sum" id="_05621ED4-9236-47F1-B93A-164A4527B136">
+ <variable name="New Decision" id="_88ECA891-3837-40E6-BCBF-77BF24B26FD0" typeRef="number" />
+ <informationRequirement id="_81BD49F4-E279-48A4-AC71-89CE92EEDE9C">
+ <requiredInput href="#_18FAE913-2B2A-479D-A53A-14A2D57C46E4" />
+ </informationRequirement>
+ <informationRequirement id="_115B4C89-EBA8-4136-A70E-9D303D8E92C5">
+ <requiredInput href="#_0D6E83A0-171F-4D7C-B0DF-8464DDE05FD1" />
+ </informationRequirement>
+ <literalExpression id="_2812E0EA-FD32-480F-89CD-D0FE5FA02172" label="Sum" typeRef="<Undefined>">
+ <text>a + b</text>
+ </literalExpression>
+</decision>
+<inputData name="a" id="_18FAE913-2B2A-479D-A53A-14A2D57C46E4">
+ <variable name="New Input Data" id="_250DB850-97F5-45C8-AFBB-207218BDAF7E" typeRef="number" />
+</inputData>
+<inputData name="b" id="_0D6E83A0-171F-4D7C-B0DF-8464DDE05FD1">
+ <variable name="New Input Data" id="_0FD3E491-93E4-4AC1-AB46-19A6C6A3E4F1" typeRef="number" />
+</inputData>
+<decision name="Diff" id="_1991FB34-1253-4A54-AD3D-89697938DDFA">
+ <variable name="New Decision" id="_47AD6FB1-1892-4E27-B46D-0AF95B18602E" typeRef="number" />
+ <informationRequirement id="_5CA8EA11-FDFF-410A-AB4F-AA7ACB9F7FDC">
+ <requiredInput href="#_18FAE913-2B2A-479D-A53A-14A2D57C46E4" />
+ </informationRequirement>
+ <informationRequirement id="_D9E5FB0F-1A8C-4733-BBAB-E4A55113F806">
+ <requiredInput href="#_0D6E83A0-171F-4D7C-B0DF-8464DDE05FD1" />
+ </informationRequirement>
+ <literalExpression id="_C6BBFC2E-695F-4668-8A0E-D175FE7302C7" label="Diff" typeRef="<Undefined>">
+ <text>a - b</text>
+ </literalExpression>
+</decision>
+<itemDefinition id="_842F4E16-C9A2-47D4-A221-081673B98C2D" name="SumDiffContext" isCollection="false">
+ <itemComponent id="_4882D4FB-2146-4198-8640-33B951B893A7" name="Sum" isCollection="false">
+ <typeRef>number</typeRef>
+ </itemComponent>
+ <itemComponent id="_3EF27B7D-3378-429E-B6DB-B856F5EB0AE7" name="Diff" isCollection="false">
+ <typeRef>number</typeRef>
+ </itemComponent>
+</itemDefinition>
+<itemDefinition id="_2B4E9593-3239-4E04-A213-345F0AA0AF9E" name="Fine" isCollection="false">
+ <itemComponent id="_3EF27B7D-3378-429E-B6DB-B856F5EB0AEE" name="Value" isCollection="false">
+ <typeRef>number</typeRef>
+ <allowedValues id="_5BD13D9D-412F-4E6B-914A-3D8AAAC6A701">
+ <text>100,500,1000</text>
+ </allowedValues>
+ </itemComponent>
+</itemDefinition>
+<dmndi:DMNDI>
+ <dmndi:DMNDiagram>
+ <dmndi:DMNShape id="_495CB0A7-017A-4A2F-A4D7-3133C82708E0" dmnElementRef="_721B7634-7227-42B4-AAA4-17DE60A7A967" isCollapsed="false" isListedInputData="false">
+ <dc:Bounds x="80" y="120" width="520" height="320" />
+ <dmndi:DMNDecisionServiceDividerLine>
+ <di:waypoint x="80" y="280" />
+ <di:waypoint x="500" y="280" />
+ </dmndi:DMNDecisionServiceDividerLine>
+ </dmndi:DMNShape>
+ <dmndi:DMNShape id="_80C42026-FF56-44C6-84F9-F7A03686F5E8" dmnElementRef="_05621ED4-9236-47F1-B93A-164A4527B136" isCollapsed="false" isListedInputData="false">
+ <dc:Bounds x="140" y="180" width="160" height="80" />
+ </dmndi:DMNShape>
+ <dmndi:DMNShape id="_218ED5E8-FB01-417F-A1F6-867D60BCBF19" dmnElementRef="_18FAE913-2B2A-479D-A53A-14A2D57C46E4" isCollapsed="false" isListedInputData="false">
+ <dc:Bounds x="80" y="480" width="160" height="80" />
+ </dmndi:DMNShape>
+ <dmndi:DMNShape id="_7C134BBF-2B96-4C9A-B985-D1FE10DC30D2" dmnElementRef="_0D6E83A0-171F-4D7C-B0DF-8464DDE05FD1" isCollapsed="false" isListedInputData="false">
+ <dc:Bounds x="440" y="480" width="160" height="80" />
+ </dmndi:DMNShape>
+ <dmndi:DMNEdge id="_768C5D6E-9A41-4AA3-921C-A545E8F11597" dmnElementRef="_81BD49F4-E279-48A4-AC71-89CE92EEDE9C" sourceElement="_218ED5E8-FB01-417F-A1F6-867D60BCBF19" targetElement="_80C42026-FF56-44C6-84F9-F7A03686F5E8">
+ <di:waypoint x="160" y="520" />
+ <di:waypoint x="220" y="260" />
+ </dmndi:DMNEdge>
+ <dmndi:DMNEdge id="_79E2E532-8D84-40C9-91DA-07EA606798E5" dmnElementRef="_115B4C89-EBA8-4136-A70E-9D303D8E92C5" sourceElement="_7C134BBF-2B96-4C9A-B985-D1FE10DC30D2" targetElement="_80C42026-FF56-44C6-84F9-F7A03686F5E8">
+ <di:waypoint x="520" y="520" />
+ <di:waypoint x="220" y="260" />
+ </dmndi:DMNEdge>
+ <dmndi:DMNShape id="_1553EB6C-2BE8-4BC3-8972-A87308B98C86" dmnElementRef="_1991FB34-1253-4A54-AD3D-89697938DDFA" isCollapsed="false" isListedInputData="false">
+ <dc:Bounds x="380" y="180" width="160" height="80" />
+ </dmndi:DMNShape>
+ <dmndi:DMNEdge id="_C6979483-DBBA-4AC0-AB28-E12EFEF08654" dmnElementRef="_5CA8EA11-FDFF-410A-AB4F-AA7ACB9F7FDC" sourceElement="_218ED5E8-FB01-417F-A1F6-867D60BCBF19" targetElement="_1553EB6C-2BE8-4BC3-8972-A87308B98C86">
+ <di:waypoint x="160" y="520" />
+ <di:waypoint x="460" y="260" />
+ </dmndi:DMNEdge>
+ <dmndi:DMNEdge id="_378C5213-084B-4A1D-ADCC-CEFE41F7E9FC" dmnElementRef="_D9E5FB0F-1A8C-4733-BBAB-E4A55113F806" sourceElement="_7C134BBF-2B96-4C9A-B985-D1FE10DC30D2" targetElement="_1553EB6C-2BE8-4BC3-8972-A87308B98C86">
+ <di:waypoint x="520" y="520" />
+ <di:waypoint x="460" y="260" />
+ </dmndi:DMNEdge>
+ <di:extension>
+ <kie:ComponentsWidthsExtension>
+ <kie:ComponentWidths dmnElementRef="_2812E0EA-FD32-480F-89CD-D0FE5FA02172">
+ <kie:width>190</kie:width>
+ </kie:ComponentWidths>
+ <kie:ComponentWidths dmnElementRef="_C6BBFC2E-695F-4668-8A0E-D175FE7302C7">
+ <kie:width>190</kie:width>
+ </kie:ComponentWidths>
+ </kie:ComponentsWidthsExtension>
+ </di:extension>
+ </dmndi:DMNDiagram>
+</dmndi:DMNDI>
+</definitions>
+`;
+// Copied from kogito-examples/kogito-quarkus-examples/dmn-pmml-quarkus-example/src/main/resources
+export const testTreePmml = `<PMML version="4.2" xsi:schemaLocation="http://www.dmg.org/PMML-4_2 http://www.dmg.org/v4-2-1/pmml-4-2.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xmlns="http://www.dmg.org/PMML-4_2">
+<Header>
+ <Application name="Drools-PMML" version="7.0.0-SNAPSHOT" />
+</Header>
+
+<DataDictionary numberOfFields="3">
+ <DataField name="temperature" dataType="double" optype="continuous" />
+ <DataField name="humidity" dataType="double" optype="continuous" />
+ <DataField name="decision" dataType="string" optype="categorical">
+ <Value value="sunglasses" />
+ <Value value="umbrella" />
+ <Value value="nothing" />
+ </DataField>
+</DataDictionary>
+
+<TreeModel modelName="DecisionTree" functionName="classification">
+ <MiningSchema>
+ <MiningField name="temperature" usageType="active" />
+ <MiningField name="humidity" usageType="active" />
+ <MiningField name="decision" usageType="predicted" />
+ </MiningSchema>
+ <Output>
+ <OutputField name="weatherdecision" targetField="decision" />
+ </Output>
+
+ <Node score="nothing" id="1">
+ <True />
+ <Node score="sunglasses" id="2">
+ <CompoundPredicate booleanOperator="and">
+ <SimplePredicate field="temperature" operator="greaterThan" value="25" />
+ <SimplePredicate field="humidity" operator="lessOrEqual" value="20" />
+ </CompoundPredicate>
+ </Node>
+ <Node score="umbrella" id="3">
+ <SimplePredicate field="humidity" operator="greaterThan" value="50" />
+ </Node>
+ <Node score="nothing" id="4">
+ <True />
+ </Node>
+ </Node>
+</TreeModel>
+</PMML>
+`;
diff --git a/packages/dmn-editor/tests-e2e/__fixtures__/base.ts b/packages/dmn-editor/tests-e2e/__fixtures__/base.ts
index 1143c264a87..5c81eb6b0b9 100644
--- a/packages/dmn-editor/tests-e2e/__fixtures__/base.ts
+++ b/packages/dmn-editor/tests-e2e/__fixtures__/base.ts
@@ -41,6 +41,8 @@ import { BeePropertiesPanel } from "./propertiesPanel/beePropertiesPanel";
import { BoxedExpressionEditor } from "@kie-tools/boxed-expression-component/tests-e2e/__fixtures__/boxedExpression";
import { Monaco } from "@kie-tools/boxed-expression-component/tests-e2e/__fixtures__/monaco";
import { ProjectName } from "@kie-tools/playwright-base/projectNames";
+import { Stories } from "./stories";
+import { IncludedModels } from "./includedModels";
type DmnEditorFixtures = {
bee: BoxedExpressionEditor;
@@ -65,6 +67,8 @@ type DmnEditorFixtures = {
knowledgeSourcePropertiesPanel: KnowledgeSourcePropertiesPanel;
multipleNodesPropertiesPanel: MultipleNodesPropertiesPanel;
textAnnotationPropertiesPanel: TextAnnotationPropertiesPanel;
+ stories: Stories;
+ includedModels: IncludedModels;
};
export const test = base.extend<DmnEditorFixtures>({
@@ -134,6 +138,12 @@ export const test = base.extend<DmnEditorFixtures>({
textAnnotationPropertiesPanel: async ({ diagram, page }, use) => {
await use(new TextAnnotationPropertiesPanel(diagram, page));
},
+ stories: async ({ baseURL, page }, use) => {
+ await use(new Stories(page, baseURL));
+ },
+ includedModels: async ({ page }, use) => {
+ await use(new IncludedModels(page));
+ },
});
export { expect } from "@playwright/test";
diff --git a/packages/dmn-editor/tests-e2e/__fixtures__/includedModels.ts b/packages/dmn-editor/tests-e2e/__fixtures__/includedModels.ts
new file mode 100644
index 00000000000..5301356a574
--- /dev/null
+++ b/packages/dmn-editor/tests-e2e/__fixtures__/includedModels.ts
@@ -0,0 +1,54 @@
+/*
+ * 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 { Page } from "@playwright/test";
+
+export class IncludedModels {
+ constructor(private readonly page: Page) {}
+
+ public get() {
+ return this.page.getByTestId("kie-tools--dmn-editor--included-models-container");
+ }
+
+ private getModal() {
+ return this.page.getByTestId("kie-tools--dmn-editor--included-models-modal");
+ }
+
+ public getIncludeModelButton() {
+ return this.get().getByRole("button", { name: "Include model" });
+ }
+
+ public async fillModelToInclude(args: { modelName: string }) {
+ await this.getModal().getByPlaceholder("Select a model to include...").click();
+ await this.getModal().getByPlaceholder("Select a model to include...").fill(args.modelName);
+ }
+
+ public async selectModel(args: { modelName: string }) {
+ await this.page.getByText(args.modelName).click();
+ }
+
+ public async fillModelName(args: { modelName: string }) {
+ await this.getModal().getByPlaceholder("<Default>").click();
+ await this.getModal().getByPlaceholder("<Default>").fill(args.modelName);
+ }
+
+ public async includeModel() {
+ return await this.page.getByRole("button", { name: "Include model" }).click();
+ }
+}
diff --git a/packages/dmn-editor/tests-e2e/__fixtures__/nodes.ts b/packages/dmn-editor/tests-e2e/__fixtures__/nodes.ts
index 82c5546c765..bdaa359a059 100644
--- a/packages/dmn-editor/tests-e2e/__fixtures__/nodes.ts
+++ b/packages/dmn-editor/tests-e2e/__fixtures__/nodes.ts
@@ -140,9 +140,19 @@ export class Nodes {
}
public async move(args: { name: string; targetPosition: { x: number; y: number } }) {
- await this.get({ name: args.name }).dragTo(this.diagram.get(), {
- targetPosition: args.targetPosition,
- });
+ if (args.name.includes(DefaultNodeName.DECISION_SERVICE)) {
+ // Decision Services only have some draggable areas near the borders.
+ // If you drag it to the center, you'll drag the divide line.
+ // Also, neither the entire upper area nor the entire downer area is draggable.
+ await this.get({ name: args.name }).dragTo(this.diagram.get(), {
+ targetPosition: args.targetPosition,
+ sourcePosition: { x: 20, y: 20 },
+ });
+ } else {
+ await this.get({ name: args.name }).dragTo(this.diagram.get(), {
+ targetPosition: args.targetPosition,
+ });
+ }
}
public async rename(args: { current: string; new: string }) {
diff --git a/packages/dmn-editor/tests-e2e/__fixtures__/palette.ts b/packages/dmn-editor/tests-e2e/__fixtures__/palette.ts
index 4fdaedf0d6a..5b55375104a 100644
--- a/packages/dmn-editor/tests-e2e/__fixtures__/palette.ts
+++ b/packages/dmn-editor/tests-e2e/__fixtures__/palette.ts
@@ -40,6 +40,21 @@ export class Palette {
}
}
+ public async dragExternalNode(args: {
+ includedModelName: string;
+ nodeName: string;
+ targetPosition: { x: number; y: number };
+ }) {
+ await this.page
+ .getByTestId("kie-tools--dmn-editor--external-nodes-popover")
+ .getByTestId(`kie-tools--dmn-editor--external-node-${args.includedModelName}-${args.nodeName}`)
+ .dragTo(this.diagram.get(), { targetPosition: args.targetPosition });
+ }
+
+ public async toggleExternalNodesPanel() {
+ await this.page.getByRole("button", { name: "External nodes" }).click();
+ }
+
private getNewNodeProperties(type: NodeType) {
switch (type) {
case NodeType.INPUT_DATA:
diff --git a/packages/dmn-editor/tests-e2e/__fixtures__/stories.ts b/packages/dmn-editor/tests-e2e/__fixtures__/stories.ts
new file mode 100644
index 00000000000..301188efe55
--- /dev/null
+++ b/packages/dmn-editor/tests-e2e/__fixtures__/stories.ts
@@ -0,0 +1,38 @@
+/*
+ * 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 { Page } from "@playwright/test";
+
+export class Stories {
+ constructor(
+ private readonly page: Page,
+ private readonly baseURL?: string
+ ) {}
+
+ private getIframeURL(iframeId: string) {
+ return `iframe.html?id=${iframeId}&viewMode=story`;
+ }
+
+ public async openEmptyWithAvailableExternalModels() {
+ await this.page.goto(
+ `${this.baseURL}/${this.getIframeURL(`misc-emptywithavailableexternalmodels--empty-with-available-external-models`)}` ??
+ ""
+ );
+ }
+}
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/add-external-node-inside-decision-service.png b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/add-external-node-inside-decision-service.png
new file mode 100644
index 00000000000..fbaeb34075e
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/add-external-node-inside-decision-service.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/delete-external-node-inside-decision-service-after-delete.png b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/delete-external-node-inside-decision-service-after-delete.png
new file mode 100644
index 00000000000..6e352cbf62e
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/delete-external-node-inside-decision-service-after-delete.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/delete-external-node-inside-decision-service-before-delete.png b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/delete-external-node-inside-decision-service-before-delete.png
new file mode 100644
index 00000000000..ca04d943acf
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/delete-external-node-inside-decision-service-before-delete.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-from-ds-to-another-ds-after-move.png b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-from-ds-to-another-ds-after-move.png
new file mode 100644
index 00000000000..e9eddcaace8
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-from-ds-to-another-ds-after-move.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-from-ds-to-another-ds-before-move.png b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-from-ds-to-another-ds-before-move.png
new file mode 100644
index 00000000000..5e600c04fce
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-from-ds-to-another-ds-before-move.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-inside-decision-service-after-move-to-out.png b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-inside-decision-service-after-move-to-out.png
new file mode 100644
index 00000000000..e54b0c1908f
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-inside-decision-service-after-move-to-out.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-inside-decision-service-before-move-to-out.png b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-inside-decision-service-before-move-to-out.png
new file mode 100644
index 00000000000..d6029255909
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-inside-decision-service-before-move-to-out.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-out-from-decision-service-after.png b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-out-from-decision-service-after.png
new file mode 100644
index 00000000000..d986c13f00b
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-out-from-decision-service-after.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-out-from-decision-service-before.png b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-out-from-decision-service-before.png
new file mode 100644
index 00000000000..d6029255909
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/move-external-decision-out-from-decision-service-before.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/resize-external-node-inside-decision-service-after-resizing.png b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/resize-external-node-inside-decision-service-after-resizing.png
new file mode 100644
index 00000000000..e3065c65ff8
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/resize-external-node-inside-decision-service-after-resizing.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/resize-external-node-inside-decision-service-before-resizing.png b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/resize-external-node-inside-decision-service-before-resizing.png
new file mode 100644
index 00000000000..ca04d943acf
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/Google-Chrome/drgElements/resize-external-node-inside-decision-service-before-resizing.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/add-external-node-inside-decision-service.png b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/add-external-node-inside-decision-service.png
new file mode 100644
index 00000000000..fbaeb34075e
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/add-external-node-inside-decision-service.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/delete-external-node-inside-decision-service-after-delete.png b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/delete-external-node-inside-decision-service-after-delete.png
new file mode 100644
index 00000000000..6e352cbf62e
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/delete-external-node-inside-decision-service-after-delete.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/delete-external-node-inside-decision-service-before-delete.png b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/delete-external-node-inside-decision-service-before-delete.png
new file mode 100644
index 00000000000..ca04d943acf
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/delete-external-node-inside-decision-service-before-delete.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-from-ds-to-another-ds-after-move.png b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-from-ds-to-another-ds-after-move.png
new file mode 100644
index 00000000000..e9eddcaace8
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-from-ds-to-another-ds-after-move.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-from-ds-to-another-ds-before-move.png b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-from-ds-to-another-ds-before-move.png
new file mode 100644
index 00000000000..5e600c04fce
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-from-ds-to-another-ds-before-move.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-inside-decision-service-after-move-to-out.png b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-inside-decision-service-after-move-to-out.png
new file mode 100644
index 00000000000..e54b0c1908f
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-inside-decision-service-after-move-to-out.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-inside-decision-service-before-move-to-out.png b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-inside-decision-service-before-move-to-out.png
new file mode 100644
index 00000000000..d6029255909
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-inside-decision-service-before-move-to-out.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-out-from-decision-service-after.png b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-out-from-decision-service-after.png
new file mode 100644
index 00000000000..d986c13f00b
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-out-from-decision-service-after.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-out-from-decision-service-before.png b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-out-from-decision-service-before.png
new file mode 100644
index 00000000000..d6029255909
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/move-external-decision-out-from-decision-service-before.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/resize-external-node-inside-decision-service-after-resizing.png b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/resize-external-node-inside-decision-service-after-resizing.png
new file mode 100644
index 00000000000..e3065c65ff8
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/resize-external-node-inside-decision-service-after-resizing.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/resize-external-node-inside-decision-service-before-resizing.png b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/resize-external-node-inside-decision-service-before-resizing.png
new file mode 100644
index 00000000000..ca04d943acf
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/chromium/drgElements/resize-external-node-inside-decision-service-before-resizing.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/add-external-node-inside-decision-service.png b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/add-external-node-inside-decision-service.png
new file mode 100644
index 00000000000..792fed153c2
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/add-external-node-inside-decision-service.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/delete-external-node-inside-decision-service-after-delete.png b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/delete-external-node-inside-decision-service-after-delete.png
new file mode 100644
index 00000000000..cfef54c3c49
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/delete-external-node-inside-decision-service-after-delete.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/delete-external-node-inside-decision-service-before-delete.png b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/delete-external-node-inside-decision-service-before-delete.png
new file mode 100644
index 00000000000..b7fd36e9d74
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/delete-external-node-inside-decision-service-before-delete.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-from-ds-to-another-ds-after-move.png b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-from-ds-to-another-ds-after-move.png
new file mode 100644
index 00000000000..1dbeb169713
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-from-ds-to-another-ds-after-move.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-from-ds-to-another-ds-before-move.png b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-from-ds-to-another-ds-before-move.png
new file mode 100644
index 00000000000..dae01161904
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-from-ds-to-another-ds-before-move.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-inside-decision-service-after-move-to-out.png b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-inside-decision-service-after-move-to-out.png
new file mode 100644
index 00000000000..2c658212a6f
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-inside-decision-service-after-move-to-out.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-inside-decision-service-before-move-to-out.png b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-inside-decision-service-before-move-to-out.png
new file mode 100644
index 00000000000..ff393609e88
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-inside-decision-service-before-move-to-out.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-out-from-decision-service-after.png b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-out-from-decision-service-after.png
new file mode 100644
index 00000000000..cea16561ae7
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-out-from-decision-service-after.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-out-from-decision-service-before.png b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-out-from-decision-service-before.png
new file mode 100644
index 00000000000..ff393609e88
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/move-external-decision-out-from-decision-service-before.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/resize-external-node-inside-decision-service-after-resizing.png b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/resize-external-node-inside-decision-service-after-resizing.png
new file mode 100644
index 00000000000..44dc6063f39
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/resize-external-node-inside-decision-service-after-resizing.png differ
diff --git a/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/resize-external-node-inside-decision-service-before-resizing.png b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/resize-external-node-inside-decision-service-before-resizing.png
new file mode 100644
index 00000000000..b7fd36e9d74
Binary files /dev/null and b/packages/dmn-editor/tests-e2e/__screenshots__/webkit/drgElements/resize-external-node-inside-decision-service-before-resizing.png differ
diff --git a/packages/dmn-editor/tests-e2e/drgElements/addExternalNode.spec.ts b/packages/dmn-editor/tests-e2e/drgElements/addExternalNode.spec.ts
new file mode 100644
index 00000000000..0a4e2a9bab9
--- /dev/null
+++ b/packages/dmn-editor/tests-e2e/drgElements/addExternalNode.spec.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.
+ */
+
+import { expect, test } from "../__fixtures__/base";
+import { TabName } from "../__fixtures__/editor";
+import { DefaultNodeName, NodeType } from "../__fixtures__/nodes";
+
+test.beforeEach(async ({ stories }) => {
+ await stories.openEmptyWithAvailableExternalModels();
+});
+
+test.describe("Add external node - Decision", () => {
+ test("add to a Decision Service", async ({ editor, palette, diagram, nodes, includedModels }) => {
+ await editor.changeTab({ tab: TabName.INCLUDED_MODELS });
+
+ await includedModels.getIncludeModelButton().click();
+ await includedModels.fillModelToInclude({ modelName: "sumDiffDs.dmn" });
+ await includedModels.selectModel({ modelName: "sumDiffDs.dmn" });
+ await includedModels.fillModelName({ modelName: "MY_MODEL" });
+ await includedModels.includeModel();
+
+ await editor.changeTab({ tab: TabName.EDITOR });
+ await palette.dragNewNode({ type: NodeType.DECISION_SERVICE, targetPosition: { x: 400, y: 200 } });
+
+ await palette.toggleExternalNodesPanel();
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Sum",
+ targetPosition: {
+ x: 200,
+ y: 200,
+ },
+ });
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Diff",
+ targetPosition: {
+ x: 300,
+ y: 300,
+ },
+ });
+
+ // We're hiding the panel now.
+ await palette.toggleExternalNodesPanel();
+
+ await diagram.resetFocus();
+ await nodes.move({ name: "Sum", targetPosition: { x: 500, y: 300 } });
+ await nodes.move({ name: "Diff", targetPosition: { x: 500, y: 430 } });
+
+ await diagram.resetFocus();
+
+ // We move it to make it sure that the nodes are added inside the Decision Services
+ // and are really attached to it, not only "visually over it".
+ await nodes.move({ name: DefaultNodeName.DECISION_SERVICE, targetPosition: { x: 120, y: 120 } });
+
+ await expect(diagram.get()).toHaveScreenshot("add-external-node-inside-decision-service.png");
+ });
+});
diff --git a/packages/dmn-editor/tests-e2e/drgElements/deleteExternalNode.spec.ts b/packages/dmn-editor/tests-e2e/drgElements/deleteExternalNode.spec.ts
new file mode 100644
index 00000000000..6b7dc780eb2
--- /dev/null
+++ b/packages/dmn-editor/tests-e2e/drgElements/deleteExternalNode.spec.ts
@@ -0,0 +1,80 @@
+/*
+ * 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";
+import { TabName } from "../__fixtures__/editor";
+import { DefaultNodeName, NodeType } from "../__fixtures__/nodes";
+
+test.beforeEach(async ({ stories }) => {
+ await stories.openEmptyWithAvailableExternalModels();
+});
+
+test.describe("Delete external node - Decision", () => {
+ test("delete an external node from Decision Service", async ({ editor, palette, diagram, nodes, includedModels }) => {
+ await editor.changeTab({ tab: TabName.INCLUDED_MODELS });
+
+ await includedModels.getIncludeModelButton().click();
+ await includedModels.fillModelToInclude({ modelName: "sumDiffDs.dmn" });
+ await includedModels.selectModel({ modelName: "sumDiffDs.dmn" });
+ await includedModels.fillModelName({ modelName: "MY_MODEL" });
+ await includedModels.includeModel();
+
+ await editor.changeTab({ tab: TabName.EDITOR });
+ await palette.dragNewNode({ type: NodeType.DECISION_SERVICE, targetPosition: { x: 400, y: 200 } });
+
+ await palette.toggleExternalNodesPanel();
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Sum",
+ targetPosition: {
+ x: 200,
+ y: 200,
+ },
+ });
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Diff",
+ targetPosition: {
+ x: 300,
+ y: 300,
+ },
+ });
+
+ // We're hiding the panel now.
+ await palette.toggleExternalNodesPanel();
+
+ await diagram.resetFocus();
+ await nodes.move({ name: "Sum", targetPosition: { x: 500, y: 300 } });
+ await nodes.move({ name: "Diff", targetPosition: { x: 500, y: 430 } });
+
+ await expect(diagram.get()).toHaveScreenshot("delete-external-node-inside-decision-service-before-delete.png");
+
+ await nodes.delete({ name: "Sum" });
+
+ await diagram.resetFocus();
+
+ // We move it to make it sure that the nodes are added inside the Decision Services
+ // and are really attached to it, not only "visually over it".
+ await nodes.move({ name: DefaultNodeName.DECISION_SERVICE, targetPosition: { x: 120, y: 120 } });
+
+ await expect(diagram.get()).toHaveScreenshot("delete-external-node-inside-decision-service-after-delete.png");
+ });
+});
diff --git a/packages/dmn-editor/tests-e2e/drgElements/moveExternalNode.spec.ts b/packages/dmn-editor/tests-e2e/drgElements/moveExternalNode.spec.ts
new file mode 100644
index 00000000000..c553643934a
--- /dev/null
+++ b/packages/dmn-editor/tests-e2e/drgElements/moveExternalNode.spec.ts
@@ -0,0 +1,227 @@
+/*
+ * 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";
+import { TabName } from "../__fixtures__/editor";
+import { DefaultNodeName, NodeType } from "../__fixtures__/nodes";
+
+test.beforeEach(async ({ stories }) => {
+ await stories.openEmptyWithAvailableExternalModels();
+});
+
+test.describe("Move external node - Decision", () => {
+ test("move an external node from Decision Service to out", async ({
+ editor,
+ palette,
+ diagram,
+ nodes,
+ includedModels,
+ }) => {
+ await editor.changeTab({ tab: TabName.INCLUDED_MODELS });
+
+ await includedModels.getIncludeModelButton().click();
+ await includedModels.fillModelToInclude({ modelName: "sumDiffDs.dmn" });
+ await includedModels.selectModel({ modelName: "sumDiffDs.dmn" });
+ await includedModels.fillModelName({ modelName: "MY_MODEL" });
+ await includedModels.includeModel();
+
+ await editor.changeTab({ tab: TabName.EDITOR });
+ await palette.dragNewNode({ type: NodeType.DECISION_SERVICE, targetPosition: { x: 400, y: 200 } });
+
+ await palette.toggleExternalNodesPanel();
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Sum",
+ targetPosition: {
+ x: 200,
+ y: 200,
+ },
+ });
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Diff",
+ targetPosition: {
+ x: 300,
+ y: 300,
+ },
+ });
+
+ // We're hiding the panel now.
+ await palette.toggleExternalNodesPanel();
+
+ await diagram.resetFocus();
+ await nodes.move({ name: "Sum", targetPosition: { x: 500, y: 300 } });
+ await nodes.move({ name: "Diff", targetPosition: { x: 500, y: 430 } });
+
+ // We move it to make it sure that the nodes are added inside the Decision Services
+ // and are really attached to it, not only "visually over it".
+ await nodes.move({ name: DefaultNodeName.DECISION_SERVICE, targetPosition: { x: 100, y: 400 } });
+
+ await expect(diagram.get()).toHaveScreenshot(
+ "move-external-decision-inside-decision-service-before-move-to-out.png"
+ );
+
+ await diagram.resetFocus();
+
+ await nodes.move({ name: "Sum", targetPosition: { x: 400, y: 120 } });
+ await nodes.move({ name: "Diff", targetPosition: { x: 400, y: 400 } });
+
+ // Move again to make sure that nodes are not attached anymore to Decision Service.
+ await nodes.move({ name: DefaultNodeName.DECISION_SERVICE, targetPosition: { x: 600, y: 100 } });
+
+ await expect(diagram.get()).toHaveScreenshot(
+ "move-external-decision-inside-decision-service-after-move-to-out.png"
+ );
+ });
+
+ test("move an external decision from one Decision Service to another Decision Service", async ({
+ editor,
+ page,
+ palette,
+ diagram,
+ nodes,
+ includedModels,
+ }) => {
+ await editor.changeTab({ tab: TabName.INCLUDED_MODELS });
+
+ await includedModels.getIncludeModelButton().click();
+ await includedModels.fillModelToInclude({ modelName: "sumDiffDs.dmn" });
+ await includedModels.selectModel({ modelName: "sumDiffDs.dmn" });
+ await includedModels.fillModelName({ modelName: "MY_MODEL" });
+ await includedModels.includeModel();
+
+ await editor.changeTab({ tab: TabName.EDITOR });
+ await palette.dragNewNode({
+ type: NodeType.DECISION_SERVICE,
+ targetPosition: { x: 250, y: 50 },
+ thenRenameTo: DefaultNodeName.DECISION_SERVICE + " 1",
+ });
+ await palette.dragNewNode({
+ type: NodeType.DECISION_SERVICE,
+ targetPosition: { x: 650, y: 50 },
+ thenRenameTo: DefaultNodeName.DECISION_SERVICE + " 2",
+ });
+
+ await palette.toggleExternalNodesPanel();
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Sum",
+ targetPosition: {
+ x: 600,
+ y: 400,
+ },
+ });
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Diff",
+ targetPosition: {
+ x: 200,
+ y: 400,
+ },
+ });
+
+ // We're hiding the panel now.
+ await palette.toggleExternalNodesPanel();
+
+ await diagram.resetFocus();
+ await nodes.move({ name: "Sum", targetPosition: { x: 350, y: 150 } });
+ await nodes.move({ name: "Diff", targetPosition: { x: 350, y: 250 } });
+ await nodes.move({ name: DefaultNodeName.DECISION_SERVICE + " 1", targetPosition: { x: 200, y: 300 } });
+ await nodes.move({ name: DefaultNodeName.DECISION_SERVICE + " 2", targetPosition: { x: 600, y: 300 } });
+
+ await expect(diagram.get()).toHaveScreenshot("move-external-decision-from-ds-to-another-ds-before-move.png");
+
+ await diagram.resetFocus();
+
+ await nodes.move({ name: "Sum", targetPosition: { x: 680, y: 350 } });
+ await nodes.move({ name: "Diff", targetPosition: { x: 680, y: 500 } });
+
+ // We move it to make it sure that the nodes are added inside the Decision Services
+ // and are really attached to it, not only "visually over it".
+ await nodes.move({ name: DefaultNodeName.DECISION_SERVICE + " 1", targetPosition: { x: 100, y: 100 } });
+ await nodes.move({ name: DefaultNodeName.DECISION_SERVICE + " 2", targetPosition: { x: 400, y: 400 } });
+
+ await expect(diagram.get()).toHaveScreenshot("move-external-decision-from-ds-to-another-ds-after-move.png");
+ });
+
+ test("move an external decision out from Decision Service", async ({
+ editor,
+ page,
+ palette,
+ diagram,
+ nodes,
+ includedModels,
+ }) => {
+ await editor.changeTab({ tab: TabName.INCLUDED_MODELS });
+
+ await includedModels.getIncludeModelButton().click();
+ await includedModels.fillModelToInclude({ modelName: "sumDiffDs.dmn" });
+ await includedModels.selectModel({ modelName: "sumDiffDs.dmn" });
+ await includedModels.fillModelName({ modelName: "MY_MODEL" });
+ await includedModels.includeModel();
+
+ await editor.changeTab({ tab: TabName.EDITOR });
+ await palette.dragNewNode({ type: NodeType.DECISION_SERVICE, targetPosition: { x: 400, y: 200 } });
+
+ await palette.toggleExternalNodesPanel();
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Sum",
+ targetPosition: {
+ x: 200,
+ y: 200,
+ },
+ });
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Diff",
+ targetPosition: {
+ x: 300,
+ y: 300,
+ },
+ });
+
+ // We're hiding the panel now.
+ await palette.toggleExternalNodesPanel();
+
+ await diagram.resetFocus();
+ await nodes.move({ name: "Sum", targetPosition: { x: 500, y: 300 } });
+ await nodes.move({ name: "Diff", targetPosition: { x: 500, y: 430 } });
+
+ // We move it to make it sure that the nodes are added inside the Decision Services
+ // and are really attached to it, not only "visually over it".
+ await nodes.move({ name: DefaultNodeName.DECISION_SERVICE, targetPosition: { x: 100, y: 400 } });
+
+ await expect(diagram.get()).toHaveScreenshot("move-external-decision-out-from-decision-service-before.png");
+
+ await nodes.move({ name: "Sum", targetPosition: { x: 530, y: 100 } });
+ await nodes.move({ name: "Diff", targetPosition: { x: 530, y: 200 } });
+ await nodes.move({ name: DefaultNodeName.DECISION_SERVICE, targetPosition: { x: 120, y: 200 } });
+
+ await diagram.resetFocus();
+
+ await expect(diagram.get()).toHaveScreenshot("move-external-decision-out-from-decision-service-after.png");
+ });
+});
diff --git a/packages/dmn-editor/tests-e2e/drgElements/resizeExternalNode.spec.ts b/packages/dmn-editor/tests-e2e/drgElements/resizeExternalNode.spec.ts
new file mode 100644
index 00000000000..cfe758c7506
--- /dev/null
+++ b/packages/dmn-editor/tests-e2e/drgElements/resizeExternalNode.spec.ts
@@ -0,0 +1,87 @@
+/*
+ * 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";
+import { TabName } from "../__fixtures__/editor";
+import { DefaultNodeName, NodeType } from "../__fixtures__/nodes";
+
+test.beforeEach(async ({ stories }) => {
+ await stories.openEmptyWithAvailableExternalModels();
+});
+
+test.describe("Resize external node - Decision", () => {
+ test("resize an external node inside Decision Service", async ({
+ editor,
+ palette,
+ diagram,
+ nodes,
+ includedModels,
+ }) => {
+ await editor.changeTab({ tab: TabName.INCLUDED_MODELS });
+
+ await includedModels.getIncludeModelButton().click();
+ await includedModels.fillModelToInclude({ modelName: "sumDiffDs.dmn" });
+ await includedModels.selectModel({ modelName: "sumDiffDs.dmn" });
+ await includedModels.fillModelName({ modelName: "MY_MODEL" });
+ await includedModels.includeModel();
+
+ await editor.changeTab({ tab: TabName.EDITOR });
+ await palette.dragNewNode({ type: NodeType.DECISION_SERVICE, targetPosition: { x: 400, y: 200 } });
+
+ await palette.toggleExternalNodesPanel();
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Sum",
+ targetPosition: {
+ x: 200,
+ y: 200,
+ },
+ });
+
+ await palette.dragExternalNode({
+ includedModelName: "MY_MODEL",
+ nodeName: "Diff",
+ targetPosition: {
+ x: 300,
+ y: 300,
+ },
+ });
+
+ // We're hiding the panel now.
+ await palette.toggleExternalNodesPanel();
+
+ await diagram.resetFocus();
+ await nodes.move({ name: "Sum", targetPosition: { x: 500, y: 300 } });
+ await nodes.move({ name: "Diff", targetPosition: { x: 500, y: 430 } });
+
+ await expect(diagram.get()).toHaveScreenshot("resize-external-node-inside-decision-service-before-resizing.png");
+
+ await nodes.resize({ nodeName: "Sum", xOffset: 50, yOffset: 10 });
+ await nodes.resize({ nodeName: "Diff", xOffset: 100, yOffset: 20 });
+
+ await diagram.resetFocus();
+
+ // We move it to make it sure that the nodes are added inside the Decision Services
+ // and are really attached to it, not only "visually over it".
+ await nodes.move({ name: DefaultNodeName.DECISION_SERVICE, targetPosition: { x: 120, y: 120 } });
+
+ await expect(diagram.get()).toHaveScreenshot("resize-external-node-inside-decision-service-after-resizing.png");
+ });
+});
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@kie.apache.org
For additional commands, e-mail: commits-help@kie.apache.org