Skip to content

Commit

Permalink
kie-issues#1019: React-Based DMN Editor: allowedValues not allowed fo…
Browse files Browse the repository at this point in the history
…r custom type (apache#2274)
  • Loading branch information
ljmotta authored May 3, 2024
1 parent b4dc7ba commit fe9f570
Show file tree
Hide file tree
Showing 9 changed files with 393 additions and 213 deletions.
481 changes: 302 additions & 179 deletions packages/dmn-editor/src/dataTypes/Constraints.tsx

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/dmn-editor/src/dataTypes/ConstraintsEnum.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { ConstraintComponentProps, TypeHelper } from "./Constraints";
export const ENUM_SEPARATOR = ",";

export function ConstraintsEnum({
id,
isReadonly,
value,
expressionValue,
Expand Down Expand Up @@ -182,7 +183,7 @@ export function ConstraintsEnum({
<>
<br />
<br />
<ConstraintsExpression isReadonly={true} value={expressionValue ?? ""} type={type} />
<ConstraintsExpression id={id} isReadonly={true} value={expressionValue ?? ""} type={type} />
</>
)}
</div>
Expand Down
45 changes: 39 additions & 6 deletions packages/dmn-editor/src/dataTypes/ConstraintsExpression.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

import * as React from "react";
import { useMemo, useState, useCallback } from "react";
import { useMemo, useState, useCallback, useRef, useEffect } from "react";
import { Title } from "@patternfly/react-core/dist/js/components/Title";
import { FeelInput } from "@kie-tools/feel-input-component/dist";
import "./ConstraintsExpression.css";
Expand All @@ -28,10 +28,12 @@ import { DmnBuiltInDataType } from "@kie-tools/boxed-expression-component/dist/a
import { TypeHelper } from "./Constraints";

export function ConstraintsExpression({
id,
isReadonly,
value,
onSave,
}: {
id: string;
isReadonly: boolean;
value?: string;
savedValue?: string;
Expand All @@ -41,15 +43,43 @@ export function ConstraintsExpression({
isDisabled?: boolean;
}) {
const [preview, setPreview] = useState(value ?? "");
const [editingValue, setEditingValue] = useState(value);
const [isEditing, setEditing] = useState(false);
const valueCopy = useRef(value);

const onFeelBlur = useCallback((valueOnBlur: string) => {
setEditing(false);
}, []);

const onFeelChange = useCallback(
(_, content, preview) => {
onSave?.(content.trim());
setPreview(preview);
onSave?.(content.trim());
},
[onSave]
);

const onPreviewChanged = useCallback((newPreview: string) => setPreview(newPreview), []);

useEffect(() => {
valueCopy.current = isEditing ? valueCopy.current : value;
}, [isEditing, value]);

const onKeyDown = useCallback(
(e) => {
// When inside FEEL Input, all keyboard events should be kept inside it.
// Exceptions to this strategy are handled on `onFeelKeyDown`.
if (!isReadonly && isEditing) {
e.stopPropagation();
}

// This is used to start editing a cell without being in edit mode.
if (!isReadonly && !isEditing) {
setEditing(true);
}
},
[isEditing, isReadonly]
);

const monacoOptions = useMemo(
() => ({
fixedOverflowWidgets: true,
Expand All @@ -64,7 +94,9 @@ export function ConstraintsExpression({
);

return (
<div style={{ display: "flex", flexDirection: "column", width: "100%" }}>
// FeelInput doens't react to `onFeelChange` updates
// making it necessary to add a key to force a re-render;
<div key={id} style={{ display: "flex", flexDirection: "column", width: "100%" }} onKeyDown={onKeyDown}>
{isReadonly && (
<Title size={"md"} headingLevel="h5" style={{ paddingBottom: "10px" }}>
Equivalent FEEL expression:
Expand All @@ -85,9 +117,10 @@ export function ConstraintsExpression({
<p style={{ fontStyle: "italic" }}>{`<None>`}</p>
))}
<FeelInput
value={isReadonly ? value : editingValue}
value={isEditing ? valueCopy.current : value}
onChange={onFeelChange}
onPreviewChanged={setPreview}
onBlur={onFeelBlur}
onPreviewChanged={onPreviewChanged}
enabled={!isReadonly}
options={monacoOptions as any}
/>
Expand Down
3 changes: 2 additions & 1 deletion packages/dmn-editor/src/dataTypes/ConstraintsRange.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const CONSTRAINT_START_ID = "start";
const CONSTRAINT_END_ID = "end";

export function ConstraintsRange({
id,
isReadonly,
value,
expressionValue,
Expand Down Expand Up @@ -312,7 +313,7 @@ export function ConstraintsRange({
{!renderOnPropertiesPanel && (
<>
<br />
<ConstraintsExpression isReadonly={true} value={expressionValue ?? ""} type={type} />
<ConstraintsExpression id={id} isReadonly={true} value={expressionValue ?? ""} type={type} />
</>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/dmn-editor/src/dataTypes/DataTypePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ export function DataTypePanel({
isDisabled={isReadonly}
typeRef={resolvedTypeRef}
onChange={changeTypeRef}
removeDataTypes={[dataType]}
/>
<br />
<br />
Expand Down
2 changes: 1 addition & 1 deletion packages/dmn-editor/src/dataTypes/DataTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export type EditItemDefinition = (
export function DataTypes() {
const thisDmnsNamespace = useDmnEditorStore((s) => s.dmn.model.definitions["@_namespace"]);
const dmnEditorStoreApi = useDmnEditorStoreApi();
const { activeItemDefinitionId } = useDmnEditorStore((s) => s.dataTypesEditor);
const activeItemDefinitionId = useDmnEditorStore((s) => s.dataTypesEditor.activeItemDefinitionId);

const [filter, setFilter] = useState("");
const { externalModelsByNamespace } = useExternalModels();
Expand Down
34 changes: 27 additions & 7 deletions packages/dmn-editor/src/dataTypes/ItemComponentsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import {
import { getNewDmnIdRandomizer } from "../idRandomizer/dmnIdRandomizer";
import { isEnum } from "./ConstraintsEnum";
import { isRange } from "./ConstraintsRange";
import { constraintTypeHelper } from "./Constraints";
import { constraintTypeHelper, recursivelyGetRootItemDefinition } from "./Constraints";
import { builtInFeelTypeNames } from "./BuiltInFeelTypes";
import { useDmnEditor } from "../DmnEditorContext";
import { DMN15__tItemDefinition } from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
Expand Down Expand Up @@ -95,6 +95,9 @@ export function ItemComponentsTable({
const allTopLevelDataTypesByFeelName = useDmnEditorStore(
(s) => s.computed(s).getDataTypes(externalModelsByNamespace).allTopLevelDataTypesByFeelName
);
const allTopLevelItemDefinitionUniqueNames = useDmnEditorStore(
(s) => s.computed(s).getDataTypes(externalModelsByNamespace).allTopLevelItemDefinitionUniqueNames
);
const importsByNamespace = useDmnEditorStore((s) => s.computed(s).importsByNamespace());

const thisDmnsNamespace = useDmnEditorStore((s) => s.dmn.model.definitions["@_namespace"]);
Expand Down Expand Up @@ -288,21 +291,37 @@ export function ItemComponentsTable({
return <>Range</>;
}

const constraintValue = dt.itemDefinition.allowedValues?.text.__$$text;
const typeRef =
(dt.itemDefinition.typeRef?.__$$text as DmnBuiltInDataType) ?? DmnBuiltInDataType.Undefined;
const constraintValue =
dt.itemDefinition.typeConstraint?.text.__$$text ?? dt.itemDefinition.allowedValues?.text.__$$text;

const typeHelper = constraintTypeHelper(
dt.itemDefinition,
allDataTypesById,
allTopLevelItemDefinitionUniqueNames
);

if (constraintValue === undefined) {
return <>None</>;
}
if (isEnum(constraintValue, constraintTypeHelper(typeRef).check)) {
if (isEnum(constraintValue, typeHelper.check)) {
return <>Enumeration</>;
}
if (isRange(constraintValue, constraintTypeHelper(typeRef).check)) {
if (isRange(constraintValue, typeHelper.check)) {
return <>Range</>;
}
return <>Expression</>;
};

const rootItemDefinition = recursivelyGetRootItemDefinition(
dt.itemDefinition,
allDataTypesById,
allTopLevelItemDefinitionUniqueNames
);

const isItemComponent = !!parent.itemDefinition?.itemComponent?.find(
(ic) => ic["@_id"] === rootItemDefinition["@_id"]
);

return (
<React.Fragment key={dt.itemDefinition["@_id"]}>
{shouldShowRow && (
Expand Down Expand Up @@ -439,7 +458,8 @@ export function ItemComponentsTable({
/>
</td>
<td>
{canHaveConstraints(dt.itemDefinition) ? (
{canHaveConstraints(rootItemDefinition) ||
(isStruct(rootItemDefinition) && !isItemComponent) ? (
<Button
variant={ButtonVariant.link}
onClick={() => {
Expand Down
35 changes: 18 additions & 17 deletions packages/dmn-editor/src/dataTypes/TypeRefSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { ArrowUpIcon } from "@patternfly/react-icons/dist/js/icons/arrow-up-icon
import { DmnEditorTab } from "../store/Store";
import { useDmnEditorStore, useDmnEditorStoreApi } from "../store/StoreContext";
import { Button, ButtonVariant } from "@patternfly/react-core/dist/js/components/Button";
import { Tooltip } from "@patternfly/react-core/dist/js/components/Tooltip";
import { DataType } from "./DataTypes";
import { builtInFeelTypeNames, builtInFeelTypes } from "./BuiltInFeelTypes";
import { Flex } from "@patternfly/react-core/dist/js/layouts/Flex";
Expand All @@ -48,6 +47,7 @@ export function TypeRefSelector({
menuAppendTo,
onCreate,
onToggle,
removeDataTypes,
}: {
zoom?: number;
heightRef: React.RefObject<HTMLElement>;
Expand All @@ -57,6 +57,7 @@ export function TypeRefSelector({
onCreate?: OnCreateDataType;
onToggle?: OnToggle;
menuAppendTo?: "parent";
removeDataTypes?: DataType[];
}) {
const [isOpen, setOpen] = useState(false);
const { externalModelsByNamespace } = useExternalModels();
Expand Down Expand Up @@ -87,7 +88,9 @@ export function TypeRefSelector({
}

if (s.namespace === state.dmn.model.definitions["@_namespace"]) {
customDataTypes.push(s);
if ((removeDataTypes ?? []).findIndex((removeDataType) => removeDataType.feelName === s.feelName) < 0) {
customDataTypes.push(s);
}
} else {
externalDataTypes.push(s);
}
Expand All @@ -113,21 +116,19 @@ export function TypeRefSelector({
spaceItems={{ default: "spaceItemsNone" }}
>
{selectedDataType?.itemDefinition && (
<Tooltip content="Jump to definition" appendTo={() => document.getElementById(id)!}>
<Button
title={"Jump to definition"}
className={"kie-dmn-editor--data-type-jump-to-definition"}
variant={ButtonVariant.control}
onClick={(e) =>
dmnEditorStoreApi.setState((state) => {
state.navigation.tab = DmnEditorTab.DATA_TYPES;
state.dataTypesEditor.activeItemDefinitionId = selectedDataType?.itemDefinition?.["@_id"];
})
}
>
<ArrowUpIcon />
</Button>
</Tooltip>
<Button
title={"Jump to definition"}
className={"kie-dmn-editor--data-type-jump-to-definition"}
variant={ButtonVariant.control}
onClick={(e) =>
dmnEditorStoreApi.setState((state) => {
state.navigation.tab = DmnEditorTab.DATA_TYPES;
state.dataTypesEditor.activeItemDefinitionId = selectedDataType?.itemDefinition?.["@_id"];
})
}
>
<ArrowUpIcon />
</Button>
)}
<Select
toggleRef={toggleRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

import * as React from "react";
import { useEffect, useState, useRef } from "react";
import { useEffect, useState } from "react";
import { FormGroup } from "@patternfly/react-core/dist/js/components/Form";
import { InlineFeelNameInput } from "../../feel/InlineFeelNameInput";
import { TextArea } from "@patternfly/react-core/dist/js/components/TextArea";
Expand Down

0 comments on commit fe9f570

Please sign in to comment.