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

[NU-1806] Add activity parameters #6860

Draft
wants to merge 20 commits into
base: staging
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package pl.touk.nussknacker.engine.api.definition

import io.circe.generic.JsonCodec

@JsonCodec case class FixedExpressionValue(expression: String, label: String)
@JsonCodec case class FixedExpressionValue(expression: String, label: String, hintText: Option[String] = None)

object FixedExpressionValue {
val nullFixedValue: FixedExpressionValue = FixedExpressionValue("", "")
def apply(expression: String, label: String): FixedExpressionValue = FixedExpressionValue(expression, label, None)
val nullFixedValue: FixedExpressionValue = FixedExpressionValue("", "")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package pl.touk.nussknacker.engine.api.editor;

public enum FixedValuesEditorMode {
LIST, RADIO;

public static FixedValuesEditorMode fromName(String name) {
switch (name) {
case "LIST":
return LIST;
case "RADIO":
default:
return RADIO;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package pl.touk.nussknacker.engine.api.component

import io.circe.generic.extras.semiauto.{deriveUnwrappedDecoder, deriveUnwrappedEncoder}
import io.circe.{Decoder, Encoder}
import pl.touk.nussknacker.engine.api.NodeId
import pl.touk.nussknacker.engine.api.component.NodesDeploymentData.NodeDeploymentData

final case class NodesDeploymentData(dataByNodeId: Map[NodeId, NodeDeploymentData])

object NodesDeploymentData {

// Raw deployment parameters (name -> value) that are used as additional node configuration during deployment.
// Each node can be provided with dedicated set of parameters.
// TODO: consider replacing NodeDeploymentData with Json
type NodeDeploymentData = Map[String, String]

val empty: NodesDeploymentData = NodesDeploymentData(Map.empty)

implicit val nodesDeploymentDataEncoder: Encoder[NodesDeploymentData] = Encoder
Expand All @@ -18,19 +23,3 @@ object NodesDeploymentData {
Decoder.decodeMap[NodeId, NodeDeploymentData].map(NodesDeploymentData(_))

}

sealed trait NodeDeploymentData

final case class SqlFilteringExpression(sqlExpression: String) extends NodeDeploymentData

object NodeDeploymentData {

implicit val nodeDeploymentDataEncoder: Encoder[NodeDeploymentData] =
deriveUnwrappedEncoder[SqlFilteringExpression].contramap { case sqlExpression: SqlFilteringExpression =>
sqlExpression
}

implicit val nodeDeploymentDataDecoder: Decoder[NodeDeploymentData] =
deriveUnwrappedDecoder[SqlFilteringExpression].map(identity)

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package pl.touk.nussknacker.engine.api.definition

import io.circe.generic.JsonCodec
import io.circe.generic.extras.ConfiguredJsonCodec
import io.circe.{Decoder, Encoder, Json}
import io.circe.generic.semiauto.deriveEncoder
import io.circe.{Decoder, Encoder, HCursor, Json}
import pl.touk.nussknacker.engine.api.CirceUtil._
import pl.touk.nussknacker.engine.api.editor.FixedValuesEditorMode
import pl.touk.nussknacker.engine.api.editor.DualEditorMode

import java.time.temporal.ChronoUnit
Expand Down Expand Up @@ -73,8 +75,10 @@ object PeriodParameterEditor {
*/
case object CronParameterEditor extends SimpleParameterEditor

@JsonCodec case class FixedValuesParameterEditor(possibleValues: List[FixedExpressionValue])
extends SimpleParameterEditor
case class FixedValuesParameterEditor(
possibleValues: List[FixedExpressionValue],
mode: FixedValuesEditorMode = FixedValuesEditorMode.LIST
) extends SimpleParameterEditor

@JsonCodec case class FixedValuesWithIconParameterEditor(possibleValues: List[FixedExpressionValueWithIcon])
extends SimpleParameterEditor
Expand All @@ -100,3 +104,33 @@ object DualParameterEditor {
}

}

object FixedValuesParameterEditor {
def apply(possibleValues: List[FixedExpressionValue]): FixedValuesParameterEditor =
FixedValuesParameterEditor(possibleValues, mode = FixedValuesEditorMode.LIST)

implicit val fixedValuesEditorModeEncoder: Encoder[FixedValuesEditorMode] = new Encoder[FixedValuesEditorMode] {
override def apply(a: FixedValuesEditorMode): Json = Encoder.encodeString(a.name())
}

implicit val fixedValuesEditorModeDecoder: Decoder[FixedValuesEditorMode] =
Decoder.decodeString.emapTry(name => Try(FixedValuesEditorMode.fromName(name)))

implicit val fixedValuesParameterEditorEncoder: Encoder[FixedValuesParameterEditor] =
deriveEncoder[FixedValuesParameterEditor]

implicit val fixedValuesParameterEditorDecoder: Decoder[FixedValuesParameterEditor] = { (c: HCursor) =>
{
for {
possibleValues <- c.downField("possibleValues").as[List[FixedExpressionValue]]
modeOpt <- c.downField("mode").as[Option[String]]
} yield {
FixedValuesParameterEditor(
possibleValues,
modeOpt.map(FixedValuesEditorMode.fromName).getOrElse(FixedValuesEditorMode.LIST)
)
}
}
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package pl.touk.nussknacker.engine.api.process

import pl.touk.nussknacker.engine.api.component.Component._
import pl.touk.nussknacker.engine.api.component.{Component, ProcessingMode}
import pl.touk.nussknacker.engine.api.component.{Component, ParameterConfig, ProcessingMode}
import pl.touk.nussknacker.engine.api.context.ContextTransformation
import pl.touk.nussknacker.engine.api.definition.{Parameter, WithExplicitTypesToExtract}
import pl.touk.nussknacker.engine.api.parameter.ParameterName
Expand Down Expand Up @@ -49,6 +49,15 @@ trait TestWithParametersSupport[+T] { self: Source =>
def parametersToTestData(params: Map[ParameterName, AnyRef]): T
}

/**
* Used to define Source parameters for each activity
* e.g.
* {"DEPLOY": { "parametername": ...parameter configuration... }
*/
trait WithActivityParameters { self: Source =>
def activityParametersDefinition: Map[String, Map[String, ParameterConfig]]
}

/**
* [[pl.touk.nussknacker.engine.api.process.SourceFactory]] has to have method annotated with [[pl.touk.nussknacker.engine.api.MethodToInvoke]]
* that returns [[pl.touk.nussknacker.engine.api.process.Source]]
Expand Down
1 change: 1 addition & 0 deletions designer/client/src/actions/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type ActionTypes =
| "PROCESS_RENAME"
| "EDIT_LABELS"
| "SHOW_METRICS"
| "UPDATE_ACTIVITY_PARAMETERS"
| "UPDATE_TEST_CAPABILITIES"
| "UPDATE_TEST_FORM_PARAMETERS"
| "DISPLAY_PROCESS"
Expand Down
10 changes: 10 additions & 0 deletions designer/client/src/actions/nk/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ export function loadProcessState(processName: ProcessName): ThunkAction {
);
}

export function fetchActivityParameters(processName: ProcessName, scenarioGraph: ScenarioGraph) {
return (dispatch) =>
HttpService.getActivityParameters(processName, scenarioGraph).then(({ data }) => {
dispatch({
type: "UPDATE_ACTIVITY_PARAMETERS",
activityParameters: data,
});
});
}

export function fetchTestFormParameters(processName: ProcessName, scenarioGraph: ScenarioGraph) {
return (dispatch) =>
HttpService.getTestFormParameters(processName, scenarioGraph).then(({ data }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ExpressionObj } from "./types";
import { isEmpty } from "lodash";
import { cx } from "@emotion/css";
import { selectStyled } from "../../../../../stylesheets/SelectStyled";
import { Stack, styled, Typography, useTheme } from "@mui/material";
import { FormControlLabel, Radio, RadioGroup, Stack, styled, Typography, useTheme } from "@mui/material";
import { ExtendedEditor } from "./Editor";
import { FieldError } from "../Validators";
import { FixedValuesOption } from "../../fragment-input-definition/item";
Expand All @@ -26,26 +26,34 @@ interface Option {
label: string;
value: string;
icon: string | null;
hintText: string | null;
}

function getOptions(values: FixedValuesOption[]): Option[] {
return values.map((value) => ({
value: value.expression,
label: value.label,
icon: value.icon,
hintText: value.hintText,
}));
}

enum FixedValuesEditorMode {
LIST = "LIST",
RADIO = "RADIO",
}

export const FixedValuesEditor: ExtendedEditor<Props> = (props: Props) => {
const handleCurrentOption = (expressionObj: ExpressionObj, options: Option[]): Option => {
return (
(expressionObj && options.find((option) => option.value === expressionObj.expression)) || // current value with label taken from options
(expressionObj && { value: expressionObj.expression, label: expressionObj.expression, icon: null }) || // current value is no longer valid option? Show it anyway, let user know. Validation should take care
(expressionObj && { value: expressionObj.expression, label: expressionObj.expression, icon: null, hintText: null }) || // current value is no longer valid option? Show it anyway, let user know. Validation should take care
null
); // just leave undefined and let the user explicitly select one
};

const { expressionObj, readOnly, onValueChange, className, showValidation, editorConfig, fieldErrors } = props;
const mode = FixedValuesEditorMode[editorConfig.mode || "LIST"];
const options = getOptions(editorConfig.possibleValues);
const currentOption = handleCurrentOption(expressionObj, options);
const theme = useTheme();
Expand All @@ -58,7 +66,17 @@ export const FixedValuesEditor: ExtendedEditor<Props> = (props: Props) => {

const { control, input, valueContainer, singleValue, menuPortal, menu, menuList, menuOption, indicatorSeparator, dropdownIndicator } =
selectStyled(theme);
return (
return mode == FixedValuesEditorMode.RADIO ? (
<div className={cx(className)}>
<RadioGroup value={currentOption.value} onChange={(event) => onValueChange(event.target.value)}>
{options.map((option: Option) => {
const label = option.value === "" ? `${option.value} (default)` : option.value;
return <FormControlLabel key={option.value} value={option.value} control={<Radio />} label={label} />;
})}
</RadioGroup>
{currentOption.hintText ? <Typography sx={{ width: "100%" }}>{currentOption.hintText}</Typography> : null}
</div>
) : (
<div className={cx(className)}>
<Creatable
value={currentOption}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface FixedValuesOption {
expression: string;
label: string;
icon?: string;
hintText?: string;
}

export interface GenericParameterVariant {
Expand Down
21 changes: 21 additions & 0 deletions designer/client/src/components/modals/ActivityCommentTextField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { styled, TextField, TextFieldProps } from "@mui/material";
import React from "react";

export const ActivityCommentTextField = styled((props: TextFieldProps) => (
<TextField
fullWidth
multiline
minRows={1}
maxRows={3}
InputLabelProps={{ shrink: true }}
variant="outlined"
label="Comment"
{...props}
/>
))({
flexDirection: "column",
".MuiFormLabel-root": {
margin: 0,
flexDirection: "column",
},
});
28 changes: 28 additions & 0 deletions designer/client/src/components/modals/ActivityHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useSelector } from "react-redux";
import { getProcessName } from "../../reducers/selectors/graph";
import { 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 (
<>
<Typography
variant={"body2"}
sx={{ width: "100%", "::after": { content: "':'" }, fontWeight: 600, fontSize: "16px", lineHeight: "24px" }}
>
{props.title}
</Typography>
<Typography variant={"body2"} sx={{ width: "100%", fontWeight: 600, fontSize: "20px", lineHeight: "30px" }}>
{processName}
</Typography>
{props.displayWarnings && <ProcessDialogWarnings />}
</>
);
}
49 changes: 49 additions & 0 deletions designer/client/src/components/modals/ActivityProperty.tsx
Original file line number Diff line number Diff line change
@@ -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: <K extends keyof ActivityNodeParameters["parameters"]>(
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 (
<EditableEditor
key={propertyName}
param={propertyConfig}
fieldLabel={propertyConfig.label || propertyName}
onValueChange={onValueChange}
expressionObj={expressionObj}
renderFieldLabel={() => (
<FieldLabel title={propertyConfig.label} label={propertyConfig.label} hintText={propertyConfig.hintText} />
)}
readOnly={false}
showSwitch={false}
showValidation={true}
//ScenarioProperties do not use any variables
variableTypes={{}}
fieldErrors={getValidationErrorsForField(errors, propertyName)}
/>
);
}
Original file line number Diff line number Diff line change
@@ -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<Props>): JSX.Element {
return (
<Accordion disableGutters elevation={0} sx={{ border: 0, "&::before": { display: "none" } }}>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={{ color: "inherit" }} />}
aria-controls={`${nodeId}-content`}
id={`${nodeId}-header`}
sx={{ flexDirection: "row-reverse", border: 0 }}
>
<Typography>{nodeId}</Typography>
</AccordionSummary>
<AccordionDetails>{children}</AccordionDetails>
</Accordion>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function CountsDialog({ children, ...props }: PropsWithChildren<WindowCon
classname: LoadingButtonTypes.secondaryButton,
},
{
title: t("dialog.button.ok", "Ok"),
title: t("dialog.button.ok", "Apply"),
disabled: !isStateValid,
action: async () => {
await confirm();
Expand Down
Loading
Loading