Skip to content

Commit 8cda14f

Browse files
[Server] Add support for highlight text preview (#399)
* feat(typesense): add support for highlighted search results * fix: correct highlightFields usage in SearchParameters for Typesense search * test: add unit test for TypesenseService search with highlights
1 parent 86a9723 commit 8cda14f

3 files changed

Lines changed: 67 additions & 8 deletions

File tree

server/src/main/java/dev/findfirst/core/service/SearchService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ public List<BookmarkDTO> bookmarksByTags(List<String> tags) {
4242

4343
public List<BookmarkDTO> bookmarksByText(String text) {
4444
var userID = userContext.getUserId();
45-
var bookmarks = bookmarkRepo.findAllById(typesense.search(text));
45+
var ids = typesense.search(text).stream()
46+
.map(TypesenseService.SearchHighlightResult::id)
47+
.toList();
48+
var bookmarks = bookmarkRepo.findAllById(ids);
4649
return bookmarkService.convertBookmarkJDBCToDTO(bookmarks, userID);
4750

4851
}

server/src/main/java/dev/findfirst/core/service/TypesenseService.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
@Slf4j
2828
public class TypesenseService {
2929

30+
public record SearchHighlightResult(Long id, String highlight) {};
3031
private final TypsenseInitializationRepository initRepo;
3132

3233
private final Client client;
@@ -86,16 +87,29 @@ public void addText(BookmarkJDBC bookmark, Document retDoc) {
8687
}
8788
}
8889

89-
public List<Long> search(String text) {
90-
SearchParameters searchParameters = new SearchParameters().q(text).queryBy("text");
90+
public List<SearchHighlightResult> search(String text) {
91+
SearchParameters searchParameters = new SearchParameters()
92+
.q(text)
93+
.queryBy("text")
94+
.highlightFields("text")
95+
.highlightStartTag("<mark>")
96+
.highlightEndTag("<mark>");
9197
try {
9298
log.debug("searching");
9399
SearchResult searchResult =
94100
client.collections(schemaName).documents().search(searchParameters);
95101
log.debug(searchResult.toString());
96102

97103
return searchResult.getHits().stream()
98-
.map(h -> Long.parseLong(h.getDocument().get("id").toString())).toList();
104+
.map(hit -> {
105+
Long id = Long.parseLong(hit.getDocument().get("id").toString());
106+
String highlight = hit.getHighlights().stream()
107+
.filter(h -> "text".equals(h.getField()))
108+
.map(h-> h.getSnippet())
109+
.findFirst()
110+
.orElse("");
111+
return new SearchHighlightResult(id, highlight);
112+
}).toList();
99113
} catch (Exception e) {
100114
log.error(e.toString());
101115
}

server/src/test/java/dev/findfirst/core/service/TypesenseServiceTest.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package dev.findfirst.core.service;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4-
import static org.mockito.Mockito.any;
5-
import static org.mockito.Mockito.when;
4+
import static org.mockito.Mockito.*;
65

76
import java.util.Date;
7+
import java.util.HashMap;
8+
import java.util.List;
9+
import java.util.Map;
10+
11+
import org.typesense.model.*;
12+
import org.typesense.api.Documents;
13+
import org.typesense.api.Collection;
814

915
import dev.findfirst.core.model.jdbc.TypesenseInitRecord;
1016
import dev.findfirst.core.repository.jdbc.TypsenseInitializationRepository;
@@ -17,8 +23,6 @@
1723
import org.mockito.junit.jupiter.MockitoExtension;
1824
import org.typesense.api.Client;
1925
import org.typesense.api.Collections;
20-
import org.typesense.model.CollectionResponse;
21-
import org.typesense.model.CollectionSchema;
2226

2327
/**
2428
* Tests for typsense operations such as intitilization, queries/imports.
@@ -78,4 +82,42 @@ void initializationWasNotFinished() throws Exception {
7882
@Test
7983
@Disabled("Implement test to save storeScrapedText")
8084
void storeScrapedText() throws Exception {}
85+
86+
@Test
87+
void returnSearchHighlighted() throws Exception {
88+
89+
String query = "example";
90+
Long expectedId = 123L;
91+
String expectedSnippet = "<mark>example</mark> text around";
92+
93+
// Mock highlight
94+
SearchHighlight highlight = mock(SearchHighlight.class);
95+
when(highlight.getField()).thenReturn("text");
96+
when(highlight.getSnippet()).thenReturn(expectedSnippet);
97+
98+
// Simula un hit como Map
99+
SearchResultHit hit = mock(SearchResultHit.class);
100+
when(hit.getDocument()).thenReturn(Map.of("id", expectedId.toString()));
101+
when(hit.getHighlights()).thenReturn(List.of(highlight));
102+
103+
// Mock search result
104+
SearchResult searchResult = new SearchResult();
105+
searchResult.setHits(List.of(hit));
106+
107+
108+
// Mock client behavior
109+
var documents = mock(Documents.class);
110+
var collection = mock(Collection.class);
111+
when(client.collections("bookmark")).thenReturn(collection);
112+
when(collection.documents()).thenReturn(documents);
113+
when(documents.search(any(SearchParameters.class))).thenReturn(searchResult);
114+
115+
// Act
116+
var results = typesense.search(query);
117+
118+
// Assert
119+
assertEquals(1, results.size());
120+
assertEquals(expectedId, results.get(0).id());
121+
assertEquals(expectedSnippet, results.get(0).highlight());
122+
}
81123
}

0 commit comments

Comments
 (0)