Skip to content

add max allowed computations#102

Merged
EtienneLt merged 6 commits into
mainfrom
add-max-allow-computations
Jun 29, 2026
Merged

add max allowed computations#102
EtienneLt merged 6 commits into
mainfrom
add-max-allow-computations

Conversation

@EtienneLt

Copy link
Copy Markdown
Contributor

PR Summary

add all quotas for each computation service

Signed-off-by: Etienne LESOT <etienne.lesot@rte-france.com>
@EtienneLt EtienneLt self-assigned this Jun 16, 2026
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@EtienneLt, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 39 minutes and 42 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7befb442-8d58-4a21-b3ac-367db652998a

📥 Commits

Reviewing files that changed from the base of the PR and between 3d1740d and 5a95046.

📒 Files selected for processing (1)
  • src/main/java/org/gridsuite/useradmin/server/dto/UserProfile.java
📝 Walkthrough

Walkthrough

Eleven new analysis-type quota fields are added to the user profile system. A Liquibase migration extends user_profile, DTOs and entities are expanded, services add fallback/default handling, 11 new REST endpoints expose the values, and tests plus config defaults are updated.

Changes

New maxAllowed quota dimensions for user profiles

Layer / File(s) Summary
DB migration and JPA entity fields
src/main/resources/db/changelog/changesets/changelog_20260616T125854Z.xml, src/main/resources/db/changelog/db.changelog-master.yaml, src/main/java/org/gridsuite/useradmin/server/entity/UserProfileEntity.java
Liquibase adds 11 nullable INTEGER columns to user_profile; UserProfileEntity declares the corresponding JPA-mapped Integer fields.
DTO contracts and application config defaults
src/main/java/org/gridsuite/useradmin/server/UserAdminApplicationProps.java, src/main/java/org/gridsuite/useradmin/server/dto/UserProfile.java, src/main/java/org/gridsuite/useradmin/server/dto/UserInfos.java
UserAdminApplicationProps gains 11 defaultMaxAllowed* config properties; UserProfile becomes a class with maxAllowValuesMap and quota keys; UserInfos gains 11 matching Integer components.
Service layer mapping and fallback logic
src/main/java/org/gridsuite/useradmin/server/entity/UserInfosEntity.java, src/main/java/org/gridsuite/useradmin/server/service/UserProfileService.java, src/main/java/org/gridsuite/useradmin/server/service/UserInfosService.java, src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java
UserInfosEntity expands DTO conversion signatures; UserProfileService maps the new fields between DTO and entity forms; UserInfosService resolves and propagates quota values; UserAdminService resolves quota getters and builds default profiles from the new key map.
REST endpoints for new quota getters
src/main/java/org/gridsuite/useradmin/server/controller/UserAdminController.java
11 new admin-only GET endpoints under /users/{sub}/profile/ return Integer responses from the matching UserAdminService getters.
Tests and configuration updates
src/test/java/org/gridsuite/useradmin/server/DtoConverterTest.java, src/test/java/org/gridsuite/useradmin/server/NoQuotaTest.java, src/test/java/org/gridsuite/useradmin/server/UserProfileTest.java, src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java, src/test/java/org/gridsuite/useradmin/server/controller/UserInfosControllerTest.java, src/test/java/org/gridsuite/useradmin/server/service/UserInfosServiceTest.java, src/main/resources/config/application.yaml, src/test/resources/application-default.yml, src/test/resources/application-noquota.yml
Tests are updated for the expanded quota model, and application YAML files add the new defaultMaxAllowed* values used by the services.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.20% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: adding max-allowed computation quotas across services.
Description check ✅ Passed The description is directly related to the change set and describes adding quotas for computation services.
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.

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.

@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: 2

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/useradmin/server/service/UserProfileService.java (1)

119-135: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

updateProfile ignores the 11 newly introduced quota fields.

This method updates only maxAllowedCases/maxAllowedBuilds; incoming values for the new quota dimensions are never persisted.

Suggested fix
         profile.setMaxAllowedCases(userProfile.maxAllowedCases());
         profile.setMaxAllowedBuilds(userProfile.maxAllowedBuilds());
