Skip to content

Handle async network modification application results#994

Open
ayolab wants to merge 4 commits into
mainfrom
ayolab/handle-async-midification-application-results
Open

Handle async network modification application results#994
ayolab wants to merge 4 commits into
mainfrom
ayolab/handle-async-midification-application-results

Conversation

@ayolab

@ayolab ayolab commented May 17, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ccde3891-fd4f-46df-ae62-a30fde5ad8de

📥 Commits

Reviewing files that changed from the base of the PR and between 2f2e3d8 and 67588b5.

📒 Files selected for processing (14)
  • src/main/java/org/gridsuite/study/server/controller/StudyController.java
  • src/main/java/org/gridsuite/study/server/dto/modification/ModificationReceiver.java
  • src/main/java/org/gridsuite/study/server/service/ConsumerService.java
  • src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java
  • src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java
  • src/main/java/org/gridsuite/study/server/service/StudyService.java
  • src/main/resources/config/application.yaml
  • src/test/java/org/gridsuite/study/server/NetworkModificationTest.java
  • src/test/java/org/gridsuite/study/server/VoltageInitTest.java
  • src/test/java/org/gridsuite/study/server/WorkflowTest.java
  • src/test/java/org/gridsuite/study/server/rootnetworks/ModificationToExcludeTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java
  • src/test/java/org/gridsuite/study/server/utils/TestUtils.java
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/main/java/org/gridsuite/study/server/controller/StudyController.java
  • src/main/java/org/gridsuite/study/server/dto/modification/ModificationReceiver.java
  • src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java
  • src/main/java/org/gridsuite/study/server/service/ConsumerService.java
  • src/test/java/org/gridsuite/study/server/utils/TestUtils.java
  • src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java
  • src/test/java/org/gridsuite/study/server/rootnetworks/ModificationToExcludeTest.java
  • src/test/java/org/gridsuite/study/server/VoltageInitTest.java
  • src/test/java/org/gridsuite/study/server/NetworkModificationTest.java

📝 Walkthrough

Walkthrough

This PR refactors network modification workflows from synchronous result handling to asynchronous message-driven completion. The changes introduce ModificationReceiver as a message envelope, update NetworkModificationService method signatures to accept a receiver parameter and return UUID lists, and refactor StudyService to handle notifications through message-driven callbacks instead of iterating results directly. Unblocking behavior is changed to exception-only in several code paths, and test infrastructure is extended to simulate async result delivery via Spring Cloud Stream.

Changes

Asynchronous modification result messaging

Layer / File(s) Summary
ModificationReceiver DTO contract
src/main/java/org/gridsuite/study/server/dto/modification/ModificationReceiver.java
New public record encapsulates study UUID, target node UUID, origin node UUID, and root network UUIDs required to route modification results and emit impact notifications.
Message consumer beans and RabbitMQ configuration
src/main/java/org/gridsuite/study/server/service/ConsumerService.java, src/main/resources/config/application.yaml
ConsumerService defines consumeModificationApplicationResult and consumeModificationApplicationFailed beans that deserialize ModificationReceiver from URL-encoded message headers and invoke StudyService callbacks. Configuration establishes Spring Cloud Stream bindings to modification.result and modification.run.dlx destinations with consumer groups studyModificationResultGroup and dlq.
NetworkModificationService API modernization
src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java
Five modification methods now accept optional ModificationReceiver parameter and return List<UUID> instead of NetworkModificationsResult. URL-encoded receiver is appended as QUERY_PARAM_RECEIVER query parameter when provided. New buildModificationReceiver helper encodes receiver payload for transmission.
StudyService async callback refactoring
src/main/java/org/gridsuite/study/server/service/StudyService.java
Modification flows (create, move, duplicate, insertComposite, insertVoltageInit) construct ModificationReceiver and call updated NetworkModificationService methods. New public methods handleModificationApplicationResult and handleApplicationFailed receive asynchronous callbacks from message consumers and centralize impact notification emission and end-modification notifications. Synchronous NetworkModificationsResult iteration removed; exclusion mapping updated to derive from UUID lists.
Unblocking control-flow changes
src/main/java/org/gridsuite/study/server/controller/StudyController.java, src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java
Several finally-based unconditional node-tree unblocking blocks replaced with exception-only catch handlers that unblock and rethrow, so node-tree unblocking is performed only on exceptions rather than on all code paths.
Test infrastructure: sendApplicationResult helpers
src/test/java/org/gridsuite/study/server/utils/TestUtils.java
New sendApplicationResult overload family serializes ModificationReceiver from test UUIDs, URL-encodes the JSON, and sends NetworkModificationsResult to Spring Cloud Stream test input with HEADER_RECEIVER header set, enabling tests to simulate asynchronous result delivery without relying on WireMock response shapes.
NetworkModificationTest updates
src/test/java/org/gridsuite/study/server/NetworkModificationTest.java
Test cases for modification creation, move, duplicate, and composite operations stub POST/PUT with List<UUID> payloads. Explicit sendApplicationResult(...) calls injected after equipment-creation assertions simulate async result arrival. Group PUT request-body verifications updated to match new ModificationApplicationContext structure.
VoltageInitTest updates
src/test/java/org/gridsuite/study/server/VoltageInitTest.java
HTTP response mocks simplified to return UUID lists for duplication/insert operations. Duplicate mock stubs use different overload signatures returning empty or UUID lists. Assertion ordering adjusted to send explicit application results before equipment-impact and build-status checks.
ModificationToExcludeTest updates
src/test/java/org/gridsuite/study/server/rootnetworks/ModificationToExcludeTest.java
Mockito stubs simplified to return UUID lists directly for duplication and move operations. Argument matchers relaxed for updated method signatures. Exclusion-mapping logic expectations derive from saved UUID lists instead of result wrapper objects.
NodeControllerTest updates
src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java
Cut-paste, duplicate, and cross-study node controller tests update network-modification POST stubs to use List<UUID> payloads. Explicit sendApplicationResult(...) calls inserted between equipment-creating and equipment-updating-finished assertions, synchronizing message verification flow with asynchronous result callback delivery.
StudyTest updates
src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java
Duplicate-study flow test updates modification POST stubs to return List<UUID> payloads and adds explicit sendApplicationResult(...) calls after equipment-creation assertions for both transformer and load modifications.
WorkflowTest: message consumer unit tests
src/test/java/org/gridsuite/study/server/WorkflowTest.java
Two new JUnit test cases validate consumeModificationApplicationResult and consumeModificationApplicationFailed consumer bean behavior by constructing NetworkModificationsResult and ModificationReceiver, setting HEADER_RECEIVER, and verifying StudyService callback invocation.

