Skip to content
Closed
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
1 change: 0 additions & 1 deletion designer/client/src/actions/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export type ActionTypes =
| "DELETE_NODES"
| "NODES_CONNECTED"
| "NODES_DISCONNECTED"
| "VALIDATION_RESULT"
| "COPY_SELECTION"
| "CUT_SELECTION"
| "PASTE_SELECTION"
Expand Down
12 changes: 8 additions & 4 deletions designer/client/src/actions/nk/editNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { getEdgesForNode } from "../../components/graph/node-modal/node/useNodeS
import { replaceNodeData } from "../../components/graph/node-modal/NodeSwitcherUtils";
import type { Scenario } from "../../components/Process/types";
import HttpService from "../../http/HttpService";
import { updateValidationResult } from "../../reducers/graph";
import { updateAfterNodeDelete } from "../../reducers/graph/utils";
import { getGraph } from "../../reducers/selectors/graph";
import { getGraph, getNodeResults } from "../../reducers/selectors/graph";
import { getProcessDefinitionData } from "../../reducers/selectors/processDefinitionData";
import type { Edge, NodeType, ScenarioGraph, ValidationResult } from "../../types";
import type { ThunkAction } from "../reduxTypes";
Expand All @@ -30,16 +31,19 @@ export function editScenarioLabels(scenarioLabels: string[]) {
}

export function editNode(scenarioBefore: Scenario, before: NodeType, after: NodeType, outputEdges?: Edge[]): ThunkAction {
return async (dispatch) => {
return async (dispatch, getState) => {
const scenarioGraph = await dispatch(calculateProcessAfterChange(scenarioBefore, before, after, outputEdges));
const response = await HttpService.validateProcess(scenarioBefore.name, scenarioBefore.name, scenarioGraph);
const { data } = await HttpService.validateProcess(scenarioBefore.name, scenarioBefore.name, scenarioGraph);
const state = getState();
const currentNodeResults = getNodeResults(state);
const validationResult = updateValidationResult(currentNodeResults, data);

dispatch(clearProcessCounts());
dispatch({
type: "EDIT_NODE",
before,
after,
validationResult: response.data,
validationResult,
scenarioGraphAfterChange: scenarioGraph,
});
};
Expand Down
16 changes: 12 additions & 4 deletions designer/client/src/actions/nk/editProperties.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { alignFragmentWithSchema } from "../../components/graph/utils/fragmentSchemaAligner";
import type { Scenario } from "../../components/Process/types";
import HttpService from "../../http/HttpService";
import { updateValidationResult } from "../../reducers/graph";
import { getNodeResults } from "../../reducers/selectors/graph";
import type { ProcessDefinitionData, PropertiesType, ScenarioGraph, ValidationResult } from "../../types";
import type { ThunkAction } from "../reduxTypes";
import { fetchProcessDefinition } from "./processDefinitionData";
Expand Down Expand Up @@ -28,18 +30,24 @@ const calculateProperties = (scenario: Scenario, changedProperties: PropertiesTy
const processDefinitionData = await dispatch(fetchProcessDefinition(scenario.processingType, scenario.isFragment));
const processWithNewFragmentSchema = alignFragmentsNodeWithSchema(scenario.scenarioGraph, processDefinitionData);

return { ...processWithNewFragmentSchema, properties: changedProperties };
return {
...processWithNewFragmentSchema,
properties: changedProperties,
};
};
};

export function editProperties(scenario: Scenario, changedProperties: PropertiesType): ThunkAction {
return async (dispatch) => {
return async (dispatch, getState) => {
const scenarioGraph = await dispatch(calculateProperties(scenario, changedProperties));
const response = await HttpService.validateProcess(scenario.name, scenarioGraph.properties.name, scenarioGraph);
const { data } = await HttpService.validateProcess(scenario.name, scenarioGraph.properties.name, scenarioGraph);
const state = getState();
const currentNodeResults = getNodeResults(state);
const validationResult = updateValidationResult(currentNodeResults, data);

dispatch({
type: "EDIT_PROPERTIES",
validationResult: response.data,
validationResult,
scenarioGraphAfterChange: scenarioGraph,
});
};
Expand Down
7 changes: 4 additions & 3 deletions designer/client/src/actions/nk/fetchVisualizationData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ProcessName } from "../../components/Process/types";
import HttpService from "../../http/HttpService";
import type { ThunkAction } from "../reduxTypes";
import { loadProcessToolbarsConfiguration } from "./loadProcessToolbarsConfiguration";
import { validationResult } from "./node";
import { displayTestCapabilities } from "./process";
import { fetchProcessDefinition } from "./processDefinitionData";

Expand Down Expand Up @@ -30,9 +31,9 @@ export function fetchVisualizationData(processName: ProcessName, onSuccess: () =
});
dispatch(loadProcessToolbarsConfiguration(name));
dispatch(displayTestCapabilities(name, scenario.scenarioGraph));
HttpService.validateProcess(name, name, scenario.scenarioGraph).then(({ data }) =>
dispatch({ type: "VALIDATION_RESULT", validationResult: data }),
);
HttpService.validateProcess(name, name, scenario.scenarioGraph).then(({ data }) => {
return dispatch(validationResult(data));
});
onSuccess();
return scenario;
} catch (error) {
Expand Down
33 changes: 30 additions & 3 deletions designer/client/src/actions/nk/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import type { Dictionary } from "lodash";
import { flushSync } from "react-dom";

import NodeUtils from "../../components/graph/NodeUtils";
import { updateValidationResult } from "../../reducers/graph";
import { batchGroupBy } from "../../reducers/graph/batchGroupBy";
import { prepareNewNodesWithLayout } from "../../reducers/graph/utils";
import { getNodes, getScenarioGraph } from "../../reducers/selectors/graph";
import { getNodeResults, getNodes, getScenarioGraph } from "../../reducers/selectors/graph";
import { getProcessDefinitionData } from "../../reducers/selectors/processDefinitionData";
import type {
Dimensions,
Expand All @@ -22,7 +23,10 @@ import type { EditNodeAction, EditScenarioLabels } from "./editNode";
import type { NodePosition, Position } from "./ui/layout";
import { layoutChanged } from "./ui/layout";

export type NodesWithPositions = { node: NodeType; position: Position }[];
export type NodesWithPositions = {
node: NodeType;
position: Position;
}[];

type DeleteNodesAction = {
type: "DELETE_NODES";
Expand Down Expand Up @@ -96,6 +100,20 @@ export function nodesConnected(fromNode: NodeType, toNode: NodeType, edgeType?:
};
}

export function validationResult(data: ValidationResult): ThunkAction {
return (dispatch, getState) => {
const state = getState();

const currentNodeResults = getNodeResults(state);
const validationResult = updateValidationResult(currentNodeResults, data);

return dispatch({
type: "VALIDATION_RESULT",
validationResult,
});
};
}

export function nodesDisconnected(from: NodeId, to: NodeId): ThunkAction {
return (dispatch) => {
dispatch({
Expand Down Expand Up @@ -153,7 +171,16 @@ export function nodeAdded(node: NodeType, position: Position): ThunkAction {
// since it breaks redux undo in this case
flushSync(() => {
const currentNodes = getNodes(getState());
const { nodes, layout } = prepareNewNodesWithLayout(currentNodes, [{ node, position }], false);
const { nodes, layout } = prepareNewNodesWithLayout(
currentNodes,
[
{
node,
position,
},
],
false,
);

dispatch({
type: "NODE_ADDED",
Expand Down
126 changes: 22 additions & 104 deletions designer/client/src/common/ProcessUtils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
/* eslint-disable i18next/no-literal-string */
import { flatten, isEmpty, isEqual, omit, pickBy, transform } from "lodash";
import { flatten, isEmpty, pickBy, transform } from "lodash";
import type { Scenario } from "src/components/Process/types";

import { StickyNoteDefinition, StickyNoteType } from "../components/graph/utils/stickyNotesUtils";
import type { ScenarioLabelValidationError } from "../components/Labels/types";
import type { RootState } from "../reducers";
import { getHistoryPast } from "../reducers/selectors/getHistory";
import { getScenario, isProcessRenamed } from "../reducers/selectors/graph";
import type {
ComponentDefinition,
NodeId,
Expand All @@ -22,82 +18,19 @@ import type {
} from "../types";

class ProcessUtils {
nothingToSave = (state: RootState): boolean => {
const scenario: Scenario = getScenario(state);
const savedProcessState: Scenario = getHistoryPast(state)?.[0]?.scenario || scenario;

/**
* It's a fix of https://touk-jira.atlassian.net/browse/NU-2194
* When node is added from a toolbar, branchParametersTemplate are initially added to the node, but when we perform a scenario save, node has no branchParametersTemplate
* Let's ignore branchParametersTemplate in a button save state checking
*/
const omitBranchParametersTemplate = (details: ScenarioGraph) => {
if (!details.nodes?.length) {
return details;
}

return {
...details,
nodes: details.nodes.map((node) => omit(node, ["branchParametersTemplate"])),
};
};
const processRenamed = isProcessRenamed(state);

if (processRenamed) {
return false;
}

if (isEmpty(scenario)) {
return true;
}

const labelsFor = (scenario: Scenario): string[] => {
return scenario.labels ? scenario.labels.slice().sort((a, b) => a.localeCompare(b)) : [];
};

const isGraphUpdated = isEqual(
omitBranchParametersTemplate(scenario.scenarioGraph),
omitBranchParametersTemplate(savedProcessState.scenarioGraph),
);
const areScenarioLabelsUpdated = isEqual(labelsFor(scenario), labelsFor(savedProcessState));

return !savedProcessState || (isGraphUpdated && areScenarioLabelsUpdated);
};

canExport = (state: RootState): boolean => {
const scenario = getScenario(state);
return isEmpty(scenario) ? false : !isEmpty(scenario.scenarioGraph.nodes);
};

isValidationResultPresent = (scenario: Scenario) => {
return Boolean(scenario.validationResult);
};

//fixme maybe return hasErrors flag from backend?
hasNeitherErrorsNorWarnings = (scenario: Scenario) => {
return this.isValidationResultPresent(scenario) && this.hasNoErrors(scenario) && this.hasNoWarnings(scenario);
};

extractInvalidNodes = (invalidNodes: Pick<ValidationResult, "warnings">) => {
return flatten(
Object.keys(invalidNodes || {}).map((key, _) =>
invalidNodes[key].map((error) => {
return { error: error, key: key };
return {
error: error,
key: key,
};
}),
),
);
};

hasNoErrors = (scenario: Scenario) => {
const result = this.getValidationErrors(scenario);
return (
!result ||
(Object.keys(result.invalidNodes || {}).length == 0 &&
(result.globalErrors || []).length == 0 &&
(result.processPropertiesErrors || []).length == 0)
);
};

getValidationResult = (scenario: Scenario): ValidationResult =>
scenario?.validationResult || {
validationErrors: [],
Expand All @@ -110,21 +43,6 @@ class ProcessUtils {
},
};

hasNoWarnings = (scenario: Scenario) => {
const warnings = this.getValidationResult(scenario).warnings;
return isEmpty(warnings) || Object.keys(warnings.invalidNodes || {}).length == 0;
};

hasNoPropertiesErrors = (scenario: Scenario) => {
return isEmpty(this.getValidationErrors(scenario)?.processPropertiesErrors);
};

getLabelsErrors = (scenario: Scenario): ScenarioLabelValidationError[] => {
return this.getValidationResult(scenario)
.errors.globalErrors.filter((e) => e.error.typ == "ScenarioLabelValidationError")
.map((e) => <ScenarioLabelValidationError>{ label: e.error.fieldName, messages: [e.error.description] });
};

getValidationErrors(scenario: Scenario): ValidationErrors {
return this.getValidationResult(scenario).errors;
}
Expand All @@ -150,43 +68,44 @@ class ProcessUtils {

getNodeResults = (scenario: Scenario): NodeResults => this.getValidationResult(scenario).nodeResults;

//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
escapeNodeIdForRegexp = (id: string) => id && id.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");

findAvailableVariables =
(components: Record<string, ComponentDefinition>, scenario: Scenario) =>
(nodeId: NodeId, parameterDefinition?: UIParameter): VariableTypes => {
const nodeResults = this.getNodeResults(scenario);
const variablesFromValidation = this.getVariablesFromValidation(nodeResults, nodeId);
const variablesForNode =
variablesFromValidation || this._findVariablesDeclaredBeforeNode(nodeId, scenario.scenarioGraph, components);
variablesFromValidation || this.findVariablesDeclaredBeforeNode(nodeId, scenario.scenarioGraph, components);
const variablesToHideForParam = parameterDefinition?.variablesToHide || [];
const withoutVariablesToHide = pickBy(variablesForNode, (va, key) => !variablesToHideForParam.includes(key));
const additionalVariablesForParam = parameterDefinition?.additionalVariables || {};
return { ...withoutVariablesToHide, ...additionalVariablesForParam };
};

extractComponentDefinition = (node: NodeType, components?: Record<string, ComponentDefinition>): ComponentDefinition | null => {
return node.type == StickyNoteType ? StickyNoteDefinition : components?.[this.determineComponentId(node)];
};

getVariablesFromValidation = (nodeResults: NodeResults, nodeId: string) => nodeResults?.[nodeId]?.variableTypes;

_findVariablesDeclaredBeforeNode = (
private findVariablesDeclaredBeforeNode = (
nodeId: NodeId,
scenarioGraph: ScenarioGraph,
components: Record<string, ComponentDefinition>,
): VariableTypes => {
const previousNodes = this._findPreviousNodes(nodeId, scenarioGraph);
const previousNodes = this.findPreviousNodes(nodeId, scenarioGraph);
const variablesDefinedBeforeNodeList = previousNodes.flatMap((nodeId) => {
return this._findVariablesDefinedInProcess(nodeId, scenarioGraph, components);
return this.findVariablesDefinedInProcess(nodeId, scenarioGraph, components);
});
return this._listOfObjectsToObject(variablesDefinedBeforeNodeList);
return this.listOfObjectsToObject(variablesDefinedBeforeNodeList);
};

_listOfObjectsToObject = <T>(list: Record<string, T>[]): Record<string, T> => {
private listOfObjectsToObject = <T>(list: Record<string, T>[]): Record<string, T> => {
return list.reduce((memo, current) => {
return { ...memo, ...current };
}, {});
};

_findVariablesDefinedInProcess = (
private findVariablesDefinedInProcess = (
nodeId: NodeId,
scenarioGraph: ScenarioGraph,
components: Record<string, ComponentDefinition>,
Expand Down Expand Up @@ -229,16 +148,14 @@ class ProcessUtils {
}
};

extractComponentDefinition = (node: NodeType, components?: Record<string, ComponentDefinition>): ComponentDefinition | null => {
return node.type == StickyNoteType ? StickyNoteDefinition : components?.[this.determineComponentId(node)];
};

determineComponentId = (node?: NodeType): string | null => {
const componentType = this.determineComponentType(node);
const componentName = this.determineComponentName(node);
return componentType && componentName ? `${componentType}-${componentName}` : null;
};

humanReadableType = (typingResult?: Pick<TypingResult, "display">): string | null => typingResult?.display || null;

// It should be synchronized with ComponentInfoExtractor.fromScenarioNode
private determineComponentType = (node?: NodeType): string | null => {
switch (node?.type) {
Expand Down Expand Up @@ -312,14 +229,15 @@ class ProcessUtils {
}
};

humanReadableType = (typingResult?: Pick<TypingResult, "display">): string | null => typingResult?.display || null;
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
private escapeNodeIdForRegexp = (id: string) => id && id.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");

_findPreviousNodes = (nodeId: NodeId, scenarioGraph: ScenarioGraph): NodeId[] => {
private findPreviousNodes = (nodeId: NodeId, scenarioGraph: ScenarioGraph): NodeId[] => {
const nodeEdge = scenarioGraph.edges?.find((edge) => edge.to === nodeId);
if (isEmpty(nodeEdge)) {
return [];
} else {
const previousNodes = this._findPreviousNodes(nodeEdge.from, scenarioGraph);
const previousNodes = this.findPreviousNodes(nodeEdge.from, scenarioGraph);
return [nodeEdge.from].concat(previousNodes);
}
};
Expand Down
3 changes: 1 addition & 2 deletions designer/client/src/components/Process/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable i18next/no-literal-string */
import type { ProcessingMode } from "../../http/HttpService";
import type { ScenarioGraph, ValidationResult } from "../../types";
import type { ScenarioGraph } from "../../types";
import type { Instant } from "../../types/common";

export enum PredefinedActionName {
Expand Down Expand Up @@ -39,7 +39,6 @@ export interface Scenario {
state: ProcessStateType;
history?: ProcessVersionType[];
scenarioGraph: ScenarioGraph;
validationResult: ValidationResult;
processingType: string;
processingMode: ProcessingMode;
engineSetupName: string;
Expand Down
Loading
Loading