Skip to content

Commit

Permalink
NO-ISSUE: Removing an Included Model should remove all references of …
Browse files Browse the repository at this point in the history
…it on the current model (apache#2365)
  • Loading branch information
ljmotta authored May 27, 2024
1 parent 275d9e6 commit 326a19b
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 175 deletions.
320 changes: 149 additions & 171 deletions packages/dmn-editor/src/includedModels/IncludedModels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@ import { allPmmlImportNamespaces, getPmmlNamespace } from "../pmml/pmml";
import { allDmnImportNamespaces } from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/Dmn15Spec";
import { getNamespaceOfDmnImport } from "./importNamespaces";
import { Alert, AlertVariant } from "@patternfly/react-core/dist/js/components/Alert/Alert";
import { Dropdown, DropdownItem, KebabToggle } from "@patternfly/react-core/dist/js/components/Dropdown";
import { KebabToggle } from "@patternfly/react-core/dist/js/components/Dropdown";
import { TrashIcon } from "@patternfly/react-icons/dist/js/icons/trash-icon";
import { useInViewSelect } from "../responsiveness/useInViewSelect";
import { useCancelableEffect } from "@kie-tools-core/react-hooks/dist/useCancelableEffect";
import { State } from "../store/Store";
import "./IncludedModels.css";
import { Normalized } from "../normalization/normalize";
import { Popover, PopoverPosition } from "@patternfly/react-core/dist/js/components/Popover";
import { AlertActionCloseButton, AlertActionLink } from "@patternfly/react-core/dist/js/components/Alert";

export const EMPTY_IMPORT_NAME_NAMESPACE_IDENTIFIER = "<Default>";

Expand Down Expand Up @@ -394,10 +396,11 @@ export function IncludedModels() {
(!isModalOpen && index === thisDmnsImports.length - 1 ? selectedModel : undefined); // Use the selected model to avoid showing the "unknown included model" card.

return !externalModel ? (
<UnknownIncludedModelCard
<IncludedModelCard
key={dmnImport["@_id"]}
_import={dmnImport}
index={index}
externalModel={undefined}
isReadonly={false}
/>
) : (
Expand Down Expand Up @@ -444,25 +447,32 @@ function IncludedModelCard({
isReadonly,
}: {
_import: Normalized<DMN15__tImport>;
externalModel: ExternalModel;
externalModel: ExternalModel | undefined;
index: number;
isReadonly: boolean;
}) {
const { externalModelsByNamespace } = useExternalModels();
const dmnEditorStoreApi = useDmnEditorStoreApi();

const { onRequestToJumpToPath, onRequestToResolvePath } = useDmnEditor();

const remove = useCallback(
(index: number) => {
setRemovePopoverOpen(false);
dmnEditorStoreApi.setState((state) => {
deleteImport({ definitions: state.dmn.model.definitions, index });
const externalModelTypesByNamespace = state
.computed(state)
.getExternalModelTypesByNamespace(externalModelsByNamespace);
deleteImport({
definitions: state.dmn.model.definitions,
__readonly_index: index,
__readonly_externalModelTypesByNamespace: externalModelTypesByNamespace,
});
});
},
[dmnEditorStoreApi]
[dmnEditorStoreApi, externalModelsByNamespace]
);

const { externalModelsByNamespace } = useExternalModels();

const rename = useCallback<OnInlineFeelNameRenamed>(
(newName) => {
dmnEditorStoreApi.setState((state) => {
Expand All @@ -489,173 +499,121 @@ function IncludedModelCard({
}, [_import]);

const title = useMemo(() => {
if (externalModel === undefined) {
return "";
}
if (externalModel.type === "dmn") {
return externalModel.model.definitions["@_name"];
} else if (externalModel.type === "pmml") {
return "";
}
}, [externalModel.model, externalModel.type]);
}, [externalModel]);

const pathDisplayed = useMemo(() => {
if (externalModel !== undefined) {
return (
onRequestToResolvePath?.(externalModel.normalizedPosixPathRelativeToTheOpenFile) ??
externalModel.normalizedPosixPathRelativeToTheOpenFile
);
}
}, [onRequestToResolvePath, externalModel]);

const pathDisplayed = useMemo(
() =>
onRequestToResolvePath?.(externalModel.normalizedPosixPathRelativeToTheOpenFile) ??
externalModel.normalizedPosixPathRelativeToTheOpenFile,
[onRequestToResolvePath, externalModel.normalizedPosixPathRelativeToTheOpenFile]
const [isRemovePopoverOpen, setRemovePopoverOpen] = useState(false);
const [isConfirmationPopoverOpen, setConfirmationPopoverOpen] = useState(false);
const shouldRenderConfirmationMessage = useMemo(
() => isRemovePopoverOpen && isConfirmationPopoverOpen,
[isConfirmationPopoverOpen, isRemovePopoverOpen]
);

const [isCardActionsOpen, setCardActionsOpen] = useState(false);

return (
<Card isHoverable={true} isCompact={false}>
<CardHeader>
<CardActions>
<Dropdown
toggle={<KebabToggle id={"toggle-kebab-top-level"} onToggle={setCardActionsOpen} />}
onSelect={() => setCardActionsOpen(false)}
isOpen={isCardActionsOpen}
menuAppendTo={document.body}
isPlain={true}
position={"right"}
dropdownItems={[
<React.Fragment key={"remove-fragment"}>
{!isReadonly && (
<DropdownItem
style={{ minWidth: "240px" }}
icon={<TrashIcon />}
onClick={() => {
if (isReadonly) {
return;
}

remove(index);
}}
>
Remove
</DropdownItem>
)}
</React.Fragment>,
]}
/>
</CardActions>
<CardTitle>
<InlineFeelNameInput
placeholder={EMPTY_IMPORT_NAME_NAMESPACE_IDENTIFIER}
isPlain={true}
allUniqueNames={useCallback((s) => s.computed(s).getAllFeelVariableUniqueNames(), [])}
id={_import["@_id"]!}
name={_import["@_name"]}
isReadonly={false}
shouldCommitOnBlur={true}
onRenamed={rename}
validate={DMN15_SPEC.IMPORT.name.isValid}
/>
<br />
<br />
<ExternalModelLabel extension={extension} />
<br />
<br />
</CardTitle>
</CardHeader>
<CardBody>
{`${title}`}
<br />
<br />
<small>
<Button
variant={ButtonVariant.link}
style={{ paddingLeft: 0, whiteSpace: "break-spaces", textAlign: "left" }}
onClick={() => {
onRequestToJumpToPath?.(externalModel.normalizedPosixPathRelativeToTheOpenFile);
<Popover
bodyContent={
shouldRenderConfirmationMessage ? (
<Alert
isInline
variant={AlertVariant.warning}
title={"This action have major impact to your model"}
actionClose={
<AlertActionCloseButton
onClose={() => {
setRemovePopoverOpen(false);
setConfirmationPopoverOpen(false);
}}
/>
}
actionLinks={
<>
<AlertActionLink
onClick={(ev) => {
remove(index);
ev.stopPropagation();
ev.preventDefault();
}}
variant={"link"}
style={{ color: "var(--pf-global--danger-color--200)", fontWeight: "bold" }}
>
{`Yes, remove included ${extension.toUpperCase()}`}
</AlertActionLink>
</>
}
>
{extension === "dmn" && (
<>
Removing an included DMN will erase all its imported nodes and connected edges from your model.
The references to item definitions, Business Knowledge Model functions, and Decision expressions
will remain, requiring to be manually removed.
</>
)}
{extension === "pmml" && (
<>
Removing an included PMML will not erase references on Boxed Function expressions, requiring it to
be manually removed.
</>
)}
</Alert>
) : (
<Button
variant={"plain"}
onClick={(ev) => {
ev.stopPropagation();
ev.preventDefault();
if (isReadonly) {
return;
}
setConfirmationPopoverOpen(true);
}}
>
<TrashIcon />
{" "}
Remove
</Button>
)
}
hasNoPadding={shouldRenderConfirmationMessage}
maxWidth={shouldRenderConfirmationMessage ? "300px" : "150px"}
minWidth={shouldRenderConfirmationMessage ? "300px" : "150px"}
isVisible={isRemovePopoverOpen}
showClose={false}
shouldClose={() => {
setRemovePopoverOpen(false);
setConfirmationPopoverOpen(false);
}}
position={PopoverPosition.bottom}
shouldOpen={() => setRemovePopoverOpen(true)}
>
<i>{pathDisplayed}</i>
</Button>
</small>
</CardBody>
</Card>
);
}

function UnknownIncludedModelCard({
_import,
index,
isReadonly,
}: {
_import: Normalized<DMN15__tImport>;
index: number;
isReadonly: boolean;
}) {
const dmnEditorStoreApi = useDmnEditorStoreApi();

const remove = useCallback(
(index: number) => {
dmnEditorStoreApi.setState((state) => {
deleteImport({ definitions: state.dmn.model.definitions, index });
});
},
[dmnEditorStoreApi]
);

const { externalModelsByNamespace } = useExternalModels();

const rename = useCallback<OnInlineFeelNameRenamed>(
(newName) => {
dmnEditorStoreApi.setState((state) => {
renameImport({
definitions: state.dmn.model.definitions,
index,
newName,
allTopLevelDataTypesByFeelName: state.computed(state).getDataTypes(externalModelsByNamespace)
.allTopLevelDataTypesByFeelName,
});
});
},
[dmnEditorStoreApi, externalModelsByNamespace, index]
);

const extension = useMemo(() => {
if (allDmnImportNamespaces.has(_import["@_importType"])) {
return "dmn";
} else if (allPmmlImportNamespaces.has(_import["@_importType"])) {
return "pmml";
} else {
return "Unknwon";
}
}, [_import]);

const [isCardActionsOpen, setCardActionsOpen] = useState(false);

return (
<Card isHoverable={true} isCompact={false}>
<CardHeader>
<CardActions>
<Dropdown
toggle={<KebabToggle id={"toggle-kebab-top-level"} onToggle={setCardActionsOpen} />}
onSelect={() => setCardActionsOpen(false)}
isOpen={isCardActionsOpen}
menuAppendTo={document.body}
isPlain={true}
position={"right"}
dropdownItems={[
<React.Fragment key={"remove-fragment"}>
{!isReadonly && (
<DropdownItem
style={{ minWidth: "240px" }}
icon={<TrashIcon />}
onClick={() => {
if (isReadonly) {
return;
}

remove(index);
}}
>
Remove
</DropdownItem>
)}
</React.Fragment>,
]}
/>
<Button
variant={"plain"}
onClick={(ev) => {
ev.stopPropagation();
ev.preventDefault();
}}
>
<KebabToggle />
</Button>
</Popover>
</CardActions>
<CardTitle>
<InlineFeelNameInput
Expand All @@ -676,18 +634,38 @@ function UnknownIncludedModelCard({
<br />
</CardTitle>
</CardHeader>
<CardBody>
<Alert title={"External model not found."} isInline={true} variant={AlertVariant.danger}>
<Divider style={{ marginTop: "16px" }} />
{externalModel ? (
<CardBody>
{`${title}`}
<br />
<br />
<p>
<b>Namespace:</b>&nbsp;{_import["@_namespace"]}
</p>
<p>
<b>URI:</b>&nbsp;{_import["@_locationURI"] ?? <i>None</i>}
</p>
</Alert>
</CardBody>
<small>
<Button
variant={ButtonVariant.link}
style={{ paddingLeft: 0, whiteSpace: "break-spaces", textAlign: "left" }}
onClick={() => {
onRequestToJumpToPath?.(externalModel.normalizedPosixPathRelativeToTheOpenFile);
}}
>
<i>{pathDisplayed}</i>
</Button>
</small>
</CardBody>
) : (
// unknown
<CardBody>
<Alert title={"External model not found."} isInline={true} variant={AlertVariant.danger}>
<Divider style={{ marginTop: "16px" }} />
<br />
<p>
<b>Namespace:</b>&nbsp;{_import["@_namespace"]}
</p>
<p>
<b>URI:</b>&nbsp;{_import["@_locationURI"] ?? <i>None</i>}
</p>
</Alert>
</CardBody>
)}
</Card>
);
}
Loading

0 comments on commit 326a19b

Please sign in to comment.