Skip to content

Commit

Permalink
[frontend/backend] Fixing full text search wrong indicators
Browse files Browse the repository at this point in the history
  • Loading branch information
Dimfacion committed Jun 13, 2024
1 parent dae2a50 commit 2b66651
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public class FullTextSearchService<T extends Base> {

private Map<Class<T>, JpaSpecificationExecutor<T>> repositoryMap;

private Map<Class<T>, List<String>> searchListByClassMap ;

@PostConstruct
@SuppressWarnings("unchecked")
public void init() {
Expand All @@ -51,6 +53,15 @@ public void init() {
(Class<T>) Scenario.class, (JpaSpecificationExecutor<T>) this.scenarioRepository,
(Class<T>) Exercise.class, (JpaSpecificationExecutor<T>) this.exerciseRepository
);

this.searchListByClassMap = Map.of((Class<T>) Asset.class, List.of("name", "id"),
(Class<T>) AssetGroup.class, List.of("name", "id"),
(Class<T>) User.class, List.of("email", "id"),
(Class<T>) Team.class, List.of("name", "id"),
(Class<T>) Organization.class, List.of("name", "id"),
(Class<T>) Scenario.class, List.of("name", "id"),
(Class<T>) Exercise.class, List.of("name", "id")
);
}

public Page<FullTextSearchResult> fullTextSearch(
Expand All @@ -69,9 +80,12 @@ public Page<FullTextSearchResult> fullTextSearch(

JpaSpecificationExecutor<T> repository = repositoryMap.get(clazzT);

String finalSearchTerm = getFinalSearchTerm(searchPaginationInput.getTextSearch());

return buildPaginationJPA(
repository::findAll,
searchPaginationInput,
SpecificationUtils.fullTextSearch(finalSearchTerm, searchListByClassMap.get(clazzT)),
clazzT
).map(this::transform);
}
Expand Down Expand Up @@ -161,45 +175,22 @@ public Map<Class<T>, FullTextSearchCountResult> fullTextSearch(@Nullable final S
}

Map<Class<T>, 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<T>) Asset.class, new FullTextSearchCountResult(Asset.class.getSimpleName(), assets));

// Search on asset groups
long assetGroups = this.assetGroupRepository.count(
SpecificationUtils.fullTextSearch(finalSearchTerm, "name"));
results.put((Class<T>) AssetGroup.class, new FullTextSearchCountResult(AssetGroup.class.getSimpleName(), assetGroups));

// Search on users
long users = this.userRepository.count(SpecificationUtils.fullTextSearch(finalSearchTerm, "email"));
results.put((Class<T>) User.class, new FullTextSearchCountResult(User.class.getSimpleName(), users));

// Search on teams
long teams = this.teamRepository.count(SpecificationUtils.fullTextSearch(finalSearchTerm, "name"));
results.put((Class<T>) Team.class, new FullTextSearchCountResult(Team.class.getSimpleName(), teams));

// Search on organizations
long organizations = this.organizationRepository.count(
SpecificationUtils.fullTextSearch(finalSearchTerm, "name"));
results.put((Class<T>) Organization.class, new FullTextSearchCountResult(Organization.class.getSimpleName(), organizations));

// Search on scenarios
long scenarios = this.scenarioRepository.count(
SpecificationUtils.fullTextSearch(finalSearchTerm, "name"));
results.put((Class<T>) Scenario.class, new FullTextSearchCountResult(Scenario.class.getSimpleName(), scenarios));

// Search on simulations
long exercises = this.exerciseRepository.count(
SpecificationUtils.fullTextSearch(finalSearchTerm, "name"));
results.put((Class<T>) 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,26 @@ public static <T, U> Page<U> 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 <T> Page<T> buildPaginationJPA(
@NotNull final BiFunction<Specification<T>, Pageable, Page<T>> findAll,
@NotNull final SearchPaginationInput input,
Specification<T> specification,
@NotNull final Class<T> clazz) {
// Specification
Specification<T> filterSpecifications = computeFilterGroupJpa(input.getFilterGroup());

// Pageable
Pageable pageable = PageRequest.of(input.getPage(), input.getSize(), toSortJpa(input.getSorts(), clazz));

return findAll.apply(filterSpecifications.and(specification), pageable);
}

}
6 changes: 5 additions & 1 deletion openbas-front/src/admin/components/nav/TopBar.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -149,6 +149,9 @@ const TopBar: React.FC = () => {
}
};

const [searchParams] = useSearchParams();
const [search] = searchParams.getAll('search');

return (
<AppBar
position="fixed"
Expand All @@ -172,6 +175,7 @@ const TopBar: React.FC = () => {
placeholder={`${t('Search the platform')}...`}
fullWidth={true}
onSubmit={onFullTextSearch}
keyword={search}
/>
</div>
<div className={classes.barRight}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T extends Base> Specification<T> fullTextSearch(
@NotBlank final String searchTerm,
@NotBlank final String property) {
@NotBlank final String searchTerm,
@NotBlank final List<String> properties) {
return (root, query, cb) -> {
List<Predicate> listOfPredicates = new ArrayList<>();
for (String property: properties) {
Expression<Double> tsVector = cb.function("to_tsvector", Double.class, cb.literal("simple"), root.get(property));
Expression<Double> tsQuery = cb.function("to_tsquery", Double.class, cb.literal("simple"), cb.literal(searchTerm));
Expression<Double> rank = cb.function("ts_rank", Double.class, tsVector, tsQuery);
query.orderBy(cb.desc(rank));
listOfPredicates.add(cb.greaterThan(rank, 0.01));
}

Expression<Double> tsVector = cb.function("to_tsvector", Double.class, cb.literal("simple"), root.get(property));
Expression<Double> tsQuery = cb.function("to_tsquery", Double.class, cb.literal("simple"), cb.literal(searchTerm));
Expression<Double> 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]));
};
}

Expand Down

0 comments on commit 2b66651

Please sign in to comment.