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

fixed search focus and keyboard/clipboard events #7207

Merged
merged 2 commits into from
Nov 25, 2024

Conversation

JulianWielga
Copy link
Member

@JulianWielga JulianWielga commented Nov 21, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced clipboard functionality for improved text copying and pasting.
    • Added robust error handling for clipboard actions and user interactions.
    • New function to calculate positions for pasted nodes.
    • Introduced a new reducer for managing the selection state of nodes.
    • Updated state management for node actions to simplify handling of node-related data.
  • Bug Fixes

    • Improved handling of keyboard shortcuts to prevent unintended actions in input fields.
  • Refactor

    • Streamlined event handling logic for keyboard shortcuts and clipboard actions.
    • Optimized node management functions for better clarity and maintainability.
    • Simplified reducer logic for handling node addition and selection state.
    • Updated clipboard writing logic to accommodate modern and legacy environments.
    • Adjusted initialization of process definition data for better structure.
  • Style

    • Minor improvements to the clear button interaction in the input component.

@github-actions github-actions bot added client client main fe ui labels Nov 21, 2024
Copy link

coderabbitai bot commented Nov 21, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces several modifications across multiple files to enhance functionality related to node management, clipboard interactions, and user actions within the application. In node.ts, action creators are improved for better management of node connections, with updates to the NodesWithEdgesAddedAction and NodeAddedAction types to reflect changes in data handling. The ClipboardUtils.ts file sees the introduction of a new WriteText interface and a more robust writeText function that accommodates both modern and legacy clipboard writing methods. The SelectionContextProvider.tsx file enhances clipboard functionality, including improved permission checks, error handling during paste operations, and new functions for calculating pasted node positions. The InputWithIcon.tsx file updates the clear button's event handling to ensure the input remains focused post-clear. Lastly, BindKeyboardShortcuts.tsx refines keyboard shortcut handling by preventing copy and cut actions when interacting with input fields. Overall, these changes collectively improve user experience and operational efficiency within the application.

Possibly related PRs

Suggested reviewers

  • arkadius
  • mateuszkp96

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (10)
designer/client/src/components/themed/InputWithIcon.tsx (1)

75-81: LGTM! Consider adding error handling for the focus operation.

The change improves user experience by maintaining focus after clearing the input while preventing unwanted scrolling. This is a good enhancement to the component's behavior.

Consider wrapping the focus call in a try-catch block to handle potential errors, as focus operations can fail in certain edge cases (e.g., when the input is removed from DOM):

                     <div
                         className={addonStyles}
                         onClick={() => {
                             onClear();
-                            focus({ preventScroll: true });
+                            try {
+                                focus({ preventScroll: true });
+                            } catch (error) {
+                                console.warn('Failed to focus input after clear:', error);
+                            }
                         }}
                     >
designer/client/src/containers/BindKeyboardShortcuts.tsx (1)

49-59: Refactor clipboard handlers to reduce code duplication.

The clipboard handlers follow a similar pattern and could be refactored to improve maintainability.

Consider this refactoring:

-copy: (event) => {
-    if (isInputEvent(event)) return;
-    userActions.copy ? eventWithStatistics({ selector: EventTrackingSelector.CopyNode }, userActions.copy(event)) : null;
-},
-paste: (event) => {
-    userActions.paste ? eventWithStatistics({ selector: EventTrackingSelector.PasteNode }, userActions.paste(event)) : null;
-},
-cut: (event) => {
-    if (isInputEvent(event)) return;
-    userActions.cut ? eventWithStatistics({ selector: EventTrackingSelector.CutNode }, userActions.cut(event)) : null;
-},
+const createClipboardHandler = (
+    action: keyof typeof userActions,
+    selector: EventTrackingSelector,
+    skipInputCheck?: boolean
+) => (event: ClipboardEvent) => {
+    if (!skipInputCheck && isInputEvent(event)) return;
+    userActions[action]?.((e) => eventWithStatistics({ selector }, e))(event);
+};
+
+copy: createClipboardHandler('copy', EventTrackingSelector.CopyNode),
+paste: createClipboardHandler('paste', EventTrackingSelector.PasteNode, true),
+cut: createClipboardHandler('cut', EventTrackingSelector.CutNode),
designer/client/src/actions/nk/node.ts (2)

Line range hint 119-131: Consider enhancing the batching behavior documentation

While the comment explains why flushSync is needed, it would be helpful to document:

  1. The specific Redux undo issue that this solves
  2. Why batch grouping is still needed outside of flushSync

Line range hint 1-158: Consider documenting the node management lifecycle

The changes introduce more sophisticated node management with proper batching, validation, and selection handling. Consider adding documentation or a sequence diagram that illustrates:

  1. The lifecycle of node operations (add/delete/connect)
  2. The batching strategy and its interaction with React's rendering
  3. The validation rules for node connections

This would help future maintainers understand the system's behavior and constraints.

designer/client/src/common/ClipboardUtils.ts (2)

20-23: Consider using a type alias instead of an interface for function types

In TypeScript, it's more idiomatic to use a type alias when defining function types. Changing interface WriteText to a type can improve code readability.

Suggested change:

-interface WriteText {
-    (text: string): Promise<string>;
-}
+type WriteText = (text: string) => Promise<string>;

Line range hint 31-50: Ensure the promise always resolves in fallbackWriteText

Relying on the oncopy event to resolve the promise may lead to issues if the event doesn't fire as expected. To guarantee the promise resolves, consider resolving it immediately after executing the copy command.

Suggested change:

 const fallbackWriteText: WriteText = (text) => {
     return new Promise((resolve) => {
         const el = document.createElement("textarea");
         el.value = text;
         el.setAttribute("readonly", "");
         el.className = css({
             position: "absolute",
             left: "-9999px",
         });
-        el.oncopy = (e) => {
-            // Skip event triggered by writing selection to the clipboard.
-            e.stopPropagation();
-            resolve(text);
-        };
         document.body.appendChild(el);
         el.select();
         document.execCommand("copy");
+        resolve(text);
         document.body.removeChild(el);
     });
 };
designer/client/src/components/graph/SelectionContextProvider.tsx (4)

1-1: Consider using native Math.min for minimum value calculation

Instead of importing min from "lodash", you can use the native Math.min function along with the spread operator for basic minimum value calculations. This reduces dependencies and may improve performance.


Line range hint 104-106: Clarify the return type of useClipboardPermission

The hook returns state === "prompt" || content;, which can be a boolean or a string. This might cause confusion when the return value is used in boolean contexts.

Refactor the return statement to consistently return a boolean indicating whether the clipboard can be accessed. For example:

return state === "granted";

If you need to return the parsed content separately, consider returning an object with explicit properties.


Line range hint 149-156: Avoid using random offsets for pasted node positions

In calculatePastedNodePosition, adding a random offset to node positions can lead to unpredictable placement, which may confuse users.

Replace the random offset with a fixed offset to ensure consistent positioning. For example:

- const random = Math.floor(Math.random() * 20) + 1;
  const randomizedNodePosition = {
-   x: selectionLayoutNodePosition.x + random,
-   y: selectionLayoutNodePosition.y + random,
+   x: selectionLayoutNodePosition.x + FIXED_OFFSET_X,
+   y: selectionLayoutNodePosition.y + FIXED_OFFSET_Y,
  };

Define FIXED_OFFSET_X and FIXED_OFFSET_Y as needed.


Line range hint 155-165: Handle potential undefined values from min function

The min function from "lodash" returns undefined when provided an empty array. This can lead to NaN values in position calculations if selection.nodes is empty.

Add checks to ensure selection.nodes is not empty before proceeding with calculations. For example:

if (selection.nodes.length === 0) {
  dispatch(error(t("userActions.paste.failed", "No nodes to paste from clipboard")));
  return;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d5154a3 and 135688e.

📒 Files selected for processing (5)
  • designer/client/src/actions/nk/node.ts (2 hunks)
  • designer/client/src/common/ClipboardUtils.ts (2 hunks)
  • designer/client/src/components/graph/SelectionContextProvider.tsx (3 hunks)
  • designer/client/src/components/themed/InputWithIcon.tsx (1 hunks)
  • designer/client/src/containers/BindKeyboardShortcuts.tsx (2 hunks)
🔇 Additional comments (7)
designer/client/src/containers/BindKeyboardShortcuts.tsx (2)

3-3: LGTM! Clean import statement organization.

The consolidated import statement improves code organization and readability.


49-59: Verify clipboard permissions and handle errors.

The clipboard operations should verify permissions and handle potential errors.

Let's check if clipboard permissions are handled elsewhere in the codebase:

designer/client/src/actions/nk/node.ts (3)

1-1: LGTM: New imports serve clear purposes

The addition of flushSync and toggleSelection imports aligns well with the implementation needs:

  • flushSync handles React 18's automatic batching behavior
  • toggleSelection supports the enhanced node deletion workflow

Also applies to: 9-9


Line range hint 89-117: LGTM: Robust validation for node connections

The implementation properly validates node connection capabilities before attempting connections, preventing invalid graph states. The batch grouping ensures atomic operations.


56-58: Verify toggleSelection's argument handling

The spread operator usage with toggleSelection(...ids) assumes the function accepts multiple arguments. Let's verify this implementation.

✅ Verification successful

toggleSelection correctly handles spread arguments

The implementation in designer/client/src/actions/nk/selection.ts confirms that toggleSelection is correctly defined to accept variable arguments using rest parameters:

export function toggleSelection(...nodeIds: string[]): ThunkAction

The spread operator usage in the review code matches this implementation perfectly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for toggleSelection implementation to verify argument handling
ast-grep --pattern 'export function toggleSelection($$$) {
  $$$
}'

