diff --git a/openbas-api/src/main/java/io/openbas/search/FullTextSearchService.java b/openbas-api/src/main/java/io/openbas/search/FullTextSearchService.java index 60fa25f2b6..31283d36ab 100644 --- a/openbas-api/src/main/java/io/openbas/search/FullTextSearchService.java +++ b/openbas-api/src/main/java/io/openbas/search/FullTextSearchService.java @@ -39,6 +39,8 @@ public class FullTextSearchService { private Map, JpaSpecificationExecutor> repositoryMap; + private Map, List> searchListByClassMap ; + @PostConstruct @SuppressWarnings("unchecked") public void init() { @@ -51,6 +53,15 @@ public void init() { (Class) Scenario.class, (JpaSpecificationExecutor) this.scenarioRepository, (Class) Exercise.class, (JpaSpecificationExecutor) this.exerciseRepository ); + + this.searchListByClassMap = Map.of((Class) Asset.class, List.of("name", "id"), + (Class) AssetGroup.class, List.of("name", "id"), + (Class) User.class, List.of("email", "id"), + (Class) Team.class, List.of("name", "id"), + (Class) Organization.class, List.of("name", "id"), + (Class) Scenario.class, List.of("name", "id"), + (Class) Exercise.class, List.of("name", "id") + ); } public Page fullTextSearch( @@ -69,9 +80,12 @@ public Page fullTextSearch( JpaSpecificationExecutor repository = repositoryMap.get(clazzT); + String finalSearchTerm = getFinalSearchTerm(searchPaginationInput.getTextSearch()); + return buildPaginationJPA( repository::findAll, searchPaginationInput, + SpecificationUtils.fullTextSearch(finalSearchTerm, searchListByClassMap.get(clazzT)), clazzT ).map(this::transform); } @@ -161,45 +175,22 @@ public Map, FullTextSearchCountResult> fullTextSearch(@Nullable final S } Map, FullTextSearchCountResult> results = new HashMap<>(); - String finalSearchTerm = Arrays.stream(searchTerm.split(" ")) - .map((s) -> "(" + s + ":*)") - .collect(Collectors.joining(" & ")); - - // Search on assets - long assets = this.assetRepository.count(SpecificationUtils.fullTextSearch(finalSearchTerm, "name")); - results.put((Class) Asset.class, new FullTextSearchCountResult(Asset.class.getSimpleName(), assets)); - - // Search on asset groups - long assetGroups = this.assetGroupRepository.count( - SpecificationUtils.fullTextSearch(finalSearchTerm, "name")); - results.put((Class) AssetGroup.class, new FullTextSearchCountResult(AssetGroup.class.getSimpleName(), assetGroups)); - - // Search on users - long users = this.userRepository.count(SpecificationUtils.fullTextSearch(finalSearchTerm, "email")); - results.put((Class) User.class, new FullTextSearchCountResult(User.class.getSimpleName(), users)); - - // Search on teams - long teams = this.teamRepository.count(SpecificationUtils.fullTextSearch(finalSearchTerm, "name")); - results.put((Class) Team.class, new FullTextSearchCountResult(Team.class.getSimpleName(), teams)); - - // Search on organizations - long organizations = this.organizationRepository.count( - SpecificationUtils.fullTextSearch(finalSearchTerm, "name")); - results.put((Class) Organization.class, new FullTextSearchCountResult(Organization.class.getSimpleName(), organizations)); - - // Search on scenarios - long scenarios = this.scenarioRepository.count( - SpecificationUtils.fullTextSearch(finalSearchTerm, "name")); - results.put((Class) Scenario.class, new FullTextSearchCountResult(Scenario.class.getSimpleName(), scenarios)); - - // Search on simulations - long exercises = this.exerciseRepository.count( - SpecificationUtils.fullTextSearch(finalSearchTerm, "name")); - results.put((Class) Exercise.class, new FullTextSearchCountResult(Exercise.class.getSimpleName(), exercises)); + String finalSearchTerm = getFinalSearchTerm(searchTerm); + + repositoryMap.forEach((className, repository) -> { + long count = repository.count(SpecificationUtils.fullTextSearch(finalSearchTerm, searchListByClassMap.get(className))); + results.put(className, new FullTextSearchCountResult(className.getSimpleName(), count)); + }); return results; } + private static String getFinalSearchTerm(String searchTerm) { + return Arrays.stream(searchTerm.split(" ")) + .map((s) -> "(" + s + ":*)") + .collect(Collectors.joining(" & ")); + } + @AllArgsConstructor @Data public static class FullTextSearchCountResult { diff --git a/openbas-api/src/main/java/io/openbas/utils/pagination/PaginationUtils.java b/openbas-api/src/main/java/io/openbas/utils/pagination/PaginationUtils.java index 8fefed80bf..790ba1b4a7 100644 --- a/openbas-api/src/main/java/io/openbas/utils/pagination/PaginationUtils.java +++ b/openbas-api/src/main/java/io/openbas/utils/pagination/PaginationUtils.java @@ -46,4 +46,26 @@ public static Page buildPaginationCriteriaBuilder( return findAll.apply(filterSpecifications.and(searchSpecifications), pageable); } + /** + * Build PaginationJPA with a specified specifications + * @param findAll the find all method + * @param input the search inputs + * @param specification the specified specification + * @param clazz the class that we're looking for + * @return a Page of results + */ + public static Page buildPaginationJPA( + @NotNull final BiFunction, Pageable, Page> findAll, + @NotNull final SearchPaginationInput input, + Specification specification, + @NotNull final Class clazz) { + // Specification + Specification filterSpecifications = computeFilterGroupJpa(input.getFilterGroup()); + + // Pageable + Pageable pageable = PageRequest.of(input.getPage(), input.getSize(), toSortJpa(input.getSorts(), clazz)); + + return findAll.apply(filterSpecifications.and(specification), pageable); + } + } diff --git a/openbas-front/src/admin/components/nav/TopBar.tsx b/openbas-front/src/admin/components/nav/TopBar.tsx index e09d393019..9a7ef110c9 100644 --- a/openbas-front/src/admin/components/nav/TopBar.tsx +++ b/openbas-front/src/admin/components/nav/TopBar.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { AppBar, Badge, Box, Grid, IconButton, Menu, MenuItem, Popover, Toolbar, Tooltip } from '@mui/material'; -import { Link, useLocation, useNavigate } from 'react-router-dom'; +import {Link, useLocation, useNavigate, useSearchParams } from 'react-router-dom'; import { AccountCircleOutlined, AppsOutlined, ImportantDevicesOutlined, NotificationsOutlined } from '@mui/icons-material'; import { makeStyles, useTheme } from '@mui/styles'; import { logout } from '../../../actions/Application'; @@ -149,6 +149,9 @@ const TopBar: React.FC = () => { } }; + const [searchParams] = useSearchParams(); + const [search] = searchParams.getAll('search'); + return ( { placeholder={`${t('Search the platform')}...`} fullWidth={true} onSubmit={onFullTextSearch} + keyword={search} />
diff --git a/openbas-model/src/main/java/io/openbas/database/specification/SpecificationUtils.java b/openbas-model/src/main/java/io/openbas/database/specification/SpecificationUtils.java index 6291755060..a276e3859e 100644 --- a/openbas-model/src/main/java/io/openbas/database/specification/SpecificationUtils.java +++ b/openbas-model/src/main/java/io/openbas/database/specification/SpecificationUtils.java @@ -2,23 +2,34 @@ import io.openbas.database.model.Base; import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Predicate; import jakarta.validation.constraints.NotBlank; import org.springframework.data.jpa.domain.Specification; +import java.util.ArrayList; +import java.util.List; + public class SpecificationUtils { + /** + * Full Text Search with several properties instead of just one + * @param searchTerm the search term + * @param properties the properties to check + */ public static Specification fullTextSearch( - @NotBlank final String searchTerm, - @NotBlank final String property) { + @NotBlank final String searchTerm, + @NotBlank final List properties) { return (root, query, cb) -> { + List listOfPredicates = new ArrayList<>(); + for (String property: properties) { + Expression tsVector = cb.function("to_tsvector", Double.class, cb.literal("simple"), root.get(property)); + Expression tsQuery = cb.function("to_tsquery", Double.class, cb.literal("simple"), cb.literal(searchTerm)); + Expression rank = cb.function("ts_rank", Double.class, tsVector, tsQuery); + query.orderBy(cb.desc(rank)); + listOfPredicates.add(cb.greaterThan(rank, 0.01)); + } - Expression tsVector = cb.function("to_tsvector", Double.class, cb.literal("simple"), root.get(property)); - Expression tsQuery = cb.function("to_tsquery", Double.class, cb.literal("simple"), cb.literal(searchTerm)); - Expression rank = cb.function("ts_rank", Double.class, tsVector, tsQuery); - query.orderBy(cb.desc(rank)); - - return cb.greaterThan(rank, 0.0); - + return cb.or(listOfPredicates.toArray(new Predicate[0])); }; }