diff --git a/designer/client/src/components/modals/ActivityCommentTextField.tsx b/designer/client/src/components/modals/ActivityCommentTextField.tsx new file mode 100644 index 00000000000..256808dc198 --- /dev/null +++ b/designer/client/src/components/modals/ActivityCommentTextField.tsx @@ -0,0 +1,21 @@ +import { styled, TextField, TextFieldProps } from "@mui/material"; +import React from "react"; + +export const ActivityCommentTextField = styled((props: TextFieldProps) => ( + +))({ + flexDirection: "column", + ".MuiFormLabel-root": { + margin: 0, + flexDirection: "column", + }, +}); diff --git a/designer/client/src/components/modals/ActivityHeader.tsx b/designer/client/src/components/modals/ActivityHeader.tsx new file mode 100644 index 00000000000..4c02dcf8f43 --- /dev/null +++ b/designer/client/src/components/modals/ActivityHeader.tsx @@ -0,0 +1,33 @@ +import { useSelector } from "react-redux"; +import { getProcessName } from "../../reducers/selectors/graph"; +import { Box, Typography } from "@mui/material"; +import React from "react"; +import ProcessDialogWarnings from "./ProcessDialogWarnings"; + +interface Props { + title: string; + displayWarnings?: boolean; +} + +export function ActivityHeader(props: Props): JSX.Element { + const processName = useSelector(getProcessName); + return ( + + + {props.title} + + + {processName} + + {props.displayWarnings && } + + ); +} diff --git a/designer/client/src/components/modals/ActivityProperty.tsx b/designer/client/src/components/modals/ActivityProperty.tsx new file mode 100644 index 00000000000..c401d025e16 --- /dev/null +++ b/designer/client/src/components/modals/ActivityProperty.tsx @@ -0,0 +1,49 @@ +import { ExpressionLang } from "../graph/node-modal/editors/expression/types"; +import React, { useCallback } from "react"; +import { FieldLabel } from "../graph/node-modal/FieldLabel"; +import { getValidationErrorsForField } from "../graph/node-modal/editors/Validators"; +import { ActivityNodeParameters, ActivityParameterConfig } from "../../types/activity"; +import { NodesDeploymentData } from "../../http/HttpService"; +import { NodeValidationError } from "../../types"; +import { default as EditableEditor } from "../graph/node-modal/editors/EditableEditor"; + +interface Props { + nodeName: string; + propertyName: string; + propertyConfig: ActivityParameterConfig; + nodesData: NodesDeploymentData; + onChange: ( + nodeId: string, + property: K, + newValue: ActivityNodeParameters["parameters"][K], + defaultValue?: ActivityNodeParameters["parameters"][K], + ) => void; + errors: NodeValidationError[]; +} + +export function ActivityProperty(props: Props): JSX.Element { + const { nodeName, propertyName, propertyConfig, errors, nodesData, onChange } = props; + + const current = nodesData[nodeName][propertyName] || ""; + const expressionObj = { expression: current, value: current, language: ExpressionLang.String }; + const onValueChange = useCallback((newValue) => onChange(nodeName, propertyName, newValue), [onChange, nodeName, propertyName]); + + return ( + ( + + )} + readOnly={false} + showSwitch={false} + showValidation={true} + //ScenarioProperties do not use any variables + variableTypes={{}} + fieldErrors={getValidationErrorsForField(errors, propertyName)} + /> + ); +} diff --git a/designer/client/src/components/modals/AdvancedParametersSection.tsx b/designer/client/src/components/modals/AdvancedParametersSection.tsx new file mode 100644 index 00000000000..74cb8897190 --- /dev/null +++ b/designer/client/src/components/modals/AdvancedParametersSection.tsx @@ -0,0 +1,26 @@ +import React, { PropsWithChildren } from "react"; +import Accordion from "@mui/material/Accordion"; +import AccordionSummary from "@mui/material/AccordionSummary"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import { Typography } from "@mui/material"; +import AccordionDetails from "@mui/material/AccordionDetails"; + +interface Props { + nodeId: string; +} + +export function AdvancedParametersSection({ children, nodeId }: PropsWithChildren): JSX.Element { + return ( + + } + aria-controls={`${nodeId}-content`} + id={`${nodeId}-header`} + sx={{ flexDirection: "row-reverse", px: 0, border: 0 }} + > + {nodeId} + + {children} + + ); +} diff --git a/designer/client/src/components/modals/CalculateCounts/CalculateCountsDialog.tsx b/designer/client/src/components/modals/CalculateCounts/CalculateCountsDialog.tsx index 7391493f8c5..3a2f045e048 100644 --- a/designer/client/src/components/modals/CalculateCounts/CalculateCountsDialog.tsx +++ b/designer/client/src/components/modals/CalculateCounts/CalculateCountsDialog.tsx @@ -59,7 +59,7 @@ export function CountsDialog({ children, ...props }: PropsWithChildren { await confirm(); diff --git a/designer/client/src/components/modals/CustomActionDialog.tsx b/designer/client/src/components/modals/CustomActionDialog.tsx index 679f2a0afb6..7818d1f87d4 100644 --- a/designer/client/src/components/modals/CustomActionDialog.tsx +++ b/designer/client/src/components/modals/CustomActionDialog.tsx @@ -7,7 +7,7 @@ import { loadProcessState } from "../../actions/nk"; import HttpService, { CustomActionValidationRequest } from "../../http/HttpService"; import { CustomAction, NodeValidationError } from "../../types"; import { UnknownRecord } from "../../types/common"; -import { WindowContent, WindowKind } from "../../windowManager"; +import { PromptContent, WindowKind } from "../../windowManager"; import { ChangeableValue } from "../ChangeableValue"; import { editors, ExtendedEditor, SimpleEditor } from "../graph/node-modal/editors/expression/Editor"; import { ExpressionLang } from "../graph/node-modal/editors/expression/types"; @@ -18,7 +18,8 @@ import { LoadingButtonTypes } from "../../windowManager/LoadingButton"; import { nodeValue } from "../graph/node-modal/NodeDetailsContent/NodeTableStyled"; import { getValidationErrorsForField } from "../graph/node-modal/editors/Validators"; import { getFeatureSettings } from "../../reducers/selectors/settings"; -import CommentInput from "../comment/CommentInput"; +import { ActivityCommentTextField } from "./ActivityCommentTextField"; +import { ActivityHeader } from "./ActivityHeader"; interface CustomActionFormProps extends ChangeableValue { action: CustomAction; @@ -123,32 +124,25 @@ export function CustomActionDialog(props: WindowContentProps [ { title: t("dialog.button.cancel", "Cancel"), action: () => props.close(), classname: LoadingButtonTypes.secondaryButton }, - { title: t("dialog.button.confirm", "Ok"), action: () => confirmAction() }, + { title: t("dialog.button.confirm", "Apply"), action: () => confirmAction() }, ], [confirmAction, props, t], ); return ( - +
- + setComment(e.target.value)} - value={comment} - defaultValue={deploymentCommentSettings?.exampleComment} - className={cx( - css({ - minWidth: 600, - minHeight: 80, - }), - )} autoFocus /> - - {validationError} -
-
+ ); } diff --git a/designer/client/src/components/modals/DeployProcessDialog.tsx b/designer/client/src/components/modals/DeployProcessDialog.tsx index a9c468f2b10..2eae9482915 100644 --- a/designer/client/src/components/modals/DeployProcessDialog.tsx +++ b/designer/client/src/components/modals/DeployProcessDialog.tsx @@ -7,26 +7,43 @@ import { getActivityParameters, getProcessName } from "../../reducers/selectors/ import { getFeatureSettings } from "../../reducers/selectors/settings"; import { ProcessName } from "../Process/types"; import { PromptContent, WindowKind } from "../../windowManager"; -import CommentInput from "../comment/CommentInput"; -import ProcessDialogWarnings from "./ProcessDialogWarnings"; -import { FormHelperText, Typography } from "@mui/material"; import { LoadingButtonTypes } from "../../windowManager/LoadingButton"; +import { ActivityNodeParameters } from "../../types/activity"; +import { AdvancedParametersSection } from "./AdvancedParametersSection"; +import { mapValues } from "lodash"; +import { NodesDeploymentData } from "../../http/HttpService"; +import { ActivityProperty } from "./ActivityProperty"; +import { ActivityCommentTextField } from "./ActivityCommentTextField"; +import { ActivityHeader } from "./ActivityHeader"; +import { NodeTable } from "../graph/node-modal/NodeDetailsContent/NodeTable"; export type ToggleProcessActionModalData = { - action: (processName: ProcessName, comment: string) => Promise; + action: (processName: ProcessName, comment: string, nodeData: NodesDeploymentData) => Promise; + activityName: string; displayWarnings?: boolean; }; +function initialNodesData(params: ActivityNodeParameters[]) { + return params.reduce( + (paramObj, { nodeId, parameters }) => ({ + ...paramObj, + [nodeId]: mapValues(parameters, (value) => value.defaultValue || ""), + }), + {}, + ); +} + export function DeployProcessDialog(props: WindowContentProps): JSX.Element { // TODO: get rid of meta const { - meta: { action, displayWarnings }, + meta: { action, activityName, displayWarnings }, } = props.data; const processName = useSelector(getProcessName); const activityParameters = useSelector(getActivityParameters); - const activityNodeParameters = activityParameters["DEPLOY"] || []; - console.log(activityNodeParameters); + const activityNodeParameters = activityParameters[activityName] || ([] as ActivityNodeParameters[]); + const initialValues = useMemo(() => initialNodesData(activityNodeParameters), [activityNodeParameters]); + const [values, setValues] = useState(initialValues); const [comment, setComment] = useState(""); const [validationError, setValidationError] = useState(""); @@ -37,7 +54,7 @@ export function DeployProcessDialog(props: WindowContentProps { try { - await action(processName, comment); + await action(processName, comment, values); props.close(); } catch (error) { setValidationError(error?.response?.data); @@ -47,32 +64,54 @@ export function DeployProcessDialog(props: WindowContentProps [ - { title: t("dialog.button.cancel", "Cancel"), action: () => props.close(), classname: LoadingButtonTypes.secondaryButton }, - { title: t("dialog.button.ok", "Ok"), action: () => confirmAction() }, + { + title: t("dialog.button.cancel", "Cancel"), + action: () => props.close(), + classname: LoadingButtonTypes.secondaryButton, + }, + { title: t("dialog.button.ok", "Apply"), action: () => confirmAction() }, ], [confirmAction, props, t], ); return ( -
- {props.data.title} - {displayWarnings && } - + + setComment(e.target.value)} - value={comment} - defaultValue={deploymentCommentSettings?.exampleComment} - className={cx( - css({ - minWidth: 600, - minHeight: 80, - }), - )} autoFocus /> - - {validationError} - + {activityNodeParameters.map((anp: ActivityNodeParameters) => ( + + + {Object.entries(anp.parameters).map(([paramName, paramConfig]) => { + return ( + { + setValues({ + ...values, + [nodeId]: { + ...values[nodeId], + [paramName]: newValue, + }, + }); + }} + nodesData={values} + /> + ); + })} + + + ))}
); diff --git a/designer/client/src/components/modals/GenerateTestDataDialog.tsx b/designer/client/src/components/modals/GenerateTestDataDialog.tsx index 03d80ea3905..a9ae5ab792e 100644 --- a/designer/client/src/components/modals/GenerateTestDataDialog.tsx +++ b/designer/client/src/components/modals/GenerateTestDataDialog.tsx @@ -46,7 +46,7 @@ function GenerateTestDataDialog(props: WindowContentProps): JSX.Element { const buttons: WindowButtonProps[] = useMemo( () => [ { title: t("dialog.button.cancel", "Cancel"), action: () => props.close(), classname: LoadingButtonTypes.secondaryButton }, - { title: t("dialog.button.ok", "Ok"), disabled: !isValid, action: () => confirmAction() }, + { title: t("dialog.button.ok", "Apply"), disabled: !isValid, action: () => confirmAction() }, ], [t, confirmAction, props, isValid], ); diff --git a/designer/client/src/components/modals/SaveProcessDialog.tsx b/designer/client/src/components/modals/SaveProcessDialog.tsx index d77f2222c57..8853392368e 100644 --- a/designer/client/src/components/modals/SaveProcessDialog.tsx +++ b/designer/client/src/components/modals/SaveProcessDialog.tsx @@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; import { displayCurrentProcessVersion, displayProcessActivity, loadProcessToolbarsConfiguration } from "../../actions/nk"; import { PromptContent } from "../../windowManager"; -import { CommentInput } from "../comment/CommentInput"; import { ThunkAction } from "../../actions/reduxTypes"; import { getScenarioGraph, @@ -18,8 +17,9 @@ import HttpService from "../../http/HttpService"; import { ActionCreators as UndoActionCreators } from "redux-undo"; import { visualizationUrl } from "../../common/VisualizationUrl"; import { useLocation, useNavigate } from "react-router-dom"; -import { Typography } from "@mui/material"; import { LoadingButtonTypes } from "../../windowManager/LoadingButton"; +import { ActivityCommentTextField } from "./ActivityCommentTextField"; +import { ActivityHeader } from "./ActivityHeader"; export function SaveProcessDialog(props: WindowContentProps): JSX.Element { const location = useLocation(); @@ -70,7 +70,7 @@ export function SaveProcessDialog(props: WindowContentProps): JSX.Element { const buttons: WindowButtonProps[] = useMemo( () => [ { title: t("dialog.button.cancel", "Cancel"), action: () => props.close(), classname: LoadingButtonTypes.secondaryButton }, - { title: t("dialog.button.ok", "Ok"), action: () => confirmAction() }, + { title: t("dialog.button.ok", "Apply"), action: () => confirmAction() }, ], [confirmAction, props, t], ); @@ -78,16 +78,8 @@ export function SaveProcessDialog(props: WindowContentProps): JSX.Element { return (
- {props.data.title} - setState(e.target.value)} - value={comment} - className={css({ - minWidth: 600, - minHeight: 80, - })} - autoFocus - /> + + setState(e.target.value)} autoFocus />
); diff --git a/designer/client/src/components/toolbars/process/buttons/SaveButton.tsx b/designer/client/src/components/toolbars/process/buttons/SaveButton.tsx index 44e25d994ee..19812e0277a 100644 --- a/designer/client/src/components/toolbars/process/buttons/SaveButton.tsx +++ b/designer/client/src/components/toolbars/process/buttons/SaveButton.tsx @@ -17,8 +17,8 @@ function SaveButton(props: ToolbarButtonProps): JSX.Element { const unsavedNewName = useSelector(getProcessUnsavedNewName); const isRenamed = useSelector(isProcessRenamed); const title = isRenamed - ? t("saveProcess.renameTitle", "Save scenario as {{name}}", { name: unsavedNewName }) - : t("saveProcess.title", "Save scenario {{name}}", { name: processName }); + ? t("saveProcess.renameTitle", "Save scenario as", { name: unsavedNewName }) + : t("saveProcess.title", "Save scenario", { name: processName }); const { open } = useWindows(); const onClick = () => diff --git a/designer/client/src/components/toolbars/scenarioActions/buttons/CancelDeployButton.tsx b/designer/client/src/components/toolbars/scenarioActions/buttons/CancelDeployButton.tsx index cc14a764dcb..1bb36088e26 100644 --- a/designer/client/src/components/toolbars/scenarioActions/buttons/CancelDeployButton.tsx +++ b/designer/client/src/components/toolbars/scenarioActions/buttons/CancelDeployButton.tsx @@ -23,7 +23,7 @@ export default function CancelDeployButton(props: ToolbarButtonProps) { const { open } = useWindows(); const action = (p, c) => HttpService.cancel(p, c).finally(() => dispatch(loadProcessState(processName))); - const message = t("panels.actions.deploy-canel.dialog", "Cancel scenario {{name}}", { name: processName }); + const message = t("panels.actions.deploy-canel.dialog", "Cancel scenario"); return ( HttpService.deploy(p, c).finally(() => dispatch(loadProcessState(processName))); + const message = t("panels.actions.deploy.dialog", "Deploy scenario"); + const action = (p, c, d) => HttpService.deploy(p, c, d).finally(() => dispatch(loadProcessState(processName))); return ( ; + parameters: { [key: ActivityParameterName]: ActivityParameterConfig }; } export type ActivityName = string; -export type ActivityParameters = Record; +export type ActivityParameters = { [key: ActivityName]: ActivityNodeParameters[] }; diff --git a/designer/client/src/windowManager/PromptContent.tsx b/designer/client/src/windowManager/PromptContent.tsx index 37a76f38740..785d9d6137d 100644 --- a/designer/client/src/windowManager/PromptContent.tsx +++ b/designer/client/src/windowManager/PromptContent.tsx @@ -16,7 +16,13 @@ export function PromptContent(props: PropsWithChildren): JS paddingLeft: theme.custom.spacing.baseUnit * 6, paddingRight: theme.custom.spacing.baseUnit * 6, }); - return { ...props.classnames, content }; + return { + footer: css({ + justifyContent: "flex-end", + }), + ...props.classnames, + content, + }; }, [props.classnames, theme.custom.spacing.baseUnit]); const components = useMemo( diff --git a/nussknacker-dist/src/universal/conf/dev-application.conf b/nussknacker-dist/src/universal/conf/dev-application.conf index ddf3c4c27aa..3082ff0a08c 100644 --- a/nussknacker-dist/src/universal/conf/dev-application.conf +++ b/nussknacker-dist/src/universal/conf/dev-application.conf @@ -359,8 +359,8 @@ commentSettings: { } deploymentCommentSettings: { - validationPattern: "(.*)" - exampleComment: "issues/1234" + validationPattern: "issues/\\d+" + exampleComment: "issues/1234 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ultricies nec sapien id bibendum. Ut in mollis risus. Curabitur efficitur maximus interdum. Vivamus convallis eu nibh ut rhoncus. Quisque finibus maximus dui vel finibus." } countsSettings {