Skip to content

Fix tree cross-tab navigation : sync tree model globally#3831

Open
ayolab wants to merge 2 commits intomainfrom
ayolab/fix-sync-tree-model-globally-to-fix-cross-tab-navigation
Open

Fix tree cross-tab navigation : sync tree model globally#3831
ayolab wants to merge 2 commits intomainfrom
ayolab/fix-sync-tree-model-globally-to-fix-cross-tab-navigation

Conversation

@ayolab
Copy link
Contributor

@ayolab ayolab commented Mar 24, 2026

No description provided.

Signed-off-by: Ayoub LABIDI <ayoub.labidi@protonmail.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

Centralizes tree-model event routing into a new exported handler (handleTreeModelUpdate) that interprets incoming StudyUpdateEventData and performs appropriate bulk/single fetches and Redux dispatches; component-level code was simplified to local UI cleanup only, while root-network selection validation was tightened in navigation sync.

Changes

Cohort / File(s) Summary
Centralized Tree Update Handler
src/components/network-modification-tree-pane-event-handlers.ts
Added exported handleTreeModelUpdate(dispatch, studyUuid, rootNetworkUuid, eventData). Added fetchAndDispatchUpdatedNodes() using Promise.allSettled to fetch multiple nodes and dispatch networkModificationTreeNodesUpdated in bulk. Imported StudyUpdateEventData, specific event payload types, NotificationType, and new Redux actions (networkModificationTreeNodesUpdated, networkModificationTreeNodesRemoved, reorderNetworkModificationTreeNodes). Routes update types (build-status, create/move, subtree ops, column-position, deletions, bulk updates, single edits) via a switch.
Component Tree Pane Simplification
src/components/network-modification-tree-pane.jsx
Removed in-component node/subtree fetch-and-dispatch logic and related helpers/imports (updateNodes, fetchAndDispatchAddedNode, fetchAndDispatchMovedNode, fetchAndHandleSubtree, reorderSubtree, fetchNetworkModificationTreeNode). Retained clipboard invalidation/local cleanup for creation/move/delete events. Updated membership checks from .some() to .includes() and reduced useCallback dependencies.
Study Container Delegation
src/components/study-container.jsx
Removed inline websocket handling that fetched nodes on NODE_BUILD_STATUS_UPDATED and dispatched updates. Replaced prior tree-sync logic with a unified call to handleTreeModelUpdate(dispatch, studyUuid, currentRootNetworkUuidRef.current, eventData) for non-STUDY_ALERT events; added early return when no current root network is set. Simplified STUDY_ALERT check.
Study Navigation Validation
src/hooks/use-study-navigation-sync.ts
Changed updateRootNetworkUuid to validate the provided UUID exists in Redux rootNetworks (matching rn.rootNetworkUuid === uuid) before dispatching setCurrentRootNetworkUuid. Added rootNetworks to dependencies so the callback updates when root networks change.
Tree Model Reorder Safety
src/components/graph/network-modification-tree-model.ts
reorderChildrenNodes(parentNodeId, orderedNodeIds) now verifies orderedNodeIds is the exact complete set of current children (same length and membership) before attempting reorder; logs and returns false if validation fails.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant EventSource as StudyUpdateEventSource
participant Container as StudyContainer
participant Handler as TreeModelEventHandler
participant Fetch as FetchAPI
participant Store as ReduxStore

EventSource->>Container: emit StudyUpdateEventData
Container->>Handler: handleTreeModelUpdate(dispatch, studyUuid, rootNetworkUuid, eventData)
alt updateType is NODE_BUILD_STATUS_UPDATED or NODES_UPDATED
    Handler->>Fetch: fetchNetworkModificationTreeNode(nodeId) [parallel: Promise.allSettled]
    Fetch-->>Handler: nodeData (fulfilled/rejected)
    Handler->>Store: dispatch(networkModificationTreeNodesUpdated(nodes[]))
else updateType is NODES_DELETED
    Handler->>Store: dispatch(networkModificationTreeNodesRemoved(nodeIds))
else updateType is COLUMN_POSITION_CHANGED
    Handler->>Store: dispatch(reorderNetworkModificationTreeNodes(parentId, orderedIds))