Length of output: 69


Script:

#!/bin/bash
# Let's try searching for toggleSelection with ripgrep to find its implementation
rg "toggleSelection" -A 5

Length of output: 6243

designer/client/src/components/graph/SelectionContextProvider.tsx (2)

Line range hint 78-97: Ensure cross-browser compatibility for navigator.permissions API

The navigator.permissions API and the "clipboard-read" permission name are experimental features and may not be supported in all browsers. Relying on them could lead to inconsistent behavior across different user environments.

Implement feature detection and provide fallbacks for browsers that do not support this API. For example:

useEffect(() => {
  if (navigator.permissions && navigator.permissions.query) {
    navigator.permissions
      .query({ name: "clipboard-read" as PermissionName })
      .then(/* ... */)
      .catch(/* ... */);
  } else {
    // Fallback logic or default state
  }
}, []);

Line range hint 201-212: Verify that text selection state updates correctly

The selectionchange event listener updates the hasSelection state using hasTextSelection, but this might not reflect the latest selection state accurately due to closure over the initial value.

Ensure that hasTextSelection is called within the event handler to get the current selection:

useDocumentListeners(
  useMemo(
    () => ({
-     selectionchange: () => setHasSelection(hasTextSelection),
+     selectionchange: () => setHasSelection(hasTextSelection()),
    }),
    [],
  ),
);

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
designer/client/src/components/graph/SelectionContextProvider.tsx (2)

Line range hint 156-166: Add input validation and document magic numbers

The position calculation function needs defensive programming:

  1. Validate required properties (layoutData) before access
  2. Document or extract the magic number (20) used in random calculation

Consider this safer implementation:

 function calculatePastedNodePosition(node, pasteX, minNodeX, pasteY, minNodeY, random) {
+    if (!node?.additionalFields?.layoutData) {
+        throw new Error("Node layout data is required for position calculation");
+    }
     const currentNodePosition = node.additionalFields.layoutData;
     const pasteNodePosition = { x: currentNodePosition.x + pasteX, y: currentNodePosition.y + pasteY };
     const selectionLayoutNodePosition = { x: pasteNodePosition.x - minNodeX, y: pasteNodePosition.y - minNodeY };
+    // Add slight randomization (1-20 pixels) to prevent exact overlay of nodes
     const randomizedNodePosition = {
         x: selectionLayoutNodePosition.x + random,
         y: selectionLayoutNodePosition.y + random,
     };
     return randomizedNodePosition;
 }

Line range hint 168-184: Improve error handling and add cleanup

The node insertion implementation has two areas for improvement:

  1. The empty catch block silently swallows errors
  2. The debounced callback should be cleaned up on unmount