+        profile.setMaxAllowedLoadflow(userProfile.maxAllowedLoadflow());
+        profile.setMaxAllowedSecurity(userProfile.maxAllowedSecurity());
+        profile.setMaxAllowedSensitivity(userProfile.maxAllowedSensitivity());
+        profile.setMaxAllowedShortCircuit(userProfile.maxAllowedShortCircuit());
+        profile.setMaxAllowedVoltageInit(userProfile.maxAllowedVoltageInit());
+        profile.setMaxAllowedPccMin(userProfile.maxAllowedPccMin());
+        profile.setMaxAllowedStateEstimation(userProfile.maxAllowedStateEstimation());
+        profile.setMaxAllowedBalanceAdjustement(userProfile.maxAllowedBalanceAdjustement());
+        profile.setMaxAllowedDynamicSimulation(userProfile.maxAllowedDynamicSimulation());
+        profile.setMaxAllowedDynamicSecurity(userProfile.maxAllowedDynamicSecurity());
+        profile.setMaxAllowedDynamicMargin(userProfile.maxAllowedDynamicMargin());
         profile.setSpreadsheetConfigCollectionId(userProfile.spreadsheetConfigCollectionId());
🤖 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/useradmin/server/service/UserProfileService.java`
around lines 119 - 135, The updateProfile method in UserProfileService is not
persisting the 11 newly introduced quota fields from the incoming userProfile
object. Add setter calls to the profile entity for each of the missing quota
dimension fields, following the same pattern as the existing setters like
setMaxAllowedCases and setMaxAllowedBuilds. Extract each quota field value from
the userProfile parameter and call the corresponding setter method on the
profile entity to ensure all new quota fields are properly persisted.
🤖 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/useradmin/server/controller/UserAdminController.java`:
- Around line 151-237: The endpoints getUserProfileMaxAllowedLoadflows,
getUserProfileMaxAllowedSecurity, getUserProfileMaxAllowedSensitivity,
getUserProfileMaxAllowedShortCircuit, getUserProfileMaxAllowedVoltageInit,
getUserProfileMaxAllowedPccMin, getUserProfileMaxAllowedStateEstimation,
getUserProfileMaxAllowedBalanceAdjustement,
getUserProfileMaxAllowedDynamicSimulation,
getUserProfileMaxAllowedDynamicSecurity, and
getUserProfileMaxAllowedDynamicMargin all document 404 responses in their
ApiResponse annotations, but they always return ResponseEntity.ok() which means
they never actually return 404. Resolve this inconsistency by either removing
the 404 ApiResponse annotations from all these endpoints if the service never
returns not-found errors, or if the service can return null or throw an
exception for unknown users, modify these handlers to check the result and
return ResponseEntity.notFound().build() when appropriate.

In `@src/main/resources/db/changelog/changesets/changelog_20260616T125854Z.xml`:
- Around line 5-55: The Liquibase changeset column names use snake_case
(max_allowed_*) but the UserProfileEntity JPA entity maps these fields to
camelCase column names (maxAllowed*). Update all column name attributes in the
addColumn elements across all changeSet entries (ids 1781614744540-2 through
1781614744540-12) to change from the snake_case format
(max_allowed_balance_adjustement, max_allowed_dynamic_margin,
max_allowed_dynamic_security, max_allowed_dynamic_simulation,
max_allowed_loadflow, max_allowed_pcc_min, max_allowed_security,
max_allowed_sensitivity, max_allowed_short_circuit,
max_allowed_state_estimation, max_allowed_voltage_init, etc.) to the camelCase
format that matches the entity mapping (maxAllowedBalanceAdjustement,
maxAllowedDynamicMargin, maxAllowedDynamicSecurity, maxAllowedDynamicSimulation,
maxAllowedLoadflow, maxAllowedPccMin, maxAllowedSecurity, maxAllowedSensitivity,
maxAllowedShortCircuit, maxAllowedStateEstimation, maxAllowedVoltageInit, etc.)
to ensure the database schema aligns with the JPA entity column name mappings.

---

Outside diff comments:
In
`@src/main/java/org/gridsuite/useradmin/server/service/UserProfileService.java`:
- Around line 119-135: The updateProfile method in UserProfileService is not
persisting the 11 newly introduced quota fields from the incoming userProfile
object. Add setter calls to the profile entity for each of the missing quota
dimension fields, following the same pattern as the existing setters like
setMaxAllowedCases and setMaxAllowedBuilds. Extract each quota field value from
the userProfile parameter and call the corresponding setter method on the
profile entity to ensure all new quota fields are properly persisted.
🪄 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: 1970dc4b-649a-4c36-8255-1a8515762f0e

📥 Commits

Reviewing files that changed from the base of the PR and between 8912ca4 and 0be3953.