end
Store-->>Container: state updates (subscribers notified)
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No pull request description was provided by the author. Add a pull request description explaining the motivation, approach, and impact of the changes for better context during code review.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: refactoring tree model synchronization to be global rather than localized, which directly addresses the cross-tab navigation issue described in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/network-modification-tree-pane-event-handlers.ts`:
- Around line 121-148: The switch handles notifications for all studies but only
NODE_BUILD_STATUS_UPDATED checks headers.rootNetworkUuid, so other branches
(NODE_CREATED, SUBTREE_CREATED, NODE_MOVED, SUBTREE_MOVED, NODES_DELETED,
NODES_UPDATED, NODE_EDITED) can act on the wrong tree; add a single guard using
eventData.headers.rootNetworkUuid and rootNetworkUuid at the top of the handler
(before the switch) or immediately inside the switch for every root-scoped case
to return/skip when they differ, ensuring functions like
fetchAndDispatchAddedNode, fetchAndHandleSubtree, fetchAndDispatchMovedNode,
fetchAndDispatchUpdatedNodes and fetchNetworkModificationTreeNode only run for
matching rootNetworkUuid.
- Around line 115-152: Add a switch branch for
NotificationType.NODES_COLUMN_POSITION_CHANGED inside handleTreeModelUpdate so
subtree reorder events update the shared Redux tree model even when the pane
component isn't mounted; mirror the existing checks (verify
eventData.headers.rootNetworkUuid === rootNetworkUuid if applicable) and
dispatch the same Redux action used by
src/components/network-modification-tree-pane.jsx for
NODES_COLUMN_POSITION_CHANGED (pass eventData.headers.nodes/parent info exactly
as that pane does) so the global tree order is updated consistently across tabs.
- Around line 104-113: The current fetchAndDispatchUpdatedNodes uses Promise.all
which fails the whole batch if any fetchNetworkModificationTreeNode rejects;
change it to aggregate results with Promise.allSettled (or individually catch
each fetch) so that successful node fetches are collected and passed to
dispatch(networkModificationTreeNodesUpdated(...)) while failed fetches are
logged/ignored; update fetchAndDispatchUpdatedNodes to map nodeIds to settled
results, filter for status === "fulfilled", extract values, dispatch only those
values, and optionally log the rejected reasons including the nodeId.

In `@src/components/study-container.jsx`:
- Around line 265-279: handleStudyUpdate can forward undefined into
handleTreeModelUpdate because useStudy sets studyUuid before fetchRootNetworks
resolves; modify handleStudyUpdate so it only calls handleTreeModelUpdate when
currentRootNetworkUuidRef.current is set (truthy). In the handler (function
handleStudyUpdate) add a guard around the handleTreeModelUpdate call (checking
currentRootNetworkUuidRef.current) and skip or buffer tree sync when it's
undefined, keeping the existing alert and error handling paths intact; this
prevents openStudy/notifications from forwarding undefined into the tree-update
helpers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f2079f08-6758-4b5e-9596-69548bb04237

📥 Commits

Reviewing files that changed from the base of the PR and between a40f0f4 and d5b327e.

📒 Files selected for processing (4)
  • src/components/network-modification-tree-pane-event-handlers.ts
  • src/components/network-modification-tree-pane.jsx
  • src/components/study-container.jsx
  • src/hooks/use-study-navigation-sync.ts

Comment on lines +121 to +148
switch (eventData.headers.updateType) {
case NotificationType.NODE_BUILD_STATUS_UPDATED:
if (eventData.headers.rootNetworkUuid !== rootNetworkUuid) break;
fetchAndDispatchUpdatedNodes(dispatch, studyUuid, rootNetworkUuid, eventData.headers.nodes);
break;
case NotificationType.NODE_CREATED:
fetchAndDispatchAddedNode(dispatch, studyUuid, rootNetworkUuid, eventData as NodeCreatedEventData);
break;
case NotificationType.SUBTREE_CREATED:
fetchAndHandleSubtree(dispatch, studyUuid, eventData.headers.newNode, eventData.headers.parentNode);
break;
case NotificationType.NODE_MOVED:
fetchAndDispatchMovedNode(dispatch, studyUuid, rootNetworkUuid, eventData as NodeMovedEventData);
break;
case NotificationType.SUBTREE_MOVED:
fetchAndHandleSubtree(dispatch, studyUuid, eventData.headers.movedNode, eventData.headers.parentNode);
break;
case NotificationType.NODES_DELETED:
dispatch(networkModificationTreeNodesRemoved(eventData.headers.nodes));
break;
case NotificationType.NODES_UPDATED:
fetchAndDispatchUpdatedNodes(dispatch, studyUuid, rootNetworkUuid, eventData.headers.nodes);
break;
case NotificationType.NODE_EDITED:
fetchNetworkModificationTreeNode(studyUuid, eventData.headers.node, rootNetworkUuid).then((node) =>
dispatch(networkModificationTreeNodesUpdated([node]))
);
break;
Copy link

@coderabbitai coderabbitai bot Mar 24, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Filter root-scoped events before mutating the active tree.

The store only holds the tree for the current root network, but this centralized handler now runs for every study notification and only the build-status branch checks headers.rootNetworkUuid. A create/move/update/edit event from another root can now fetch or remove nodes against the wrong model.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/network-modification-tree-pane-event-handlers.ts` around lines
121 - 148, The switch handles notifications for all studies but only
NODE_BUILD_STATUS_UPDATED checks headers.rootNetworkUuid, so other branches
(NODE_CREATED, SUBTREE_CREATED, NODE_MOVED, SUBTREE_MOVED, NODES_DELETED,
NODES_UPDATED, NODE_EDITED) can act on the wrong tree; add a single guard using
eventData.headers.rootNetworkUuid and rootNetworkUuid at the top of the handler
(before the switch) or immediately inside the switch for every root-scoped case
to return/skip when they differ, ensuring functions like
fetchAndDispatchAddedNode, fetchAndHandleSubtree, fetchAndDispatchMovedNode,
fetchAndDispatchUpdatedNodes and fetchNetworkModificationTreeNode only run for
matching rootNetworkUuid.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The tree is shared across all root networks by design these events correctly apply globally. Only NODE_BUILD_STATUS_UPDATED is per-root-network-scoped, and it is already filtered