Suggested reviewers

  • SlimaneAmar
  • etiennehomer
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.08% 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, making it impossible to assess whether a description exists or its relevance to the changeset. Please provide a pull request description explaining the purpose, context, and key changes of this async modification application result handling implementation.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: introducing async handling of network modification application results through new consumer beans and callback mechanisms.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.43.0)
src/main/java/org/gridsuite/study/server/controller/StudyController.java
src/main/java/org/gridsuite/study/server/service/StudyService.java
src/test/java/org/gridsuite/study/server/NetworkModificationTest.java

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
src/test/java/org/gridsuite/study/server/NetworkModificationTest.java (1)

695-703: 🏗️ Heavy lift

Extract the repeated async-modification assertion flow into a helper.

The same sequence is duplicated throughout this file (request → in-progress assertions → sendApplicationResult(...) → finished assertions). Centralizing it would reduce brittleness when message ordering/contracts evolve.

♻️ Helper sketch
+private void assertAsyncModificationFlow(
+        UUID studyUuid,
+        UUID nodeUuid,
+        Runnable triggerRequest,
+        Runnable preResultAssertions,
+        Runnable postResultAssertions) {
+    triggerRequest.run();
+    checkUpdateStatusMessagesReceived(studyUuid, nodeUuid, output);
+    checkEquipmentCreatingMessagesReceived(studyUuid, nodeUuid);
+    preResultAssertions.run();
+    studyTestUtils.sendApplicationResult(studyUuid, nodeUuid);
+    checkEquipmentUpdatingFinishedMessagesReceived(studyUuid, nodeUuid);
+    postResultAssertions.run();
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/org/gridsuite/study/server/NetworkModificationTest.java` around
lines 695 - 703, Extract the repeated async-modification flow into a single
helper method (e.g., assertAsyncModificationFlow or
performAsyncModificationAndAssert) that encapsulates the POST call and the four
assertions: mockMvc.perform(post(URI_NETWORK_MODIF, studyNameUserIdUuid,
modificationNode1Uuid).content(bodyJson).contentType(MediaType.APPLICATION_JSON).header(USER_ID_HEADER,
userId)).andExpect(status().isOk()),
checkUpdateStatusMessagesReceived(studyNameUserIdUuid, modificationNode1Uuid,
output), checkEquipmentCreatingMessagesReceived(studyNameUserIdUuid,
modificationNode1Uuid),
studyTestUtils.sendApplicationResult(studyNameUserIdUuid,
modificationNode1Uuid), and
checkEquipmentUpdatingFinishedMessagesReceived(studyNameUserIdUuid,
modificationNode1Uuid); make the helper accept the needed parameters
(studyNameUserIdUuid, modificationNode1Uuid, bodyJson, userId, output) and
replace all duplicated sequences in NetworkModificationTest with calls to this
new helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/org/gridsuite/study/server/service/ConsumerService.java`:
- Around line 223-229: The catch blocks in consumeApplicationResult and
consumeApplicationFailed currently swallow all exceptions by logging only
e.toString(), which prevents retries/DLQ; update those methods (and other
consumer handlers named consumeQueuedCommand, consumeSessionStart,
consumeSessionEnd, consumeApplicationResult, consumeApplicationFailed, etc.) to
rethrow the exception after logging (or wrap and throw a RuntimeException)
instead of returning normally—locate the try/catch around objectMapper.readValue
and studyService.handleApplicationResult/handleApplicationFailed and replace the
silent LOGGER.error(e.toString()) with LOGGER.error(..., e) followed by throw e;
or if checked, throw new RuntimeException("Error processing consumer message",
e) so the framework can trigger retries/DLQ.

In `@src/main/java/org/gridsuite/study/server/service/StudyService.java`:
- Around line 2433-2441: The loop in handleApplicationResult assumes
modificationResults() and receiver.rootNetworkUuids() are the same size and may
skip emitApplicationEnd if an index error or emitNetworkModificationImpacts
throws; validate sizes first (compute minSize =
Math.min(modificationResults.size(), rootNetworkUuids.size())) and iterate only
to minSize, and wrap the per-item call to emitNetworkModificationImpacts in a
try/catch so one failure doesn’t abort the loop; move the
emitApplicationEnd(receiver) call into a finally block so it always runs
regardless of exceptions, referencing the existing methods modificationResults,
rootNetworkUuids, emitNetworkModificationImpacts, and emitApplicationEnd.
- Around line 3385-3387: The finally block currently calls
networkModificationTreeService.unblockNodeTree(rootNetworkUuid, nodeUuid)
immediately, but duplicateModificationsFromGroup(...) is now asynchronous so
unblocking there can race with the remote apply; remove the unblock from the
finally and instead invoke
networkModificationTreeService.unblockNodeTree(rootNetworkUuid, nodeUuid) inside
the async duplicateModificationsFromGroup(...) completion handlers (both success
and failure paths) so the tree is only unblocked after the async apply finishes;
locate the callsite invoking duplicateModificationsFromGroup(...) and move the
unblock logic into its async callback/then/exception handling blocks.
- Around line 2501-2515: In copyModificationsToExclude, before doing positional
mapping into mappingModificationsUuids, validate that savedUuids.size() ==
modificationsUuids.size() and copyChildren.size() == originalChildren.size(); if
either size differs, fail fast (throw an IllegalStateException or a
domain-specific exception) with a clear message identifying the mismatched
lists; then proceed to build the maps using the verified lists (references:
method copyModificationsToExclude, variables mappingModificationsUuids,
modificationsUuids, savedUuids, originalChildren, copyChildren, and calls to
networkModificationService.findAllChildrenUuids).

In `@src/test/java/org/gridsuite/study/server/NetworkModificationTest.java`:
- Around line 2228-2229: The test sends two modification UUIDs via copyUuids2
but only supplies one result entry, so update the call to
studyTestUtils.sendApplicationResult to provide a result element for each UUID
(e.g., pass a List with two Optional<NetworkModificationResult> entries) so
NetworkModificationsResult aligns with copyUuids2; adjust the List.of(...)
argument currently wrapping
Optional.of(NetworkModificationResult.builder().build()) to include a second
Optional (or an explicit Optional.empty() if intended) so
checkEquipmentMessagesReceived receives payloads for both duplicated
modification UUIDs.

In `@src/test/java/org/gridsuite/study/server/utils/TestUtils.java`:
- Around line 114-117: The test helper currently injects InputDestination
optionally which leads to a late NPE in sendApplicationResult(...) when the test
binder is missing; make the dependency required so startup fails fast (change
the setter injection for InputDestination in setInput(InputDestination) to
`@Autowired` with required=true or drop required=false) or alternatively add a
`@PostConstruct` check that throws an IllegalStateException with a clear message
referencing sendApplicationResult when input is null; apply the same pattern to
the analogous setter/field referenced around lines 139-145 so both input/output
test-binder dependencies fail fast with a clear setup error.

---

Nitpick comments:
In `@src/test/java/org/gridsuite/study/server/NetworkModificationTest.java`:
- Around line 695-703: Extract the repeated async-modification flow into a
single helper method (e.g., assertAsyncModificationFlow or
performAsyncModificationAndAssert) that encapsulates the POST call and the four
assertions: mockMvc.perform(post(URI_NETWORK_MODIF, studyNameUserIdUuid,
modificationNode1Uuid).content(bodyJson).contentType(MediaType.APPLICATION_JSON).header(USER_ID_HEADER,
userId)).andExpect(status().isOk()),
checkUpdateStatusMessagesReceived(studyNameUserIdUuid, modificationNode1Uuid,
output), checkEquipmentCreatingMessagesReceived(studyNameUserIdUuid,
modificationNode1Uuid),
studyTestUtils.sendApplicationResult(studyNameUserIdUuid,
modificationNode1Uuid), and
checkEquipmentUpdatingFinishedMessagesReceived(studyNameUserIdUuid,
modificationNode1Uuid); make the helper accept the needed parameters
(studyNameUserIdUuid, modificationNode1Uuid, bodyJson, userId, output) and
replace all duplicated sequences in NetworkModificationTest with calls to this
new helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 077a9acf-f37f-45ca-8939-fa8e20c0e760

📥 Commits

Reviewing files that changed from the base of the PR and between f495cde and 5ddd50d.

📒 Files selected for processing (11)
  • src/main/java/org/gridsuite/study/server/dto/modification/ModificationReceiver.java
  • src/main/java/org/gridsuite/study/server/service/ConsumerService.java
  • src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java
  • src/main/java/org/gridsuite/study/server/service/StudyService.java
  • src/main/resources/config/application.yaml
  • src/test/java/org/gridsuite/study/server/NetworkModificationTest.java
  • src/test/java/org/gridsuite/study/server/VoltageInitTest.java
  • src/test/java/org/gridsuite/study/server/rootnetworks/ModificationToExcludeTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java
  • src/test/java/org/gridsuite/study/server/utils/TestUtils.java

Comment thread src/main/java/org/gridsuite/study/server/service/StudyService.java Outdated
Comment thread src/main/java/org/gridsuite/study/server/service/StudyService.java
Comment thread src/main/java/org/gridsuite/study/server/service/StudyService.java Outdated
Comment thread src/test/java/org/gridsuite/study/server/utils/TestUtils.java
@ayolab ayolab force-pushed the ayolab/handle-async-midification-application-results branch from 5ddd50d to 2f2e3d8 Compare May 17, 2026 18:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java (1)

30-32: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Defer rebuilds until async application completion.

These entry points still run through handleRebuildNode(...), which rebuilds previously built root networks immediately after studyService.createNetworkModification(...) / studyService.moveNetworkModifications(...) returns. After this refactor those calls only submit async application work, so the rebuild can now run before the modification result callback has applied the change and unblocked the tree. Please trigger the rebuild from the async completion path instead of keeping these operations under the synchronous handleRebuildNode(...) flow.

Also applies to: 37-42, 84-86, 123-131

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java`
around lines 30 - 32, The current createNetworkModification (and the other
similar entry points that call handleRebuildNode) invokes handleRebuildNode
synchronously around the call to handleCreateNetworkModification, which causes
rebuilds to run before the async application of the modification completes;
instead remove the synchronous handleRebuildNode wrapper and invoke
handleRebuildNode (or the rebuild logic) from the async
completion/callback/future returned by the studyService call (the path that
actually applies the modification result), so change createNetworkModification,
moveNetworkModifications and the other listed methods to only submit the async
work (call handleCreateNetworkModification / handleMoveNetworkModifications) and
attach a continuation that calls handleRebuildNode(studyUuid, nodeUuid, userId)
after the studyService.createNetworkModification /
studyService.moveNetworkModifications completion promise/callback indicates the
change is applied.
🧹 Nitpick comments (1)
src/main/java/org/gridsuite/study/server/service/StudyService.java (1)

2445-2457: 💤 Low value

Past review concern partially addressed: finally block now ensures cleanup.

The emitApplicationEnd(receiver) is now in a finally block, so the node will always be unblocked even if emitNetworkModificationImpacts throws. However, if modificationResults.size() > rootNetworkUuids.size(), an IndexOutOfBoundsException would occur at line 2452. While cleanup will still happen, adding a size check would provide clearer error handling.

🛡️ Optional defensive size validation
 public void handleApplicationResult(ModificationReceiver receiver, NetworkModificationsResult result) {
     try {
         List<Optional<NetworkModificationResult>> modificationResults = result.modificationResults();
         List<UUID> rootNetworkUuids = receiver.rootNetworkUuids();
+        if (modificationResults.size() != rootNetworkUuids.size()) {
+            LOGGER.warn("Mismatch between modification results ({}) and root networks ({})",
+                modificationResults.size(), rootNetworkUuids.size());
+        }
+        int limit = Math.min(modificationResults.size(), rootNetworkUuids.size());
-        for (int i = 0; i < modificationResults.size(); i++) {
+        for (int i = 0; i < limit; i++) {
             final int index = i;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/gridsuite/study/server/service/StudyService.java` around
lines 2445 - 2457, In handleApplicationResult, guard against a possible
IndexOutOfBoundsException when modificationResults.size() >
rootNetworkUuids.size() by validating sizes before accessing rootNetworkUuids:
compute the smaller of result.modificationResults().size() and
receiver.rootNetworkUuids().size() (or explicitly check and log/throw a
descriptive IllegalStateException) and iterate up to that bound when calling
emitNetworkModificationImpacts(receiver.studyUuid(), receiver.nodeUuid(),
rootNetworkUuids.get(index), r); keep the existing finally block with
emitApplicationEnd(receiver) unchanged so cleanup still runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java`:
- Around line 30-32: The current createNetworkModification (and the other
similar entry points that call handleRebuildNode) invokes handleRebuildNode
synchronously around the call to handleCreateNetworkModification, which causes
rebuilds to run before the async application of the modification completes;
instead remove the synchronous handleRebuildNode wrapper and invoke
handleRebuildNode (or the rebuild logic) from the async
completion/callback/future returned by the studyService call (the path that
actually applies the modification result), so change createNetworkModification,
moveNetworkModifications and the other listed methods to only submit the async
work (call handleCreateNetworkModification / handleMoveNetworkModifications) and
attach a continuation that calls handleRebuildNode(studyUuid, nodeUuid, userId)
after the studyService.createNetworkModification /
studyService.moveNetworkModifications completion promise/callback indicates the
change is applied.

---

Nitpick comments:
In `@src/main/java/org/gridsuite/study/server/service/StudyService.java`:
- Around line 2445-2457: In handleApplicationResult, guard against a possible
IndexOutOfBoundsException when modificationResults.size() >
rootNetworkUuids.size() by validating sizes before accessing rootNetworkUuids:
compute the smaller of result.modificationResults().size() and
receiver.rootNetworkUuids().size() (or explicitly check and log/throw a
descriptive IllegalStateException) and iterate up to that bound when calling
emitNetworkModificationImpacts(receiver.studyUuid(), receiver.nodeUuid(),
rootNetworkUuids.get(index), r); keep the existing finally block with
emitApplicationEnd(receiver) unchanged so cleanup still runs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 405080c3-8cfe-4e3c-9a8b-e4a0403e7d98

📥 Commits

Reviewing files that changed from the base of the PR and between 5ddd50d and 2f2e3d8.

📒 Files selected for processing (13)
  • src/main/java/org/gridsuite/study/server/controller/StudyController.java
  • src/main/java/org/gridsuite/study/server/dto/modification/ModificationReceiver.java
  • src/main/java/org/gridsuite/study/server/service/ConsumerService.java
  • src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java
  • src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java
  • src/main/java/org/gridsuite/study/server/service/StudyService.java
  • src/main/resources/config/application.yaml
  • src/test/java/org/gridsuite/study/server/NetworkModificationTest.java
  • src/test/java/org/gridsuite/study/server/VoltageInitTest.java
  • src/test/java/org/gridsuite/study/server/rootnetworks/ModificationToExcludeTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java
  • src/test/java/org/gridsuite/study/server/utils/TestUtils.java
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/main/java/org/gridsuite/study/server/dto/modification/ModificationReceiver.java
  • src/main/resources/config/application.yaml
  • src/test/java/org/gridsuite/study/server/utils/TestUtils.java
  • src/test/java/org/gridsuite/study/server/rootnetworks/ModificationToExcludeTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java
  • src/test/java/org/gridsuite/study/server/NetworkModificationTest.java
  • src/main/java/org/gridsuite/study/server/service/ConsumerService.java
  • src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java
  • src/test/java/org/gridsuite/study/server/VoltageInitTest.java

Signed-off-by: Ayoub LABIDI <ayoub.labidi@protonmail.com>
@ayolab ayolab force-pushed the ayolab/handle-async-midification-application-results branch from 2f2e3d8 to 61d2ea7 Compare May 17, 2026 18:55

@EtienneLt EtienneLt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

small things

}
}

public void handleApplicationResult(ModificationReceiver receiver, NetworkModificationsResult result) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
public void handleApplicationResult(ModificationReceiver receiver, NetworkModificationsResult result) {
public void handleModificationApplicationResult(ModificationReceiver receiver, NetworkModificationsResult result) {

what do you think ?

}

@Bean
public Consumer<Message<NetworkModificationsResult>> consumeApplicationResult() {

@EtienneLt EtienneLt Jun 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

consumeModificationApplicationResult ?

ModificationReceiver receiverObj = objectMapper.readValue(
URLDecoder.decode(receiver, StandardCharsets.UTF_8), ModificationReceiver.class);
studyService.handleApplicationResult(receiverObj, message.getPayload());
} catch (Exception e) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

exception is not thrown ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

because for notification we just logs error ?

}

@Bean
public Consumer<Message<String>> consumeApplicationFailed() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

consumeModificationApplicationFailed ?


public NetworkModificationsResult createModification(UUID groupUuid,
Pair<String, List<ModificationApplicationContext>> modificationContextInfos) {
public List<UUID> createModification(UUID groupUuid, ModificationReceiver receiver,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the return of the method is never used, should it return void ?

ayolab and others added 3 commits June 15, 2026 11:31
Signed-off-by: Ayoub LABIDI <ayoub.labidi@protonmail.com>
Signed-off-by: Ayoub LABIDI <ayoub.labidi@protonmail.com>
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java (2)

139-149: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Complete cleanup on receiver-null bulk moves. When isTargetInDifferentNodeTree is false, StudyService passes receiver = null, so no async callback will run. The success path still needs synchronous cleanup.

  • src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java#L139-L149: after a successful receiver-null move, unblock originNodeUuid because the callback will not do it.
  • src/main/java/org/gridsuite/study/server/service/StudyService.java#L2266-L2269: inside the receiver == null branch, also emit the end notification for originNodeUuid when isTargetDifferentNode.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java`
around lines 139 - 149, The issue spans two locations. In
RebuildNodeService.java within the handleMoveNetworkModifications method, after
the successful moveNetworkModifications call completes (before the catch block),
add synchronous cleanup to unblock the originNodeUuid since the async callback
will not execute when receiver is null. In StudyService.java within the receiver
== null branch (around lines 2266-2269), add logic to emit the end notification
for originNodeUuid when isTargetDifferentNode is true, to ensure cleanup occurs
synchronously when no callback will run.

181-192: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Delay rebuilds until async modification application completes.

action.get() can now return right after scheduling an async modification apply, but this code immediately rebuilds previously built nodes. For create/cross-tree move flows, that can rebuild descendants before the parent node has received its async application result.

Move the rebuild trigger into the async success path, or carry enough rebuild context in the async receiver/callback to rebuild only after handleModificationApplicationResult(...) has applied impacts and cleaned up the node state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java`
around lines 181 - 192, The rebuild loop triggered by the forEach on
rootNetworkUuidsByNodeBuilt executes immediately after action.get() returns, but
this is too early when async modification application is still pending. The
rebuild calls to studyService.buildNode need to be deferred until after the
async modification has completed. Either move the entire forEach rebuild block
into the async success callback path where it will execute after
handleModificationApplicationResult has applied impacts and cleaned up node
state, or capture the rootNetworkUuidsByNodeBuilt map and pass it through the
async receiver/callback mechanism so the rebuild can be triggered at the correct
time once the async processing finishes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/org/gridsuite/study/server/service/StudyService.java`:
- Around line 3709-3711: The call to doUnbuildNodeTree with
skipDeleteVariants=true skips variant deletion for all root networks in the
study, but rootNetworkService.invalidateRootNetworkRemoteInfos only invalidates
the specific rootNetworkUuid. This leaves stale variants on other root networks.
Modify the logic to skip variant deletion only for the specific rootNetworkUuid
being invalidated; call doUnbuildNodeTree once with skipDeleteVariants=true
specifically for that root network, and then call it again for any other root
networks with skipDeleteVariants=false to ensure their variants are properly
cleaned up.
- Around line 2320-2326: The finally block in the StudyService code only
unblocks the receiver's root network tree nodes but fails to unblock the origin
tree for cross-tree async moves. When receiver.originNodeUuid() is not null
(indicating a cross-tree move), the origin tree remains blocked after the
callback. In the finally block, after unblocking receiver.rootNetworkUuids(),
add additional logic to unblock the origin tree by calling
networkModificationTreeService.unblockNodeTree() with receiver.originNodeUuid()
and nodeUuid, but only when receiver.originNodeUuid() is not null. This ensures
both the origin and receiver trees are properly unblocked on async move
completion.
- Around line 3188-3193: The code conditionally deactivates modifications on
other root networks only when savedUuids contains exactly one element, but the
resetModificationsGroupUuid() call after this block executes unconditionally. If
duplicateModificationsFromGroup() returns 0 or more than 1 UUID, the
deactivation is silently skipped while the group reset still proceeds, leaving
modifications active on unintended root networks. Add a fail-fast validation
check immediately before the resetModificationsGroupUuid() call to ensure
savedUuids.size() equals 1, throwing an exception if the invariant is violated
instead of silently proceeding.

In `@src/test/java/org/gridsuite/study/server/WorkflowTest.java`:
- Around line 124-126: The tests are sending raw JSON strings in the
HEADER_RECEIVER headers, but ConsumerService expects URL-encoded content before
calling URLDecoder.decode(). At both locations (line 125 where
objectMapper.writeValueAsString(receiver) is written to the header, and line 138
with the same pattern), you need to URL-encode the JSON string before putting it
into the headers map. Apply URLEncoder.encode() to the
objectMapper.writeValueAsString(receiver) result to match the actual async wire
format that ConsumerService expects, ensuring the tests properly validate the
end-to-end contract.

---

Outside diff comments:
In `@src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java`:
- Around line 139-149: The issue spans two locations. In RebuildNodeService.java
within the handleMoveNetworkModifications method, after the successful
moveNetworkModifications call completes (before the catch block), add
synchronous cleanup to unblock the originNodeUuid since the async callback will
not execute when receiver is null. In StudyService.java within the receiver ==
null branch (around lines 2266-2269), add logic to emit the end notification for
originNodeUuid when isTargetDifferentNode is true, to ensure cleanup occurs
synchronously when no callback will run.
- Around line 181-192: The rebuild loop triggered by the forEach on
rootNetworkUuidsByNodeBuilt executes immediately after action.get() returns, but
this is too early when async modification application is still pending. The
rebuild calls to studyService.buildNode need to be deferred until after the
async modification has completed. Either move the entire forEach rebuild block
into the async success callback path where it will execute after
handleModificationApplicationResult has applied impacts and cleaned up node
state, or capture the rootNetworkUuidsByNodeBuilt map and pass it through the
async receiver/callback mechanism so the rebuild can be triggered at the correct
time once the async processing finishes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ccde3891-fd4f-46df-ae62-a30fde5ad8de

📥 Commits

Reviewing files that changed from the base of the PR and between 2f2e3d8 and 67588b5.

📒 Files selected for processing (14)
  • src/main/java/org/gridsuite/study/server/controller/StudyController.java
  • src/main/java/org/gridsuite/study/server/dto/modification/ModificationReceiver.java
  • src/main/java/org/gridsuite/study/server/service/ConsumerService.java
  • src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java
  • src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java
  • src/main/java/org/gridsuite/study/server/service/StudyService.java
  • src/main/resources/config/application.yaml
  • src/test/java/org/gridsuite/study/server/NetworkModificationTest.java
  • src/test/java/org/gridsuite/study/server/VoltageInitTest.java
  • src/test/java/org/gridsuite/study/server/WorkflowTest.java
  • src/test/java/org/gridsuite/study/server/rootnetworks/ModificationToExcludeTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java
  • src/test/java/org/gridsuite/study/server/utils/TestUtils.java
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/main/java/org/gridsuite/study/server/controller/StudyController.java
  • src/main/java/org/gridsuite/study/server/dto/modification/ModificationReceiver.java
  • src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java
  • src/main/java/org/gridsuite/study/server/service/ConsumerService.java
  • src/test/java/org/gridsuite/study/server/utils/TestUtils.java
  • src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java
  • src/test/java/org/gridsuite/study/server/rootnetworks/ModificationToExcludeTest.java
  • src/test/java/org/gridsuite/study/server/VoltageInitTest.java
  • src/test/java/org/gridsuite/study/server/NetworkModificationTest.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java (2)

139-149: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Complete cleanup on receiver-null bulk moves. When isTargetInDifferentNodeTree is false, StudyService passes receiver = null, so no async callback will run. The success path still needs synchronous cleanup.

  • src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java#L139-L149: after a successful receiver-null move, unblock originNodeUuid because the callback will not do it.
  • src/main/java/org/gridsuite/study/server/service/StudyService.java#L2266-L2269: inside the receiver == null branch, also emit the end notification for originNodeUuid when isTargetDifferentNode.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java`
around lines 139 - 149, The issue spans two locations. In
RebuildNodeService.java within the handleMoveNetworkModifications method, after
the successful moveNetworkModifications call completes (before the catch block),
add synchronous cleanup to unblock the originNodeUuid since the async callback
will not execute when receiver is null. In StudyService.java within the receiver
== null branch (around lines 2266-2269), add logic to emit the end notification
for originNodeUuid when isTargetDifferentNode is true, to ensure cleanup occurs
synchronously when no callback will run.

181-192: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Delay rebuilds until async modification application completes.

action.get() can now return right after scheduling an async modification apply, but this code immediately rebuilds previously built nodes. For create/cross-tree move flows, that can rebuild descendants before the parent node has received its async application result.

Move the rebuild trigger into the async success path, or carry enough rebuild context in the async receiver/callback to rebuild only after handleModificationApplicationResult(...) has applied impacts and cleaned up the node state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java`
around lines 181 - 192, The rebuild loop triggered by the forEach on
rootNetworkUuidsByNodeBuilt executes immediately after action.get() returns, but
this is too early when async modification application is still pending. The
rebuild calls to studyService.buildNode need to be deferred until after the
async modification has completed. Either move the entire forEach rebuild block
into the async success callback path where it will execute after
handleModificationApplicationResult has applied impacts and cleaned up node
state, or capture the rootNetworkUuidsByNodeBuilt map and pass it through the
async receiver/callback mechanism so the rebuild can be triggered at the correct
time once the async processing finishes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/org/gridsuite/study/server/service/StudyService.java`:
- Around line 3709-3711: The call to doUnbuildNodeTree with
skipDeleteVariants=true skips variant deletion for all root networks in the
study, but rootNetworkService.invalidateRootNetworkRemoteInfos only invalidates
the specific rootNetworkUuid. This leaves stale variants on other root networks.
Modify the logic to skip variant deletion only for the specific rootNetworkUuid
being invalidated; call doUnbuildNodeTree once with skipDeleteVariants=true
specifically for that root network, and then call it again for any other root
networks with skipDeleteVariants=false to ensure their variants are properly
cleaned up.
- Around line 2320-2326: The finally block in the StudyService code only
unblocks the receiver's root network tree nodes but fails to unblock the origin
tree for cross-tree async moves. When receiver.originNodeUuid() is not null
(indicating a cross-tree move), the origin tree remains blocked after the
callback. In the finally block, after unblocking receiver.rootNetworkUuids(),
add additional logic to unblock the origin tree by calling
networkModificationTreeService.unblockNodeTree() with receiver.originNodeUuid()
and nodeUuid, but only when receiver.originNodeUuid() is not null. This ensures
both the origin and receiver trees are properly unblocked on async move
completion.
- Around line 3188-3193: The code conditionally deactivates modifications on
other root networks only when savedUuids contains exactly one element, but the
resetModificationsGroupUuid() call after this block executes unconditionally. If
duplicateModificationsFromGroup() returns 0 or more than 1 UUID, the
deactivation is silently skipped while the group reset still proceeds, leaving
modifications active on unintended root networks. Add a fail-fast validation
check immediately before the resetModificationsGroupUuid() call to ensure
savedUuids.size() equals 1, throwing an exception if the invariant is violated
instead of silently proceeding.

In `@src/test/java/org/gridsuite/study/server/WorkflowTest.java`:
- Around line 124-126: The tests are sending raw JSON strings in the
HEADER_RECEIVER headers, but ConsumerService expects URL-encoded content before
calling URLDecoder.decode(). At both locations (line 125 where
objectMapper.writeValueAsString(receiver) is written to the header, and line 138
with the same pattern), you need to URL-encode the JSON string before putting it
into the headers map. Apply URLEncoder.encode() to the
objectMapper.writeValueAsString(receiver) result to match the actual async wire
format that ConsumerService expects, ensuring the tests properly validate the
end-to-end contract.

---

Outside diff comments:
In `@src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java`:
- Around line 139-149: The issue spans two locations. In RebuildNodeService.java
within the handleMoveNetworkModifications method, after the successful
moveNetworkModifications call completes (before the catch block), add
synchronous cleanup to unblock the originNodeUuid since the async callback will
not execute when receiver is null. In StudyService.java within the receiver ==
null branch (around lines 2266-2269), add logic to emit the end notification for
originNodeUuid when isTargetDifferentNode is true, to ensure cleanup occurs
synchronously when no callback will run.
- Around line 181-192: The rebuild loop triggered by the forEach on
rootNetworkUuidsByNodeBuilt executes immediately after action.get() returns, but
this is too early when async modification application is still pending. The
rebuild calls to studyService.buildNode need to be deferred until after the
async modification has completed. Either move the entire forEach rebuild block
into the async success callback path where it will execute after
handleModificationApplicationResult has applied impacts and cleaned up node
state, or capture the rootNetworkUuidsByNodeBuilt map and pass it through the
async receiver/callback mechanism so the rebuild can be triggered at the correct
time once the async processing finishes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ccde3891-fd4f-46df-ae62-a30fde5ad8de

📥 Commits

Reviewing files that changed from the base of the PR and between 2f2e3d8 and 67588b5.

📒 Files selected for processing (14)
  • src/main/java/org/gridsuite/study/server/controller/StudyController.java
  • src/main/java/org/gridsuite/study/server/dto/modification/ModificationReceiver.java
  • src/main/java/org/gridsuite/study/server/service/ConsumerService.java
  • src/main/java/org/gridsuite/study/server/service/NetworkModificationService.java
  • src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java
  • src/main/java/org/gridsuite/study/server/service/StudyService.java
  • src/main/resources/config/application.yaml
  • src/test/java/org/gridsuite/study/server/NetworkModificationTest.java
  • src/test/java/org/gridsuite/study/server/VoltageInitTest.java
  • src/test/java/org/gridsuite/study/server/WorkflowTest.java
  • src/test/java/org/gridsuite/study/server/rootnetworks/ModificationToExcludeTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java
  • src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java
  • src/test/java/org/gridsuite/study/server/utils/TestUtils.java
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/main/java/org/gridsuite/study/server/controller/StudyController.java
  • src/main/java/org/gridsuite/study/server/dto/modification/ModificationReceiver.java
  • src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java
  • src/main/java/org/gridsuite/study/server/service/ConsumerService.java
  • src/test/java/org/gridsuite/study/server/utils/TestUtils.java
  • src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java
  • src/test/java/org/gridsuite/study/server/rootnetworks/ModificationToExcludeTest.java
  • src/test/java/org/gridsuite/study/server/VoltageInitTest.java
  • src/test/java/org/gridsuite/study/server/NetworkModificationTest.java
🛑 Comments failed to post (4)
src/main/java/org/gridsuite/study/server/service/StudyService.java (3)

2320-2326: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unblock the origin tree on async move completion.

This ends origin-node notifications, but the finally only unblocks receiver.nodeUuid(). For cross-tree async moves, the origin tree was invalidated before the request and RebuildNodeService no longer unblocks it on success, so the origin can remain blocked after the callback.

Suggested fix
         } finally {
-            receiver.rootNetworkUuids().forEach(rn -> networkModificationTreeService.unblockNodeTree(rn, nodeUuid));
+            receiver.rootNetworkUuids().forEach(rn -> {
+                networkModificationTreeService.unblockNodeTree(rn, nodeUuid);
+                if (receiver.originNodeUuid() != null) {
+                    networkModificationTreeService.unblockNodeTree(rn, receiver.originNodeUuid());
+                }
+            });
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/gridsuite/study/server/service/StudyService.java` around
lines 2320 - 2326, The finally block in the StudyService code only unblocks the
receiver's root network tree nodes but fails to unblock the origin tree for
cross-tree async moves. When receiver.originNodeUuid() is not null (indicating a
cross-tree move), the origin tree remains blocked after the callback. In the
finally block, after unblocking receiver.rootNetworkUuids(), add additional
logic to unblock the origin tree by calling
networkModificationTreeService.unblockNodeTree() with receiver.originNodeUuid()
and nodeUuid, but only when receiver.originNodeUuid() is not null. This ensures
both the origin and receiver trees are properly unblocked on async move
completion.

3188-3193: ⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect declared Java/toolchain targets before relying on List.getFirst().

set -euo pipefail

fd -HI '^(pom.xml|build.gradle|build.gradle.kts|gradle.properties|\.java-version|toolchains.xml|Dockerfile.*|.*\.ya?ml)$' \
  | xargs -r rg -n -C2 '(<maven\.compiler\.(source|target|release)>|<release>|sourceCompatibility|targetCompatibility|languageVersion|java-version|temurin|openjdk|eclipse-temurin|JAVA_VERSION)'

Repository: gridsuite/study-server

Length of output: 50


🏁 Script executed:

# Find and inspect the StudyService.java file
fd -H 'StudyService\.java' | head -5

Repository: gridsuite/study-server

Length of output: 133


🏁 Script executed:

# Once we find the file, read the relevant lines with context
sed -n '3170,3210p' "$(fd -H 'StudyService\.java' | head -1)"

Repository: gridsuite/study-server

Length of output: 2719


🏁 Script executed:

# Search for Java version configuration in build files
fd -H '(pom\.xml|build\.gradle|\.java-version)' | head -10

Repository: gridsuite/study-server

Length of output: 74


🏁 Script executed:

# Read pom.xml to check Java version configuration
head -100 "$(fd -H 'pom\.xml' | head -1)" | rg -A5 -B5 '(maven\.compiler\.|<java|<release>|sourceCompatibility|targetCompatibility)'

Repository: gridsuite/study-server

Length of output: 48


🏁 Script executed:

# Also check if there are other build files
fd -H '(build\.gradle|gradle\.properties)'

Repository: gridsuite/study-server

Length of output: 48


🏁 Script executed:

# Read the full pom.xml to find Java version configuration
cat "$(fd -H 'pom\.xml' | head -1)" | head -200

Repository: gridsuite/study-server

Length of output: 7637


🏁 Script executed:

# Search for Java version markers anywhere in pom.xml
rg '(java|release|compiler|version)' "$(fd -H 'pom\.xml' | head -1)" -i | head -20

Repository: gridsuite/study-server

Length of output: 727


🏁 Script executed:

# Check for Java version configuration in parent or anywhere
rg 'java.version|java.compiler|java.source|java.target|<java>|<release>' "$(fd -H 'pom\.xml' | head -1)" -A2 -B2

Repository: gridsuite/study-server

Length of output: 48


🏁 Script executed:

# Look for .java-version, .sdkmanrc, or similar files
fd -H '\.(java-version|sdkmanrc|tool-versions|jdk)'

Repository: gridsuite/study-server

Length of output: 48


🏁 Script executed:

# Check if List.getFirst() is used elsewhere in the codebase
rg 'getFirst\(\)' --type java | head -20

Repository: gridsuite/study-server

Length of output: 5061


🏁 Script executed:

# Check the powsybl-parent-ws to understand Java version targeting
# First, look for common Java version patterns more broadly
rg 'source|target|release' "$(fd -H 'pom\.xml' | head -1)" --type xml | grep -i 'java\|release\|compiler\|source\|target' | head -20

Repository: gridsuite/study-server

Length of output: 193


🏁 Script executed:

# Since the project has a parent pom, search for any Java toolchain configuration
rg 'java\.version|maven\.compiler|maven-compiler-plugin' --type xml

Repository: gridsuite/study-server

Length of output: 48


🏁 Script executed:

# Check what the actual Java version requirement is by looking at Maven compiler plugin or similar
cat "$(fd -H 'pom\.xml' | head -1)" | tail -200

Repository: gridsuite/study-server

Length of output: 7173


🏁 Script executed:

# Search for Java version documentation or requirements
fd -H 'README|CHANGELOG|\.md' | xargs rg -l 'java|jdk|21|20|19' | head -5

Repository: gridsuite/study-server

Length of output: 76


🏁 Script executed:

# Check if there's a .github/workflows that shows Java version
fd -H '\.github/workflows' -type d

Repository: gridsuite/study-server

Length of output: 237


🏁 Script executed:

# Look for GitHub Actions workflow files to see what Java version is used
fd -H '\.yml$|\.yaml$' '.github/workflows' 2>/dev/null | head -5

Repository: gridsuite/study-server

Length of output: 190


🏁 Script executed:

# Check the build workflow to see Java version used
cat .github/workflows/build.yml | head -50

Repository: gridsuite/study-server

Length of output: 564


🏁 Script executed:

# Also look for any maven or gradle configuration that specifies Java version
rg 'java-version|setup-java|jdk' .github/workflows/ -A2 -B2

Repository: gridsuite/study-server

Length of output: 48


🏁 Script executed:

# Check README for Java version requirements
cat README.md | head -100

Repository: gridsuite/study-server

Length of output: 1214


🏁 Script executed:

# Search for Java version in powsybl dependencies or documentation
rg 'java|jdk|21|20' README.md -i

Repository: gridsuite/study-server

Length of output: 281


🏁 Script executed:

# Look at the code context more carefully - what happens after the if block?
sed -n '3185,3205p' src/main/java/org/gridsuite/study/server/service/StudyService.java

Repository: gridsuite/study-server

Length of output: 1259


🏁 Script executed:

# Check if there are other usages of this pattern (duplicateModificationsFromGroup) to understand expectations
rg 'duplicateModificationsFromGroup' --type java -B3 -A10

Repository: gridsuite/study-server

Length of output: 5826


🏁 Script executed:

# Understand the data flow: what does resetModificationsGroupUuid do?
rg 'resetModificationsGroupUuid' --type java -B2 -A5

Repository: gridsuite/study-server

Length of output: 1930


🏁 Script executed:

# Check what voltageInitService.resetModificationsGroupUuid does specifically
rg 'class.*VoltageInitService' -A 100 --type java | head -150

Repository: gridsuite/study-server

Length of output: 12392


Add validation to ensure voltage-init modification count is exactly one before deactivation.

The code's documentation indicates a single-copy invariant, but resetModificationsGroupUuid() is called unconditionally after the deactivation block (lines 3194+). If duplicateModificationsFromGroup() returns 0 or >1 UUIDs, the deactivation is silently skipped while the voltage-init group is still reset, leaving modifications active on unintended root networks. Add a fail-fast check before reset.

Suggested fix
-            // We expect a single voltageInit modification, deactivate it for all other root networks synchronously
-            if (savedUuids.size() == 1) {
-                for (UUID otherRootNetwork : rootNetworkToDeactivateUuids) {
-                    rootNetworkNodeInfoService.updateModificationsToExclude(nodeUuid, otherRootNetwork, Set.of(savedUuids.getFirst()), false);
-                }
+            if (savedUuids.size() != 1) {
+                throw new IllegalStateException("Expected exactly one voltage-init modification copy, got " + savedUuids.size());
             }
+
+            // We expect a single voltageInit modification, deactivate it for all other root networks synchronously
+            UUID savedUuid = savedUuids.get(0);
+            for (UUID otherRootNetwork : rootNetworkToDeactivateUuids) {
+                rootNetworkNodeInfoService.updateModificationsToExclude(nodeUuid, otherRootNetwork, Set.of(savedUuid), false);
+            }
📝 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.

            if (savedUuids.size() != 1) {
                throw new IllegalStateException("Expected exactly one voltage-init modification copy, got " + savedUuids.size());
            }

            // We expect a single voltageInit modification, deactivate it for all other root networks synchronously
            UUID savedUuid = savedUuids.get(0);
            for (UUID otherRootNetwork : rootNetworkToDeactivateUuids) {
                rootNetworkNodeInfoService.updateModificationsToExclude(nodeUuid, otherRootNetwork, Set.of(savedUuid), false);
            }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/gridsuite/study/server/service/StudyService.java` around
lines 3188 - 3193, The code conditionally deactivates modifications on other
root networks only when savedUuids contains exactly one element, but the
resetModificationsGroupUuid() call after this block executes unconditionally. If
duplicateModificationsFromGroup() returns 0 or more than 1 UUID, the
deactivation is silently skipped while the group reset still proceeds, leaving
modifications active on unintended root networks. Add a fail-fast validation
check immediately before the resetModificationsGroupUuid() call to ensure
savedUuids.size() equals 1, throwing an exception if the invariant is violated
instead of silently proceeding.

3709-3711: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only skip variant deletion for the root network being invalidated.

doUnbuildNodeTree(..., skipDeleteVariants=true) applies to every root network in the study, but the subsequent remote invalidation only targets rootNetworkUuid. That skips variant deletion for the other root networks while still invalidating their node tree state, leaving stale remote variants behind.

Suggested direction
-        doUnbuildNodeTree(studyUuid, rootNodeUuid, true, true, userId);
+        // Skip variant deletion only for rootNetworkUuid, whose remote data is erased below.
+        // Other root networks still need their invalidated variants deleted.
+        doUnbuildNodeTreeForRootNetworkInvalidation(studyUuid, rootNodeUuid, rootNetworkUuid, userId);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/org/gridsuite/study/server/service/StudyService.java` around
lines 3709 - 3711, The call to doUnbuildNodeTree with skipDeleteVariants=true
skips variant deletion for all root networks in the study, but
rootNetworkService.invalidateRootNetworkRemoteInfos only invalidates the
specific rootNetworkUuid. This leaves stale variants on other root networks.
Modify the logic to skip variant deletion only for the specific rootNetworkUuid
being invalidated; call doUnbuildNodeTree once with skipDeleteVariants=true
specifically for that root network, and then call it again for any other root
networks with skipDeleteVariants=false to ensure their variants are properly
cleaned up.
src/test/java/org/gridsuite/study/server/WorkflowTest.java (1)

124-126: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Encode HEADER_RECEIVER in tests to match the consumer contract.

At Line 125 and Line 138, the tests send raw JSON while ConsumerService expects URL-encoded header content before URLDecoder.decode(...). Please encode these headers so the tests actually validate the async wire format.

Proposed test fix
@@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.UUID;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
@@
-        headers.put(HEADER_RECEIVER, objectMapper.writeValueAsString(receiver));
+        headers.put(HEADER_RECEIVER,
+            URLEncoder.encode(objectMapper.writeValueAsString(receiver), StandardCharsets.UTF_8));
@@
-        headers.put(HEADER_RECEIVER, objectMapper.writeValueAsString(receiver));
+        headers.put(HEADER_RECEIVER,
+            URLEncoder.encode(objectMapper.writeValueAsString(receiver), StandardCharsets.UTF_8));

Also applies to: 137-139

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/org/gridsuite/study/server/WorkflowTest.java` around lines 124
- 126, The tests are sending raw JSON strings in the HEADER_RECEIVER headers,
but ConsumerService expects URL-encoded content before calling
URLDecoder.decode(). At both locations (line 125 where
objectMapper.writeValueAsString(receiver) is written to the header, and line 138
with the same pattern), you need to URL-encode the JSON string before putting it
into the headers map. Apply URLEncoder.encode() to the
objectMapper.writeValueAsString(receiver) result to match the actual async wire
format that ConsumerService expects, ensuring the tests properly validate the
end-to-end contract.

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.

2 participants