📒 Files selected for processing (18)
  • src/main/java/org/gridsuite/useradmin/server/UserAdminApplicationProps.java
  • src/main/java/org/gridsuite/useradmin/server/controller/UserAdminController.java
  • src/main/java/org/gridsuite/useradmin/server/dto/UserInfos.java
  • src/main/java/org/gridsuite/useradmin/server/dto/UserProfile.java
  • src/main/java/org/gridsuite/useradmin/server/entity/UserInfosEntity.java
  • src/main/java/org/gridsuite/useradmin/server/entity/UserProfileEntity.java
  • src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java
  • src/main/java/org/gridsuite/useradmin/server/service/UserIdentityService.java
  • src/main/java/org/gridsuite/useradmin/server/service/UserInfosService.java
  • src/main/java/org/gridsuite/useradmin/server/service/UserProfileService.java
  • src/main/resources/db/changelog/changesets/changelog_20260616T125854Z.xml
  • src/main/resources/db/changelog/db.changelog-master.yaml
  • src/test/java/org/gridsuite/useradmin/server/DtoConverterTest.java
  • src/test/java/org/gridsuite/useradmin/server/NoQuotaTest.java
  • src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java
  • src/test/java/org/gridsuite/useradmin/server/UserProfileTest.java
  • src/test/java/org/gridsuite/useradmin/server/controller/UserInfosControllerTest.java
  • src/test/java/org/gridsuite/useradmin/server/service/UserInfosServiceTest.java

Comment on lines +151 to +237
@GetMapping(value = "/users/{sub}/profile/max-loadflow")
@Operation(summary = "Get the user's max allowed loadflows")
@ApiResponse(responseCode = "200", description = "The user max allowed loadflows")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<Integer> getUserProfileMaxAllowedLoadflows(@PathVariable String sub) {
return ResponseEntity.ok().body(service.getUserProfileMaxAllowedLoadflows(sub));
}

@GetMapping(value = "/users/{sub}/profile/max-security")
@Operation(summary = "Get the user's max allowed security analysis")
@ApiResponse(responseCode = "200", description = "The user max allowed security analysis")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<Integer> getUserProfileMaxAllowedSecurity(@PathVariable String sub) {
return ResponseEntity.ok().body(service.getUserProfileMaxAllowedSecurity(sub));
}

@GetMapping(value = "/users/{sub}/profile/max-sensitivity")
@Operation(summary = "Get the user's max allowed sensitivity analysis")
@ApiResponse(responseCode = "200", description = "The user max allowed sensitivity analysis")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<Integer> getUserProfileMaxAllowedSensitivity(@PathVariable String sub) {
return ResponseEntity.ok().body(service.getUserProfileMaxAllowedSensitivity(sub));
}

@GetMapping(value = "/users/{sub}/profile/max-shortcircuit")
@Operation(summary = "Get the user's max allowed short circuit analysis")
@ApiResponse(responseCode = "200", description = "The user max allowed short circuit analysis")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<Integer> getUserProfileMaxAllowedShortCircuit(@PathVariable String sub) {
return ResponseEntity.ok().body(service.getUserProfileMaxAllowedShortCircuit(sub));
}

@GetMapping(value = "/users/{sub}/profile/max-voltage-init")
@Operation(summary = "Get the user's max allowed voltage init analysis")
@ApiResponse(responseCode = "200", description = "The user max allowed voltage init analysis")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<Integer> getUserProfileMaxAllowedVoltageInit(@PathVariable String sub) {
return ResponseEntity.ok().body(service.getUserProfileMaxAllowedVoltageInit(sub));
}

@GetMapping(value = "/users/{sub}/profile/max-pcc-min")
@Operation(summary = "Get the user's max allowed pcc min analysis")
@ApiResponse(responseCode = "200", description = "The user max allowed pcc min analysis")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<Integer> getUserProfileMaxAllowedPccMin(@PathVariable String sub) {
return ResponseEntity.ok().body(service.getUserProfileMaxAllowedPccMin(sub));
}

@GetMapping(value = "/users/{sub}/profile/max-state-estimation")
@Operation(summary = "Get the user's max allowed state estimation analysis")
@ApiResponse(responseCode = "200", description = "The user max allowed state estimation analysis")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<Integer> getUserProfileMaxAllowedStateEstimation(@PathVariable String sub) {
return ResponseEntity.ok().body(service.getUserProfileMaxAllowedStateEstimation(sub));
}

@GetMapping(value = "/users/{sub}/profile/max-balance-adjustement")
@Operation(summary = "Get the user's max allowed balance adjustement analysis")
@ApiResponse(responseCode = "200", description = "The user max allowed balance adjustement analysis")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<Integer> getUserProfileMaxAllowedBalanceAdjustement(@PathVariable String sub) {
return ResponseEntity.ok().body(service.getUserProfileMaxAllowedBalanceAdjustement(sub));
}

