Skip to content

Commit

Permalink
[Nu-1858] separate properties and node details (#7129)
Browse files Browse the repository at this point in the history
* NU-1858 separate properties and node details
  • Loading branch information
Dzuming authored Nov 19, 2024
1 parent 580adec commit 1539ae5
Show file tree
Hide file tree
Showing 66 changed files with 573 additions and 392 deletions.
4 changes: 1 addition & 3 deletions designer/client/cypress/e2e/description.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ describe("Description", () => {
it("should display markdown", () => {
cy.get(`[title="toggle description view"]`).should("not.exist");

cy.contains(/^properties$/i)
.should("be.enabled")
.dblclick();
cy.contains(/^properties$/i).click();
cy.get("[data-testid=window]").should("be.visible").as("window");

cy.get("[data-testid=window]").contains("Description").next().find(".ace_editor").should("be.visible").click("center")
Expand Down
3 changes: 2 additions & 1 deletion designer/client/src/actions/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ export type ActionTypes =
| "PROCESS_VERSIONS_LOADED"
| "UPDATE_BACKEND_NOTIFICATIONS"
| "MARK_BACKEND_NOTIFICATION_READ"
| "ARCHIVED";
| "ARCHIVED"
| "EDIT_PROPERTIES";
34 changes: 3 additions & 31 deletions designer/client/src/actions/nk/calculateProcessAfterChange.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,16 @@
import NodeUtils from "../../components/graph/NodeUtils";
import { fetchProcessDefinition } from "./processDefinitionData";
import { getProcessDefinitionData } from "../../reducers/selectors/settings";
import { mapProcessWithNewNode, replaceNodeOutputEdges } from "../../components/graph/utils/graphUtils";
import { alignFragmentWithSchema } from "../../components/graph/utils/fragmentSchemaAligner";
import { Edge, NodeType, ScenarioGraph, ProcessDefinitionData, ScenarioGraphWithName } from "../../types";
import { Edge, NodeType, ScenarioGraphWithName } from "../../types";
import { ThunkAction } from "../reduxTypes";
import { Scenario } from "../../components/Process/types";

function alignFragmentsNodeWithSchema(scenarioGraph: ScenarioGraph, processDefinitionData: ProcessDefinitionData): ScenarioGraph {
return {
...scenarioGraph,
nodes: scenarioGraph.nodes.map((node) => {
return node.type === "FragmentInput" ? alignFragmentWithSchema(processDefinitionData, node) : node;
}),
};
}

export function calculateProcessAfterChange(
scenario: Scenario,
before: NodeType,
after: NodeType,
outputEdges: Edge[],
): ThunkAction<Promise<ScenarioGraphWithName>> {
return async (dispatch, getState) => {
if (NodeUtils.nodeIsProperties(after)) {
const processDefinitionData = await dispatch(fetchProcessDefinition(scenario.processingType, scenario.isFragment));
const processWithNewFragmentSchema = alignFragmentsNodeWithSchema(scenario.scenarioGraph, processDefinitionData);
// TODO: We shouldn't keep scenario name in properties.id - it is a top-level scenario property
if (after.id !== before.id) {
dispatch({ type: "PROCESS_RENAME", name: after.id });
}

const { id, ...properties } = after;

return {
processName: after.id,
scenarioGraph: { ...processWithNewFragmentSchema, properties },
};
}

return async (_, getState) => {
let changedProcess = scenario.scenarioGraph;
if (outputEdges) {
const processDefinitionData = getProcessDefinitionData(getState());
Expand All @@ -54,7 +26,7 @@ export function calculateProcessAfterChange(
}

return {
processName: scenario.scenarioGraph.properties.id || scenario.name,
processName: scenario.name,
scenarioGraph: mapProcessWithNewNode(changedProcess, before, after),
};
};
Expand Down
4 changes: 0 additions & 4 deletions designer/client/src/actions/nk/editNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ export type EditNodeAction = {
validationResult: ValidationResult;
scenarioGraphAfterChange: ScenarioGraph;
};
export type RenameProcessAction = {
type: "PROCESS_RENAME";
name: string;
};

export type EditScenarioLabels = {
type: "EDIT_LABELS";
Expand Down
58 changes: 58 additions & 0 deletions designer/client/src/actions/nk/editProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { ProcessDefinitionData, PropertiesType, ScenarioGraph, ScenarioGraphWithName, ValidationResult } from "../../types";
import { alignFragmentWithSchema } from "../../components/graph/utils/fragmentSchemaAligner";
import { fetchProcessDefinition } from "./processDefinitionData";
import { Scenario } from "../../components/Process/types";
import HttpService from "../../http/HttpService";
import { ThunkAction } from "../reduxTypes";

type EditPropertiesAction = {
type: "EDIT_PROPERTIES";
validationResult: ValidationResult;
scenarioGraphAfterChange: ScenarioGraph;
};

type RenameProcessAction = {
type: "PROCESS_RENAME";
name: string;
};

export type PropertiesActions = EditPropertiesAction | RenameProcessAction;

// TODO: We synchronize fragment changes with a scenario in case of properties changes. We need to find a better way to hande it
function alignFragmentsNodeWithSchema(scenarioGraph: ScenarioGraph, processDefinitionData: ProcessDefinitionData): ScenarioGraph {
return {
...scenarioGraph,
nodes: scenarioGraph.nodes.map((node) => {
return node.type === "FragmentInput" ? alignFragmentWithSchema(processDefinitionData, node) : node;
}),
};
}

const calculateProperties = (scenario: Scenario, changedProperties: PropertiesType): ThunkAction<Promise<ScenarioGraphWithName>> => {
return async (dispatch) => {
const processDefinitionData = await dispatch(fetchProcessDefinition(scenario.processingType, scenario.isFragment));
const processWithNewFragmentSchema = alignFragmentsNodeWithSchema(scenario.scenarioGraph, processDefinitionData);

if (scenario.name !== changedProperties.name) {
dispatch({ type: "PROCESS_RENAME", name: changedProperties.name });
}

return {
processName: changedProperties.name,
scenarioGraph: { ...processWithNewFragmentSchema, properties: changedProperties },
};
};
};

export function editProperties(scenario: Scenario, changedProperties: PropertiesType): ThunkAction {
return async (dispatch) => {
const { processName, scenarioGraph } = await dispatch(calculateProperties(scenario, changedProperties));
const response = await HttpService.validateProcess(scenario.name, processName, scenarioGraph);

dispatch({
type: "EDIT_PROPERTIES",
validationResult: response.data,
scenarioGraphAfterChange: scenarioGraph,
});
};
}
1 change: 1 addition & 0 deletions designer/client/src/actions/nk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from "./ui/layout";
export * from "./zoom";
export * from "./nodeDetails";
export * from "./loadProcessToolbarsConfiguration";
export * from "./editProperties";
3 changes: 1 addition & 2 deletions designer/client/src/actions/nk/node.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Edge, EdgeType, NodeId, NodeType, ProcessDefinitionData, ValidationResult } from "../../types";
import { ThunkAction } from "../reduxTypes";
import { layoutChanged, Position } from "./ui/layout";
import { EditNodeAction, EditScenarioLabels, RenameProcessAction } from "./editNode";
import { EditNodeAction, EditScenarioLabels } from "./editNode";
import { getProcessDefinitionData } from "../../reducers/selectors/settings";
import { batchGroupBy } from "../../reducers/graph/batchGroupBy";
import NodeUtils from "../../components/graph/NodeUtils";
Expand Down Expand Up @@ -154,5 +154,4 @@ export type NodeActions =
| NodesWithEdgesAddedAction
| ValidationResultAction
| EditNodeAction
| RenameProcessAction
| EditScenarioLabels;
6 changes: 1 addition & 5 deletions designer/client/src/actions/nk/nodeDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,7 @@ const validate = debounce(
validationRequestData: ValidationRequest,
callback: (nodeId: NodeId, data?: ValidationData | void) => void,
) => {
const validate = (node: NodeType) =>
NodeUtils.nodeIsProperties(node)
? //NOTE: we don't validationRequestData contains processProperties, but they are refreshed only on modal open
HttpService.validateProperties(processName, { additionalFields: node.additionalFields, name: node.id })
: HttpService.validateNode(processName, { ...validationRequestData, nodeData: node });
const validate = (node: NodeType) => HttpService.validateNode(processName, { ...validationRequestData, nodeData: node });

const nodeId = validationRequestData.nodeData.id;
const nodeWithChangedName = applyIdFromFakeName(validationRequestData.nodeData);
Expand Down
5 changes: 3 additions & 2 deletions designer/client/src/actions/reduxTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AnyAction, Reducer as ReduxReducer } from "redux";
import { ThunkAction as TA, ThunkDispatch as TD } from "redux-thunk";

import { ActionTypes } from "./actionTypes";
import { CountsActions, NodeActions, ScenarioActions, SelectionActions, NodeDetailsActions } from "./nk";
import { CountsActions, NodeActions, ScenarioActions, SelectionActions, NodeDetailsActions, PropertiesActions } from "./nk";
import { UserSettingsActions } from "./nk/userSettings";
import { UiActions } from "./nk/ui/uiActions";
import { SettingsActions } from "./settingsActions";
Expand All @@ -25,7 +25,8 @@ type TypedAction =
| NotificationActions
| DisplayTestResultsDetailsAction
| CountsActions
| ScenarioActions;
| ScenarioActions
| PropertiesActions;

interface UntypedAction extends AnyAction {
type: Exclude<ActionTypes, TypedAction["type"]>;
Expand Down
3 changes: 0 additions & 3 deletions designer/client/src/assets/json/nodeAttributes.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@
"Aggregate": {
"name": "Aggregate"
},
"Properties": {
"name": "Properties"
},
"CustomNode": {
"name": "CustomNode"
},
Expand Down
4 changes: 4 additions & 0 deletions designer/client/src/components/ComponentDragPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export const ComponentDragPreview = forwardRef<HTMLDivElement, { scale: () => nu
willChange: "transform",
});

if (!node) {
return null;
}

return createPortal(
<div ref={forwardedRef} className={wrapperStyles} style={{ transform: `translate(${x}px, ${y}px)` }}>
<div
Expand Down
2 changes: 1 addition & 1 deletion designer/client/src/components/ComponentPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function ComponentPreview({ node, isActive, isOver }: { node: NodeType; i
});

const imageColors = css({
background: theme.palette.custom.getNodeStyles(node)?.fill,
background: theme.palette.custom.getNodeStyles(node.type)?.fill,
color: theme.palette.common.white,
});

Expand Down
2 changes: 1 addition & 1 deletion designer/client/src/components/graph/EspNode/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export function makeElement(processDefinitionData: ProcessDefinitionData, theme:
opacity: node.isDisabled ? 0.5 : 1,
},
iconBackground: {
fill: theme.palette.custom.getNodeStyles(node).fill,
fill: theme.palette.custom.getNodeStyles(node.type).fill,
opacity: node.isDisabled ? 0.5 : 1,
},
icon: {
Expand Down
3 changes: 1 addition & 2 deletions designer/client/src/components/graph/Graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,7 @@ export class Graph extends React.Component<Props> {
addNode(node: NodeType, position: Position): void {
if (this.props.isFragment === true) return;

const canAddNode =
this.props.capabilities.editFrontend && NodeUtils.isNode(node) && NodeUtils.isAvailable(node, this.props.processDefinitionData);
const canAddNode = this.props.capabilities.editFrontend && NodeUtils.isAvailable(node, this.props.processDefinitionData);

if (canAddNode) {
this.props.nodeAdded(node, position);
Expand Down
44 changes: 7 additions & 37 deletions designer/client/src/components/graph/NodeUtils.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,25 @@
/* eslint-disable i18next/no-literal-string */
import { has, isEmpty, isEqual, uniqBy } from "lodash";
import { isEqual, uniqBy } from "lodash";
import ProcessUtils from "../../common/ProcessUtils";
import {
Edge,
EdgeKind,
EdgeType,
FragmentNodeType,
NodeId,
NodeType,
ProcessDefinitionData,
PropertiesType,
ScenarioGraph,
UINodeType,
} from "../../types";
import { UnknownRecord } from "../../types/common";
import { Edge, EdgeKind, EdgeType, FragmentNodeType, NodeId, NodeType, ProcessDefinitionData, ScenarioGraph } from "../../types";
import { createEdge } from "../../reducers/graph/utils";
import { Scenario } from "../Process/types";

class NodeUtils {
isNode = (obj: UnknownRecord): obj is NodeType => {
return !isEmpty(obj) && has(obj, "id") && has(obj, "type");
};

nodeType = (node: UINodeType) => {
return node?.type ? node.type : "Properties";
};

nodeIsProperties = (node: UINodeType): node is PropertiesType => {
const type = node && this.nodeType(node);
return type === "Properties";
};

nodeIsFragment = (node: UINodeType): node is FragmentNodeType => {
return this.nodeType(node) === "FragmentInput";
};

isPlainNode = (node: UINodeType) => {
return !isEmpty(node) && !this.nodeIsProperties(node);
nodeIsFragment = (node: NodeType): node is FragmentNodeType => {
return node.type === "FragmentInput";
};

nodeIsJoin = (node: NodeType): boolean => {
return node && this.nodeType(node) === "Join";
return node && node.type === "Join";
};

nodesFromScenarioGraph = (scenarioGraph: ScenarioGraph): NodeType[] => scenarioGraph.nodes || [];

edgesFromScenarioGraph = (scenarioGraph: ScenarioGraph) => scenarioGraph.edges || [];

// For sake of consistency with other nodes, name must be renamed to id
getProcessPropertiesNode = ({ name, scenarioGraph: { properties } }: Scenario, unsavedName?: string) => ({
id: name || unsavedName,
getProcessProperties = ({ name, scenarioGraph: { properties } }: Scenario, unsavedName?: string) => ({
name: name || unsavedName,
...properties,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,7 @@ function useClipboardParse() {
return useCallback(
(text) => {
const selection = tryParseOrNull(text);
const isValid =
selection?.edges &&
selection?.nodes?.every(
(node) => NodeUtils.isNode(node) && NodeUtils.isPlainNode(node) && NodeUtils.isAvailable(node, processDefinitionData),
);
const isValid = selection?.edges && selection?.nodes?.every((node) => NodeUtils.isAvailable(node, processDefinitionData));
return isValid ? selection : null;
},
[processDefinitionData],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
import { NodeField } from "./NodeField";
import { FieldType } from "./editors/field/Field";
import React from "react";
import { NodeType, NodeValidationError, UINodeType } from "../../../types";
import { NodeType, NodeValidationError, NodeOrPropertiesType } from "../../../types";

interface DescriptionFieldProps {
autoFocus?: boolean;
defaultValue?: string;
isEditMode?: boolean;
node: UINodeType;
node: NodeOrPropertiesType;
readonly?: boolean;
renderFieldLabel: (paramName: string) => React.ReactNode;
setProperty: <K extends keyof NodeType>(property: K, newValue: NodeType[K], defaultValue?: NodeType[K]) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import { DescriptionView } from "../../../containers/DescriptionView";
import { FieldType } from "./editors/field/Field";
import { rowAceEditor } from "./NodeDetailsContent/NodeTableStyled";
import { NodeField } from "./NodeField";
import { NodeTypeDetailsContentProps, useNodeTypeDetailsContentLogic } from "./NodeTypeDetailsContent";
import { NodeType, PropertiesType } from "../../../types";

type DescriptionOnlyContentProps = Pick<NodeTypeDetailsContentProps, "node" | "onChange"> & {
type DescriptionOnlyContentProps = {
onChange: <K extends keyof NodeType>(property: K, newValue: NodeType[K], defaultValue?: NodeType[K]) => void;
properties: PropertiesType;
fieldPath: string;
preview?: boolean;
};

export function DescriptionOnlyContent({ fieldPath, preview, node, onChange }: DescriptionOnlyContentProps) {
const { setProperty } = useNodeTypeDetailsContentLogic({ node, onChange });

export function DescriptionOnlyContent({ fieldPath, preview, properties, onChange }: DescriptionOnlyContentProps) {
if (preview) {
return <DescriptionView>{get(node, fieldPath)}</DescriptionView>;
return <DescriptionView>{get(properties, fieldPath)}</DescriptionView>;
}

return (
Expand All @@ -31,8 +31,8 @@ export function DescriptionOnlyContent({ fieldPath, preview, node, onChange }: D
<NodeField
autoFocus
renderFieldLabel={() => null}
setProperty={setProperty}
node={node}
setProperty={onChange}
node={properties}
isEditMode={true}
showValidation={false}
readonly={false}
Expand Down
6 changes: 3 additions & 3 deletions designer/client/src/components/graph/node-modal/IdField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { extendErrors, getValidationErrorsForField, uniqueScenarioValueValidator
import Field, { FieldType } from "./editors/field/Field";
import React, { useMemo, useState } from "react";
import { useDiffMark } from "./PathsToMark";
import { NodeType, NodeValidationError, UINodeType } from "../../../types";
import { NodeType, NodeValidationError, NodeOrPropertiesType } from "../../../types";
import { useSelector } from "react-redux";
import { getProcessNodesIds } from "../../../reducers/selectors/graph";
import NodeUtils from "../NodeUtils";
Expand All @@ -11,7 +11,7 @@ import { nodeInput, nodeInputWithError } from "./NodeDetailsContent/NodeTableSty

interface IdFieldProps {
isEditMode?: boolean;
node: UINodeType;
node: NodeOrPropertiesType;
renderFieldLabel: (paramName: string) => React.ReactNode;
setProperty?: <K extends keyof NodeType>(property: K, newValue: NodeType[K], defaultValue?: NodeType[K]) => void;
showValidation?: boolean;
Expand All @@ -37,7 +37,7 @@ export function IdField({ isEditMode, node, renderFieldLabel, setProperty, showV
const value = useMemo(() => node[FAKE_NAME_PROP_NAME] ?? node[propName], [node]);
const marked = useMemo(() => isMarked(FAKE_NAME_PROP_NAME) || isMarked(propName), [isMarked]);

const isUniqueValueValidator = !NodeUtils.nodeIsProperties(node) && uniqueScenarioValueValidator(otherNodes);
const isUniqueValueValidator = uniqueScenarioValueValidator(otherNodes);

const fieldErrors = getValidationErrorsForField(
isUniqueValueValidator ? extendErrors(errors, value, FAKE_NAME_PROP_NAME, [isUniqueValueValidator]) : errors,
Expand Down
Loading

0 comments on commit 1539ae5

Please sign in to comment.