Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
afacd2b
fix
ghazwarhili Jul 22, 2026
48fe04b
Merge branch 'main' into export-import-study
ghazwarhili Jul 22, 2026
d846b9a
clean code
ghazwarhili Jul 27, 2026
97c48d3
Merge branch 'main' into export-import-study
ghazwarhili Jul 28, 2026
4e89ced
clean code
ghazwarhili Jul 28, 2026
af818e8
clean code
ghazwarhili Jul 28, 2026
c6e2f3e
add TU
ghazwarhili Jul 29, 2026
046507e
Merge branch 'main' into export-import-study
ghazwarhili Jul 29, 2026
c7a06f8
add stub for directory service
ghazwarhili Jul 29, 2026
92204b4
add stub for cases-alert-threshold
ghazwarhili Jul 29, 2026
4547f1f
fix sonar coverage issues
ghazwarhili Jul 29, 2026
7c917c3
fix sonar issue
ghazwarhili Jul 29, 2026
1370512
fix import
ghazwarhili Jul 29, 2026
59e411b
enhance and clean code
ghazwarhili Aug 5, 2026
f0143af
Merge branch 'main' into export-import-study
ghazwarhili Aug 5, 2026
185e8c8
resolve conflicts
ghazwarhili Aug 5, 2026
e4136a8
add node type
ghazwarhili Aug 5, 2026
28faa63
fix duplicated param
ghazwarhili Aug 6, 2026
27d5681
Merge branch 'main' into export-import-study
ghazwarhili Aug 6, 2026
446d425
code review remarks
ghazwarhili Aug 10, 2026
e90d641
merge main
ghazwarhili Aug 10, 2026
d932a79
fix import workflow
ghazwarhili Aug 11, 2026
72a21e0
clean endpoint
ghazwarhili Aug 14, 2026
14f47b2
enhance TU
ghazwarhili Aug 14, 2026
1b566e8
sonar issues fix
ghazwarhili Aug 14, 2026
6b678dd
fix sonar issues
ghazwarhili Aug 17, 2026
1a02fb5
import study pamaters
ghazwarhili Aug 18, 2026
bf1bee9
Revert "import study pamaters"
ghazwarhili Aug 18, 2026
ef2f631
code review part 1
ghazwarhili Sep 1, 2026
94a6997
fix code review
ghazwarhili Sep 4, 2026
ba0b85d
fix tu
ghazwarhili Sep 4, 2026
48afd93
clean code
ghazwarhili Sep 7, 2026
65686ae
code review part 1
ghazwarhili Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.gridsuite.explore.server.dto.ReferencingElementInfos;
import org.gridsuite.explore.server.services.DirectoryService;
import org.gridsuite.explore.server.services.ExploreService;
import org.gridsuite.explore.server.services.StudyImportService;
import org.gridsuite.explore.server.utils.ContingencyListType;
import org.gridsuite.explore.server.utils.ParametersType;
import org.springframework.http.HttpStatus;
Expand All @@ -42,6 +43,7 @@ public class ExploreController {

// /!\ This query parameter is used by the gateway to control access
private static final String QUERY_PARAM_NAME = "name";
private static final String QUERY_PARAM_STUDY_NAME = "studyName";
private static final String QUERY_PARAM_DESCRIPTION = "description";
private static final String QUERY_PARAM_PARENT_DIRECTORY_ID = "parentDirectoryUuid";

Expand All @@ -50,10 +52,12 @@ public class ExploreController {

private final ExploreService exploreService;
private final DirectoryService directoryService;
private final StudyImportService studyImportService;

public ExploreController(ExploreService exploreService, DirectoryService directoryService) {
public ExploreController(ExploreService exploreService, DirectoryService directoryService, StudyImportService studyImportService) {
this.exploreService = exploreService;
this.directoryService = directoryService;
this.studyImportService = studyImportService;
}

@PostMapping(value = "/explore/studies/{studyName}/cases/{caseUuid}")
Expand Down Expand Up @@ -778,4 +782,18 @@ public ResponseEntity<UUID> duplicateDynamicMapping(@PathVariable("id") UUID id,
UUID newDynamicMappingUuid = exploreService.duplicateDynamicMapping(id, targetDirectoryId, userId);
return ResponseEntity.ofNullable(newDynamicMappingUuid);
}

@PostMapping(value = "/explore/studies/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Import a study from an archive")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Study import started asynchronously")})
@PreAuthorize("@authorizationService.isAuthorized(#userId, #parentDirectoryUuid, null, T(org.gridsuite.explore.server.dto.PermissionType).WRITE)")
public ResponseEntity<Void> importStudy(@RequestParam(QUERY_PARAM_STUDY_NAME) String studyName,
@RequestPart("archiveFile") MultipartFile archiveFile,
@RequestParam(QUERY_PARAM_DESCRIPTION) String description,
@RequestParam(QUERY_PARAM_PARENT_DIRECTORY_ID) UUID parentDirectoryUuid,
@RequestHeader(QUERY_PARAM_USER_ID) String userId) {
exploreService.assertCanCreateCase(userId);
studyImportService.importStudy(archiveFile, studyName, description, userId, parentDirectoryUuid);
return ResponseEntity.ok().build();
}
}
20 changes: 20 additions & 0 deletions src/main/java/org/gridsuite/explore/server/dto/CaseInfos.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* 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.explore.server.dto;

import java.util.UUID;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public record CaseInfos(
UUID caseUuid,
UUID originalCaseUuid,
String caseName,
String caseFormat
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* 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.explore.server.dto;

import java.util.List;
import java.util.UUID;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public record NodeTreeExportInfos(
String name,
String type,
UUID modificationGroupUuid,
String nodeType,
List<NodeTreeExportInfos> children
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* 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.explore.server.dto;

import java.util.Map;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public record RootNetworkExportInfos(
String name,
String tag,
Integer index,
CaseInfos caseInfos,
Map<String, Object> importParameters
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* 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.explore.server.dto;

import java.util.List;
import java.util.UUID;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public record TreeExportInfos(
UUID studyUuid,
List<RootNetworkExportInfos> rootNetworks,
NodeTreeExportInfos nodeTree
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
* Business error codes emitted by the explore service.
*/
public enum ExploreBusinessErrorCode implements BusinessErrorCode {
EXPLORE_MAX_ELEMENTS_EXCEEDED("explore.maxElementsExceeded");
EXPLORE_MAX_ELEMENTS_EXCEEDED("explore.maxElementsExceeded"),
IMPORT_STUDY_FAILED("explore.importStudyFailed");

private final String code;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ public ExploreException(ExploreBusinessErrorCode errorCode, String message, Map<
this.businessErrorValues = businessErrorValues != null ? Map.copyOf(businessErrorValues) : Map.of();
}

public ExploreException(ExploreBusinessErrorCode errorCode, String message, Throwable cause) {
super(Objects.requireNonNull(message, "message must not be null"), cause);
this.errorCode = Objects.requireNonNull(errorCode, "errorCode must not be null");
this.businessErrorValues = Map.of();
}

public static ExploreException of(ExploreBusinessErrorCode errorCode, String message, Object... args) {
return new ExploreException(errorCode, args.length == 0 ? message : String.format(message, args));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ protected ExploreBusinessErrorCode getBusinessCode(ExploreException ex) {
protected HttpStatus mapStatus(ExploreBusinessErrorCode errorCode) {
return switch (errorCode) {
case EXPLORE_MAX_ELEMENTS_EXCEEDED -> HttpStatus.FORBIDDEN;
case IMPORT_STUDY_FAILED -> HttpStatus.INTERNAL_SERVER_ERROR;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -27,7 +28,7 @@
@Service
public class CaseService implements IDirectoryElementsService {
private static final String CASE_SERVER_API_VERSION = "v1";

private static final String CASES_URL = "cases";
private static final String DELIMITER = "/";
private final RestTemplate restTemplate;
private String caseServerBaseUri;
Expand All @@ -53,7 +54,7 @@ UUID importCase(MultipartFile multipartFile) {
}
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(
body, headers);
caseUuid = restTemplate.postForObject(caseServerBaseUri + "/" + CASE_SERVER_API_VERSION + "/cases", request,
caseUuid = restTemplate.postForObject(caseServerBaseUri + "/" + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL, request,
UUID.class);
return caseUuid;
}
Expand All @@ -69,47 +70,62 @@ public UUID importCaseWithoutDirectoryElementCreation(MultipartFile multipartFil
body.add("withExpiration", withExpiration);
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(body, headers);

String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL)
.buildAndExpand()
.toUriString();
return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.POST, request, UUID.class).getBody();
}

public ResponseEntity<Resource> downloadCase(UUID caseUuid) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/{caseUuid}")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "{caseUuid}")
.buildAndExpand(caseUuid)
.toUriString();

return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.GET, null, Resource.class);
}

public Void deleteCase(UUID caseUuid) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/{caseUuid}")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "{caseUuid}")
.buildAndExpand(caseUuid)
.toUriString();

return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.DELETE, null, Void.class).getBody();
}

public String getBaseName(String caseName) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/caseBaseName")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "caseBaseName")
.queryParam("caseName", caseName)
.buildAndExpand()
.toUriString();

return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.GET, null, String.class).getBody();
}

public UUID importCaseFromFile(File file) {
Comment thread
ghazwarhili marked this conversation as resolved.
Outdated
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);

body.add("file", new org.springframework.core.io.FileSystemResource(file));

HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(body, headers);
return restTemplate.postForObject(
caseServerBaseUri + "/" + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL,
request,
UUID.class
);
}

void persistCase(UUID caseUuid) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/" + caseUuid + "/disableExpiration")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + DELIMITER + CASES_URL + DELIMITER + caseUuid + "/disableExpiration")
.buildAndExpand()
.toUriString();

restTemplate.exchange(caseServerBaseUri + path, HttpMethod.PUT, new HttpEntity<>(new HttpHeaders()), void.class);
}

UUID duplicateCase(UUID caseId) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/{uuid}/duplicate")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "{uuid}/duplicate")
.buildAndExpand(caseId)
.toUriString();
HttpHeaders headers = new HttpHeaders();
Expand All @@ -120,7 +136,7 @@ UUID duplicateCase(UUID caseId) {

@Override
public void delete(UUID id, String userId) {
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/{id}")
String path = UriComponentsBuilder.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "{id}")
.buildAndExpand(id)
.toUriString();
HttpHeaders headers = new HttpHeaders();
Expand All @@ -132,7 +148,7 @@ public void delete(UUID id, String userId) {
public List<Map<String, Object>> getMetadata(List<UUID> casesUuids) {
var ids = casesUuids.stream().map(UUID::toString).collect(Collectors.joining(","));
String path = UriComponentsBuilder
.fromPath(DELIMITER + CASE_SERVER_API_VERSION + "/cases/metadata" + "?ids=" + ids)
.fromPath(DELIMITER + CASE_SERVER_API_VERSION + DELIMITER + CASES_URL + DELIMITER + "metadata" + "?ids=" + ids)
.buildAndExpand()
.toUriString();
return restTemplate.exchange(caseServerBaseUri + path, HttpMethod.GET, null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ public UUID duplicateDynamicMapping(UUID sourceDynamicMappingUuid, UUID targetDi
return newDynamicMappingUuid;
}

private void createDirectoryElementOrDeleteElement(ElementAttributes elementAttributes, UUID parentDirectoryUuid, String userId, BiConsumer<UUID, String> rollback) {
void createDirectoryElementOrDeleteElement(ElementAttributes elementAttributes, UUID parentDirectoryUuid, String userId, BiConsumer<UUID, String> rollback) {
executeWithRollback(() -> directoryService.createElement(elementAttributes, parentDirectoryUuid, userId), elementAttributes.getElementUuid(), userId, rollback);
}

Expand Down
Loading
Loading