Consider these improvements:

 const [parseInsertNodes] = useDebouncedCallback((clipboardText) => {
     const selection = parse(clipboardText);
     if (selection) {
         const { x, y } = props.pastePosition();
-        const minNodeX: number = min(selection.nodes.map((node) => node.additionalFields.layoutData.x));
-        const minNodeY: number = min(selection.nodes.map((node) => node.additionalFields.layoutData.y));
+        // Handle potential undefined layoutData
+        const minNodeX: number = min(selection.nodes.map((node) => node?.additionalFields?.layoutData?.x ?? 0));
+        const minNodeY: number = min(selection.nodes.map((node) => node?.additionalFields?.layoutData?.y ?? 0));
         const random = Math.floor(Math.random() * 20) + 1;
         const nodesWithPositions = selection.nodes.map((node) => ({
             node,
             position: calculatePastedNodePosition(node, x, minNodeX, y, minNodeY, random),
         }));
         dispatch(nodesWithEdgesAdded(nodesWithPositions, selection.edges));
     } else {
         dispatch(error(t("userActions.paste.failed", "Cannot paste content from clipboard")));
     }
-}, 250);
+}, 250, { maxWait: 1000 });
+
+useEffect(() => {
+    return () => {
+        parseInsertNodes.cancel();
+    };
+}, [parseInsertNodes]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 135688e and 171aa39.

📒 Files selected for processing (3)
  • designer/client/src/actions/nk/node.ts (2 hunks)
  • designer/client/src/components/graph/SelectionContextProvider.tsx (3 hunks)
  • engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/SpelTemplateLazyParameterTest.scala (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • engine/flink/components/base-tests/src/test/scala/pl/touk/nussknacker/engine/flink/SpelTemplateLazyParameterTest.scala
🚧 Files skipped from review as they are similar to previous changes (1)
  • designer/client/src/actions/nk/node.ts
🔇 Additional comments (1)
designer/client/src/components/graph/SelectionContextProvider.tsx (1)

Line range hint 186-199: Handle potential race condition in paste operation

The paste operation might encounter a race condition when multiple paste events occur rapidly, as the clipboard read and parse operations are asynchronous.

Let's verify the clipboard event handling:

Consider adding a loading state to prevent concurrent paste operations:

+const [isPasting, setIsPasting] = useState(false);
+
 const paste = useCallback(
     async (event?: Event) => {
         if (isInputEvent(event)) {
             return;
         }
+        if (isPasting) {
+            return;
+        }
         try {
+            setIsPasting(true);
             const clipboardText = await ClipboardUtils.readText(event);
             parseInsertNodes(clipboardText);
         } catch {
             dispatch(error(t("userActions.paste.notAvailable", "Paste button is not available. Try Ctrl+V")));
+        } finally {
+            setIsPasting(false);
         }
     },
-    [dispatch, parseInsertNodes, t],
+    [dispatch, parseInsertNodes, t, isPasting],
 );

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (5)
designer/client/src/common/ClipboardUtils.ts (2)

Line range hint 31-50: Add error handling and cleanup guarantees

The fallback implementation should handle errors and ensure cleanup in all scenarios.

Consider this more robust implementation:

 const fallbackWriteText: WriteText = (text) => {
-    return new Promise((resolve) => {
+    return new Promise((resolve, reject) => {
         const el = document.createElement("textarea");
+        const cleanup = () => {
+            if (document.body.contains(el)) {
+                document.body.removeChild(el);
+            }
+        };
         el.value = text;
         el.setAttribute("readonly", "");
         el.className = css({
             position: "absolute",
             left: "-9999px",
         });
         el.oncopy = (e) => {
             // Skip event triggered by writing selection to the clipboard.
             e.stopPropagation();
+            cleanup();
             resolve(text);
         };
         document.body.appendChild(el);
         el.select();
-        document.execCommand("copy");
-        document.body.removeChild(el);
+        try {
+            const success = document.execCommand("copy");
+            if (!success) {
+                cleanup();
+                reject(new Error("Copy command failed"));
+            }
+        } catch (error) {
+            cleanup();
+            reject(error);
+        }
+        // Cleanup after a timeout in case the copy event never fires
+        setTimeout(cleanup, 1000);
     });
 };

Line range hint 1-57: Consider architectural improvements for better maintainability

A few suggestions to enhance the codebase:

  1. Add type guards for clipboard API availability
  2. Extract magic numbers (like timeout) to constants
  3. Export error types for better error handling across the application

Example type guard:

const isClipboardWriteSupported = (): boolean => {
    return !!(navigator.clipboard?.writeText);
};

Example constants:

export const CLIPBOARD_CONSTANTS = {
    CLEANUP_TIMEOUT_MS: 1000,
    HIDDEN_POSITION: "-9999px",
} as const;
designer/client/src/components/graph/SelectionContextProvider.tsx (3)

Line range hint 164-173: Enhance node position calculation robustness

The position calculation function could benefit from several improvements:

  1. Add input validation to prevent NaN or undefined values
  2. Consider extracting the random offset range (20) to a named constant
  3. Consider making the random offset optional or configurable
+const POSITION_RANDOM_OFFSET_RANGE = 20;
+
 function calculatePastedNodePosition(node, pasteX, minNodeX, pasteY, minNodeY, random) {
+    if (!node?.additionalFields?.layoutData) {
+        throw new Error("Invalid node layout data");
+    }
     const currentNodePosition = node.additionalFields.layoutData;
+    if (typeof currentNodePosition.x !== "number" || typeof currentNodePosition.y !== "number") {
+        throw new Error("Invalid position coordinates");
+    }
     const pasteNodePosition = { x: currentNodePosition.x + pasteX, y: currentNodePosition.y + pasteY };
     const selectionLayoutNodePosition = { x: pasteNodePosition.x - minNodeX, y: pasteNodePosition.y - minNodeY };
     const randomizedNodePosition = {

Line range hint 175-208: Improve paste operation UX and error handling

Consider the following improvements:

  1. Extract the debounce delay to a named constant
  2. Provide more specific error messages based on failure reason
  3. Add loading state during paste operation
+const PASTE_DEBOUNCE_DELAY = 250;
+const PASTE_ERROR_TYPES = {
+    INVALID_FORMAT: "paste.invalidFormat",
+    PERMISSION_DENIED: "paste.permissionDenied",
+    NETWORK_ERROR: "paste.networkError",
+};
+
 const [parseInsertNodes] = useDebouncedCallback((clipboardText) => {
     const selection = parse(clipboardText);
     if (selection) {
         const { x, y } = props.pastePosition();
         const minNodeX: number = min(selection.nodes.map((node) => node.additionalFields.layoutData.x));
         const minNodeY: number = min(selection.nodes.map((node) => node.additionalFields.layoutData.y));
         const random = Math.floor(Math.random() * 20) + 1;
         const nodesWithPositions = selection.nodes.map((node) => ({
             node,
             position: calculatePastedNodePosition(node, x, minNodeX, y, minNodeY, random),
         }));
         dispatch(nodesWithEdgesAdded(nodesWithPositions, selection.edges));
     } else {
-        dispatch(error(t("userActions.paste.failed", "Cannot paste content from clipboard")));
+        dispatch(error(t(`userActions.${PASTE_ERROR_TYPES.INVALID_FORMAT}`, "Invalid content format in clipboard")));
     }
-}, 250);
+}, PASTE_DEBOUNCE_DELAY);

Line range hint 210-223: Add type safety for event handling

Consider improving type safety by:

  1. Using a more specific type for the event parameter
  2. Adding type guards for event handling
-type UserAction = ((e: Event) => unknown) | null;
+type ClipboardEvent = Event & { clipboardData?: DataTransfer };
+type UserAction = ((e: ClipboardEvent) => unknown) | null;

+function isClipboardEvent(event: Event): event is ClipboardEvent {
+    return 'clipboardData' in event;
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 171aa39 and 84033e5.

📒 Files selected for processing (5)
  • designer/client/src/actions/nk/node.ts (2 hunks)
  • designer/client/src/common/ClipboardUtils.ts (2 hunks)
  • designer/client/src/components/graph/SelectionContextProvider.tsx (3 hunks)
  • designer/client/src/components/themed/InputWithIcon.tsx (1 hunks)
  • designer/client/src/containers/BindKeyboardShortcuts.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • designer/client/src/actions/nk/node.ts
  • designer/client/src/components/themed/InputWithIcon.tsx
  • designer/client/src/containers/BindKeyboardShortcuts.tsx
🔇 Additional comments (3)
designer/client/src/common/ClipboardUtils.ts (2)

20-22: LGTM: Well-defined TypeScript interface

The WriteText interface is clear, concise, and properly typed.


52-57: Previous review comment about error handling is still valid

The implementation should handle clipboard API errors and attempt fallback as suggested in the previous review.

designer/client/src/components/graph/SelectionContextProvider.tsx (1)

Line range hint 76-81: Consider event-driven clipboard access instead of polling

The current implementation polls the clipboard every 500ms, which could impact performance and may not work reliably due to browser security restrictions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Outside diff range and nitpick comments (11)
designer/client/src/reducers/graph/utils.ts (1)

60-63: Consider handling empty newNodes array

If newNodes is empty, functions like map and reduce may behave unexpectedly. Ensure that the code correctly handles cases where newNodesWithPositions might be empty.

You might want to add a check at the beginning:

 if (newNodes.length === 0) {
     return {
         nodes: [],
         layout: [],
         idMapping: {},
     };
 }
designer/client/src/reducers/graph/reducer.ts (1)

34-34: Consider initializing 'selectionState' to an empty object

Setting selectionState to null may lead to null reference errors when accessing its properties. Initializing it to an empty object {} ensures safer access and aligns with standard practices.

Apply this diff to initialize selectionState as an empty object:

 const emptyGraphState: GraphState = {
   scenarioLoading: false,
   scenario: null,
   layout: [],
   testCapabilities: null,
   testFormParameters: null,
-  selectionState: null,
+  selectionState: {},
   processCounts: {},
   testResults: null,
   unsavedNewName: null,
 };
designer/client/src/reducers/graph/utils.test.ts (1)

7-7: Document the boolean parameter's purpose

The boolean parameter true passed to prepareNewNodesWithLayout lacks context. Consider:

  1. Adding a comment explaining its purpose
  2. Using a named constant instead of a magic boolean
-        expect(prepareNewNodesWithLayout(scenarioGraph.nodes, nodesWithPositions, true)).toMatchSnapshot();
+        const PRESERVE_NODE_POSITIONS = true; // or whatever this boolean controls
+        expect(prepareNewNodesWithLayout(scenarioGraph.nodes, nodesWithPositions, PRESERVE_NODE_POSITIONS)).toMatchSnapshot();

Additionally, consider adding explicit assertions alongside the snapshot test to make the test's intentions clearer:

const result = prepareNewNodesWithLayout(scenarioGraph.nodes, nodesWithPositions, PRESERVE_NODE_POSITIONS);
// Add specific assertions for critical behavior
expect(result.nodes).toBeDefined();
expect(result.idMapping).toBeDefined();
// Then use snapshot for complete verification
expect(result).toMatchSnapshot();
designer/client/src/reducers/graph/selectionState.ts (2)

1-4: Consider optimizing lodash imports

The current implementation imports both uniq and xor from lodash. Since uniq is only used once, consider using native Set for uniqueness to reduce bundle size.

-import { uniq, xor } from "lodash";
+import { xor } from "lodash";

Then update the EXPAND_SELECTION case to use Set:

-return uniq([...state, ...action.nodeIds]);
+return [...new Set([...state, ...action.nodeIds])];

4-19: Consider adding TypeScript improvements for better type safety

The reducer could benefit from stricter typing:

  1. Use a discriminated union type for actions
  2. Make the switch statement exhaustive
type SelectionAction =
  | { type: "NODES_WITH_EDGES_ADDED"; nodes: Array<{ id: string }> }
  | { type: "NODE_ADDED"; nodes: Array<{ id: string }> }
  | { type: "DELETE_NODES"; ids: string[] }
  | { type: "EXPAND_SELECTION"; nodeIds: string[] }
  | { type: "TOGGLE_SELECTION"; nodeIds: string[] }
  | { type: "RESET_SELECTION"; nodeIds?: string[] };

export const selectionState: Reducer<string[], SelectionAction> = (state = [], action) => {
  // ... rest of the implementation
designer/client/src/reducers/settings.ts (1)

57-59: LGTM! Consider adding documentation for the edgesForNodes array.

The initialization of processDefinitionData with an empty edgesForNodes array is a good practice to ensure consistent state structure. However, consider adding a comment explaining its purpose and relationship with node management.

Add documentation like this:

     processDefinitionData: {
+        // Stores edges information for nodes in the process definition
         edgesForNodes: [],
     },
designer/client/src/actions/nk/node.ts (2)

125-138: Consider extracting the node preparation logic

While the implementation is solid, the node preparation logic within the flushSync block could be extracted to a separate function for better testability and reuse. This would also make it easier to modify the preparation logic without dealing with React's batching concerns.

Consider refactoring to:

 export function nodeAdded(node: NodeType, position: Position): ThunkAction {
     return (dispatch, getState) => {
         batchGroupBy.startOrContinue();
+        const prepareNode = () => {
+            const scenarioGraph = getScenarioGraph(getState());
+            return prepareNewNodesWithLayout(scenarioGraph.nodes, [{ node, position }], false);
+        };
 
         // We need to disable automatic React batching https://react.dev/blog/2022/03/29/react-v18#new-feature-automatic-batching
         // since it breaks redux undo in this case
         flushSync(() => {
-            const scenarioGraph = getScenarioGraph(getState());
-            const { nodes, layout } = prepareNewNodesWithLayout(scenarioGraph.nodes, [{ node, position }], false);
+            const { nodes, layout } = prepareNode();
 
             dispatch({
                 type: "NODE_ADDED",
                 nodes,
                 layout,
             });
             dispatch(layoutChanged());
         });
         batchGroupBy.end();
     };
 }

147-157: Consider adding error handling for edge validation

While the implementation is good, it might benefit from validating the edges before dispatching the action. Invalid edges could lead to inconsistent graph state.

Consider adding validation:

 export function nodesWithEdgesAdded(nodesWithPositions: NodesWithPositions, edges: Edge[]): ThunkAction {
     return (dispatch, getState) => {
         const state = getState();
         const processDefinitionData = getProcessDefinitionData(state);
         const scenarioGraph = getScenarioGraph(state);
         const { nodes, layout, idMapping } = prepareNewNodesWithLayout(scenarioGraph.nodes, nodesWithPositions, true);
 
+        // Validate edges before dispatching
+        const validEdges = edges.filter(edge => {
+            const fromNode = nodes.find(n => n.id === edge.from);
+            const toNode = nodes.find(n => n.id === edge.to);
+            return fromNode && toNode && NodeUtils.canConnect(fromNode, toNode, processDefinitionData);
+        });
+
         batchGroupBy.startOrContinue();
         dispatch({
             type: "NODES_WITH_EDGES_ADDED",
             nodes,
             layout,
             idMapping,
-            edges,
+            edges: validEdges,
             processDefinitionData,
         });
         dispatch(layoutChanged());
         batchGroupBy.end();
     };
 }
designer/client/test/reducer-test.js (3)

112-127: Excellent implementation of the test reducer!

The refactored reduceAll function is a significant improvement:

  • Properly handles both synchronous and asynchronous actions
  • Maintains state through closure
  • Follows Redux patterns with dispatch and getState

Consider adding error handling for failed dispatches to make the tests more robust.


Line range hint 171-188: Enhance test coverage for multiple nodes.

While the test verifies node IDs, it should also verify:

  • Node positions are correctly set
  • Original node remains unchanged

Consider adding these assertions:

expect(result.graphReducer.layout.find((n) => n.id === "kafka-transaction (copy 1)").position).toEqual(testPosition);
expect(result.graphReducer.layout.find((n) => n.id === "kafka-transaction (copy 2)").position).toEqual(testPosition);
expect(NodeUtils.getNodeById("kafka-transaction", result.graphReducer.scenario.scenarioGraph)).toEqual(originalNode);

Based on the findings from NodeUtils-test.js, I can now generate the final response:

Test should include validation rules for edge connections

The current test is missing important edge cases that are already validated in the codebase:

  • Connections to/from sink nodes (a sink can only receive connections)
  • Connections to/from source nodes (a source can only send connections)
  • Maximum number of output connections (e.g., Filter nodes have a limit of two outputs)
  • Edge uniqueness between same nodes

Add test cases to verify these scenarios when adding nodes and edges to ensure the graph maintains its integrity constraints.

🔗 Analysis chain

Line range hint 192-213: Consider additional edge cases for node-edge connections.

The test should verify:

  • Edge cases for invalid connections
  • Proper handling of duplicate edge IDs

Let's check if there are any edge validation rules in the codebase:


Let's search for more specific validation rules and edge connection logic:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for edge validation logic
rg -A 5 "validateEdge|isValidConnection|canConnect" 

Length of output: 3186


Script:

#!/bin/bash
# Search for canMakeLink implementation and related validation logic
rg -A 10 "canMakeLink" 

# Search for edge-related tests
rg -l "test.*edge|edge.*test" --type=test

Length of output: 9764

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 84033e5 and b301007.

⛔ Files ignored due to path filters (2)
  • designer/client/src/reducers/graph/__snapshots__/utils.test.ts.snap is excluded by !**/*.snap
  • designer/client/test/__snapshots__/reducer-test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • designer/client/src/actions/actionTypes.ts (0 hunks)
  • designer/client/src/actions/nk/node.ts (5 hunks)
  • designer/client/src/reducers/graph/reducer.ts (4 hunks)
  • designer/client/src/reducers/graph/selectionState.ts (1 hunks)
  • designer/client/src/reducers/graph/utils.test.ts (1 hunks)
  • designer/client/src/reducers/graph/utils.ts (2 hunks)
  • designer/client/src/reducers/settings.ts (1 hunks)
  • designer/client/test/reducer-test.js (5 hunks)
💤 Files with no reviewable changes (1)
  • designer/client/src/actions/actionTypes.ts
🔇 Additional comments (10)
designer/client/src/reducers/graph/reducer.ts (3)

2-15: New imports are appropriate and necessary

The added imports from "lodash" and local modules (batchGroupBy, correctFetchedDetails, NestedKeyOf, selectionState) enhance the functionality of the reducer and are correctly used in the code.


236-239: Simplification of 'NODE_ADDED' action enhances readability

Refactoring the 'NODE_ADDED' case to directly use addNodesWithLayout with action.nodes and action.layout simplifies the code and reduces complexity, improving maintainability.


313-313: Integration of 'selectionState' reducer promotes modularity

Including selectionState in mergeReducers allows for better state management by delegating selection-specific logic to its own reducer, enhancing code organization.

designer/client/src/reducers/graph/utils.test.ts (1)

5-5: Clarify test description to match implementation

The test description mentions "union output expression parameter" but the test implementation doesn't explicitly verify this behavior. Consider either:

  1. Adding explicit assertions to verify the union output expression update, or
  2. Updating the description to better match what's being tested

Let's verify the union output expression handling in the codebase:

designer/client/src/reducers/graph/selectionState.ts (1)

4-19: Verify consistent usage of selection actions

Let's verify that all components dispatching these selection actions are using the correct payload structure.

✅ Verification successful

All selection actions are being used correctly with proper payload structure

Based on the verification results, the selection actions are being dispatched consistently across the codebase:

  • TOGGLE_SELECTION and EXPAND_SELECTION actions in actions/nk/selection.ts use nodeIds payload
  • RESET_SELECTION in actions/nk/selection.ts uses optional nodeIds payload
  • NODE_ADDED and NODES_WITH_EDGES_ADDED in actions/nk/node.ts use nodes payload
  • DELETE_NODES in actions/nk/node.ts uses ids payload

All dispatched actions match the expected payload structure handled by the reducer, ensuring type safety and consistency.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for dispatch calls of selection actions
echo "Searching for selection action dispatches..."
rg -A 2 "dispatch\(.*(?:NODES_WITH_EDGES_ADDED|NODE_ADDED|DELETE_NODES|EXPAND_SELECTION|TOGGLE_SELECTION|RESET_SELECTION)"

# Search for action creator usage
echo "Searching for action creators..."
ast-grep --pattern 'dispatch({ type: $_, $$$})'

Length of output: 20559

designer/client/src/reducers/settings.ts (1)

57-59: Verify the impact on node-related operations.

Since this initialization is connected to node management functionality, let's verify its usage across the codebase.

✅ Verification successful

Based on the search results, I can now provide a final response:

The edgesForNodes initialization is safe and correctly used across the codebase

The empty array initialization is appropriate as this property is populated by the server-side DefinitionsService and consumed by client-side components, primarily in NodeUtils.ts. The usage pattern shows:

  • Server defines edges via EdgeTypesPreparer.prepareEdgeTypes
  • Client uses it for node connection validation and edge type determination
  • Test files confirm the expected structure with empty arrays as valid initial states
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check how edgesForNodes is used in node-related operations

# Find direct references to edgesForNodes
echo "Direct references to edgesForNodes:"
rg "edgesForNodes" -A 3

# Find node-related actions that might depend on this initialization
echo "\nNode-related actions:"
ast-grep --pattern 'type = "NODES_WITH_EDGES_ADDED"' 

Length of output: 15911

designer/client/src/actions/nk/node.ts (3)

36-38: LGTM! Improved separation of concerns

The restructuring of NodesWithEdgesAddedAction to separate node data from layout information is a good architectural improvement. The addition of idMapping provides better support for node identification and potential duplication scenarios.


50-51: LGTM! Consistent type structure

The NodeAddedAction type now follows the same pattern as NodesWithEdgesAddedAction, supporting batch operations and maintaining a clear separation between node data and layout information.


1-6: Verify the necessity of flushSync usage

The addition of flushSync suggests a workaround for React 18's automatic batching. While it's used to prevent issues with redux undo, this approach might indicate an underlying architectural issue.

Let's check if this is a common pattern in the codebase:

✅ Verification successful

flushSync usage appears to be necessary for Redux state synchronization

The flushSync usage is justified in this case. It's used in the node actions to ensure synchronous state updates when adding nodes to the scenario graph. This is important because:

  1. The code needs to get the current scenario graph state
  2. Prepare new nodes with layout
  3. Dispatch multiple actions in sequence (NODE_ADDED and layoutChanged)

Without flushSync, React 18's automatic batching could cause race conditions between these dependent state updates.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other flushSync usages to understand the pattern
rg "flushSync" --type ts --type tsx

Length of output: 63


Script:

#!/bin/bash
# Search for flushSync usages with correct ripgrep syntax
rg "flushSync"

# Also search for react-dom imports to understand the context
rg "from ['\"]react-dom['\"]"

Length of output: 131122


Script:

#!/bin/bash
# Search for flushSync usage in the codebase
ast-grep --pattern 'flushSync($$$)'

# Also search for any imports of flushSync
ast-grep --pattern 'import { $$$flushSync$$$ } from "react-dom"'

Length of output: 3285

designer/client/test/reducer-test.js (1)

1-1: LGTM! Good practice using action creators.

The change to import action creators instead of manually constructing action objects improves maintainability and consistency.

designer/client/src/reducers/graph/utils.ts Show resolved Hide resolved
designer/client/src/reducers/graph/utils.ts Show resolved Hide resolved
designer/client/src/reducers/graph/utils.ts Show resolved Hide resolved
designer/client/src/reducers/graph/reducer.ts Show resolved Hide resolved
designer/client/src/reducers/graph/reducer.ts Show resolved Hide resolved
designer/client/test/reducer-test.js Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
designer/client/src/reducers/graph/reducer.ts (1)

313-313: Good architectural change: Selection state extraction

Moving selection state management to a separate reducer improves code organization and follows the Single Responsibility Principle. This change:

  • Improves maintainability by isolating selection logic
  • Makes the code more modular
  • Follows better separation of concerns

Consider applying similar pattern to other complex state management areas in the reducer.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a0c4a14 and 822087a.

📒 Files selected for processing (1)
  • designer/client/src/reducers/graph/reducer.ts (3 hunks)
🔇 Additional comments (3)
designer/client/src/reducers/graph/reducer.ts (3)

242-262: Previous review comments about edge handling remain valid

The code still needs improvements in robustness:

  1. Missing null checks for fromNode/toNode before edge creation
  2. No fallback handling for missing IDs in idMapping

Referring to previous review comments about:

  • Adding null checks before edge creation
  • Ensuring robustness in ID mapping for edges

15-15: Verify selectionState initialization consistency

There appears to be an inconsistency in how selectionState is initialized and handled:

  • emptyGraphState initializes it as an empty array []
  • The reducer composition suggests different handling via imported selectionState

This could lead to unexpected behavior in selection state management.

Also applies to: 313-313

✅ Verification successful

No inconsistency found in selectionState initialization and handling

The selectionState implementation is consistent across the codebase:

  • The emptyGraphState correctly initializes selectionState as []
  • The imported selectionState reducer from selectionState.ts also uses [] as its default state
  • The reducer composition at line 313 properly combines these consistent implementations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check selectionState initialization and usage patterns
rg "selectionState.*[=:]" --type typescript

Length of output: 80


Script:

#!/bin/bash
# Check selectionState initialization and usage patterns with ripgrep
rg "selectionState.*[=:]"

# Also check the specific file content
cat designer/client/src/reducers/graph/reducer.ts

# Check the selectionState file
cat designer/client/src/reducers/graph/selectionState.ts

Length of output: 15088


236-239: Verify NODE_ADDED action payload structure

The simplified node addition logic assumes the action payload contains properly structured nodes and layout. Ensure that all code paths creating this action provide these fields correctly.

✅ Verification successful

Action payload structure is correctly handled

The verification confirms that:

  1. The NODE_ADDED action is dispatched with proper nodes and layout fields, which are obtained from prepareNewNodesWithLayout utility.
  2. The prepareNewNodesWithLayout function properly structures the data, returning both nodes and layout in the expected format.
  3. The reducer's addNodesWithLayout handler correctly processes these fields as seen in the reducer implementation.

The action payload structure is consistent across the codebase and properly validated through TypeScript types and utility functions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find all dispatches of NODE_ADDED action
ast-grep --pattern 'dispatch({ type: "NODE_ADDED", $$$})'

Length of output: 396


Script:

#!/bin/bash
# Let's examine the context around the NODE_ADDED dispatch to understand how nodes and layout are structured
rg -B 10 -A 5 'type: "NODE_ADDED",' designer/client/src/actions/nk/node.ts

# Also check the type definitions for the action
ast-grep --pattern 'interface $_ { type: "NODE_ADDED"; $$$ }'

Length of output: 778


Script:

#!/bin/bash
# Let's check the prepareNewNodesWithLayout function to understand how nodes and layout are structured
ast-grep --pattern 'function prepareNewNodesWithLayout($$$) { $$$ }'

# Also check the type definition of the state to understand the expected structure
ast-grep --pattern 'type $_ = { nodes: $_; layout: $_; $$$ }'

Length of output: 134


Script:

#!/bin/bash
# Let's try a different approach to find the prepareNewNodesWithLayout implementation
rg -B 2 -A 10 "prepareNewNodesWithLayout" 

# Also check for type definitions in the reducer file
cat designer/client/src/reducers/graph/reducer.ts

Length of output: 19548

Copy link
Contributor

github-actions bot commented Nov 22, 2024

created: #7226
⚠️ Be careful! Snapshot changes are not necessarily the cause of the error. Check the logs.

@JulianWielga JulianWielga changed the base branch from staging to release/1.18 November 22, 2024 17:24
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (11)
designer/client/src/reducers/graph/utils.test.ts (1)

5-5: Improve test description clarity

The current test description is lengthy and could be more concise. Consider breaking it down into multiple test cases for better clarity and maintainability.

-    it("prepareNewNodesWithLayout should update union output expression parameter with an updated node name when new unique node ids created", () => {
+    it("should update node names in union output expressions when creating unique IDs", () => {
designer/client/src/actions/nk/node.ts (3)

1-6: Add documentation for flushSync usage.

The imports look good and align with the new functionality. However, consider adding a more detailed comment explaining why flushSync is necessary for handling React 18's automatic batching behavior in this specific case.


50-51: Document multi-node addition scenarios.

The type now supports adding multiple nodes at once. Consider adding documentation to explain scenarios where multiple nodes would be added in a single action.


147-157: LGTM! Consider adding batch grouping tests.

The changes improve consistency and make state access more explicit. The batch grouping ensures atomic operations, but consider adding tests to verify this behavior.

Would you like me to help create test cases for the batch grouping behavior?

designer/client/test/reducer-test.js (3)

112-127: Consider adding error handling to the dispatch function.

While the implementation correctly handles both synchronous and asynchronous actions, it could benefit from error handling to catch and report dispatch failures.

Consider this enhancement:

 const dispatch = (action) => {
+    try {
         if (typeof action === "function") {
-            action(dispatch, getState);
+            return action(dispatch, getState);
         } else {
-            currentState = reducer(currentState, action);
+            currentState = reducer(currentState, action);
+            return action;
         }
+    } catch (error) {
+        console.error('Action dispatch failed:', error);
+        throw error;
+    }
 };

Line range hint 157-209: Consider adding error case test coverage.

While the happy path scenarios are well covered, consider adding tests for:

  • Invalid node data
  • Duplicate edge creation
  • Invalid edge references
  • Position validation

Example test case:

it("should handle invalid node data", () => {
    expect(() => 
        reduceAll([
            nodeAdded({ id: "invalid" }, testPosition)
        ])
    ).toThrow(/Invalid node type/);
});

159-160: Consider grouping related assertions.

The test assertions could be organized better for readability by grouping related checks together.

Example refactor:

it("should add single node", () => {
    const result = reduceAll([nodeAdded(testNode, testPosition)]);
    
    // Node structure validation
    const addedNode = NodeUtils.getNodeById(testNode.id, result.graphReducer.scenario.scenarioGraph);
    expect(addedNode).toMatchSnapshot();
    
    // Layout validation
    const nodeLayout = result.graphReducer.layout.find((n) => n.id === testNode.id);
    expect(nodeLayout.position).toEqual(testPosition);
});

Also applies to: 166-167, 187-188, 208-209

designer/client/src/components/graph/SelectionContextProvider.tsx (3)

Line range hint 165-174: Enhance type safety and predictability of node positioning

The function could benefit from TypeScript types and more predictable positioning logic.

Consider these improvements:

- function calculatePastedNodePosition(node, pasteX, minNodeX, pasteY, minNodeY, random) {
+ interface LayoutData {
+   x: number;
+   y: number;
+ }
+ 
+ interface Node {
+   additionalFields: {
+     layoutData: LayoutData;
+   };
+ }
+ 
+ function calculatePastedNodePosition(
+   node: Node,
+   targetPosition: LayoutData,
+   selectionBounds: LayoutData,
+   offset: number = 10
+ ): LayoutData {
    const currentNodePosition = node.additionalFields.layoutData;
-   const pasteNodePosition = { x: currentNodePosition.x + pasteX, y: currentNodePosition.y + pasteY };
-   const selectionLayoutNodePosition = { x: pasteNodePosition.x - minNodeX, y: pasteNodePosition.y - minNodeY };
-   const randomizedNodePosition = {
-     x: selectionLayoutNodePosition.x + random,
-     y: selectionLayoutNodePosition.y + random,
-   };
-   return randomizedNodePosition;
+   return {
+     x: currentNodePosition.x + targetPosition.x - selectionBounds.x + offset,
+     y: currentNodePosition.y + targetPosition.y - selectionBounds.y + offset,
+   };
}

Line range hint 176-195: Improve paste operation robustness and error handling

The paste operation could benefit from more specific error handling and consistent node positioning.

Consider these improvements:

 const [parseInsertNodes] = useDebouncedCallback((clipboardText) => {
     const selection = parse(clipboardText);
     if (selection) {
         const { x, y } = props.pastePosition();
         const minNodeX: number = min(selection.nodes.map((node) => node.additionalFields.layoutData.x));
         const minNodeY: number = min(selection.nodes.map((node) => node.additionalFields.layoutData.y));
-        const random = Math.floor(Math.random() * 20) + 1;
+        const GRID_SPACING = 10; // Use consistent spacing instead of random
         const nodesWithPositions = selection.nodes.map((node) => ({
             node,
-            position: calculatePastedNodePosition(node, x, minNodeX, y, minNodeY, random),
+            position: calculatePastedNodePosition(
+               node,
+               { x, y },
+               { x: minNodeX, y: minNodeY },
+               GRID_SPACING
+            ),
         }));
         dispatch(nodesWithEdgesAdded(nodesWithPositions, selection.edges));
     } else {
-        dispatch(error(t("userActions.paste.failed", "Cannot paste content from clipboard")));
+        dispatch(error(t("userActions.paste.failed", "Invalid content format in clipboard. Expected a valid node selection.")));
     }
- }, 250);
+ }, 100); // Reduce debounce time for better responsiveness

Line range hint 197-208: Enhance error handling and debugging capabilities

The current error handling is too generic and might hide important issues from developers.

Consider improving error handling:

 const paste = useCallback(
     async (event?: Event) => {
         if (isInputEvent(event)) {
             return;
         }
         try {
             const clipboardText = await ClipboardUtils.readText(event);
             parseInsertNodes(clipboardText);
-        } catch {
-            dispatch(error(t("userActions.paste.notAvailable", "Paste button is not available. Try Ctrl+V")));
+        } catch (e) {
+            console.error("Paste operation failed:", e);
+            if (e instanceof Error) {
+                if (e.name === "NotAllowedError") {
+                    dispatch(error(t("userActions.paste.notAllowed", "Clipboard access denied. Please use Ctrl+V")));
+                } else if (e.name === "DataError") {
+                    dispatch(error(t("userActions.paste.invalidData", "Invalid clipboard content")));
+                } else {
+                    dispatch(error(t("userActions.paste.failed", "Failed to paste: {{message}}", { message: e.message })));
+                }
+            }
         }
     },
     [dispatch, parseInsertNodes, t],
 );
designer/client/src/reducers/graph/reducer.ts (1)

234-246: Consider functional approach for edge creation

The edge creation logic could be more functional and concise.

Consider this alternative implementation:

-const adjustedEdges = edgesWithValidIds.reduce((edges, edge) => {
-    const fromNode = nodes.find((n) => n.id === edge.from);
-    const toNode = nodes.find((n) => n.id === edge.to);
-    const currentNodeEdges = NodeUtils.getOutputEdges(fromNode.id, edges);
-    const newEdge = createEdge(fromNode, toNode, edge.edgeType, currentNodeEdges, processDefinitionData);
-    return edges.concat(newEdge);
-}, state.scenario.scenarioGraph.edges);
+const nodesMap = new Map(nodes.map(node => [node.id, node]));
+const adjustedEdges = state.scenario.scenarioGraph.edges.concat(
+    edgesWithValidIds
+        .filter(edge => nodesMap.has(edge.from) && nodesMap.has(edge.to))
+        .map(edge => {
+            const fromNode = nodesMap.get(edge.from);
+            const toNode = nodesMap.get(edge.to);
+            const currentNodeEdges = NodeUtils.getOutputEdges(fromNode.id, state.scenario.scenarioGraph.edges);
+            return createEdge(fromNode, toNode, edge.edgeType, currentNodeEdges, processDefinitionData);
+        })
+);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 822087a and f080e3e.

⛔ Files ignored due to path filters (2)
  • designer/client/src/reducers/graph/__snapshots__/utils.test.ts.snap is excluded by !**/*.snap
  • designer/client/test/__snapshots__/reducer-test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (12)
  • designer/client/src/actions/actionTypes.ts (0 hunks)
  • designer/client/src/actions/nk/node.ts (5 hunks)
  • designer/client/src/common/ClipboardUtils.ts (2 hunks)
  • designer/client/src/components/graph/SelectionContextProvider.tsx (3 hunks)
  • designer/client/src/components/themed/InputWithIcon.tsx (1 hunks)
  • designer/client/src/containers/BindKeyboardShortcuts.tsx (2 hunks)
  • designer/client/src/reducers/graph/reducer.ts (3 hunks)
  • designer/client/src/reducers/graph/selectionState.ts (1 hunks)
  • designer/client/src/reducers/graph/utils.test.ts (1 hunks)
  • designer/client/src/reducers/graph/utils.ts (2 hunks)
  • designer/client/src/reducers/settings.ts (1 hunks)
  • designer/client/test/reducer-test.js (5 hunks)
💤 Files with no reviewable changes (1)
  • designer/client/src/actions/actionTypes.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • designer/client/src/common/ClipboardUtils.ts
  • designer/client/src/components/themed/InputWithIcon.tsx
  • designer/client/src/containers/BindKeyboardShortcuts.tsx
  • designer/client/src/reducers/graph/selectionState.ts
  • designer/client/src/reducers/settings.ts
🔇 Additional comments (10)
designer/client/src/reducers/graph/utils.test.ts (1)

4-7: Verify snapshot testing implementation

Let's verify if snapshot testing is properly set up and if there are any existing snapshots.

✅ Verification successful

Let me check the actual snapshot content to verify if it's properly implemented.


Snapshot testing is properly implemented

The snapshot testing implementation is correct and follows best practices:

  • Snapshot file exists and contains valid serialized test data
  • The snapshot captures complex graph node structures with proper formatting
  • The test case has a clear, descriptive name
  • Consistent with other snapshot tests in the codebase
  • Jest snapshot configuration is present (detected in earlier search)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing snapshot files and snapshot testing setup

# Check for snapshot file
fd --type f -e snap -e snapshot

# Check for snapshot testing configuration
rg -l "toMatchSnapshot|jest.config" 

# Check if other tests are using snapshots (for consistency)
rg "toMatchSnapshot" --type ts

Length of output: 1346


Script:

#!/bin/bash
# Check the content of the snapshot file
cat designer/client/src/reducers/graph/__snapshots__/utils.test.ts.snap

# Check the test file implementation
cat designer/client/src/reducers/graph/utils.test.ts

Length of output: 2965

designer/client/src/actions/nk/node.ts (2)

36-38: LGTM! Improved type structure.

The separation of nodes and layout data, along with the addition of idMapping, provides better organization and supports node duplication scenarios.


125-138: Enhance documentation and verify redux-undo integration.

The changes look good, particularly the use of prepareNewNodesWithLayout. However:

  1. Consider expanding the comment to explain specifically how automatic batching affects redux-undo in this case.
  2. We should verify that this change properly integrates with redux-undo.
✅ Verification successful

Let me gather more information about the batching and redux state updates to better understand the integration.


Let me try another search pattern to find the NODE_ADDED action and batching behavior.


Based on the search results, I can now provide a final response since I have enough context about the batching behavior and flushSync usage:

The flushSync usage is correct and necessary for redux-undo compatibility.

The code shows a well-implemented solution where:

  1. The batchGroupBy utility is properly integrated with redux-undo to group related actions
  2. The flushSync usage is necessary because:
    • It prevents React 18's automatic batching from interfering with redux-undo's history tracking
    • It's wrapped around the specific dispatch calls that need to be processed immediately
    • The comment accurately explains the reason for disabling automatic batching

The implementation ensures that node addition operations are properly tracked in the undo history while maintaining compatibility with React 18's batching behavior.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for redux-undo usage and potential batching issues
rg -A 3 "redux-undo|undoable" 

Length of output: 5791


Script:

#!/bin/bash
# Search for relevant state updates and batching patterns
rg -A 5 "NODE_ADDED|flushSync" 

# Also check for any redux batch operations
ast-grep --pattern 'dispatch({ type: "NODE_ADDED", $$$})'

Length of output: 130388


Script:

#!/bin/bash
# Search for NODE_ADDED and flushSync usage
rg -A 5 "NODE_ADDED|flushSync|batchGroupBy" designer/client/src/actions/nk/node.ts

# Also check for redux batching related code
rg -A 5 "batchGroupBy|redux" designer/client/src/reducers/graph/

Length of output: 5537

designer/client/test/reducer-test.js (1)

1-3: LGTM: Import changes align with action creator updates.

The imports have been properly reorganized to use the new action creators, maintaining a clean separation of concerns.

designer/client/src/reducers/graph/utils.ts (3)

44-49: ⚠️ Potential issue

Critical: Unsafe ID matching in adjustBranchParameters

The current implementation using uniqueIds.find((uniqueId) => uniqueId.includes(branchId)) is unsafe as it can lead to incorrect mappings when a uniqueId contains the branchId as a substring. For example, if uniqueIds contains "node10" and branchId is "node1", it would incorrectly match.

The previous review suggestion to use idMapping is still valid. Additionally, consider adding input validation:

-function adjustBranchParameters(branchParameters: BranchParams[], uniqueIds: string[]) {
+function adjustBranchParameters(branchParameters: BranchParams[] | undefined, idMapping: Dictionary<string>) {
+    if (!branchParameters?.length) return [];
     return branchParameters?.map(({ branchId, ...branchParameter }: BranchParams) => ({
         ...branchParameter,
-        branchId: uniqueIds.find((uniqueId) => uniqueId.includes(branchId)),
+        branchId: idMapping[branchId] || branchId,
     }));
 }

80-100: ⚠️ Potential issue

Fix potential loss of existing edges

The current implementation directly assigns changes.edges which could lead to loss of existing edges when changes.edges is undefined.

The previous review suggestion to merge edges is still valid:

     return {
         ...state,
         scenario: {
             ...state.scenario,
             scenarioGraph: {
                 ...state.scenario.scenarioGraph,
                 nodes: [...currentNodes, ...changes.nodes],
-                edges: changes.edges,
+                edges: changes.edges 
+                    ? [...(state.scenario.scenarioGraph.edges || []), ...changes.edges]
+                    : state.scenario.scenarioGraph.edges,
             },
         },
         layout: [...currentLayout, ...changes.layout],
     };

52-76: ⚠️ Potential issue

Update adjustBranchParameters call to use the generated idMapping

The function generates an idMapping but doesn't use it for adjustBranchParameters. Instead, it passes uniqueIds which is used unsafely. Since idMapping is already being created (line 76), it should be used for branch parameter adjustment.

     return {
         nodes: zipWith(newNodes, uniqueIds, (node, id) => ({
             ...node,
             id,
-            branchParameters: adjustBranchParameters(node.branchParameters, uniqueIds),
+            branchParameters: adjustBranchParameters(node.branchParameters, zipObject(initialIds, uniqueIds)),
         })),
         layout: zipWith(newPositions, uniqueIds, (position, id) => ({
             id,
             position,
         })),
         idMapping: zipObject(initialIds, uniqueIds),
     };

Likely invalid or redundant comment.

designer/client/src/reducers/graph/reducer.ts (3)

12-15: LGTM! Good architectural separation

The separation of concerns through new imports (selectionState, batchGroupBy, etc.) improves modularity and maintainability.


303-303: LGTM! Good separation of selection state

Moving selection state to a separate reducer improves code organization and maintainability.


226-229: Verify layout computation before node addition

The simplified node addition logic assumes correct layout computation. Let's verify where the layout is being computed.

✅ Verification successful

Layout computation is correctly handled before node addition

The code shows that layout computation is properly handled:

  1. Layout is computed by prepareNewNodesWithLayout in reducers/graph/utils.ts before dispatching the NODE_ADDED action
  2. The computed layout is passed through the action and correctly used in addNodesWithLayout reducer
  3. This pattern is consistently used in both single node addition and batch node operations

The simplified node addition logic in the reducer is safe as it relies on pre-computed layout from the action creators.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find where node layout is computed before NODE_ADDED action
# Look for layout computation in action creators and utils
rg -t typescript -t javascript "NODE_ADDED.*layout" -B 3 -A 3
ast-grep --pattern 'export function $_($$_): $_ { $$_ layout $$_ NODE_ADDED $$_ }'

Length of output: 183


Script:

#!/bin/bash
# Search for NODE_ADDED action type definition and usage
rg --type ts --type js "NODE_ADDED" -B 3 -A 3

# Search for layout computation related to nodes
rg --type ts --type js "layout.*nodes|nodes.*layout" -B 3 -A 3

# Find functions that might compute layout
ast-grep --pattern 'function $name($params) {
  $$$
  layout
  $$$
}'

Length of output: 133875


Script:

#!/bin/bash
# Search for layout computation in action creators and graph reducer
rg --type ts --type js "addNodesWithLayout|prepareNewNodesWithLayout" -B 3 -A 3

# Search for layout computation in node-related files
rg --type ts --type js "layout.*nodes|nodes.*layout" -B 3 -A 3 designer/client/src/actions/nk/node.ts

Length of output: 6231

Comment on lines +6 to +7
const { scenarioGraph } = state.scenario;
expect(prepareNewNodesWithLayout(scenarioGraph.nodes, nodesWithPositions, true)).toMatchSnapshot();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider expanding test coverage

While snapshot testing is a valid approach, consider adding more specific test cases to verify edge scenarios and explicit expectations. This would make the test more maintainable and easier to debug when failures occur.

Here's a suggested expansion:

describe("GraphUtils", () => {
    describe("prepareNewNodesWithLayout", () => {
        const { scenarioGraph } = state.scenario;
        
        it("should match snapshot for basic node layout", () => {
            expect(prepareNewNodesWithLayout(scenarioGraph.nodes, nodesWithPositions, true)).toMatchSnapshot();
        });

        it("should handle empty current nodes", () => {
            expect(prepareNewNodesWithLayout([], nodesWithPositions, true)).toMatchSnapshot();
        });

        it("should handle empty nodes with positions", () => {
            expect(prepareNewNodesWithLayout(scenarioGraph.nodes, [], true)).toMatchSnapshot();
        });

        it("should verify specific node name updates", () => {
            const result = prepareNewNodesWithLayout(scenarioGraph.nodes, nodesWithPositions, true);
            // Add specific assertions for node name updates
            expect(result.nodes[0].name).toBeDefined();
            expect(result.idMapping).toBeDefined();
        });
    });
});

separate selectionState reducer
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (6)
designer/client/src/actions/nk/node.ts (1)

125-138: LGTM! Good handling of React 18 batching issues.

The changes correctly handle React 18's automatic batching behavior and provide consistent node preparation. The comment explaining the flushSync usage is particularly helpful.

Consider extracting the node preparation logic into a separate function for better reusability:

 return (dispatch, getState) => {
     batchGroupBy.startOrContinue();
+    const prepareNode = (scenarioGraph) => {
+        return prepareNewNodesWithLayout(scenarioGraph.nodes, [{ node, position }], false);
+    };
 
     flushSync(() => {
         const scenarioGraph = getScenarioGraph(getState());
-        const { nodes, layout } = prepareNewNodesWithLayout(scenarioGraph.nodes, [{ node, position }], false);
+        const { nodes, layout } = prepareNode(scenarioGraph);
designer/client/src/reducers/graph/utils.ts (1)

51-100: Consider adding input validation and error handling

The graph manipulation functions could benefit from:

  1. Input validation to ensure required properties exist
  2. Error handling for edge cases (e.g., duplicate IDs, invalid edges)
  3. TypeScript strict null checks for better type safety

Example improvements:

function validateNode(node: NodeType): void {
    if (!node.id) {
        throw new Error("Node must have an ID");
    }
    // Add more validation as needed
}

// Usage in prepareNewNodesWithLayout:
newNodes.forEach(validateNode);
designer/client/src/components/graph/SelectionContextProvider.tsx (2)

Line range hint 156-166: Add TypeScript types to improve maintainability

The position calculation logic is sound, but the function could benefit from explicit TypeScript types for better maintainability and type safety.

Consider this improvement:

-function calculatePastedNodePosition(node, pasteX, minNodeX, pasteY, minNodeY, random) {
+interface NodePosition {
+  x: number;
+  y: number;
+}
+
+interface NodeWithLayout {
+  additionalFields: {
+    layoutData: NodePosition;
+  };
+}
+
+function calculatePastedNodePosition(
+  node: NodeWithLayout,
+  pasteX: number,
+  minNodeX: number,
+  pasteY: number,
+  minNodeY: number,
+  random: number
+): NodePosition {

Line range hint 168-186: Extract magic numbers into named constants

The paste handling logic is well-implemented with debouncing and proper error handling. However, consider extracting magic numbers into named constants for better maintainability.

Consider this improvement:

+const DEBOUNCE_DELAY_MS = 250;
+const MIN_RANDOM_OFFSET = 1;
+const MAX_RANDOM_OFFSET = 20;

 const [parseInsertNodes] = useDebouncedCallback((clipboardText) => {
     const selection = parse(clipboardText);
     if (selection) {
         const { x, y } = props.pastePosition();
         const minNodeX: number = min(selection.nodes.map((node) => node.additionalFields.layoutData.x));
         const minNodeY: number = min(selection.nodes.map((node) => node.additionalFields.layoutData.y));
-        const random = Math.floor(Math.random() * 20) + 1;
+        const random = Math.floor(Math.random() * (MAX_RANDOM_OFFSET - MIN_RANDOM_OFFSET + 1)) + MIN_RANDOM_OFFSET;
         const nodesWithPositions = selection.nodes.map((node) => ({
             node,
             position: calculatePastedNodePosition(node, x, minNodeX, y, minNodeY, random),
         }));
         dispatch(nodesWithEdgesAdded(nodesWithPositions, selection.edges));
     } else {
         dispatch(error(t("userActions.paste.failed", "Cannot paste content from clipboard")));
     }
-}, 250);
+}, DEBOUNCE_DELAY_MS);
designer/client/src/reducers/graph/reducer.ts (1)

303-303: LGTM: Good architectural separation of selection state

Moving selection state to a separate reducer follows the Single Responsibility Principle and improves code organization.

Consider applying similar separation of concerns to other aspects of the graph state in future refactors, such as layout management or validation state.

docs/Changelog.md (1)

112-112: Consider expanding the changelog entry with more details.

The current entry "Fixed minor clipboard, keyboard and focus related bugs" is quite vague. Consider providing more specific details about:

  • What search focus issues were fixed
  • What keyboard/clipboard event bugs were addressed
  • The impact of these fixes on user experience

This would help users and developers better understand the changes and their implications.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f080e3e and 632fd1c.

⛔ Files ignored due to path filters (2)
  • designer/client/src/reducers/graph/__snapshots__/utils.test.ts.snap is excluded by !**/*.snap
  • designer/client/test/__snapshots__/reducer-test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (13)
  • designer/client/src/actions/actionTypes.ts (0 hunks)
  • designer/client/src/actions/nk/node.ts (5 hunks)
  • designer/client/src/common/ClipboardUtils.ts (2 hunks)
  • designer/client/src/components/graph/SelectionContextProvider.tsx (3 hunks)
  • designer/client/src/components/themed/InputWithIcon.tsx (1 hunks)
  • designer/client/src/containers/BindKeyboardShortcuts.tsx (2 hunks)
  • designer/client/src/reducers/graph/reducer.ts (3 hunks)
  • designer/client/src/reducers/graph/selectionState.ts (1 hunks)
  • designer/client/src/reducers/graph/utils.test.ts (1 hunks)
  • designer/client/src/reducers/graph/utils.ts (2 hunks)
  • designer/client/src/reducers/settings.ts (1 hunks)
  • designer/client/test/reducer-test.js (5 hunks)
  • docs/Changelog.md (1 hunks)
💤 Files with no reviewable changes (1)
  • designer/client/src/actions/actionTypes.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • designer/client/src/common/ClipboardUtils.ts
  • designer/client/src/components/themed/InputWithIcon.tsx
  • designer/client/src/containers/BindKeyboardShortcuts.tsx
  • designer/client/src/reducers/graph/selectionState.ts
  • designer/client/src/reducers/settings.ts
  • designer/client/test/reducer-test.js
🔇 Additional comments (11)
designer/client/src/actions/nk/node.ts (4)

1-6: LGTM! Imports are well-organized and support the new functionality.

The new imports, particularly flushSync, are necessary to handle React 18's automatic batching behavior correctly.


147-157: LGTM! Well-structured state management.

The changes follow good practices:

  • State selectors are called at the start
  • Consistent use of prepareNewNodesWithLayout
  • Proper batch grouping maintenance

50-51: Document the new batch capability of NodeAddedAction.

The type now supports batch operations with arrays of nodes and layout. While this provides more flexibility, it's a breaking change that needs documentation.

Consider adding JSDoc comments to document this new batch capability.

✅ Verification successful

Let me check the reducer implementations to understand how the batch operation is handled.


Let me check the implementation of addNodesWithLayout to ensure it properly handles the batch operation.


Add JSDoc comments to document the batch operation capability

The verification confirms that both the reducers (selectionState.ts and reducer.ts) and the utility function addNodesWithLayout properly handle arrays of nodes and layout positions. The code is functionally correct, but lacks documentation about this batch capability.

  • designer/client/src/actions/nk/node.ts: Add JSDoc comments to NodeAddedAction type to document:
    • The batch operation capability
    • The requirement that nodes and layout arrays must have matching indices
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct usage of NodeAddedAction type
# and potential switch statements handling this action

# Look for type usage
rg "NodeAddedAction" --type ts

# Look for reducer cases handling this action type
rg "case\s+['\"]NODE_ADDED['\"]" --type ts

Length of output: 359


Script:

#!/bin/bash
# Check the reducer implementations
rg -A 10 'case "NODE_ADDED"' --type ts

Length of output: 1938


Script:

#!/bin/bash
# Look for addNodesWithLayout implementation
rg -A 15 "function addNodesWithLayout" --type ts

Length of output: 1201


36-38: Verify consumers of NodesWithEdgesAddedAction type.

The type has been restructured to use separate nodes, layout, and idMapping properties instead of nodesWithPositions. While this provides better type safety, it's a breaking change.

✅ Verification successful

Breaking change impact is localized and properly handled

The type restructuring is safely handled in both consumers:

  • selectionState reducer only uses the nodes array to map IDs
  • graph/reducer.ts correctly destructures all new properties (nodes, layout, idMapping) and uses them appropriately

No other direct consumers of this type were found in the codebase, and the existing ones have been updated to handle the new structure correctly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any direct usage of NodesWithEdgesAddedAction type
# and potential switch statements handling this action

# Look for type usage
rg "NodesWithEdgesAddedAction" --type ts

# Look for reducer cases handling this action type
rg "case\s+['\"]NODES_WITH_EDGES_ADDED['\"]" --type ts

Length of output: 425


Script:

#!/bin/bash
# Let's examine the reducer implementations to understand how they handle this action
rg -B 2 -A 5 'case\s+"NODES_WITH_EDGES_ADDED"' --type ts

# Also check for any tests that might reveal usage patterns
rg "NODES_WITH_EDGES_ADDED" --type test

Length of output: 1530


Script:

#!/bin/bash
# Let's check for test files with different extensions
rg "NODES_WITH_EDGES_ADDED" -g "*.test.*" -g "*.spec.*"

# Let's also see more context of the reducer implementation
rg -B 2 -A 15 'case\s+"NODES_WITH_EDGES_ADDED"' designer/client/src/reducers/graph/reducer.ts

Length of output: 1015

designer/client/src/reducers/graph/utils.ts (3)

44-49: ⚠️ Potential issue

Critical: Unsafe branch ID mapping logic

The current implementation using uniqueIds.find((uniqueId) => uniqueId.includes(branchId)) is unsafe as it can lead to incorrect mappings if one ID is a substring of another (e.g., "node1" would match with "node10").

The previous review suggestion to use idMapping dictionary remains valid. Please implement the suggested fix:

-function adjustBranchParameters(branchParameters: BranchParams[], uniqueIds: string[]) {
+function adjustBranchParameters(branchParameters: BranchParams[], idMapping: Dictionary<string>) {
     return branchParameters?.map(({ branchId, ...branchParameter }: BranchParams) => ({
         ...branchParameter,
-        branchId: uniqueIds.find((uniqueId) => uniqueId.includes(branchId)),
+        branchId: idMapping[branchId] || branchId,
     }));
 }

80-100: ⚠️ Potential issue

Prevent potential edge loss during node addition

The current implementation directly assigns changes.edges, which could lead to loss of existing edges when changes.edges is undefined.

The previous review suggestion to merge edges remains valid. Please implement the suggested fix:

 const currentNodes = state.scenario.scenarioGraph.nodes || [];
+const currentEdges = state.scenario.scenarioGraph.edges || [];
 const currentLayout = state.layout;
 return {
     ...state,
     scenario: {
         ...state.scenario,
         scenarioGraph: {
             ...state.scenario.scenarioGraph,
             nodes: [...currentNodes, ...changes.nodes],
-            edges: changes.edges,
+            edges: changes.edges ? [...currentEdges, ...changes.edges] : currentEdges,
         },
     },
     layout: [...currentLayout, ...changes.layout],
 };

51-77: Good improvement in function signature, but needs follow-up

The change to accept currentNodes instead of full state reduces coupling and makes the function more focused. The addition of idMapping in the return type is also beneficial for tracking ID transformations.

However, once the adjustBranchParameters function is updated to use idMapping, this call needs to be updated. Let's verify the current usage:

✅ Verification successful

Function implementation is correct, no changes needed

The adjustBranchParameters function is already correctly using the uniqueIds parameter to map branch IDs, and the current call in prepareNewNodesWithLayout passes the correct arguments. The implementation shows that idMapping is not needed as the function uses uniqueIds to find corresponding new IDs for branches.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all calls to adjustBranchParameters
rg "adjustBranchParameters\(" --type ts

Length of output: 306


Script:

#!/bin/bash
# Let's check the implementation of adjustBranchParameters
ast-grep --pattern 'function adjustBranchParameters($$$) {
  $$$
}'

Length of output: 644

designer/client/src/components/graph/SelectionContextProvider.tsx (2)

1-1: LGTM: Import additions are appropriate

The new imports for min from lodash and useInterval are correctly added and necessary for the new paste position calculation and clipboard monitoring functionality.

Also applies to: 31-31


Line range hint 77-82: Consider using Clipboard API events instead of polling

This comment was previously raised and is still valid. The polling approach could impact performance and battery life.

designer/client/src/reducers/graph/reducer.ts (2)

226-229: LGTM: Clean refactor of node addition logic

The changes simplify the node addition by using the addNodesWithLayout utility function, improving code maintainability.


232-252: Skipping duplicate comments about edge handling

The previous review comments about edge ID mapping and null checks are still valid and applicable to this code segment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
designer/client/src/reducers/graph/utils.ts (2)

52-59: Add input validation for defensive programming.

The function should validate its inputs to handle edge cases gracefully.

Consider adding these checks:

 export function prepareNewNodesWithLayout(
     currentNodes: NodeType[] = [],
     newNodesWithPositions: NodesWithPositions,
     isCopy: boolean,
 ): {
     layout: NodePosition[];
     nodes: NodeType[];
     idMapping: Dictionary<string>;
 } {
+    if (!newNodesWithPositions?.length) {
+        return { nodes: [], layout: [], idMapping: {} };
+    }

80-87: Add type guards for the changes parameter.

The function should validate the structure of the input object to prevent runtime errors.

Consider adding these checks:

 export function addNodesWithLayout(
     state: GraphState,
     changes: {
         nodes: NodeType[];
         layout: NodePosition[];
         edges?: Edge[];
     },
 ): GraphState {
+    if (!changes?.nodes || !changes?.layout) {
+        throw new Error("Invalid changes object: nodes and layout are required");
+    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 632fd1c and 492e3b4.

📒 Files selected for processing (1)
  • designer/client/src/reducers/graph/utils.ts (2 hunks)
🔇 Additional comments (1)
designer/client/src/reducers/graph/utils.ts (1)

1-1: Overall structure looks good!

The file maintains good organization, immutability, and type safety. The utility functions are well-designed and follow functional programming principles.

@JulianWielga JulianWielga merged commit 00cdd3d into release/1.18 Nov 25, 2024
13 checks passed
@JulianWielga JulianWielga deleted the keyboard-events branch November 25, 2024 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
client client main fe docs ui
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants