Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NO-ISSUE: Removing an Included Model should remove all references of it on the current model #2365

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, BKMs functions, and Decision expressions will remain,
requiring to be manually removed.
</>
)}
{extension === "pmml" && (
<>
Removing an included PMML will not erase references on BKM functions, 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
Loading