-
Notifications
You must be signed in to change notification settings - Fork 2
import study #1057
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
import study #1057
Changes from 42 commits
f5553f3
c22592d
84ff55c
aa595a3
db1e926
e04ee14
34e39f6
7b241aa
6b0c1c8
2ca0b42
115841b
836cbfd
83bb25b
107d377
ca94ad3
a217703
4c79e99
43b9d7d
ae80bc2
53a1e22
cf2cda1
6223d03
b1fb871
67b9307
f504693
7742935
f613fb4
05f683e
9dfe44c
51776b7
d3c6cb8
df48eb0
52c67f8
15797be
d8e6c85
4d85977
61f2dd2
91b55b3
14d9d7b
fae45b0
a4caf4d
92964ef
56a783d
c2e2a97
a4c11be
6671a9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,7 +70,6 @@ public class ConsumerService { | |
| private final CaseService caseService; | ||
| private final LoadFlowRestService loadFlowRestService; | ||
| private final NetworkModificationTreeService networkModificationTreeService; | ||
| private final StudyConfigService studyConfigService; | ||
| private final RootNetworkNodeInfoService rootNetworkNodeInfoService; | ||
| private final RootNetworkService rootNetworkService; | ||
| private final DirectoryService directoryService; | ||
|
|
@@ -86,7 +85,6 @@ public ConsumerService(ObjectMapper objectMapper, | |
| CaseService caseService, | ||
| LoadFlowRestService loadFlowRestService, | ||
| NetworkModificationTreeService networkModificationTreeService, | ||
| StudyConfigService studyConfigService, | ||
| RootNetworkNodeInfoService rootNetworkNodeInfoService, | ||
| RootNetworkService rootNetworkService, | ||
| DirectoryService directoryService, | ||
|
|
@@ -101,7 +99,6 @@ public ConsumerService(ObjectMapper objectMapper, | |
| this.caseService = caseService; | ||
| this.loadFlowRestService = loadFlowRestService; | ||
| this.networkModificationTreeService = networkModificationTreeService; | ||
| this.studyConfigService = studyConfigService; | ||
| this.rootNetworkNodeInfoService = rootNetworkNodeInfoService; | ||
| this.rootNetworkService = rootNetworkService; | ||
| this.directoryService = directoryService; | ||
|
|
@@ -292,73 +289,15 @@ private void insertStudy(UUID studyUuid, String userId, NetworkInfos networkInfo | |
| UserProfileInfos userProfileInfos = studyService.getUserProfile(userId); | ||
|
|
||
| ComputationParameterUUIDs computationParameterUUIDs = computationParametersService.createDefaultComputationParameters(userId, userProfileInfos); | ||
| UUID networkVisualizationParametersUuid = createDefaultNetworkVisualizationParameters(userId, userProfileInfos); | ||
| UUID spreadsheetConfigCollectionUuid = createDefaultSpreadsheetConfigCollection(userId, userProfileInfos); | ||
| UUID workspacesConfigUuid = createWorkspacesConfig(userProfileInfos); | ||
| UUID networkVisualizationParametersUuid = studyService.createDefaultNetworkVisualizationParameters(userId, userProfileInfos); | ||
| UUID spreadsheetConfigCollectionUuid = studyService.createDefaultSpreadsheetConfigCollection(userId, userProfileInfos); | ||
| UUID workspacesConfigUuid = studyService.createWorkspacesConfig(userProfileInfos); | ||
|
|
||
| studyService.insertStudy(studyUuid, userId, networkInfos, caseInfos, computationParameterUUIDs, | ||
| networkVisualizationParametersUuid, spreadsheetConfigCollectionUuid, workspacesConfigUuid, | ||
| importParameters, importReportUuid); | ||
| } | ||
|
|
||
| private UUID createDefaultNetworkVisualizationParameters(String userId, UserProfileInfos userProfileInfos) { | ||
| if (userProfileInfos != null && userProfileInfos.getNetworkVisualizationParameterId() != null) { | ||
| // try to access/duplicate the user profile network visualization parameters | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not remove comment. The code should be fully unchanged for this methods ! |
||
| try { | ||
| return studyConfigService.duplicateNetworkVisualizationParameters(userProfileInfos.getNetworkVisualizationParameterId()); | ||
| } catch (Exception e) { | ||
| // TODO try to report a log in Root subreporter ? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here ! |
||
| LOGGER.error(String.format("Could not duplicate network visualization parameters with id '%s' from user/profile '%s/%s'. Using default parameters", | ||
| userProfileInfos.getNetworkVisualizationParameterId(), userId, userProfileInfos.getName()), e); | ||
| } | ||
| } | ||
| // no profile, or no/bad network visualization parameters in profile => use default values | ||
| try { | ||
| return studyConfigService.createDefaultNetworkVisualizationParameters(); | ||
| } catch (final Exception e) { | ||
| LOGGER.error("Error while creating network visualization default parameters", e); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| private UUID createDefaultSpreadsheetConfigCollection(String userId, UserProfileInfos userProfileInfos) { | ||
| if (userProfileInfos != null && userProfileInfos.getSpreadsheetConfigCollectionId() != null) { | ||
| // try to access/duplicate the user profile spreadsheet config collection | ||
| try { | ||
| return studyConfigService.duplicateSpreadsheetConfigCollection(userProfileInfos.getSpreadsheetConfigCollectionId()); | ||
| } catch (Exception e) { | ||
| // TODO try to report a log in Root subreporter ? | ||
| LOGGER.error(String.format("Could not duplicate spreadsheet config collection with id '%s' from user/profile '%s/%s'. Using default spreadsheet config collection", | ||
| userProfileInfos.getSpreadsheetConfigCollectionId(), userId, userProfileInfos.getName()), e); | ||
| } | ||
| } | ||
| // no profile, or no/bad spreadsheet config collection in profile => use default values | ||
| try { | ||
| return studyConfigService.createDefaultSpreadsheetConfigCollection(); | ||
| } catch (final Exception e) { | ||
| LOGGER.error("Error while creating default spreadsheet config collection", e); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| @SuppressWarnings("checkstyle:LambdaBodyLength") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not needed ? |
||
| private UUID createWorkspacesConfig(UserProfileInfos userProfileInfos) { | ||
| try { | ||
| List<UUID> workspaceIds = new ArrayList<>(); | ||
| if (userProfileInfos != null && userProfileInfos.getWorkspaceId() != null) { | ||
| // Create config with profile workspace as first, and two empty workspaces | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same with comment |
||
| workspaceIds.add(userProfileInfos.getWorkspaceId()); | ||
| workspaceIds.add(null); | ||
| workspaceIds.add(null); | ||
| } | ||
| // Empty list will create default config | ||
| return studyConfigService.createWorkspacesConfigFromWorkspaces(workspaceIds); | ||
| } catch (final Exception e) { | ||
| LOGGER.error("Error while creating workspace collection", e); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| @Bean | ||
| @SuppressWarnings("checkstyle:LambdaBodyLength") | ||
| public Consumer<Message<String>> consumeCaseImportFailed() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| /** | ||
| * Copyright (c) 2026, RTE (http://www.rte-france.com) | ||
| * This Source Code Form is subject to the terms of the Mozilla Public | ||
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
| */ | ||
| package org.gridsuite.study.server.service; | ||
|
|
||
| import org.gridsuite.study.server.dto.CaseInfos; | ||
| import org.gridsuite.study.server.dto.NetworkInfos; | ||
| import org.gridsuite.study.server.dto.RootNetworkInfos; | ||
| import org.gridsuite.study.server.dto.RootNetworkLoadStatus; | ||
| import org.gridsuite.study.server.dto.studyexport.RootNetworkExportInfos; | ||
| import org.gridsuite.study.server.dto.studyexport.TreeExportInfos; | ||
| import org.gridsuite.study.server.notification.NotificationService; | ||
| import org.gridsuite.study.server.repository.StudyEntity; | ||
| import org.gridsuite.study.server.repository.rootnetwork.RootNetworkEntity; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Comparator; | ||
| import java.util.List; | ||
| import java.util.UUID; | ||
|
|
||
| /** | ||
| * @author Ghazwa Rehili <ghazwa.rehili at rte-france.com> | ||
| */ | ||
| @Service | ||
| public class StudyImportService { | ||
| private static final Logger LOGGER = LoggerFactory.getLogger(StudyImportService.class); | ||
|
|
||
| private final StudyService studyService; | ||
| private final RootNetworkService rootNetworkService; | ||
| private final CaseService caseService; | ||
| private final NotificationService notificationService; | ||
|
|
||
| public StudyImportService(StudyService studyService, RootNetworkService rootNetworkService, | ||
| CaseService caseService, NotificationService notificationService) { | ||
| this.studyService = studyService; | ||
| this.rootNetworkService = rootNetworkService; | ||
| this.caseService = caseService; | ||
| this.notificationService = notificationService; | ||
| } | ||
|
|
||
| @Transactional | ||
| public void importStudy(TreeExportInfos treeExportInfos, String userId) { | ||
| StudyEntity studyEntity = studyService.createStudyEntityWithTree(treeExportInfos.studyUuid(), userId, treeExportInfos.nodeTree()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🛡️ Analyzed with Security Review | 🔴 Critical | ⚡ Quick win IDOR Reachability: External Do not persist the imported study under the request UUID.
Generate a new UUID or reject collisions before persistence. #!/bin/bash
set -euo pipefail
rg -n -C 12 'importStudy\(|createStudyEntityWithTree\(|buildStudyEntity\(' src/main/java
rg -n -C 8 '`@Id`|GeneratedValue|UUID id' src/main/java/org/gridsuite/study/server/repository/StudyEntity.java🤖 Prompt for AI Agents🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 15 'createStudyEntityWithTree|studyInfosService\.add|studyInfosService\.(delete|remove)' \
src/main/java src/test/java
rg -n -C 8 'interface StudyInfosRepository|ElasticsearchRepository|TransactionManager' \
src/main/javaRepository: gridsuite/study-server Length of output: 44566 🤖 get_repo_knowledge executed:
Length of output: 35202 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- StudyImportService ---'
cat -n src/main/java/org/gridsuite/study/server/service/StudyImportService.java | sed -n '1,130p'
printf '%s\n' '--- StudyInfosService ---'
fd -i 'StudyInfosService.java' src/main/java src/test/java -x sh -c 'cat -n "$1"' sh {}
printf '%s\n' '--- transaction and delete bindings ---'
rg -n -C 12 'class StudyInfosService|studyInfosRepository|deleteByUuid|`@Transactional`|TransactionSynchronization|afterCommit|createStudyEntityWithTree' src/main/java/org/gridsuite/study/server/service src/main/java/org/gridsuite/study/server/elasticsearchRepository: gridsuite/study-server Length of output: 50379 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- import flow ---'
cat -n src/main/java/org/gridsuite/study/server/service/StudyImportService.java | sed -n '1,115p'
printf '%s\n' '--- StudyInfosService candidates ---'
git ls-files | rg 'StudyInfosService\.java$'
printf '%s\n' '--- StudyInfosService implementation ---'
service_file="$(git ls-files | rg 'StudyInfosService\.java$' | head -n 1)"
test -n "$service_file"
cat -n "$service_file"
printf '%s\n' '--- transaction synchronization in relevant sources ---'
rg -n -C 5 'TransactionSynchronization|afterCommit|TransactionSynchronizationManager' \
src/main/java/org/gridsuite/study/server/service/StudyImportService.java \
src/main/java/org/gridsuite/study/server/service/StudyService.java \
"$service_file" || trueRepository: gridsuite/study-server Length of output: 7932 Create the search entry only after a successful import.
Move the Elasticsearch write to an after-commit step, or delete it when the import fails. 🤖 Prompt for AI Agents |
||
| List<RootNetworkExportInfos> orderedRootNetworks = treeExportInfos.rootNetworks().stream() | ||
| .sorted(Comparator.comparing(RootNetworkExportInfos::index)) | ||
| .toList(); | ||
| List<UUID> duplicatedCaseUuids = new ArrayList<>(); | ||
| try { | ||
| for (RootNetworkExportInfos rootNetworkInfos : orderedRootNetworks) { | ||
| UUID newCaseUuid = caseService.duplicateCase(rootNetworkInfos.caseInfos().getCaseUuid(), true); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline src/main/java/org/gridsuite/study/server/service/CaseService.java \
--match 'duplicateCase|disableCaseExpiration' --view expanded
rg -n -C 8 'duplicateCase\(|disableCaseExpiration\(' src/main/java src/test/javaRepository: gridsuite/study-server Length of output: 44480 🤖 get_repo_knowledge executed:
Length of output: 46683 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- StudyImportService ---'
cat -n src/main/java/org/gridsuite/study/server/service/StudyImportService.java | sed -n '40,105p'
printf '%s\n' '--- CaseService ---'
cat -n src/main/java/org/gridsuite/study/server/service/CaseService.java | sed -n '50,84p'
printf '%s\n' '--- import-path expiration calls ---'
rg -n -C 6 'disableCaseExpiration|importStudy\(' src/main/java/org/gridsuite/study/server/service src/main/java/org/gridsuite/study/server/controllerRepository: gridsuite/study-server Length of output: 11372 Disable expiration for successfully imported cases.
🤖 Prompt for AI Agents |
||
| duplicatedCaseUuids.add(newCaseUuid); | ||
| RootNetworkEntity rootNetworkEntity = rootNetworkService.createRootNetwork(studyEntity, RootNetworkInfos.builder() | ||
| .id(UUID.randomUUID()) | ||
| .name(rootNetworkInfos.name()) | ||
| .tag(rootNetworkInfos.tag()) | ||
| .caseInfos(new CaseInfos(newCaseUuid, rootNetworkInfos.caseInfos().getOriginalCaseUuid(), | ||
| rootNetworkInfos.caseInfos().getCaseName(), rootNetworkInfos.caseInfos().getCaseFormat())) | ||
| .importParameters(rootNetworkInfos.importParameters()) | ||
| .networkInfos(new NetworkInfos(UUID.randomUUID(), "")) | ||
| .build()); | ||
| rootNetworkService.updateNetworkLoadStatus(rootNetworkEntity.getId(), RootNetworkLoadStatus.UNLOADED); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Isolate each root-network import transactionally
Use a separate transaction boundary for each root network. Catch failures around that boundary and continue with the remaining entries. Delete the study and emit 🤖 Prompt for AI Agents |
||
| } catch (Exception e) { | ||
| duplicatedCaseUuids.forEach(caseUuid -> { | ||
| try { | ||
| caseService.deleteCase(caseUuid); | ||
| } catch (Exception exception) { | ||
| LOGGER.error(String.format("Could not clean up orphaned case '%s' after import failure", caseUuid), exception); | ||
| } | ||
| }); | ||
| throw e; | ||
| } | ||
| notificationService.emitStudyCreationFinished(studyEntity.getId(), userId); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the OpenAPI response description for the synchronous import.
StudyController.importStudyreturns200only afterStudyImportService.importStudycompletes. Springdoc exposes this annotation in the repository's OpenAPI documentation, so the current description can mislead API consumers.📝 Proposed documentation fix
🤖 Prompt for AI Agents