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 4adbb59c98e kie-issues-664: On the DMN Editor, make it possible to
manually change the order of Decision Service inputs via Properties Panel
(#2249)
4adbb59c98e is described below
commit 4adbb59c98e8055b48e1d6d8e56eafece496a5e6
Author: Luiz João Motta <[email protected]>
AuthorDate: Mon Apr 22 15:37:10 2024 -0300
kie-issues-664: On the DMN Editor, make it possible to manually change the
order of Decision Service inputs via Properties Panel (#2249)
---
...pulateInputDataAndDecisionsOnDecisionService.ts | 44 +++--
.../propertiesPanel/DecisionServiceProperties.tsx | 198 ++++++++++++++++++++-
.../DocumentationLinksFormGroup.tsx | 2 +-
3 files changed, 220 insertions(+), 24 deletions(-)
diff --git
a/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts
b/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts
index 75db1eb61b1..c3fce874cd1 100644
---
a/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts
+++
b/packages/dmn-editor/src/mutations/repopulateInputDataAndDecisionsOnDecisionService.ts
@@ -45,6 +45,11 @@ export function
repopulateInputDataAndDecisionsOnDecisionService({
definitions: DMN15__tDefinitions;
decisionService: DMN15__tDecisionService;
}) {
+ // Save previous values to preserve order
+ const inputDatas = new Set<string>([...(decisionService.inputData ??
[])].map((e) => e["@_href"])); // Using Set for uniqueness
+ const inputDecisions = new Set<string>([...(decisionService.inputDecision ??
[])].map((e) => e["@_href"])); // Using Set for uniqueness
+
+ // Reset the inputData and inputDecision entries
decisionService.inputData = [];
decisionService.inputDecision = [];
@@ -53,7 +58,8 @@ export function
repopulateInputDataAndDecisionsOnDecisionService({
...(decisionService.encapsulatedDecision ?? []).map((s) => s["@_href"]),
]);
- const requirements = new Array<{ href: string; type: "decisionIr" |
"inputDataIr" }>();
+ // 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") {
@@ -62,27 +68,37 @@ export function
repopulateInputDataAndDecisionsOnDecisionService({
(drgElement.informationRequirement ?? []).flatMap((ir) => {
if (ir.requiredDecision) {
- requirements.push({ href: ir.requiredDecision["@_href"], type:
"decisionIr" });
+ requirements.set(ir.requiredDecision["@_href"], "decisionIr");
} else if (ir.requiredInput) {
- requirements.push({ href: ir.requiredInput["@_href"], type:
"inputDataIr" });
+ requirements.set(ir.requiredInput["@_href"], "inputDataIr");
}
});
}
- const inputDatas = new Set<string>(); // Using Set for uniqueness
- const inputDecisions = new Set<string>(); // Using Set for uniqueness
+ // START - Remove outdated requirements
+ [...inputDatas].forEach((inputData) => {
+ if (!requirements.has(inputData)) {
+ inputDatas.delete(inputData);
+ }
+ });
- const requirementsArray = [...requirements];
- for (let i = 0; i < requirementsArray.length; i++) {
- const r = requirementsArray[i];
- if (r.type === "inputDataIr") {
- inputDatas.add(r.href);
- } else if (r.type === "decisionIr") {
- inputDecisions.add(r.href);
+ [...inputDecisions].forEach((inputDecision) => {
+ if (!requirements.has(inputDecision)) {
+ inputDecisions.delete(inputDecision);
+ }
+ });
+ // END
+
+ // Update inputDecisions and inputDatas requirements with possible new hrefs
+ requirements.forEach((type, href) => {
+ if (type === "decisionIr") {
+ inputDecisions.add(href);
+ } else if (type === "inputDataIr") {
+ inputDatas.add(href);
} else {
- throw new Error(`DMN MUTATION: Invalid type of element to be referenced
by DecisionService: '${r.type}'`);
+ throw new Error(`DMN MUTATION: Invalid type of element to be referenced
by DecisionService: '${type}'`);
}
- }
+ });
decisionService.inputData = [...inputDatas].map((iHref) => ({ "@_href":
iHref }));
decisionService.inputDecision = [...inputDecisions].flatMap(
diff --git
a/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx
b/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx
index 3ae80477c27..0581d7ce441 100644
--- a/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx
+++ b/packages/dmn-editor/src/propertiesPanel/DecisionServiceProperties.tsx
@@ -19,6 +19,7 @@
import * as React from "react";
import {
+ DMN15__tDMNElementReference,
DMN15__tDecision,
DMN15__tDecisionService,
DMN15__tInputData,
@@ -38,6 +39,9 @@ import { Divider } from
"@patternfly/react-core/dist/js/components/Divider";
import { useDmnEditor } from "../DmnEditorContext";
import { useResolvedTypeRef } from "../dataTypes/useResolvedTypeRef";
import { useExternalModels } from
"../includedModels/DmnEditorDependenciesContext";
+import { DragAndDrop, Draggable } from "../draggable/Draggable";
+import { buildFeelQNameFromNamespace } from "../feel/buildFeelQName";
+import { Alert, AlertVariant } from
"@patternfly/react-core/dist/js/components/Alert/Alert";
export type AllKnownDrgElementsByHref = Map<
string,
@@ -169,22 +173,38 @@ export function DecisionServiceProperties({
</FormGroup>
<Divider />
-
- <FormGroup label="Input data">
- <DecisionServiceElementList
+ <FormGroup label="Input decisions">
+ <DraggableDecisionServiceElementList
decisionServiceNamespace={namespace}
- elements={decisionService.inputData}
+ elements={decisionService.inputDecision}
allDrgElementsByHref={allDrgElementsByHref}
+ onChange={(newInputDecisions) => {
+ setState((state) => {
+ (state.dmn.model.definitions.drgElement![index] as
DMN15__tDecisionService).inputDecision =
+ newInputDecisions;
+ });
+ }}
/>
</FormGroup>
- <FormGroup label="Input decisions">
- <DecisionServiceElementList
+ <FormGroup label="Input data">
+ <DraggableDecisionServiceElementList
decisionServiceNamespace={namespace}
- elements={decisionService.inputDecision}
+ elements={decisionService.inputData}
allDrgElementsByHref={allDrgElementsByHref}
+ onChange={(newInputData) => {
+ setState((state) => {
+ (state.dmn.model.definitions.drgElement![index] as
DMN15__tDecisionService).inputData = newInputData;
+ });
+ }}
/>
</FormGroup>
+ <DecisionServiceEquivalentFunction
+ decisionService={decisionService}
+ decisionServiceNamespace={namespace}
+ allDrgElementsByHref={allDrgElementsByHref}
+ />
+
<DocumentationLinksFormGroup
isReadonly={isReadonly}
values={decisionService.extensionElements?.["kie:attachment"]}
@@ -214,7 +234,7 @@ export function DecisionServiceElementList({
return (
<ul>
{(elements ?? []).length <= 0 && (
- <li>
+ <li style={{ paddingLeft: "32px" }}>
<small>
<i>(Empty)</i>
</small>
@@ -237,7 +257,7 @@ export function DecisionServiceElementList({
});
return (
- <li key={potentialExternalHref}>
+ <li style={{ paddingLeft: "32px" }} key={potentialExternalHref}>
<DmnObjectListItem
dmnObjectHref={potentialExternalHref}
dmnObject={allDrgElementsByHref.get(potentialExternalHref)}
@@ -250,3 +270,163 @@ export function DecisionServiceElementList({
</ul>
);
}
+
+export function DraggableDecisionServiceElementList({
+ decisionServiceNamespace,
+ elements,
+ allDrgElementsByHref,
+ onChange,
+}: {
+ decisionServiceNamespace: string | undefined;
+ elements: DMN15__tDecisionService["outputDecision"];
+ allDrgElementsByHref: AllKnownDrgElementsByHref;
+ onChange: (hrefs: DMN15__tDMNElementReference[] | undefined) => void;
+}) {
+ const thisDmnsNamespace = useDmnEditorStore((s) =>
s.dmn.model.definitions["@_namespace"]);
+ const [keys, setKeys] = React.useState(() => elements?.map((e) =>
e["@_href"]) ?? []);
+
+ const onDragEnd = useCallback(
+ (source: number, dest: number) => {
+ const reordened = [...(elements ?? [])];
+ const [removed] = reordened.splice(source, 1);
+ reordened.splice(dest, 0, removed);
+ onChange(reordened);
+ },
+ [elements, onChange]
+ );
+
+ const reorder = useCallback((source: number, dest: number) => {
+ setKeys((prev) => {
+ const reordenedUuid = [...prev];
+ const [removedUuid] = reordenedUuid.splice(source, 1);
+ reordenedUuid.splice(dest, 0, removedUuid);
+ return reordenedUuid;
+ });
+ }, []);
+
+ const draggableItem = useCallback(
+ (element: DMN15__tDMNElementReference, index: number) => {
+ const localHref = parseXmlHref(element["@_href"]);
+
+ // If the localHref has a namespace, then that's the one to use, as it
can be that an external node is pointing to another external node in their
perspective
+ // E.g., (This DMN) --includes--> (DMN A) --includes--> (DMN B)
+ // In this case, localHref will have DMN B's namespace, and we need to
respect it. It `DMN B` in included in `This DMN`, then
+ // we can resolve it, otherwise, the dmnObject referenced by localHref
won't be present on `dmnObjectsByHref`, and we'll only show the href.
+ // Now, if the localHref doesn't have a namespace, then it is local to
the model where the Decision Service is declared, so we use
`decisionServiceNamespace`.
+ // If none of that is true, then it means that the Decision Service is
local to "This DMN", so the namespace is simply "".
+ const resolvedNamespace = localHref.namespace ??
decisionServiceNamespace ?? thisDmnsNamespace;
+
+ const potentialExternalHref = buildXmlHref({
+ namespace: resolvedNamespace,
+ id: localHref.id,
+ });
+
+ return (
+ <Draggable
+ key={keys[index]}
+ index={index}
+ handlerStyle={
+ keys[index] ? { paddingLeft: "16px", paddingRight: "16px" } : {
paddingLeft: "16px", paddingRight: "16px" }
+ }
+ >
+ <li key={potentialExternalHref}>
+ <DmnObjectListItem
+ dmnObjectHref={potentialExternalHref}
+ dmnObject={allDrgElementsByHref.get(potentialExternalHref)}
+ relativeToNamespace={thisDmnsNamespace}
+ namespace={resolvedNamespace}
+ />
+ </li>
+ </Draggable>
+ );
+ },
+ [allDrgElementsByHref, decisionServiceNamespace, keys, thisDmnsNamespace]
+ );
+
+ return (
+ <ul>
+ {(elements ?? []).length <= 0 && (
+ <li style={{ paddingLeft: "32px" }}>
+ <small>
+ <i>(Empty)</i>
+ </small>
+ </li>
+ )}
+ <DragAndDrop
+ reorder={reorder}
+ onDragEnd={onDragEnd}
+ values={elements}
+ draggableItem={draggableItem}
+ isDisabled={false}
+ />
+ </ul>
+ );
+}
+
+function DecisionServiceEquivalentFunction({
+ decisionService,
+ allDrgElementsByHref,
+ decisionServiceNamespace,
+}: {
+ decisionService: DMN15__tDecisionService;
+ allDrgElementsByHref: AllKnownDrgElementsByHref;
+ decisionServiceNamespace: string | undefined;
+}) {
+ const importsByNamespace = useDmnEditorStore((s) =>
s.computed(s).importsByNamespace());
+ const thisDmnsNamespace = useDmnEditorStore((s) =>
s.dmn.model.definitions["@_namespace"]);
+
+ const getNodeNameByHref = useCallback(
+ (href: string) => {
+ const localHref = parseXmlHref(href);
+
+ // If the localHref has a namespace, then that's the one to use, as it
can be that an external node is pointing to another external node in their
perspective
+ // E.g., (This DMN) --includes--> (DMN A) --includes--> (DMN B)
+ // In this case, localHref will have DMN B's namespace, and we need to
respect it. It `DMN B` in included in `This DMN`, then
+ // we can resolve it, otherwise, the dmnObject referenced by localHref
won't be present on `dmnObjectsByHref`, and we'll only show the href.
+ // Now, if the localHref doesn't have a namespace, then it is local to
the model where the Decision Service is declared, so we use
`decisionServiceNamespace`.
+ // If none of that is true, then it means that the Decision Service is
local to "This DMN", so the namespace is simply "".
+ const resolvedNamespace = localHref.namespace ??
decisionServiceNamespace ?? thisDmnsNamespace;
+
+ const potentialExternalHref = buildXmlHref({
+ namespace: resolvedNamespace,
+ id: localHref.id,
+ });
+
+ const dmnObject = allDrgElementsByHref.get(potentialExternalHref);
+
+ return dmnObject
+ ? buildFeelQNameFromNamespace({
+ namedElement: dmnObject,
+ importsByNamespace,
+ namespace: resolvedNamespace,
+ relativeToNamespace: thisDmnsNamespace,
+ }).full
+ : potentialExternalHref;
+ },
+ [allDrgElementsByHref, decisionServiceNamespace, importsByNamespace,
thisDmnsNamespace]
+ );
+
+ const buildFunctionArgList = useCallback(
+ (inputDecisions?: DMN15__tDMNElementReference[], inputData?:
DMN15__tDMNElementReference[]) => {
+ const inputDecisionNodeNames = inputDecisions?.map((ide) =>
getNodeNameByHref(ide["@_href"]));
+ const inputDataNodeNames = inputData?.map((ida) =>
getNodeNameByHref(ida["@_href"]));
+
+ return [...(inputDecisionNodeNames ?? []), ...(inputDataNodeNames ??
[])].reduce(
+ (acc, name) => (acc ? `${acc}, ${name}` : name),
+ ""
+ );
+ },
+ [getNodeNameByHref]
+ );
+
+ return (
+ <Alert variant={AlertVariant.info} isInline title="Invoking this Decision
Service in FEEL">
+ <p style={{ fontFamily: "monospace" }}>
+ {`${decisionService["@_name"]}(${buildFunctionArgList(
+ decisionService.inputDecision,
+ decisionService.inputData
+ )})`}
+ </p>
+ </Alert>
+ );
+}
diff --git
a/packages/dmn-editor/src/propertiesPanel/DocumentationLinksFormGroup.tsx
b/packages/dmn-editor/src/propertiesPanel/DocumentationLinksFormGroup.tsx
index f39b959716e..59edffc1f2e 100644
--- a/packages/dmn-editor/src/propertiesPanel/DocumentationLinksFormGroup.tsx
+++ b/packages/dmn-editor/src/propertiesPanel/DocumentationLinksFormGroup.tsx
@@ -165,7 +165,7 @@ export function DocumentationLinksFormGroup({
}, []);
const draggableItem = useCallback(
- (kieAttachment, index) => {
+ (kieAttachment: Namespaced<"kie", KIE__tAttachment>, index: number) => {
return (
<Draggable
key={valuesUuid?.[index] ?? generateUuid()}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]