@GetMapping(value = "/users/{sub}/profile/max-dynamic-simulation")
@Operation(summary = "Get the user's max allowed dynamic simulation analysis")
@ApiResponse(responseCode = "200", description = "The user max allowed dynamic simulation analysis")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<Integer> getUserProfileMaxAllowedDynamicSimulation(@PathVariable String sub) {
return ResponseEntity.ok().body(service.getUserProfileMaxAllowedDynamicSimulation(sub));
}

@GetMapping(value = "/users/{sub}/profile/max-dynamic-security")
@Operation(summary = "Get the user's max allowed dynamic security analysis")
@ApiResponse(responseCode = "200", description = "The user max allowed dynamic security analysis")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<Integer> getUserProfileMaxAllowedDynamicSecurity(@PathVariable String sub) {
return ResponseEntity.ok().body(service.getUserProfileMaxAllowedDynamicSecurity(sub));
}

@GetMapping(value = "/users/{sub}/profile/max-dynamic-margin")
@Operation(summary = "Get the user's max allowed dynamic margin analysis")
@ApiResponse(responseCode = "200", description = "The user max allowed dynamic margin analysis")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<Integer> getUserProfileMaxAllowedDynamicMargin(@PathVariable String sub) {
return ResponseEntity.ok().body(service.getUserProfileMaxAllowedDynamicMargin(sub));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

404 response is documented but unreachable on the new quota endpoints.

At Lines 151-237, each handler always returns ResponseEntity.ok(...), and the delegated service path falls back to defaults instead of signaling not found. The @ApiResponse(responseCode = "404") on these endpoints is therefore inconsistent with actual behavior. Align the contract by either removing 404 from docs or explicitly returning 404 for unknown users/profiles.

🤖 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/useradmin/server/controller/UserAdminController.java`
around lines 151 - 237, The endpoints getUserProfileMaxAllowedLoadflows,
getUserProfileMaxAllowedSecurity, getUserProfileMaxAllowedSensitivity,
getUserProfileMaxAllowedShortCircuit, getUserProfileMaxAllowedVoltageInit,
getUserProfileMaxAllowedPccMin, getUserProfileMaxAllowedStateEstimation,
getUserProfileMaxAllowedBalanceAdjustement,
getUserProfileMaxAllowedDynamicSimulation,
getUserProfileMaxAllowedDynamicSecurity, and
getUserProfileMaxAllowedDynamicMargin all document 404 responses in their
ApiResponse annotations, but they always return ResponseEntity.ok() which means
they never actually return 404. Resolve this inconsistency by either removing
the 404 ApiResponse annotations from all these endpoints if the service never
returns not-found errors, or if the service can return null or throw an
exception for unknown users, modify these handlers to check the result and
return ResponseEntity.notFound().build() when appropriate.

Comment on lines +5 to +55
<column name="max_allowed_balance_adjustement" type="INTEGER"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-3">
<addColumn tableName="user_profile">
<column name="max_allowed_dynamic_margin" type="INTEGER"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-4">
<addColumn tableName="user_profile">
<column name="max_allowed_dynamic_security" type="INTEGER"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-5">
<addColumn tableName="user_profile">
<column name="max_allowed_dynamic_simulation" type="INTEGER"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-6">
<addColumn tableName="user_profile">
<column name="max_allowed_loadflow" type="INTEGER"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-7">
<addColumn tableName="user_profile">
<column name="max_allowed_pcc_min" type="INTEGER"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-8">
<addColumn tableName="user_profile">
<column name="max_allowed_security" type="INTEGER"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-9">
<addColumn tableName="user_profile">
<column name="max_allowed_sensitivity" type="INTEGER"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-10">
<addColumn tableName="user_profile">
<column name="max_allowed_short_circuit" type="INTEGER"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-11">
<addColumn tableName="user_profile">
<column name="max_allowed_state_estimation" type="INTEGER"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-12">
<addColumn tableName="user_profile">
<column name="max_allowed_voltage_init" type="INTEGER"/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Align new Liquibase column names with JPA @Column names.

The added DB columns use max_allowed_*, while UserProfileEntity maps these fields to maxAllowed* column names. This schema/mapping mismatch will break persistence for the new quotas.

Suggested fix (make migration names match entity mappings)
-            <column name="max_allowed_balance_adjustement" type="INTEGER"/>
+            <column name="maxAllowedBalanceAdjustement" type="INTEGER"/>
...
-            <column name="max_allowed_dynamic_margin" type="INTEGER"/>
+            <column name="maxAllowedDynamicMargin" type="INTEGER"/>
...
-            <column name="max_allowed_dynamic_security" type="INTEGER"/>
+            <column name="maxAllowedDynamicSecurity" type="INTEGER"/>
...
-            <column name="max_allowed_dynamic_simulation" type="INTEGER"/>
+            <column name="maxAllowedDynamicSimulation" type="INTEGER"/>
...
-            <column name="max_allowed_loadflow" type="INTEGER"/>
+            <column name="maxAllowedLoadflow" type="INTEGER"/>
...
-            <column name="max_allowed_pcc_min" type="INTEGER"/>
+            <column name="maxAllowedPccMin" type="INTEGER"/>
...
-            <column name="max_allowed_security" type="INTEGER"/>
+            <column name="maxAllowedSecurity" type="INTEGER"/>
...
-            <column name="max_allowed_sensitivity" type="INTEGER"/>
+            <column name="maxAllowedSensitivity" type="INTEGER"/>
...
-            <column name="max_allowed_short_circuit" type="INTEGER"/>
+            <column name="maxAllowedShortCircuit" type="INTEGER"/>
...
-            <column name="max_allowed_state_estimation" type="INTEGER"/>
+            <column name="maxAllowedStateEstimation" type="INTEGER"/>
...
-            <column name="max_allowed_voltage_init" type="INTEGER"/>
+            <column name="maxAllowedVoltageInit" type="INTEGER"/>
🤖 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/resources/db/changelog/changesets/changelog_20260616T125854Z.xml`
around lines 5 - 55, The Liquibase changeset column names use snake_case
(max_allowed_*) but the UserProfileEntity JPA entity maps these fields to
camelCase column names (maxAllowed*). Update all column name attributes in the
addColumn elements across all changeSet entries (ids 1781614744540-2 through
1781614744540-12) to change from the snake_case format
(max_allowed_balance_adjustement, max_allowed_dynamic_margin,
max_allowed_dynamic_security, max_allowed_dynamic_simulation,
max_allowed_loadflow, max_allowed_pcc_min, max_allowed_security,
max_allowed_sensitivity, max_allowed_short_circuit,
max_allowed_state_estimation, max_allowed_voltage_init, etc.) to the camelCase
format that matches the entity mapping (maxAllowedBalanceAdjustement,
maxAllowedDynamicMargin, maxAllowedDynamicSecurity, maxAllowedDynamicSimulation,
maxAllowedLoadflow, maxAllowedPccMin, maxAllowedSecurity, maxAllowedSensitivity,
maxAllowedShortCircuit, maxAllowedStateEstimation, maxAllowedVoltageInit, etc.)
to ensure the database schema aligns with the JPA entity column name mappings.

Signed-off-by: Etienne LESOT <etienne.lesot@rte-france.com>
Signed-off-by: Etienne LESOT <etienne.lesot@rte-france.com>
Signed-off-by: Etienne LESOT <etienne.lesot@rte-france.com>
UUID pccMinParameterId,
UUID voltageInitParameterId,
Boolean allLinksValid,
Map<String, Integer> maxAllowValuesMap,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maxAllowValuesMap is not copied and is returned/accessed as is without modification protection

Signed-off-by: Etienne LESOT <etienne.lesot@rte-france.com>

@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: 2

🧹 Nitpick comments (3)
src/test/java/org/gridsuite/useradmin/server/UserProfileTest.java (1)

401-408: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore the MAX_ALLOWED_CASES round-trip assertion.

This helper updates both quota entries on Lines 279-280, but the verification only checks MAX_ALLOWED_BUILD. A regression in the cases mapping would now pass unnoticed.

Suggested fix
             assertEquals(newData.getVoltageInitParameterId(), updatedProfile.getVoltageInitParameterId());
+            assertEquals(newData.getMaxAllowValuesMap().get(MAX_ALLOWED_CASES), updatedProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_CASES));
             assertEquals(newData.getMaxAllowValuesMap().get(MAX_ALLOWED_BUILD), updatedProfile.getMaxAllowValuesMap().get(MAX_ALLOWED_BUILD));
             assertNull(updatedProfile.getAllLinksValid()); // validity not set in this case