Copy link

Choose a reason for hiding this comment

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

@ayolab, thank you for the clarification! That makes sense — since the tree model is shared across all root networks, node creation, movement, deletion, and update events correctly apply globally without needing a rootNetworkUuid guard. The existing filter on NODE_BUILD_STATUS_UPDATED is the only root-scoped check needed. I was mistaken in flagging this.


✏️ Learnings added
Learnt from: ayolab
Repo: gridsuite/gridstudy-app PR: 3831
File: src/components/network-modification-tree-pane-event-handlers.ts:121-148
Timestamp: 2026-03-24T11:06:27.690Z
Learning: In gridsuite/gridstudy-app, the network modification tree model (Redux store) is shared across all root networks by design. Tree structure events (NODE_CREATED, SUBTREE_CREATED, NODE_MOVED, SUBTREE_MOVED, NODES_DELETED, NODES_UPDATED, NODE_EDITED) apply globally and do not need to be filtered by rootNetworkUuid. Only NODE_BUILD_STATUS_UPDATED is per-root-network-scoped and must be filtered by rootNetworkUuid. This is relevant to `handleTreeModelUpdate` in `src/components/network-modification-tree-pane-event-handlers.ts`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Signed-off-by: Ayoub LABIDI <ayoub.labidi@protonmail.com>
@sonarqubecloud
Copy link

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/network-modification-tree-pane-event-handlers.ts`:
- Around line 149-153: The case handling
NotificationType.NODES_COLUMN_POSITION_CHANGED currently calls
JSON.parse(payload) directly which can throw; wrap the parse in a try-catch
inside that case (the switch branch in
network-modification-tree-pane-event-handlers.ts) so that you catch
SyntaxError/other exceptions from JSON.parse(payload), log or warn the parsing
error and safely skip or provide a sane fallback (e.g. empty array) before
calling dispatch(reorderNetworkModificationTreeNodes(headers.parentNode, ...)),
ensuring dispatch is only called with a valid parsed value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fa0d9f1d-5ea6-4d88-91c7-e3fbca88778a

📥 Commits

Reviewing files that changed from the base of the PR and between d5b327e and 072e381.

📒 Files selected for processing (4)
  • src/components/graph/network-modification-tree-model.ts
  • src/components/network-modification-tree-pane-event-handlers.ts
  • src/components/network-modification-tree-pane.jsx
  • src/components/study-container.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/study-container.jsx

Comment on lines +149 to +153
case NotificationType.NODES_COLUMN_POSITION_CHANGED: {
const { headers, payload } = eventData as NodesColumnPositionsChangedEventData;
dispatch(reorderNetworkModificationTreeNodes(headers.parentNode, JSON.parse(payload)));
break;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Wrap JSON.parse(payload) in try-catch to avoid unhandled exceptions.

If payload is undefined, empty, or malformed JSON, JSON.parse() throws a SyntaxError, which will propagate unhandled and potentially break subsequent event processing.

🛡️ Proposed defensive fix
         case NotificationType.NODES_COLUMN_POSITION_CHANGED: {
             const { headers, payload } = eventData as NodesColumnPositionsChangedEventData;
-            dispatch(reorderNetworkModificationTreeNodes(headers.parentNode, JSON.parse(payload)));
+            try {
+                dispatch(reorderNetworkModificationTreeNodes(headers.parentNode, JSON.parse(payload)));
+            } catch (e) {
+                console.warn('NODES_COLUMN_POSITION_CHANGED: failed to parse payload', e);
+            }
             break;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case NotificationType.NODES_COLUMN_POSITION_CHANGED: {
const { headers, payload } = eventData as NodesColumnPositionsChangedEventData;
dispatch(reorderNetworkModificationTreeNodes(headers.parentNode, JSON.parse(payload)));
break;
}
case NotificationType.NODES_COLUMN_POSITION_CHANGED: {
const { headers, payload } = eventData as NodesColumnPositionsChangedEventData;
try {
dispatch(reorderNetworkModificationTreeNodes(headers.parentNode, JSON.parse(payload)));
} catch (e) {
console.warn('NODES_COLUMN_POSITION_CHANGED: failed to parse payload', e);
}
break;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/network-modification-tree-pane-event-handlers.ts` around lines
149 - 153, The case handling NotificationType.NODES_COLUMN_POSITION_CHANGED
currently calls JSON.parse(payload) directly which can throw; wrap the parse in
a try-catch inside that case (the switch branch in
network-modification-tree-pane-event-handlers.ts) so that you catch
SyntaxError/other exceptions from JSON.parse(payload), log or warn the parsing
error and safely skip or provide a sane fallback (e.g. empty array) before
calling dispatch(reorderNetworkModificationTreeNodes(headers.parentNode, ...)),
ensuring dispatch is only called with a valid parsed value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant