Skip to content

Commit dc19e87

Browse files
DzumingJulianWielgaCopilot
authored
NU-2160-show-loader-during-dynamic-parameters-loading (#8059)
* NU-2160 loading POC * NU-2160 fixing state * checkpoint * fixed adjust node * fixed adjust node * change loading approach * Update designer/client/src/components/graph/node-modal/node-action-buttons/GenerateNewEndpoint.tsx Co-authored-by: Copilot <[email protected]> * Update designer/client/src/components/graph/node-modal/useNodeTypeDetailsContentLogic.tsx Co-authored-by: Copilot <[email protected]> * NU-2160 adjustments to the new endpoint backend changes * NU-2160 update state --------- Co-authored-by: Julian Wielga <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 3d0f467 commit dc19e87

File tree

11 files changed

+275
-114
lines changed

11 files changed

+275
-114
lines changed

designer/client/src/actions/nk/nodeDetails.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,21 @@ import type { ThunkAction } from "../reduxTypes";
99
type NodeValidationUpdated = { type: "NODE_VALIDATION_UPDATED"; validationData: ValidationData; nodeId: string };
1010
type NodeDetailsOpened = { type: "NODE_DETAILS_OPENED"; nodeId: string; windowId: string };
1111
type NodeDetailsClosed = { type: "NODE_DETAILS_CLOSED"; nodeId: string; windowId: string };
12-
13-
export type NodeDetailsActions = NodeValidationUpdated | NodeDetailsOpened | NodeDetailsClosed;
12+
type NodeValidationDynamicParametersLoading = {
13+
type: "NODE_VALIDATION_DYNAMIC_PARAMETERS_LOADING";
14+
nodeId: string;
15+
dynamicParametersChanged: string[];
16+
};
17+
type NodeValidationDynamicParametersLoaded = {
18+
type: "NODE_VALIDATION_DYNAMIC_PARAMETERS_LOADED";
19+
nodeId: string;
20+
};
21+
export type NodeDetailsActions =
22+
| NodeValidationUpdated
23+
| NodeDetailsOpened
24+
| NodeValidationDynamicParametersLoading
25+
| NodeValidationDynamicParametersLoaded
26+
| NodeDetailsClosed;
1427

1528
export interface ValidationData {
1629
parameters?: UIParameter[];
@@ -35,6 +48,24 @@ export function nodeValidationDataUpdated(nodeId: string, validationData: Valida
3548
};
3649
}
3750

51+
export function nodeValidationDynamicParametersLoading(
52+
nodeId: string,
53+
dynamicParametersChanged: string[],
54+
): NodeValidationDynamicParametersLoading {
55+
return {
56+
type: "NODE_VALIDATION_DYNAMIC_PARAMETERS_LOADING",
57+
nodeId,
58+
dynamicParametersChanged,
59+
};
60+
}
61+
62+
export function nodeValidationDynamicParametersLoaded(nodeId: string): NodeValidationDynamicParametersLoaded {
63+
return {
64+
type: "NODE_VALIDATION_DYNAMIC_PARAMETERS_LOADED",
65+
nodeId,
66+
};
67+
}
68+
3869
export function nodeDetailsOpened(nodeId: string, windowId: string): NodeDetailsOpened {
3970
return {
4071
type: "NODE_DETAILS_OPENED",
@@ -69,14 +100,20 @@ const validate = debounce(
69100
500,
70101
);
71102

72-
export function validateNodeData(processName: string, validationRequestData: ValidationRequest, callback?: () => void): ThunkAction {
103+
export function validateNodeData(
104+
processName: string,
105+
validationRequestData: ValidationRequest,
106+
callback?: ({ status }: { status: "allowDataUpdate" | "unknown" }) => void,
107+
): ThunkAction {
73108
return (dispatch, getState) => {
74109
validate(processName, validationRequestData, (nodeId, data) => {
110+
const allowDataUpdate = data && getNodeDetails(getState())(nodeId);
75111
// node details view creates this on open and removes after close
76-
if (data && getNodeDetails(getState())(nodeId)) {
112+
if (allowDataUpdate) {
77113
dispatch(nodeValidationDataUpdated(nodeId, data));
78-
callback?.();
79114
}
115+
116+
callback?.({ status: allowDataUpdate ? "allowDataUpdate" : "unknown" });
80117
});
81118
};
82119
}

designer/client/src/components/graph/node-modal/NodeDetailsContent/selectors.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const getCurrentErrors = createSelector(
5757
(originalNodeId: NodeId, nodeErrors: NodeValidationError[] = []) =>
5858
validationPerformed(originalNodeId) ? validationErrors(originalNodeId) : nodeErrors,
5959
);
60-
export const getDynamicParameterDefinitions = createSelector(
60+
export const getDynamicParameterDefinitions = createDeepEqualSelector(
6161
getValidationPerformed,
6262
getDetailsParameters,
6363
getResultParameters,
@@ -78,3 +78,11 @@ export const getVariableTypes = createSelector(
7878
getNodeResults,
7979
(nodeResults) => (originalNodeId) => ProcessUtils.getVariablesFromValidation(nodeResults, originalNodeId) || {},
8080
);
81+
82+
export const getDynamicParametersChanged =
83+
(state: RootState) =>
84+
(nodeId: string): string[] | undefined => {
85+
const nodeDetails = getNodeDetails(state);
86+
const nodeDetail = nodeDetails(nodeId);
87+
return nodeDetail?.changingDynamicParameters;
88+
};

designer/client/src/components/graph/node-modal/SourceSinkCommon.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { IdField } from "./IdField";
99
import { ParametersListAdvanced } from "./parametersListAdvanced";
1010
import type { SetProperty } from "./useNodeTypeDetailsContentLogic";
1111

12+
const getListFieldPath = (index: number) => `ref.parameters[${index}]`;
13+
1214
interface SourceSinkCommonProps {
1315
errors: NodeValidationError[];
1416
findAvailableVariables?: ReturnType<typeof ProcessUtils.findAvailableVariables>;
@@ -54,7 +56,7 @@ export const SourceSinkCommon = ({
5456
errors={errors}
5557
renderFieldLabel={renderFieldLabel}
5658
setProperty={setProperty}
57-
getListFieldPath={(index: number) => `ref.parameters[${index}]`}
59+
getListFieldPath={getListFieldPath}
5860
>
5961
{children}
6062
<DescriptionField

designer/client/src/components/graph/node-modal/node-action-buttons/GenerateNewEndpoint.tsx

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,27 @@ export const GenerateNewEndpoint = ({ node, handleNewEndpointGenerated }: Props)
2727
const handleSendHttpRequest = useCallback(async () => {
2828
try {
2929
const { result } = await HttpService.nodeActions(scenarioName, "generate-endpoint", node);
30-
const newTopic = result?.actionName === "GenerateEndpointResult" ? result.topic : "";
31-
dispatch(
32-
validateNodeData(
33-
scenarioName,
34-
{
35-
outgoingEdges: scenarioGraph.edges,
36-
nodeData: node,
37-
processProperties: scenarioGraph.properties,
38-
branchVariableTypes: getBranchVariableTypes(node.id),
39-
variableTypes,
40-
},
41-
() => handleNewEndpointGenerated(newTopic),
42-
),
43-
);
30+
const newTopic = result?.actionName === "GENERATE_ENDPOINT" ? result?.topic?.expression : "";
31+
await new Promise<void>((resolve) => {
32+
dispatch(
33+
validateNodeData(
34+
scenarioName,
35+
{
36+
outgoingEdges: scenarioGraph.edges,
37+
nodeData: node,
38+
processProperties: scenarioGraph.properties,
39+
branchVariableTypes: getBranchVariableTypes(node.id),
40+
variableTypes,
41+
},
42+
({ status }) => {
43+
if (status === "allowDataUpdate") {
44+
handleNewEndpointGenerated(newTopic);
45+
}
46+
resolve();
47+
},
48+
),
49+
);
50+
});
4451
} catch (error) {
4552
console.error("Error sending request:", error);
4653
}

designer/client/src/components/graph/node-modal/node/useNodeState.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { type SetStateAction, useCallback, useEffect, useMemo, useState } from "
44
import { useDispatch, useSelector } from "react-redux";
55
import { useDebounce } from "rooks";
66

7-
import { editNode } from "../../../../actions/nk";
7+
import { editNode, nodeValidationDynamicParametersLoaded } from "../../../../actions/nk";
88
import { PendingPromise } from "../../../../common/PendingPromise";
99
import { useUserSettings } from "../../../../common/userSettings";
1010
import { parseWindowsQueryParams, replaceSearchQuery } from "../../../../containers/hooks/useSearchQuery";
@@ -93,6 +93,10 @@ export function useNodeState(data: NodeDetailsMeta): NodeState {
9393
} catch (e) {
9494
console.error(e);
9595
setStatus("error");
96+
} finally {
97+
if (autoApply) {
98+
dispatch(nodeValidationDynamicParametersLoaded(node.id));
99+
}
96100
}
97101
},
98102
[setStatus, dispatch, scenario, node, autoApply],

designer/client/src/components/graph/node-modal/parametersList.tsx

Lines changed: 16 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1-
import { Box } from "@mui/material";
2-
import React from "react";
3-
import { useTranslation } from "react-i18next";
1+
import { Box, Skeleton } from "@mui/material";
2+
import { isEqual } from "lodash";
3+
import React, { Fragment } from "react";
44
import { useSelector } from "react-redux";
55

6-
import { CopyIconButton, useCopyClipboard } from "../../../common/copyToClipboard";
7-
import { useUserSettings } from "../../../common/userSettings";
8-
import { getProcessState } from "../../../reducers/selectors/scenarioState";
96
import type { Parameter } from "../../../types";
10-
import { getValidationErrorsForField } from "./editors/Validators";
11-
import { GenerateNewEndpoint } from "./node-action-buttons/GenerateNewEndpoint";
12-
import { SendRequestButton } from "./node-action-buttons/SendRequestButton";
7+
import { getDynamicParametersChanged } from "./NodeDetailsContent/selectors";
138
import type { ParameterExpressionFieldProps } from "./ParameterExpressionField";
14-
import { ParameterExpressionField } from "./ParameterExpressionField";
9+
import { ParametersListField } from "./parametersListField";
1510

1611
type ParametersListItemProps = Omit<ParameterExpressionFieldProps, "listFieldPath" | "parameter">;
1712

@@ -25,76 +20,23 @@ export type ParametersListProps = ParametersListItemProps & {
2520
getListFieldPath: (index: number) => string;
2621
};
2722

28-
export const ParametersList = ({ parameters = [], getListFieldPath, ...props }: ParametersListProps) => {
29-
const { node } = props;
30-
const scenarioState = useSelector(getProcessState);
31-
const { t } = useTranslation();
32-
const [isCopied, copy] = useCopyClipboard();
33-
const [settings] = useUserSettings();
23+
export const ParametersList = (props: ParametersListProps) => {
24+
const { parameters = [], node } = props;
25+
const dynamicParametersChanged = useSelector(getDynamicParametersChanged, isEqual)(node.id);
3426

3527
return (
3628
<>
3729
{parameters.map((paramWithIndex) => (
38-
<React.Fragment key={node.id + paramWithIndex.param.name + paramWithIndex.index}>
39-
<ParameterExpressionField
40-
listFieldPath={getListFieldPath(paramWithIndex.index)}
41-
parameter={paramWithIndex.param}
42-
endAdornment={
43-
paramWithIndex.param.name === "Endpoint" && (
44-
<CopyIconButton
45-
onClick={() => {
46-
const possibleValues = props.parameterDefinitions.find(
47-
(parameterDefinition) => parameterDefinition.name === "Endpoint",
48-
).editors[0].possibleValues;
49-
50-
const selectedValue = possibleValues.find(
51-
(possibleValue) => possibleValue.expression === paramWithIndex.param.expression.expression,
52-
);
53-
copy(selectedValue.label);
54-
}}
55-
isCopied={isCopied}
56-
/>
57-
)
58-
}
59-
{...props}
60-
/>
61-
{/*
62-
* TODO: Remove it when the backend is ready and action buttons will be send by default
63-
*/}
64-
{paramWithIndex.param.name === "Endpoint" && settings["node.showGenerateEndpointButton"] && (
65-
<Box display={"flex"} justifyContent={"flex-end"}>
66-
<GenerateNewEndpoint
67-
node={node}
68-
handleNewEndpointGenerated={(topic: string) => {
69-
const expressionProperty = "expression.expression";
70-
const expressionPath = `${getListFieldPath(paramWithIndex.index)}${expressionProperty}`;
71-
72-
props.setProperty(expressionPath, topic);
73-
}}
74-
/>
75-
</Box>
76-
)}
77-
78-
{/*
79-
* TODO: Remove it when the backend is ready and action buttons will be send by default
80-
*/}
81-
{paramWithIndex.param.name === "Data sample" && settings["node.showSendRequestButton"] && (
82-
<Box display={"flex"} justifyContent={"flex-end"}>
83-
<SendRequestButton
84-
disabled={
85-
getValidationErrorsForField(props.errors, paramWithIndex.param.name).length > 0 ||
86-
scenarioState.status.name !== "RUNNING"
87-
}
88-
infoTooltip={
89-
scenarioState.status.name !== "RUNNING" &&
90-
t("node.actions.sendRequest.tooltip.deployScenarioFirst", "Deploy your scenario first")
91-
}
92-
expression={paramWithIndex.param.expression.expression}
93-
node={node}
94-
/>
30+
<Fragment key={node.id + paramWithIndex.param.name + paramWithIndex.index}>
31+
{dynamicParametersChanged?.length > 0 && !dynamicParametersChanged.includes(paramWithIndex.param.name) ? (
32+
<Box display={"flex"} justifyContent={"space-between"} mt={2}>
33+
<Skeleton variant="rectangular" height={15} width={"100%"} sx={{ flexBasis: "10%", mt: "9px" }} />
34+
<Skeleton variant="rectangular" height={35} width={"100%"} sx={{ flexBasis: "80%" }} />
9535
</Box>
36+
) : (
37+
<ParametersListField {...props} paramWithIndex={paramWithIndex} />
9638
)}
97-
</React.Fragment>
39+
</Fragment>
9840
))}
9941
</>
10042
);
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { Box } from "@mui/material";
2+
import React, { useCallback } from "react";
3+
import { useTranslation } from "react-i18next";
4+
import { useSelector } from "react-redux";
5+
6+
import { CopyIconButton, useCopyClipboard } from "../../../common/copyToClipboard";
7+
import { useUserSettings } from "../../../common/userSettings";
8+
import { getProcessState } from "../../../reducers/selectors/scenarioState";
9+
import { getValidationErrorsForField } from "./editors/Validators";
10+
import { GenerateNewEndpoint } from "./node-action-buttons/GenerateNewEndpoint";
11+
import { SendRequestButton } from "./node-action-buttons/SendRequestButton";
12+
import { ParameterExpressionField } from "./ParameterExpressionField";
13+
import type { ParametersListProps, ParameterWithIndex } from "./parametersList";
14+
15+
interface Props extends ParametersListProps {
16+
paramWithIndex: ParameterWithIndex;
17+
}
18+
19+
export const ParametersListField = (props: Props) => {
20+
const { node, getListFieldPath, paramWithIndex, parameterDefinitions, setProperty } = props;
21+
const handleGetListFieldPath = useCallback(
22+
(index: number) => {
23+
return getListFieldPath(index);
24+
},
25+
[getListFieldPath],
26+
);
27+
28+
const scenarioState = useSelector(getProcessState);
29+
const { t } = useTranslation();
30+
const [isCopied, copy] = useCopyClipboard();
31+
const [settings] = useUserSettings();
32+
33+
if (paramWithIndex.param.name === "Endpoint") {
34+
return (
35+
<>
36+
<ParameterExpressionField
37+
listFieldPath={handleGetListFieldPath(paramWithIndex.index)}
38+
parameter={paramWithIndex.param}
39+
endAdornment={
40+
paramWithIndex.param.name === "Endpoint" && (
41+
<CopyIconButton
42+
onClick={() => {
43+
const possibleValues = parameterDefinitions.find(
44+
(parameterDefinition) => parameterDefinition.name === "Endpoint",
45+
).editors[0].possibleValues;
46+
47+
const selectedValue = possibleValues.find(
48+
(possibleValue) => possibleValue.expression === paramWithIndex.param.expression.expression,
49+
);
50+
copy(selectedValue.label);
51+
}}
52+
isCopied={isCopied}
53+
/>
54+
)
55+
}
56+
{...props}
57+
/>
58+
{/*
59+
* TODO: Remove it when the backend is ready and action buttons will be send by default
60+
*/}
61+
{settings["node.showGenerateEndpointButton"] && (
62+
<Box display={"flex"} justifyContent={"flex-end"}>
63+
<GenerateNewEndpoint
64+
node={node}
65+
handleNewEndpointGenerated={(topic: string) => {
66+
const expressionProperty = "expression.expression";
67+
const expressionPath = `${getListFieldPath(paramWithIndex.index)}.${expressionProperty}`;
68+
69+
setProperty(expressionPath, topic);
70+
}}
71+
/>
72+
</Box>
73+
)}
74+
</>
75+
);
76+
}
77+
78+
return (
79+
<>
80+
<ParameterExpressionField
81+
listFieldPath={handleGetListFieldPath(paramWithIndex.index)}
82+
parameter={paramWithIndex.param}
83+
{...props}
84+
/>
85+
{paramWithIndex.param.name === "Data sample" && settings["node.showSendRequestButton"] && (
86+
<Box display={"flex"} justifyContent={"flex-end"}>
87+
<SendRequestButton
88+
disabled={
89+
getValidationErrorsForField(props.errors, paramWithIndex.param.name).length > 0 ||
90+
scenarioState.status.name !== "RUNNING"
91+
}
92+
infoTooltip={
93+
scenarioState.status.name !== "RUNNING" &&
94+
t("node.actions.sendRequest.tooltip.deployScenarioFirst", "Deploy your scenario first")
95+
}
96+
expression={paramWithIndex.param.expression.expression}
97+
node={node}
98+
/>
99+
</Box>
100+
)}
101+
</>
102+
);
103+
};

0 commit comments

Comments
 (0)