🤖 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/useradmin/server/UserProfileTest.java` around
lines 401 - 408, The round-trip verification in UserProfileTest is missing the
MAX_ALLOWED_CASES assertion, so a regression in the cases quota mapping could
slip through. Update the test’s updatedProfile checks to also compare the
MAX_ALLOWED_CASES entry from newData.getMaxAllowValuesMap() against
updatedProfile.getMaxAllowValuesMap(), alongside the existing MAX_ALLOWED_BUILD
assertion.
src/test/java/org/gridsuite/useradmin/server/NoQuotaTest.java (2)

88-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the no-profile branch for the new max-* endpoints.

Lines 92-93 only verify max-builds and max-cases when the user has no associated profile. The new quota accessors are only exercised later for a user linked to an empty profile, which is a different branch. Adding the remaining max-* assertions here would close that gap.

Suggested change
     `@Test`
     void testUserCreationWithoutProfile() throws Exception {
         createUser(USER_SUB);

         assertTrue(getMaxAllowedBuilds(USER_SUB).isEmpty());
         assertTrue(createMaxAllowedCases(USER_SUB).isEmpty());
+        assertTrue(getMaxAllowedLoadflow(USER_SUB).isEmpty());
+        assertTrue(getMaxAllowedSecurity(USER_SUB).isEmpty());
+        assertTrue(getMaxAllowedSensitivity(USER_SUB).isEmpty());
+        assertTrue(getMaxAllowedShortCircuit(USER_SUB).isEmpty());
+        assertTrue(getMaxAllowedVoltageInit(USER_SUB).isEmpty());
+        assertTrue(getMaxAllowedPccMin(USER_SUB).isEmpty());
+        assertTrue(getMaxAllowedStateEstimation(USER_SUB).isEmpty());
+        assertTrue(getMaxAllowedBalanceAdjustement(USER_SUB).isEmpty());
+        assertTrue(getMaxAllowedDynamicSimulation(USER_SUB).isEmpty());
+        assertTrue(getMaxAllowedDynamicSecurity(USER_SUB).isEmpty());
+        assertTrue(getMaxAllowedDynamicMargin(USER_SUB).isEmpty());
     }
🤖 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/useradmin/server/NoQuotaTest.java` around lines
88 - 94, The no-profile test currently only covers the existing max-builds and
max-cases endpoints, leaving the new max-* accessors untested for the branch
where a user has no associated profile. Update testUserCreationWithoutProfile in
NoQuotaTest to also assert the new quota endpoints return empty results for
USER_SUB, using the existing helper methods on the same test flow so this branch
is fully covered.

