Skip to content
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

Search elements are now returned paged #149

Merged
merged 15 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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 @@ -15,6 +15,7 @@
import org.gridsuite.directory.server.dto.RootDirectoryAttributes;
import org.gridsuite.directory.server.dto.elasticsearch.DirectoryElementInfos;
import org.gridsuite.directory.server.services.DirectoryRepositoryService;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -253,7 +254,7 @@ public ResponseEntity<Void> elementExists(@PathVariable("directoryUuid") UUID di
@GetMapping(value = "/elements/indexation-infos", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Search elements in elasticsearch")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "List of elements found")})
public ResponseEntity<List<DirectoryElementInfos>> searchElements(
public ResponseEntity<Page<DirectoryElementInfos>> searchElements(
@Parameter(description = "User input") @RequestParam(value = "userInput") String userInput,
@Parameter(description = "Current directory UUID") @RequestParam(value = "directoryUuid", required = false, defaultValue = "") String directoryUuid) {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -51,6 +54,7 @@ public class DirectoryService {
public static final String UPDATE_TYPE_STUDIES = "studies";
private static final String CATEGORY_BROKER_INPUT = DirectoryService.class.getName() + ".input-broker-messages";
private static final Logger LOGGER = LoggerFactory.getLogger(DirectoryService.class);
private static final int ES_PAGE_MAX_SIZE = 50;

private final StudyService studyService;

Expand Down Expand Up @@ -587,8 +591,9 @@ public String getDuplicateNameCandidate(UUID directoryUuid, String elementName,
return nameCandidate(elementName, i);
}

public List<DirectoryElementInfos> searchElements(@NonNull String userInput, String directoryUuid) {
return directoryElementInfosService.searchElements(userInput, directoryUuid);
public Page<DirectoryElementInfos> searchElements(@NonNull String userInput, String directoryUuid) {
Pageable pageRequest = PageRequest.of(0, ES_PAGE_MAX_SIZE);
return directoryElementInfosService.searchElements(userInput, directoryUuid, pageRequest);
}

public boolean areDirectoryElementsDeletable(List<UUID> elementsUuid, String userId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.gridsuite.directory.server.elasticsearch;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;

import java.util.List;

public final class ESUtils {

private ESUtils() {
// This constructor is private to prevent instantiation of the utility class.
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}

public static <T> Page<T> searchHitsToPage(SearchHits<T> searchHits, Pageable pageable) {
List<T> content = searchHits.stream().map(SearchHit::getContent).toList();
return new PageImpl<>(content, pageable, searchHits.getTotalHits());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,35 @@
*/
package org.gridsuite.directory.server.services;

import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import lombok.Getter;
import lombok.NonNull;
import org.gridsuite.directory.server.dto.elasticsearch.DirectoryElementInfos;
import org.gridsuite.directory.server.elasticsearch.ESConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.client.elc.Queries;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.stereotype.Service;

import java.util.List;

import static org.gridsuite.directory.server.DirectoryService.DIRECTORY;
import static org.gridsuite.directory.server.elasticsearch.ESUtils.searchHitsToPage;

/**
* @author Ghazwa Rehili <ghazwa.rehili at rte-france.com>
*/
@Service
public class DirectoryElementInfosService {

private static final int PAGE_MAX_SIZE = 10;

private final ElasticsearchOperations elasticsearchOperations;

private static final String ELEMENT_NAME = "name.fullascii";
private static final String FULL_PATH_UUID = "fullPathUuid.keyword";
private static final String PATH_UUID = "pathUuid.keyword";
private static final String PARENT_ID = "parentId.keyword";
static final String ELEMENT_TYPE = "type.keyword";

Expand All @@ -50,7 +46,7 @@ public DirectoryElementInfosService(ElasticsearchOperations elasticsearchOperati
this.elasticsearchOperations = elasticsearchOperations;
}

public List<DirectoryElementInfos> searchElements(@NonNull String userInput, String currentDirectoryUuid) {
public Page<DirectoryElementInfos> searchElements(@NonNull String userInput, String currentDirectoryUuid, Pageable pageable) {
float defaultBoostValue = 1.0f;

// We don't want to show the directories
Expand All @@ -59,9 +55,16 @@ public List<DirectoryElementInfos> searchElements(@NonNull String userInput, Str
// The documents whose name contains the user input
Query matchNameWilcardQuery = Queries.wildcardQuery(ELEMENT_NAME, "*" + escapeLucene(userInput) + "*")._toQuery();

// Boosting the relevance of starts with input text
Query prefixQuery = PrefixQuery.of(m -> m
.field(ELEMENT_NAME)
.value(userInput)
.boost(defaultBoostValue))
._toQuery();

// The document is in path
Query fullPathQuery = TermQuery.of(m -> m
.field(FULL_PATH_UUID)
.field(PATH_UUID)
.value(currentDirectoryUuid)
.boost(defaultBoostValue)
)._toQuery();
Expand All @@ -74,7 +77,7 @@ public List<DirectoryElementInfos> searchElements(@NonNull String userInput, Str
)._toQuery();

// All queries with default default value
List<Query> queriesWithDefaultBoostValue = List.of(parentIdQuery, fullPathQuery);
List<Query> queriesWithDefaultBoostValue = List.of(parentIdQuery, fullPathQuery, prefixQuery);

// The documents whose name exactly matches the user input
// If parentIdQuery match then fullPathQuery will also match
Expand All @@ -91,13 +94,15 @@ public List<DirectoryElementInfos> searchElements(@NonNull String userInput, Str
.should(queriesWithDefaultBoostValue) // All queries with default default value
.should(exactMatchNameQuery)
.build();

NativeQuery nativeQuery = new NativeQueryBuilder()
.withQuery(query._toQuery())
.withPageable(PageRequest.of(0, PAGE_MAX_SIZE))
.withPageable(pageable)
.build();

return elasticsearchOperations.search(nativeQuery, DirectoryElementInfos.class).stream().map(SearchHit::getContent).toList();
return searchHitsToPage(
elasticsearchOperations.search(nativeQuery, DirectoryElementInfos.class),
pageable
);
}

public static String escapeLucene(String s) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.time.Instant;
Expand Down Expand Up @@ -86,15 +88,27 @@ void searchElementInfos() {
List<DirectoryElementInfos> infos = List.of(directoryInfos, filterInfos, studyInfos, caseInfos, contingencyListInfos);
repositoryService.saveElementsInfos(infos);

Set<DirectoryElementInfos> hits = new HashSet<>(directoryElementInfosService.searchElements("a", ""));
assertEquals(4, hits.size());
assertTrue(hits.contains(studyInfos));
assertTrue(hits.contains(caseInfos));
assertTrue(hits.contains(filterInfos));
assertTrue(hits.contains(contingencyListInfos));
Page<DirectoryElementInfos> pagedHits = directoryElementInfosService.searchElements("a", "", PageRequest.of(0, 10));
assertEquals(4, pagedHits.getTotalElements());
assertTrue(pagedHits.getContent().contains(studyInfos));
assertTrue(pagedHits.getContent().contains(caseInfos));
assertTrue(pagedHits.getContent().contains(filterInfos));
assertTrue(pagedHits.getContent().contains(contingencyListInfos));

hits = new HashSet<>(directoryElementInfosService.searchElements("aDirectory", ""));
assertEquals(0, hits.size());
pagedHits = directoryElementInfosService.searchElements("aDirectory", "", PageRequest.of(0, 10));
assertEquals(0, pagedHits.getTotalElements());
}

@Test
void searchPagedElementInfos() {
List<DirectoryElementInfos> elements = new ArrayList<>(20);
for (int i = 0; i < 20; i++) {
elements.add(createFilter("filter" + i));
Copy link
Contributor

Choose a reason for hiding this comment

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

the same thing here.

Copy link
Contributor

Choose a reason for hiding this comment

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

here "filter" is just used as a name , not a type

Copy link
Contributor

Choose a reason for hiding this comment

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

done

}
repositoryService.saveElementsInfos(elements);
Page<DirectoryElementInfos> pagedHits = directoryElementInfosService.searchElements("filter", "", PageRequest.of(0, 10));
assertEquals(20, pagedHits.getTotalElements());
assertEquals(10, pagedHits.getContent().size());
}

@Test
Expand Down Expand Up @@ -124,7 +138,11 @@ void searchSpecialChars() {
}

private void testNameFullAscii(String pat) {
assertEquals(1, directoryElementInfosService.searchElements(pat, "").size());
assertEquals(1, directoryElementInfosService.searchElements(pat, "", PageRequest.of(0, 10)).getTotalElements());
}

private DirectoryElementInfos createFilter(String name) {
Copy link
Contributor

Choose a reason for hiding this comment

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

As we already discussed , the element type such as STUDY,FILTER... should not be specified on directory server.

Copy link
Contributor

Choose a reason for hiding this comment

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

i renamed the function 'createElement'

return DirectoryElementInfos.builder().id(UUID.randomUUID()).name(name).type(FILTER).owner("admin").parentId(UUID.randomUUID()).subdirectoriesCount(0L).lastModificationDate(Instant.now().truncatedTo(ChronoUnit.SECONDS)).build();
Copy link
Contributor

Choose a reason for hiding this comment

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

You steel using "FILTER" and "STUDY" as equipment type

}

private DirectoryElementInfos makeElementDir(String name) {
Expand Down Expand Up @@ -195,14 +213,14 @@ HashMap<String, DirectoryElementInfos> createFilesElements() {
void testExactMatchFromSubDirectory() {
Map<String, DirectoryElementInfos> allDirs = createFilesElements();
UUID currentDirUuid = allDirs.get("sub_sub_directory1_2").getId();
List<DirectoryElementInfos> hitsCommunFile = directoryElementInfosService.searchElements("common_file", currentDirUuid.toString());
List<DirectoryElementInfos> hitsCommunFile = directoryElementInfosService.searchElements("common_file", currentDirUuid.toString(), PageRequest.of(0, 10)).stream().toList();
assertEquals(6, hitsCommunFile.size());
assertEquals(currentDirUuid, hitsCommunFile.get(0).getParentId()); // we get first the element in the current directory
assertEquals("common_file", hitsCommunFile.get(0).getName());

//now using another current dir , we expect similar results
currentDirUuid = allDirs.get("sub_sub_directory2_2").getId();
hitsCommunFile = directoryElementInfosService.searchElements("common_file", currentDirUuid.toString());
hitsCommunFile = directoryElementInfosService.searchElements("common_file", currentDirUuid.toString(), PageRequest.of(0, 10)).stream().toList();
assertEquals(6, hitsCommunFile.size());
assertEquals(currentDirUuid, hitsCommunFile.get(0).getParentId()); // we get first the element in the current directory
assertEquals("common_file", hitsCommunFile.get(0).getName());
Expand All @@ -212,7 +230,7 @@ void testExactMatchFromSubDirectory() {
void testExactMatchFromOtherDirectory() {
Map<String, DirectoryElementInfos> allDirs = createFilesElements();
UUID currentDirUuid = allDirs.get("sub_sub_directory1_2").getId();
List<DirectoryElementInfos> hits = directoryElementInfosService.searchElements("file3", currentDirUuid.toString());
List<DirectoryElementInfos> hits = directoryElementInfosService.searchElements("file3", currentDirUuid.toString(), PageRequest.of(0, 10)).stream().toList();
assertEquals(1, hits.size());
assertEquals(allDirs.get("sub_directory3").getId(), hits.get(0).getParentId());
assertEquals("file3", hits.get(0).getName());
Expand Down Expand Up @@ -244,7 +262,7 @@ void testExactMatchingParentDirectory() { // when a file is in a sub directory o
//we want to have the files in the current directory if any
// then the files in the path of the current directory (sub directories and parent directories)
// then the files in the other directories
List<DirectoryElementInfos> hitsFile = directoryElementInfosService.searchElements("new-file", currentDirUuid.toString());
List<DirectoryElementInfos> hitsFile = directoryElementInfosService.searchElements("new-file", currentDirUuid.toString(), PageRequest.of(0, 10)).stream().toList();
assertEquals(3, hitsFile.size());
assertEquals(newFile1, hitsFile.get(0));
assertEquals(newFile2, hitsFile.get(1));
Expand All @@ -255,28 +273,43 @@ void testExactMatchingParentDirectory() { // when a file is in a sub directory o
void testPartialMatchFromSubDirectory() {
HashMap<String, DirectoryElementInfos> allDirs = createFilesElements();
UUID currentDirUuid = allDirs.get("sub_sub_directory1_2").getId();
List<DirectoryElementInfos> hitsFile = directoryElementInfosService.searchElements("file", currentDirUuid.toString());
List<DirectoryElementInfos> hitsFile = directoryElementInfosService.searchElements("file", currentDirUuid.toString(), PageRequest.of(0, 10)).stream().toList();
assertEquals(9, hitsFile.size());
assertEquals(currentDirUuid, hitsFile.get(0).getParentId()); // we get first the elements in the current directory
assertEquals("common_file", hitsFile.get(0).getName());
assertEquals("file1", hitsFile.get(1).getName()); // we get second the elements in the path
}


/*
root_directory
├── sub_directory1
....
├── sub_directory2
│ ├── bnew-filebbbb
│ ├── anew-file
│ ├── new-file
│ ├── test-new-file
...
*/
@Test
void testExactMatchInCurrentDir() {
HashMap<String, DirectoryElementInfos> allDirs = createFilesElements();
UUID currentDirUuid = allDirs.get("sub_sub_directory1_2").getId();
String fileName = "new-file";
var newFile = makeElementFile(fileName, allDirs.get("sub_sub_directory1_2").getId());
var newFile1 = makeElementFile(fileName + "1", allDirs.get("sub_sub_directory1_2").getId());
var newFile2 = makeElementFile("1" + fileName + "2", allDirs.get("sub_sub_directory1_2").getId());
repositoryService.saveElementsInfos(List.of(newFile1, newFile, newFile2));
void testTermStartByUserInput() { // when a file start with search term
Map<String, DirectoryElementInfos> allDirs = createFilesElements();
UUID currentDirUuid = allDirs.get("sub_directory2").getId();
var anewFile1 = makeElementFile("anew-file", allDirs.get("sub_directory2").getId());
var newFile2 = makeElementFile("new-file-Ok", allDirs.get("sub_directory2").getId());
var bNewFile = makeElementFile("bnew-filebbbb", allDirs.get("sub_directory2").getId());
var testNewFile = makeElementFile("test-new-file", allDirs.get("sub_directory2").getId());
repositoryService.saveElementsInfos(List.of(bNewFile, newFile2, anewFile1, testNewFile));

List<DirectoryElementInfos> hitsFile = directoryElementInfosService.searchElements(fileName, currentDirUuid.toString());
assertEquals(3, hitsFile.size());
assertEquals(fileName, hitsFile.get(0).getName());
assertEquals(fileName + "1", hitsFile.get(1).getName());
assertEquals("1" + fileName + "2", hitsFile.get(2).getName());
//we want to have the files in the current directory if any
// then the files in the path of the current directory (sub directories and parent directories)
// then the files in the other directories
List<DirectoryElementInfos> hitsFile = directoryElementInfosService.searchElements("new-file", currentDirUuid.toString(), PageRequest.of(0, 10)).stream().toList();
assertEquals(4, hitsFile.size());
assertEquals(newFile2, hitsFile.get(0));
assertEquals(bNewFile, hitsFile.get(1));
assertEquals(anewFile1, hitsFile.get(2));
assertEquals(testNewFile, hitsFile.get(3));
}

}
Loading
Loading