Skip to content

Commit

Permalink
Merge pull request #7221 from TouK/1.18-ports7
Browse files Browse the repository at this point in the history
1.18 ports7
  • Loading branch information
arkadius authored Nov 22, 2024
2 parents 5e5bf70 + 80fe7a9 commit ec8c461
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 32 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isEmpty } from "lodash";
import React, { forwardRef, useMemo } from "react";
import React, { forwardRef, ReactNode, useMemo } from "react";
import { VariableTypes } from "../../../../types";
import { UnknownFunction } from "../../../../types/common";
import { editors, EditorType, ExtendedEditor, SimpleEditor } from "./expression/Editor";
Expand All @@ -24,7 +24,7 @@ interface Props {
onValueChange: (value: string) => void;
fieldErrors?: FieldError[];
variableTypes: VariableTypes;
validationLabelInfo?: string;
validationLabelInfo?: ReactNode;
placeholder?: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SqlEditor } from "./SqlEditor";
import { StringEditor } from "./StringEditor";
import { FixedValuesEditor } from "./FixedValuesEditor";
import { ExpressionObj } from "./types";
import React, { ForwardRefExoticComponent, LegacyRef } from "react";
import React, { ForwardRefExoticComponent, LegacyRef, ReactNode } from "react";
import { DateEditor, DateTimeEditor, TimeEditor } from "./DateTimeEditor";

import { DurationEditor } from "./Duration/DurationEditor";
Expand All @@ -27,7 +27,7 @@ export type EditorProps = {
className?: string;
fieldErrors: FieldError[];
formatter?: Formatter;
expressionInfo?: string;
expressionInfo?: ReactNode;
expressionObj: ExpressionObj;
readOnly?: boolean;
showSwitch?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EditableEditor } from "../../../../editors/EditableEditor";
import { ExpressionLang } from "../../../../editors/expression/types";
import AceEditor from "react-ace";
import { ListItems } from "./ListItems";
import React, { useMemo, useState } from "react";
import React, { useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { FieldName, FixedValuesOption, onChangeType } from "../../../item";
import { NodeValidationError, ReturnedType, VariableTypes } from "../../../../../../../types";
Expand All @@ -15,7 +15,11 @@ import { GenericValidationRequest } from "../../../../../../../actions/nk/adhocT
import { debounce } from "lodash";
import { EditorType } from "../../../../editors/expression/Editor";
import { useSettings } from "../../SettingsProvider";
import { Box, Button, FormControl, Stack } from "@mui/material";
import { Box, Button, CircularProgress, FormControl, Stack } from "@mui/material";
import { useDelayedEnterAction } from "../../../../../../toolbars/scenarioDetails/useDelayedEnterAction";
import { IAceEditor } from "react-ace/lib/types";

const ENTER_VALUE_COMMAND = "addValueOnEnter";

interface Props {
onChange: (path: string, value: onChangeType) => void;
Expand All @@ -42,6 +46,7 @@ export const UserDefinedListInput = ({
initialValue,
inputLabel,
}: Props) => {
const editorRef = useRef<IAceEditor>(null);
const { t } = useTranslation();
const [temporaryListItem, setTemporaryListItem] = useState("");
const [temporaryValuesTyping, setTemporaryValuesTyping] = useState(false);
Expand Down Expand Up @@ -83,13 +88,22 @@ export const UserDefinedListInput = ({
[typ.refClazzName],
);

const { setIsEnterPressed } = useDelayedEnterAction({
action: () => {
editorRef.current.execCommand(ENTER_VALUE_COMMAND);
},
errorsLength: temporaryValueErrors.length,
inputTyping: temporaryValuesTyping,
});

const handleChangeFixedValuesList = (fixedValuesList: FixedValuesOption[]) => {
onChange(`${path}.valueEditor.fixedValuesList`, fixedValuesList);
handleTemporaryUserDefinedList(fixedValuesList);
};

const handleAddNewListItem = () => {
if (temporaryValuesTyping) {
setIsEnterPressed(true);
return;
}

Expand Down Expand Up @@ -130,7 +144,6 @@ export const UserDefinedListInput = ({
}
};

const ENTER_VALUE_COMMAND = "addValueOnEnter";
const aceEditorEnterCommand = {
name: ENTER_VALUE_COMMAND,
bindKey: { win: "enter", mac: "enter" },
Expand Down Expand Up @@ -172,18 +185,22 @@ export const UserDefinedListInput = ({
<Box width={"80%"} flex={1}>
<Stack direction="row" paddingY={1} spacing={1} justifyContent={"space-between"} alignItems={"start"}>
<EditableEditor
validationLabelInfo={temporaryValuesTyping && "Typing..."}
validationLabelInfo={
temporaryValuesTyping && <CircularProgress size={"1rem"} sx={(theme) => ({ marginTop: theme.spacing(0.5) })} />
}
expressionObj={{ language: ExpressionLang.SpEL, expression: temporaryListItem }}
onValueChange={(value) => {
setTemporaryListItem(value);
setTemporaryValuesTyping(true);
setTemporaryValueErrors([]);
validateTemporaryListItem(value);
setIsEnterPressed(false);
}}
variableTypes={variableTypes}
readOnly={readOnly}
ref={(ref: AceEditor | null) => {
if (ref?.editor) {
editorRef.current = ref.editor;
ref.editor.commands.addCommand(aceEditorEnterCommand);
}
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
AutocompleteInputChangeReason,
Box,
Chip,
CircularProgress,
createFilterOptions,
Link,
styled,
Expand All @@ -15,13 +16,14 @@ import {
useTheme,
} from "@mui/material";
import { selectStyled } from "../../../stylesheets/SelectStyled";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import HttpService from "../../../http/HttpService";
import i18next from "i18next";
import { editScenarioLabels } from "../../../actions/nk";
import { debounce } from "lodash";
import { ScenarioLabelValidationError } from "../../Labels/types";
import { useTranslation } from "react-i18next";
import { useDelayedEnterAction } from "./useDelayedEnterAction";

interface AddLabelProps {
onClick: () => void;
Expand Down Expand Up @@ -109,6 +111,7 @@ interface Props {

export const ScenarioLabels = ({ readOnly }: Props) => {
const { t } = useTranslation();
const autocompleteRef = useRef(null);
const scenarioLabels = useSelector(getScenarioLabels);
const scenarioLabelOptions: LabelOption[] = useMemo(() => scenarioLabels.map(toLabelOption), [scenarioLabels]);
const initialScenarioLabelOptionsErrors = useSelector(getScenarioLabelsErrors).filter((error) =>
Expand Down Expand Up @@ -193,10 +196,13 @@ export const ScenarioLabels = ({ readOnly }: Props) => {
}
}, []);

const setLabels = (options: LabelOption[]) => {
const newLabels = options.map(toLabelValue);
dispatch(editScenarioLabels(newLabels));
};
const setLabels = useCallback(
(options: LabelOption[]) => {
const newLabels = options.map(toLabelValue);
dispatch(editScenarioLabels(newLabels));
},
[dispatch],
);

useEffect(() => {
validateSelectedOptions(scenarioLabelOptions);
Expand All @@ -216,12 +222,28 @@ export const ScenarioLabels = ({ readOnly }: Props) => {
}
}, [scenarioLabelOptions, showEditor]);

const { setIsEnterPressed } = useDelayedEnterAction({
action: () => {
const enterEvent = new KeyboardEvent("keydown", {
key: "Enter",
keyCode: 13,
code: "Enter",
bubbles: true,
cancelable: true,
});
autocompleteRef.current.dispatchEvent(enterEvent);
},
errorsLength: inputErrors.length,
inputTyping,
});

return (
<>
{!showEditor ? (
<AddLabel onClick={handleAddLabelClick} />
) : (
<StyledAutocomplete
ref={autocompleteRef}
data-testid={"Labels"}
isEdited={isEdited}
id="scenario-labels"
Expand Down Expand Up @@ -266,11 +288,7 @@ export const ScenarioLabels = ({ readOnly }: Props) => {
isOptionEqualToValue={(v1: LabelOption, v2: LabelOption) => v1.value === v2.value}
loading={isFetching || inputTyping}
clearOnBlur
loadingText={
inputTyping
? i18next.t("panels.scenarioDetails.labels.labelTyping", "Typing...")
: i18next.t("panels.scenarioDetails.labels.labelsLoading", "Loading...")
}
loadingText={<CircularProgress size={"1rem"} />}
multiple
noOptionsText={i18next.t("panels.scenarioDetails.labels.noAvailableLabels", "No labels")}
onBlur={() => {
Expand Down Expand Up @@ -300,6 +318,7 @@ export const ScenarioLabels = ({ readOnly }: Props) => {
}}
onInputChange={(_, newInputValue: string, reason: AutocompleteInputChangeReason) => {
if (reason === "input") {
setIsEnterPressed(false);
setInputTyping(true);
}
setInputErrors([]);
Expand Down Expand Up @@ -331,6 +350,7 @@ export const ScenarioLabels = ({ readOnly }: Props) => {
event.key === "Enter" &&
(inputErrors.length !== 0 || inputTyping || isInputInSelectedOptions(input))
) {
setIsEnterPressed(true);
event.stopPropagation();
}
},
Expand All @@ -354,9 +374,7 @@ export const ScenarioLabels = ({ readOnly }: Props) => {
const labelError = labelOptionsErrors.find((error) => error.label === toLabelValue(option));
return (
<StyledLabelChip
title={t("panels.scenarioDetails.tooltip.label", "Scenario label: {{label}}", {
label: option.title,
})}
title={t("panels.scenarioDetails.tooltip.label", "Label")}
key={key}
data-testid={`scenario-label-${index}`}
color={labelError ? "error" : "default"}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useEffect, useState } from "react";

interface Props {
inputTyping: boolean;
errorsLength: number;
action: () => void;
}

export const useDelayedEnterAction = ({ inputTyping, errorsLength, action }: Props) => {
const [isEnterPressed, setIsEnterPressed] = useState(false);

useEffect(() => {
if (isEnterPressed && !inputTyping && errorsLength === 0) {
action();
setIsEnterPressed(false);
}
}, [errorsLength, inputTyping, isEnterPressed, action]);

return { setIsEnterPressed };
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function LabelChip({ id, value, filterValue, setFilter }: Props): JSX.Ele

return (
<StyledLabelChip
title={t("scenariosList.tooltip.label", "Scenario label: {{label}}", { label: value })}
title={t("scenariosList.tooltip.label", "Label")}
key={id}
color={isSelected ? "primary" : "default"}
size="small"
Expand Down
3 changes: 2 additions & 1 deletion docs/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
* Flink upgrade to 1.19.1. Note: it is possible to use Nussknacker with older versions of Flink, but it requires some extra steps. See [Migration guide](MigrationGuide.md) for details.
* Performance optimisations of the serialisation of events passing through Flink's `DataStream`s.

### 1.18.0 (Not released yet)
### 1.18.0 (22 November 2024)

* [#6944](https://github.com/TouK/nussknacker/pull/6944) [#7166](https://github.com/TouK/nussknacker/pull/7166) Changes around adhoc testing feature
* `test-with-form` button was renamed to `adhoc-testing`
Expand Down Expand Up @@ -124,6 +124,7 @@
* [#7183](https://github.com/TouK/nussknacker/pull/7183) Hide categories from a scenarios list and more scenario details when only one category is available
* [#7192](https://github.com/TouK/nussknacker/pull/7192) Fix "Failed to get node validation" when opening node details referencing non-existing component
* [#7190](https://github.com/TouK/nussknacker/pull/7190) Fix "Failed to get node validation" when opening fragment node details for referencing non-existing fragment
* [#7215](https://github.com/TouK/nussknacker/pull/7215) Change typing text to spinner during validation and provide delayed adding on enter until validation finishes in a scenario labels and fragment input

## 1.17

Expand Down
2 changes: 1 addition & 1 deletion docs/MigrationGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ To see the biggest differences please consult the [changelog](Changelog.md).
* We lost support for old ConsumerRecord constructor supported by Flink 1.14 / 1.15
* If you used Kafka source/sink components in your scenarios then state of these scenarios won't be restored

## In version 1.18.0 (Not released yet)
## In version 1.18.0

### Configuration changes

Expand Down
15 changes: 7 additions & 8 deletions docs/scenarios_authoring/Spel.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,11 @@ Every unknown accessed field/element will produce `Unknown` data type, which can

### Type conversions

It is possible to cast or convert from a type to another type and this can be done by implicit and explicit conversion.
It is possible to convert from a type to another type and this can be done by implicit and explicit conversion.

#### Explicit conversions

Explicit conversions/casts are available as built-in functions.
Explicit conversions are available as built-in functions and utility classes.
List of built-in functions:
- `canBe(className)`/`to(className)`/`toOrNull(className)`
- `canBeBoolean`/`toBoolean`/`toBooleanOrNull`
Expand All @@ -282,17 +282,15 @@ List of built-in functions:
- `canBeList`/`toList`/`toListOrNull`
- `canBeMap`/`toMap`/`toMapOrNull`

The aforementioned functions first attempt to cast a value to the specified class. If the cast fails and there is a
defined conversion to that class, the conversion is applied.
The `canBe`, `to` and `toOrNull` functions take the name of target class as a parameter, in contrast to, for
example, `canBeLong` which has the name of target class in the function name and is the shortcut for: `canBe('Long')`.
We have added some functions with types in their names, for example: `canBeLong` to have shortcuts to the most common
types.

Functions with the prefix `canBe` check whether a type can be cast or converted to the appropriate type. Functions with
the `to` prefix cast or convert a value to the desired type, and if the operation fails, an exception is propagated
further. Functions with the `to` prefix and `OrNull` suffix cast or convert a value to the desired type,
and if the operation fails, a null value is returned.
Functions with the prefix `canBe` check whether a type can be converted to the appropriate type. Functions with the `to`
prefix convert a value to the desired type, and if the operation fails, an exception is propagated further. Functions
with the `to` prefix and `OrNull` suffix convert a value to the desired type, and if the operation fails, a null value
is returned.

Examples of conversions:

Expand All @@ -306,6 +304,7 @@ Examples of conversions:
| `'abc'.toOrNull('Double')` | null | Double |
| `'abc'.toLong` | exception thrown in runtime | Long |
| `{{name: 'John', age: 22}}.![{key: #this.name, value: #this.age}].toMap` | {John: 22} | Map[String, Long] |
| `'2018-10-23T12:12:13'.to('LocalDateTime')` | 2018-10-23T12:12:13+00:00 | LocalDateTime |

Conversions only make sense between specific types. We limit SpeL's suggestions to show only possible conversions.
Below is a matrix which shows which types can be converted with each other:
Expand Down

0 comments on commit ec8c461

Please sign in to comment.