117-185: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Collapse the quota assertions and endpoint wrappers into one table-driven helper.

Lines 124-136, 173-185, and 214-264 manually mirror every quota key and endpoint, and the same helper pattern is duplicated in src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java. With 13 quota dimensions now, this is getting expensive to keep in sync.

Also applies to: 214-264

🤖 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/useradmin/server/NoQuotaTest.java` around lines
117 - 185, The quota checks in NoQuotaTest are duplicated across many individual
assertions and endpoint helper methods, making them hard to maintain as quota
dimensions grow. Refactor the repeated max-allowed assertions in createProfile
and the per-quota endpoint wrappers into a single table-driven helper that
iterates over quota keys and expected values, using the existing symbols
createMaxAllowedCases, createProfile(String, Map<String, Integer>), and the
getMaxAllowed* methods as the consolidation point. Mirror the same helper
pattern in UserAdminTest so both test classes share the same maintainable
structure instead of manually repeating each quota dimension.
🤖 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/useradmin/server/dto/UserProfile.java`:
- Around line 18-50: The UserProfile DTO change breaks the existing /profiles
JSON contract by exposing maxAllowValuesMap instead of the top-level maxAllowed*
fields. Update UserProfile so Jackson still serializes/deserializes the legacy
property names while keeping the internal map, using the UserProfile class and
createDefaultProfile builder path as the main locations to adjust. Preserve
backward compatibility with existing clients by adding aliases/accessors or
another compatibility layer rather than changing the payload shape outright.

In `@src/main/resources/db/changelog/changesets/changelog_20260616T125854Z.xml`:
- Around line 5-55: Remove the hard-coded defaultValue settings from the new
quota columns in the Liquibase changeset so the schema no longer assigns 1/2
automatically. Update the user_profile addColumn entries in
changelog_20260616T125854Z.xml to keep these fields nullable and let
UserProfileService.toEntity(...) apply UserAdminApplicationProps fallbacks when
values are absent. Ensure the affected changeSet entries for the max_allowed_*
columns preserve the config-driven behavior instead of persisting defaults at
the DB level.

---

