Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -188,4 +188,11 @@ public ResponseEntity<Void> invalidateStudy(@PathVariable("studyUuid") UUID stud
return ResponseEntity.ok().build();
}

@GetMapping(value = "/studies/loaded")
@Operation(summary = "Get the study uuids whose network is currently loaded")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "List of the study uuids whose network is currently loaded")})
public ResponseEntity<List<UUID>> getLoadedStudies(@Parameter(description = "Study uuids to filter") @RequestParam("ids") List<UUID> studyUuids) {

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.

Could be moved to study controller as well no ?

return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(supervisionService.getLoadedStudyUuids(studyUuids));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* 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.dto;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
public enum NetworkLoadStatus {
LOADED,
UNLOADED,
LOADING,
UNLOADING
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import jakarta.persistence.*;
import lombok.*;
import org.gridsuite.study.server.dto.NetworkLoadStatus;
import org.gridsuite.study.server.dto.RootNetworkIndexationStatus;
import org.gridsuite.study.server.repository.rootnetwork.RootNetworkEntity;
import org.gridsuite.study.server.repository.voltageinit.StudyVoltageInitParametersEntity;
Expand Down Expand Up @@ -132,6 +133,11 @@ public class StudyEntity extends AbstractManuallyAssignedIdentifierEntity<UUID>
@Column(name = "mono_root", columnDefinition = "boolean default true")
private boolean monoRoot;

@Enumerated(EnumType.STRING)
@Column(name = "network_load_status")
@Builder.Default
private NetworkLoadStatus networkLoadStatus = NetworkLoadStatus.LOADED;

@Embedded
@Builder.Default
private SpreadsheetParametersEntity spreadsheetParameters = new SpreadsheetParametersEntity();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
*/
package org.gridsuite.study.server.repository;

import org.gridsuite.study.server.dto.NetworkLoadStatus;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

Expand All @@ -22,4 +24,6 @@
public interface StudyRepository extends JpaRepository<StudyEntity, UUID> {
@EntityGraph(attributePaths = {"rootNetworks"}, type = EntityGraph.EntityGraphType.LOAD)
Optional<StudyEntity> findWithRootNetworksById(UUID id);

List<StudyEntity> findAllByIdInAndNetworkLoadStatus(List<UUID> ids, NetworkLoadStatus networkLoadStatus);
}
10 changes: 10 additions & 0 deletions src/main/java/org/gridsuite/study/server/service/StudyService.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.gridsuite.study.server.dto.*;
import org.gridsuite.study.server.dto.InvalidateNodeTreeParameters.ComputationsInvalidationMode;
import org.gridsuite.study.server.dto.InvalidateNodeTreeParameters.InvalidationMode;
import org.gridsuite.study.server.dto.NetworkLoadStatus;
import org.gridsuite.study.server.dto.caseimport.CaseImportAction;
import org.gridsuite.study.server.dto.computation.ComputationParameterUUIDs;
import org.gridsuite.study.server.dto.elasticsearch.EquipmentInfos;
Expand Down Expand Up @@ -447,10 +448,16 @@ private void recreateNetwork(RootNetworkInfos rootNetworkInfos, UUID studyUuid,
? new HashMap<>(rootNetworkService.getImportParameters(rootNetworkInfos.getId()))
: importParameters;

self.updateNetworkLoadStatus(studyUuid, NetworkLoadStatus.LOADING);
persistNetwork(rootNetworkInfos, studyUuid, null, userId, importParametersToUse, CaseImportAction.NETWORK_RECREATION, reportId);
notificationService.emitElementUpdated(studyUuid, userId);
}

@Transactional
public void updateNetworkLoadStatus(UUID studyUuid, NetworkLoadStatus networkLoadStatus) {
getStudy(studyUuid).setNetworkLoadStatus(networkLoadStatus);
}

public UUID duplicateStudy(UUID sourceStudyUuid, String userId) {
Objects.requireNonNull(sourceStudyUuid);

Expand Down Expand Up @@ -633,6 +640,7 @@ public CreatedStudyBasicInfos updateNetwork(UUID studyUuid, UUID rootNetworkUuid
RootNetworkEntity rootNetworkEntity = rootNetworkService.getRootNetwork(rootNetworkUuid).orElseThrow(() -> new StudyException(NOT_FOUND, "Root network not found"));

rootNetworkService.updateNetwork(rootNetworkEntity, networkInfos);
studyEntity.setNetworkLoadStatus(NetworkLoadStatus.LOADED);

CreatedStudyBasicInfos createdStudyBasicInfos = toCreatedStudyBasicInfos(studyEntity);
studyInfosService.add(createdStudyBasicInfos);
Expand Down Expand Up @@ -2705,6 +2713,7 @@ public Map<ComputationType, String> getAllComputationsStatus(@NonNull UUID study

public void invalidateStudyRootNetwork(UUID studyUuid, UUID rootNetworkUuid, String userId, boolean updateCase) {
rootNetworkService.assertIsRootNetworkInStudy(studyUuid, rootNetworkUuid);
self.updateNetworkLoadStatus(studyUuid, NetworkLoadStatus.UNLOADING);
var rootNodeUuid = networkModificationTreeService.getStudyRootNodeUuid(studyUuid);
// First we unbuild all nodes
doUnbuildNodeTree(studyUuid, rootNodeUuid, true, true, userId);
Expand All @@ -2713,6 +2722,7 @@ public void invalidateStudyRootNetwork(UUID studyUuid, UUID rootNetworkUuid, Str
if (!updateCase) {
rootNetworkService.updateRootNetworkIndexationStatus(studyUuid, rootNetworkUuid, RootNetworkIndexationStatus.NOT_INDEXED);
}
self.updateNetworkLoadStatus(studyUuid, NetworkLoadStatus.UNLOADED);
notificationService.emitRootNetworksUpdated(studyUuid);
Comment on lines +2716 to 2726

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.

Those load status update might be better placed in one level above in invalidateStudy. Otherwise the study will switch to UNLOADING multiple times during an invalidation with multiple root networks. Also, in a multi root network situation, a study might have the UNLOADED status even if some root networks failed to unload

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.

Plus it would avoid using self as a workaround for transaction proxy

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import org.gridsuite.study.server.elasticsearch.EquipmentInfosService;
import org.gridsuite.study.server.elasticsearch.StudyInfosService;
import org.gridsuite.study.server.networkmodificationtree.entities.RootNetworkNodeInfoEntity;
import org.gridsuite.study.server.notification.NotificationService;
import org.gridsuite.study.server.repository.StudyEntity;
import org.gridsuite.study.server.repository.StudyRepository;
import org.gridsuite.study.server.repository.rootnetwork.RootNetworkEntity;
Expand Down Expand Up @@ -97,8 +96,6 @@ public class SupervisionService {

private final RootNetworkService rootNetworkService;

private final NotificationService notificationService;

private static final String SUPERVISION_USER = "Supervision";

public SupervisionService(StudyService studyService,
Expand All @@ -120,8 +117,7 @@ public SupervisionService(StudyService studyService,
ElasticsearchOperations elasticsearchOperations,
StudyInfosService studyInfosService,
RootNetworkService rootNetworkService,
StudyRepository studyRepository,
NotificationService notificationService) {
StudyRepository studyRepository) {
this.studyService = studyService;
this.networkModificationTreeService = networkModificationTreeService;
this.loadFlowService = loadFlowService;
Expand All @@ -143,7 +139,6 @@ public SupervisionService(StudyService studyService,
this.studyInfosService = studyInfosService;
this.rootNetworkService = rootNetworkService;
this.studyRepository = studyRepository;
this.notificationService = notificationService;
}

@Transactional
Expand Down Expand Up @@ -414,10 +409,16 @@ public void invalidateStudy(UUID studyUuid) {
var rootNodeUuid = networkModificationTreeService.getStudyRootNodeUuid(studyUuid);
studyService.unblockNodeTree(studyUuid, rootNodeUuid);
}
notificationService.emitElementUpdated(studyUuid, SUPERVISION_USER);
LOGGER.trace("Study {} nodes builds deleted and root node invalidated in : {} milliseconds", studyUuid, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime.get()));
}

@Transactional(readOnly = true)
public List<UUID> getLoadedStudyUuids(List<UUID> studyUuids) {

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.

Better fitted in StudyService no ? It may have other usages outside of supervision scope

return studyRepository.findAllByIdInAndNetworkLoadStatus(studyUuids, NetworkLoadStatus.LOADED).stream()
.map(StudyEntity::getId)
.toList();
}

@Transactional
public void recreateStudyIndices() {
recreateIndex(CreatedStudyBasicInfos.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet author="rehiligha (generated)" id="1855530442886-1">
<addColumn tableName="study">
<column name="network_load_status" type="varchar(255)" defaultValue="LOADED">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
3 changes: 3 additions & 0 deletions src/main/resources/db/changelog/db.changelog-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,6 @@ databaseChangeLog:
- include:
file: changesets/changelog_20260703T090337Z.xml
relativeToChangelogFile: true
- include:
file: changesets/changelog_20260818T152042Z.xml
relativeToChangelogFile: true
Original file line number Diff line number Diff line change
Expand Up @@ -369,5 +369,28 @@ void testInvalidateStudy() throws Exception {
allRootNetworkUuids.forEach(rootNetworkUuid ->
rootNetworkNodeInfoRepository.findAllByRootNetworkId(rootNetworkUuid)
.forEach(info -> assertThat(info.getBlockedNode()).isFalse()));

// Check that the study network is now marked as unloaded, and that invalidating a study does not
// update its last modification date in directory-server (no element-update message emitted)
assertEquals(NetworkLoadStatus.UNLOADED, studyRepository.findById(STUDY_UUID).orElseThrow().getNetworkLoadStatus());
}

@Test
void testGetLoadedStudies() throws Exception {
initStudy();
assertEquals(NetworkLoadStatus.LOADED, studyRepository.findById(STUDY_UUID).orElseThrow().getNetworkLoadStatus());
UUID unknownStudyUuid = UUID.randomUUID();
MvcResult mvcResult = mockMvc.perform(get("/v1/supervision/studies/loaded")
.queryParam("ids", STUDY_UUID.toString(), unknownStudyUuid.toString()))
.andExpectAll(status().isOk(), content().contentType(MediaType.APPLICATION_JSON)).andReturn();
List<UUID> loadedStudyUuids = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { });
assertEquals(List.of(STUDY_UUID), loadedStudyUuids);
mockMvc.perform(delete("/v1/supervision/studies/{studyUuid}/invalidate", STUDY_UUID))
.andExpect(status().isOk());
mvcResult = mockMvc.perform(get("/v1/supervision/studies/loaded")
.queryParam("ids", STUDY_UUID.toString(), unknownStudyUuid.toString()))
.andExpectAll(status().isOk(), content().contentType(MediaType.APPLICATION_JSON)).andReturn();
loadedStudyUuids = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { });
assertThat(loadedStudyUuids).isEmpty();
}
}
Loading