diff --git a/src/main/java/org/gridsuite/explore/server/services/ExploreService.java b/src/main/java/org/gridsuite/explore/server/services/ExploreService.java index 1566857f..6c41f599 100644 --- a/src/main/java/org/gridsuite/explore/server/services/ExploreService.java +++ b/src/main/java/org/gridsuite/explore/server/services/ExploreService.java @@ -471,11 +471,27 @@ public String getUsersIdentities(List elementsUuids, String userId) { * Elements the user cannot read are omitted. */ public List getReferencingElementInfos(UUID elementUuid, String userId) { - // for now only STUDY_NODE references - List referencedNodeUuids = directoryService.getElementInfos(elementUuid).getReferences().stream() - .filter(reference -> reference.getReferenceType() == ReferenceAttributes.ReferenceType.STUDY_NODE) + List references = directoryService.getElementInfos(elementUuid).getReferences(); + + // a STUDY_NODE reference points directly at a node; a NETWORK_MODIFICATION reference points at a composite + // modification nested in a node's modification group, so it has to be resolved to that node first, then it + // is described exactly like a direct STUDY_NODE reference + List networkModificationUuids = references.stream() + .filter(reference -> reference.getReferenceType() == ReferenceAttributes.ReferenceType.NETWORK_MODIFICATION) .map(ReferenceAttributes::getReferenceId) .toList(); + Map nodeUuidByNetworkModification = networkModificationUuids.isEmpty() + ? Map.of() + : studyService.getNodeUuidsByNetworkModifications(networkModificationUuids.stream().distinct().toList()); + + List referencedNodeUuids = references.stream() + .map(reference -> switch (reference.getReferenceType()) { + case STUDY_NODE -> reference.getReferenceId(); + case NETWORK_MODIFICATION -> nodeUuidByNetworkModification.get(reference.getReferenceId()); + default -> null; + }) + .filter(Objects::nonNull) + .toList(); if (referencedNodeUuids.isEmpty()) { return List.of(); } diff --git a/src/main/java/org/gridsuite/explore/server/services/StudyService.java b/src/main/java/org/gridsuite/explore/server/services/StudyService.java index 9af8edef..8ad59b98 100644 --- a/src/main/java/org/gridsuite/explore/server/services/StudyService.java +++ b/src/main/java/org/gridsuite/explore/server/services/StudyService.java @@ -98,6 +98,21 @@ public List getNodesInfos(List nodeUuids) { }).getBody(); } + /** + * @param networkModificationUuids network modification uuids referenced by a shared composite modification + * @return network modification uuid -> uuid of the study node whose modification group ultimately contains it; + * modifications that cannot be attached to a node are omitted + */ + public Map getNodeUuidsByNetworkModifications(List networkModificationUuids) { + String path = UriComponentsBuilder.fromPath(DELIMITER + STUDY_SERVER_API_VERSION + "/nodes/uuids-by-network-modification") + .queryParam("uuids", networkModificationUuids) + .buildAndExpand() + .toUriString(); + return restTemplate.exchange(studyServerBaseUri + path, HttpMethod.GET, null, + new ParameterizedTypeReference>() { + }).getBody(); + } + private HttpHeaders getHeaders(String userId) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); diff --git a/src/test/java/org/gridsuite/explore/server/ReferencingElementInfosTest.java b/src/test/java/org/gridsuite/explore/server/ReferencingElementInfosTest.java index f7ef6183..1a2c4b92 100644 --- a/src/test/java/org/gridsuite/explore/server/ReferencingElementInfosTest.java +++ b/src/test/java/org/gridsuite/explore/server/ReferencingElementInfosTest.java @@ -73,6 +73,8 @@ class ReferencingElementInfosTest { private static final UUID SHARED_ELEMENT_UUID = UUID.randomUUID(); private static final UUID NODE_1_UUID = UUID.randomUUID(); private static final UUID NODE_2_UUID = UUID.randomUUID(); + private static final UUID MODIFICATION_1_UUID = UUID.randomUUID(); + private static final UUID MODIFICATION_2_UUID = UUID.randomUUID(); private static final UUID STUDY_1_UUID = UUID.randomUUID(); private static final UUID STUDY_2_UUID = UUID.randomUUID(); @@ -80,6 +82,7 @@ class ReferencingElementInfosTest { private static final String ELEMENTS_PATH = "/v1/elements"; private static final String ELEMENTS_PATHS_PATH = "/v1/elements/paths"; private static final String NODES_INFOS_PATH = "/v1/nodes/infos"; + private static final String NODES_BY_MODIFICATION_PATH = "/v1/nodes/uuids-by-network-modification"; private static final String USERS_IDENTITIES_PATH = "/v1/users/identities"; @BeforeEach @@ -114,14 +117,23 @@ private void stubUsersIdentities() { } private void stubSharedElementReferences(UUID... referencedNodeUuids) throws Exception { - ElementAttributes sharedElement = new ElementAttributes(SHARED_ELEMENT_UUID, "sharedModification", "MODIFICATION", OWNER_SUB, 0L, null); - sharedElement.setReferences(Arrays.stream(referencedNodeUuids) + stubSharedElementReferences(Arrays.stream(referencedNodeUuids) .map(nodeUuid -> new ReferenceAttributes(nodeUuid, ReferenceAttributes.ReferenceType.STUDY_NODE)) .toList()); + } + + private void stubSharedElementReferences(List references) throws Exception { + ElementAttributes sharedElement = new ElementAttributes(SHARED_ELEMENT_UUID, "sharedModification", "MODIFICATION", OWNER_SUB, 0L, null); + sharedElement.setReferences(references); wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo(SHARED_ELEMENT_PATH)) .willReturn(jsonResponse(sharedElement))); } + private void stubNodeUuidsByNetworkModification(Map nodeUuidByModificationUuid) throws Exception { + wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo(NODES_BY_MODIFICATION_PATH)) + .willReturn(jsonResponse(nodeUuidByModificationUuid))); + } + private void stubNodesInfos(NodeInfos... nodesInfos) throws Exception { wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo(NODES_INFOS_PATH)) .willReturn(jsonResponse(List.of(nodesInfos)))); @@ -289,8 +301,89 @@ void testElementWithoutReferences() throws Exception { // without any reference, nothing is left to describe: no other server is reached wireMockServer.verify(0, WireMock.getRequestedFor(WireMock.urlPathEqualTo(NODES_INFOS_PATH))); + wireMockServer.verify(0, WireMock.getRequestedFor(WireMock.urlPathEqualTo(NODES_BY_MODIFICATION_PATH))); wireMockServer.verify(0, WireMock.getRequestedFor(WireMock.urlPathEqualTo(ELEMENTS_PATH))); wireMockServer.verify(0, WireMock.getRequestedFor(WireMock.urlPathEqualTo(ELEMENTS_PATHS_PATH))); wireMockServer.verify(0, WireMock.getRequestedFor(WireMock.urlPathEqualTo(USERS_IDENTITIES_PATH))); } + + @Test + void testNetworkModificationReferenceResolvedToItsNodeAndStudy() throws Exception { + stubSharedElementReferences(List.of( + new ReferenceAttributes(MODIFICATION_1_UUID, ReferenceAttributes.ReferenceType.NETWORK_MODIFICATION))); + // the modification lives (possibly nested) in node1's modification group + stubNodeUuidsByNetworkModification(Map.of(MODIFICATION_1_UUID, NODE_1_UUID)); + stubNodesInfos(new NodeInfos(NODE_1_UUID, "node1", STUDY_1_UUID)); + stubStudies(studyStub(STUDY_1_UUID, "study1")); + stubStudiesPaths(Map.of(STUDY_1_UUID, pathStub(STUDY_1_UUID, "study1", "root"))); + + List infos = getReferencingElementInfos(); + + assertEquals(1, infos.size()); + ReferencingElementInfos info = infos.getFirst(); + // a network modification reference is described exactly like a direct study-node reference + assertEquals("node1", info.node()); + assertEquals("study1", info.elementName()); + assertEquals("STUDY", info.type()); + assertEquals(List.of("root"), info.path()); + assertEquals("John Doe", info.ownerLabel()); + assertEquals(LAST_MODIFICATION_DATE, info.lastModificationDate()); + assertEquals(MODIFIER_SUB, info.lastModifiedByLabel()); + } + + @Test + void testStudyNodeAndNetworkModificationReferencesAreMerged() throws Exception { + stubSharedElementReferences(List.of( + new ReferenceAttributes(NODE_1_UUID, ReferenceAttributes.ReferenceType.STUDY_NODE), + new ReferenceAttributes(MODIFICATION_1_UUID, ReferenceAttributes.ReferenceType.NETWORK_MODIFICATION))); + stubNodeUuidsByNetworkModification(Map.of(MODIFICATION_1_UUID, NODE_2_UUID)); + stubNodesInfos( + new NodeInfos(NODE_1_UUID, "node1", STUDY_1_UUID), + new NodeInfos(NODE_2_UUID, "node2", STUDY_2_UUID)); + stubStudies(studyStub(STUDY_1_UUID, "study1"), studyStub(STUDY_2_UUID, "study2")); + stubStudiesPaths(Map.of( + STUDY_1_UUID, pathStub(STUDY_1_UUID, "study1", "root"), + STUDY_2_UUID, pathStub(STUDY_2_UUID, "study2", "root"))); + + List infos = getReferencingElementInfos(); + + // one row per reference, in reference order + assertEquals(2, infos.size()); + assertEquals("node1", infos.get(0).node()); + assertEquals("study1", infos.get(0).elementName()); + assertEquals("node2", infos.get(1).node()); + assertEquals("study2", infos.get(1).elementName()); + // both nodes are resolved in a single call to the study-server + wireMockServer.verify(1, WireMock.getRequestedFor(WireMock.urlPathEqualTo(NODES_INFOS_PATH))); + } + + @Test + void testUnresolvedNetworkModificationReferenceIsOmitted() throws Exception { + stubSharedElementReferences(List.of( + new ReferenceAttributes(MODIFICATION_1_UUID, ReferenceAttributes.ReferenceType.NETWORK_MODIFICATION), + new ReferenceAttributes(MODIFICATION_2_UUID, ReferenceAttributes.ReferenceType.NETWORK_MODIFICATION))); + // the study-server can only attach one of the two modifications to a node + stubNodeUuidsByNetworkModification(Map.of(MODIFICATION_1_UUID, NODE_1_UUID)); + stubNodesInfos(new NodeInfos(NODE_1_UUID, "node1", STUDY_1_UUID)); + stubStudies(studyStub(STUDY_1_UUID, "study1")); + stubStudiesPaths(Map.of(STUDY_1_UUID, pathStub(STUDY_1_UUID, "study1", "root"))); + + List infos = getReferencingElementInfos(); + + assertEquals(1, infos.size()); + assertEquals("node1", infos.getFirst().node()); + } + + @Test + void testNetworkModificationServerNotCalledWithoutSuchReference() throws Exception { + stubSharedElementReferences(NODE_1_UUID); + stubNodesInfos(new NodeInfos(NODE_1_UUID, "node1", STUDY_1_UUID)); + stubStudies(studyStub(STUDY_1_UUID, "study1")); + stubStudiesPaths(Map.of(STUDY_1_UUID, pathStub(STUDY_1_UUID, "study1", "root"))); + + assertEquals(1, getReferencingElementInfos().size()); + + // no NETWORK_MODIFICATION reference: the resolution endpoint is never hit + wireMockServer.verify(0, WireMock.getRequestedFor(WireMock.urlPathEqualTo(NODES_BY_MODIFICATION_PATH))); + } }