Nitpick comments:
In `@src/test/java/org/gridsuite/useradmin/server/NoQuotaTest.java`:
- Around line 88-94: The no-profile test currently only covers the existing
max-builds and max-cases endpoints, leaving the new max-* accessors untested for
the branch where a user has no associated profile. Update
testUserCreationWithoutProfile in NoQuotaTest to also assert the new quota
endpoints return empty results for USER_SUB, using the existing helper methods
on the same test flow so this branch is fully covered.
- Around line 117-185: The quota checks in NoQuotaTest are duplicated across
many individual assertions and endpoint helper methods, making them hard to
maintain as quota dimensions grow. Refactor the repeated max-allowed assertions
in createProfile and the per-quota endpoint wrappers into a single table-driven
helper that iterates over quota keys and expected values, using the existing
symbols createMaxAllowedCases, createProfile(String, Map<String, Integer>), and
the getMaxAllowed* methods as the consolidation point. Mirror the same helper
pattern in UserAdminTest so both test classes share the same maintainable
structure instead of manually repeating each quota dimension.

In `@src/test/java/org/gridsuite/useradmin/server/UserProfileTest.java`:
- Around line 401-408: The round-trip verification in UserProfileTest is missing
the MAX_ALLOWED_CASES assertion, so a regression in the cases quota mapping
could slip through. Update the test’s updatedProfile checks to also compare the
MAX_ALLOWED_CASES entry from newData.getMaxAllowValuesMap() against
updatedProfile.getMaxAllowValuesMap(), alongside the existing MAX_ALLOWED_BUILD
assertion.
🪄 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: e66604c6-a68a-4240-95d4-66046e18881c

📥 Commits

Reviewing files that changed from the base of the PR and between 0be3953 and 3d1740d.

📒 Files selected for processing (10)
  • src/main/java/org/gridsuite/useradmin/server/dto/UserProfile.java
  • src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java
  • src/main/java/org/gridsuite/useradmin/server/service/UserProfileService.java
  • src/main/resources/config/application.yaml
  • src/main/resources/db/changelog/changesets/changelog_20260616T125854Z.xml
  • src/test/java/org/gridsuite/useradmin/server/NoQuotaTest.java
  • src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java
  • src/test/java/org/gridsuite/useradmin/server/UserProfileTest.java
  • src/test/resources/application-default.yml
  • src/test/resources/application-noquota.yml
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/org/gridsuite/useradmin/server/service/UserProfileService.java
  • src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java
  • src/main/java/org/gridsuite/useradmin/server/service/UserAdminService.java

