Correct tag facets handling
This commit is contained in:
31
backend/src/main/java/com/storycove/dto/FacetCountDto.java
Normal file
31
backend/src/main/java/com/storycove/dto/FacetCountDto.java
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package com.storycove.dto;
|
||||||
|
|
||||||
|
public class FacetCountDto {
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
public FacetCountDto() {}
|
||||||
|
|
||||||
|
public FacetCountDto(String value, int count) {
|
||||||
|
this.value = value;
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCount(int count) {
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.storycove.dto;
|
package com.storycove.dto;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class SearchResultDto<T> {
|
public class SearchResultDto<T> {
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ public class SearchResultDto<T> {
|
|||||||
private int perPage;
|
private int perPage;
|
||||||
private String query;
|
private String query;
|
||||||
private long searchTimeMs;
|
private long searchTimeMs;
|
||||||
|
private Map<String, List<FacetCountDto>> facets;
|
||||||
|
|
||||||
public SearchResultDto() {}
|
public SearchResultDto() {}
|
||||||
|
|
||||||
@@ -22,6 +24,16 @@ public class SearchResultDto<T> {
|
|||||||
this.searchTimeMs = searchTimeMs;
|
this.searchTimeMs = searchTimeMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SearchResultDto(List<T> results, long totalHits, int page, int perPage, String query, long searchTimeMs, Map<String, List<FacetCountDto>> facets) {
|
||||||
|
this.results = results;
|
||||||
|
this.totalHits = totalHits;
|
||||||
|
this.page = page;
|
||||||
|
this.perPage = perPage;
|
||||||
|
this.query = query;
|
||||||
|
this.searchTimeMs = searchTimeMs;
|
||||||
|
this.facets = facets;
|
||||||
|
}
|
||||||
|
|
||||||
// Getters and Setters
|
// Getters and Setters
|
||||||
public List<T> getResults() {
|
public List<T> getResults() {
|
||||||
return results;
|
return results;
|
||||||
@@ -70,4 +82,12 @@ public class SearchResultDto<T> {
|
|||||||
public void setSearchTimeMs(long searchTimeMs) {
|
public void setSearchTimeMs(long searchTimeMs) {
|
||||||
this.searchTimeMs = searchTimeMs;
|
this.searchTimeMs = searchTimeMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, List<FacetCountDto>> getFacets() {
|
||||||
|
return facets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFacets(Map<String, List<FacetCountDto>> facets) {
|
||||||
|
this.facets = facets;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.storycove.service;
|
package com.storycove.service;
|
||||||
|
|
||||||
import com.storycove.dto.AuthorSearchDto;
|
import com.storycove.dto.AuthorSearchDto;
|
||||||
|
import com.storycove.dto.FacetCountDto;
|
||||||
import com.storycove.dto.SearchResultDto;
|
import com.storycove.dto.SearchResultDto;
|
||||||
import com.storycove.dto.StorySearchDto;
|
import com.storycove.dto.StorySearchDto;
|
||||||
import com.storycove.entity.Author;
|
import com.storycove.entity.Author;
|
||||||
@@ -227,6 +228,8 @@ public class TypesenseService {
|
|||||||
.highlightFields("title,description")
|
.highlightFields("title,description")
|
||||||
.highlightStartTag("<mark>")
|
.highlightStartTag("<mark>")
|
||||||
.highlightEndTag("</mark>")
|
.highlightEndTag("</mark>")
|
||||||
|
.facetBy("tagNames,authorName,rating")
|
||||||
|
.maxFacetValues(100)
|
||||||
.sortBy(buildSortParameter(normalizedQuery, sortBy, sortDir));
|
.sortBy(buildSortParameter(normalizedQuery, sortBy, sortDir));
|
||||||
|
|
||||||
// Add filters
|
// Add filters
|
||||||
@@ -268,6 +271,7 @@ public class TypesenseService {
|
|||||||
|
|
||||||
|
|
||||||
List<StorySearchDto> results = convertSearchResult(searchResult);
|
List<StorySearchDto> results = convertSearchResult(searchResult);
|
||||||
|
Map<String, List<FacetCountDto>> facets = processFacetCounts(searchResult);
|
||||||
long searchTime = System.currentTimeMillis() - startTime;
|
long searchTime = System.currentTimeMillis() - startTime;
|
||||||
|
|
||||||
return new SearchResultDto<>(
|
return new SearchResultDto<>(
|
||||||
@@ -276,7 +280,8 @@ public class TypesenseService {
|
|||||||
page,
|
page,
|
||||||
perPage,
|
perPage,
|
||||||
query,
|
query,
|
||||||
searchTime
|
searchTime,
|
||||||
|
facets
|
||||||
);
|
);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -391,6 +396,45 @@ public class TypesenseService {
|
|||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Map<String, List<FacetCountDto>> processFacetCounts(SearchResult searchResult) {
|
||||||
|
Map<String, List<FacetCountDto>> facetMap = new HashMap<>();
|
||||||
|
|
||||||
|
if (searchResult.getFacetCounts() != null) {
|
||||||
|
for (FacetCounts facetCounts : searchResult.getFacetCounts()) {
|
||||||
|
String fieldName = facetCounts.getFieldName();
|
||||||
|
List<FacetCountDto> facetValues = new ArrayList<>();
|
||||||
|
|
||||||
|
if (facetCounts.getCounts() != null) {
|
||||||
|
for (Object countObj : facetCounts.getCounts()) {
|
||||||
|
if (countObj instanceof Map) {
|
||||||
|
Map<String, Object> countMap = (Map<String, Object>) countObj;
|
||||||
|
String value = (String) countMap.get("value");
|
||||||
|
Integer count = (Integer) countMap.get("count");
|
||||||
|
|
||||||
|
if (value != null && count != null && count > 0) {
|
||||||
|
facetValues.add(new FacetCountDto(value, count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!facetValues.isEmpty()) {
|
||||||
|
// Sort by count descending, then by value ascending
|
||||||
|
facetValues.sort((a, b) -> {
|
||||||
|
int countCompare = Integer.compare(b.getCount(), a.getCount());
|
||||||
|
if (countCompare != 0) return countCompare;
|
||||||
|
return a.getValue().compareToIgnoreCase(b.getValue());
|
||||||
|
});
|
||||||
|
|
||||||
|
facetMap.put(fieldName, facetValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return facetMap;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private List<StorySearchDto> convertSearchResult(SearchResult searchResult) {
|
private List<StorySearchDto> convertSearchResult(SearchResult searchResult) {
|
||||||
return searchResult.getHits().stream()
|
return searchResult.getHits().stream()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { searchApi } from '../../lib/api';
|
import { searchApi } from '../../lib/api';
|
||||||
import { Story, Tag } from '../../types/api';
|
import { Story, Tag, FacetCount } from '../../types/api';
|
||||||
import AppLayout from '../../components/layout/AppLayout';
|
import AppLayout from '../../components/layout/AppLayout';
|
||||||
import { Input } from '../../components/ui/Input';
|
import { Input } from '../../components/ui/Input';
|
||||||
import Button from '../../components/ui/Button';
|
import Button from '../../components/ui/Button';
|
||||||
@@ -29,24 +29,16 @@ export default function LibraryPage() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Extract tags from current search results with counts
|
// Convert facet counts to Tag objects for the UI
|
||||||
const extractTagsFromResults = (stories: Story[]): Tag[] => {
|
const convertFacetsToTags = (facets?: Record<string, FacetCount[]>): Tag[] => {
|
||||||
const tagCounts: { [key: string]: number } = {};
|
if (!facets || !facets.tagNames) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
stories.forEach(story => {
|
return facets.tagNames.map(facet => ({
|
||||||
story.tagNames?.forEach(tagName => {
|
id: facet.value, // Use tag name as ID since we don't have actual IDs from search results
|
||||||
if (tagCounts[tagName]) {
|
name: facet.value,
|
||||||
tagCounts[tagName]++;
|
storyCount: facet.count
|
||||||
} else {
|
|
||||||
tagCounts[tagName] = 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return Object.entries(tagCounts).map(([tagName, count]) => ({
|
|
||||||
id: tagName, // Use tag name as ID since we don't have actual IDs from search results
|
|
||||||
name: tagName,
|
|
||||||
storyCount: count
|
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,8 +64,8 @@ export default function LibraryPage() {
|
|||||||
setTotalPages(Math.ceil((result?.totalHits || 0) / 20));
|
setTotalPages(Math.ceil((result?.totalHits || 0) / 20));
|
||||||
setTotalElements(result?.totalHits || 0);
|
setTotalElements(result?.totalHits || 0);
|
||||||
|
|
||||||
// Always update tags based on current search results (including initial wildcard search)
|
// Update tags from facets - these represent all matching stories, not just current page
|
||||||
const resultTags = extractTagsFromResults(currentStories);
|
const resultTags = convertFacetsToTags(result?.facets);
|
||||||
setTags(resultTags);
|
setTags(resultTags);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load stories:', error);
|
console.error('Failed to load stories:', error);
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ export interface AuthResponse {
|
|||||||
expiresIn: number;
|
expiresIn: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FacetCount {
|
||||||
|
value: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SearchResult {
|
export interface SearchResult {
|
||||||
results: Story[];
|
results: Story[];
|
||||||
totalHits: number;
|
totalHits: number;
|
||||||
@@ -65,6 +70,7 @@ export interface SearchResult {
|
|||||||
perPage: number;
|
perPage: number;
|
||||||
query: string;
|
query: string;
|
||||||
searchTimeMs: number;
|
searchTimeMs: number;
|
||||||
|
facets?: Record<string, FacetCount[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PagedResult<T> {
|
export interface PagedResult<T> {
|
||||||
|
|||||||
Reference in New Issue
Block a user