Comment on lines +18 to +50
@Getter
@Builder
public final class UserProfile {
public static final String DEFAULT_PROFILE_NAME = "default profile";
public static final String MAX_ALLOWED_CASES = "maxAllowedCases";
public static final String MAX_ALLOWED_BUILD = "maxAllowedBuilds";
public static final String MAX_ALLOWED_LOADFLOW = "maxAllowedLoadflow";
public static final String MAX_ALLOWED_SECURITY = "maxAllowedSecurity";
public static final String MAX_ALLOWED_SENSITIVITY = "maxAllowedSensitivity";
public static final String MAX_ALLOWED_SHORT_CIRCUIT = "maxAllowedShortCircuit";
public static final String MAX_ALLOWED_VOLTAGE_INIT = "maxAllowedVoltageInit";
public static final String MAX_ALLOWED_PCC_MIN = "maxAllowedPccMin";
public static final String MAX_ALLOWED_STATE_ESTIMATION = "maxAllowedStateEstimation";
public static final String MAX_ALLOWED_BALANCE_ADJUSTEMENT = "maxAllowedBalanceAdjustement";
public static final String MAX_ALLOWED_DYNAMIC_SIMULATION = "maxAllowedDynamicSimulation";
public static final String MAX_ALLOWED_DYNAMIC_SECURITY = "maxAllowedDynamicSecurity";
public static final String MAX_ALLOWED_DYNAMIC_MARGIN = "maxAllowedDynamicMargin";
private final UUID id;
private final String name;
private final UUID loadFlowParameterId;
private final UUID securityAnalysisParameterId;
private final UUID sensitivityAnalysisParameterId;
private final UUID shortcircuitParameterId;
private final UUID pccMinParameterId;
private final UUID voltageInitParameterId;
private final Boolean allLinksValid;
private final Map<String, Integer> maxAllowValuesMap;
private final UUID spreadsheetConfigCollectionId;
private final UUID networkVisualizationParameterId;
private final UUID workspaceId;

public static UserProfile createDefaultProfile(Integer maxAllowedCases, Integer maxAllowedBuilds) {
return new UserProfile(
null,
DEFAULT_PROFILE_NAME,
null,
null,
null,
null,
null,
null,
null,
maxAllowedCases,
maxAllowedBuilds,
null,
null,
null
);
public static UserProfile createDefaultProfile(Map<String, Integer> maxAllowValues) {
return UserProfile.builder().name(DEFAULT_PROFILE_NAME).maxAllowValuesMap(maxAllowValues).build();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

This breaks the existing /profiles JSON contract.

The DTO no longer exposes top-level maxAllowedCases / maxAllowedBuilds; callers now have to send and read maxAllowValuesMap instead. UserProfileTest was updated to post the new map shape, so existing clients of the current API version will break unless you keep backward-compatible aliases/accessors or version the endpoint.

🤖 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/useradmin/server/dto/UserProfile.java` around
lines 18 - 50, The UserProfile DTO change breaks the existing /profiles JSON
contract by exposing maxAllowValuesMap instead of the top-level maxAllowed*
fields. Update UserProfile so Jackson still serializes/deserializes the legacy
property names while keeping the internal map, using the UserProfile class and
createDefaultProfile builder path as the main locations to adjust. Preserve
backward compatibility with existing clients by adding aliases/accessors or
another compatibility layer rather than changing the payload shape outright.

Comment on lines +5 to +55
<column name="max_allowed_loadflow" type="INTEGER" defaultValue="2"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-3">
<addColumn tableName="user_profile">
<column name="max_allowed_security" type="INTEGER" defaultValue="2"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-4">
<addColumn tableName="user_profile">
<column name="max_allowed_sensitivity" type="INTEGER" defaultValue="1"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-5">
<addColumn tableName="user_profile">
<column name="max_allowed_short_circuit" type="INTEGER" defaultValue="1"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-6">
<addColumn tableName="user_profile">
<column name="max_allowed_voltage_init" type="INTEGER" defaultValue="2"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-7">
<addColumn tableName="user_profile">
<column name="max_allowed_pcc_min" type="INTEGER" defaultValue="1"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-8">
<addColumn tableName="user_profile">
<column name="max_allowed_state_estimation" type="INTEGER" defaultValue="1"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-9">
<addColumn tableName="user_profile">
<column name="max_allowed_balance_adjustement" type="INTEGER" defaultValue="1"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-10">
<addColumn tableName="user_profile">
<column name="max_allowed_dynamic_simulation" type="INTEGER" defaultValue="1"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-11">
<addColumn tableName="user_profile">
<column name="max_allowed_dynamic_security" type="INTEGER" defaultValue="1"/>
</addColumn>
</changeSet>
<changeSet author="lesoteti (generated)" id="1781614744540-12">
<addColumn tableName="user_profile">
<column name="max_allowed_dynamic_margin" type="INTEGER" defaultValue="1"/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remove the hard-coded DB defaults for these new quota columns.

These defaultValues bypass the config contract: application-noquota.yml makes the quotas nullable, and UserProfileService.toEntity(...) already applies UserAdminApplicationProps fallbacks when values are missing. Keeping 1/2 at the schema level will persist the wrong quotas for migrated rows and any insert path that omits these columns.

Suggested fix
-            <column name="max_allowed_loadflow" type="INTEGER" defaultValue="2"/>
+            <column name="max_allowed_loadflow" type="INTEGER"/>
...
-            <column name="max_allowed_dynamic_margin" type="INTEGER" defaultValue="1"/>
+            <column name="max_allowed_dynamic_margin" type="INTEGER"/>
🤖 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/resources/db/changelog/changesets/changelog_20260616T125854Z.xml`
around lines 5 - 55, Remove the hard-coded defaultValue settings from the new
quota columns in the Liquibase changeset so the schema no longer assigns 1/2
automatically. Update the user_profile addColumn entries in
changelog_20260616T125854Z.xml to keep these fields nullable and let
UserProfileService.toEntity(...) apply UserAdminApplicationProps fallbacks when
values are absent. Ensure the affected changeSet entries for the max_allowed_*
columns preserve the config-driven behavior instead of persisting defaults at
the DB level.

Signed-off-by: Etienne LESOT <etienne.lesot@rte-france.com>
@GhilesA GhilesA self-requested a review June 26, 2026 09:20
@sonarqubecloud

Copy link
Copy Markdown

@EtienneLt EtienneLt merged commit c0ae740 into main Jun 29, 2026
5 checks passed
@EtienneLt EtienneLt deleted the add-max-allow-computations branch June 29, 2026 12:44
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