Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
77 changes: 53 additions & 24 deletions src/api/endpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,35 +156,64 @@ const fetchData = async (url, method = "GET", data: object | null = null) => {
}
};

export const elasticSearch = async (query) => {
export const elasticSearch = async (
query: string,
size?: number,
from: number = 0
) => {
const url = API_CONFIG.BASE_SCICRUNCH_URL + API_CONFIG.SCICRUNCH_KEY;

let total = size;

if (!size) {
try {
const initialResponse = await fetchData(url, "POST", {
size: 1,
from: 0,
query: buildQuery(query),
});

total = initialResponse?.hits?.total ?? 0;
} catch (error) {
console.error("Failed to fetch total count from Elasticsearch:", error);
return { results: [], total: 0 };
}
}

try {
const result = await fetchData(url, "POST", {
"size": 20,
"from": 0,
"query": {
"bool": {
"must": [
{
"query_string": {
"fields": [
"*"
],
"query": query,
"type": "cross_fields",
"default_operator": "and",
"lenient": "true"
}
}
]
}
}
const fullResponse = await fetchData(url, "POST", {
size: total,
from,
query: buildQuery(query),
});
return elasticSearhParser(result?.hits?.hits)

return {
results: elasticSearhParser(fullResponse?.hits?.hits),
total,
};
} catch (error) {
console.error("ElasticSearch Query Failed:", error);
console.error("Error when performing elastic search", error);
return { results: [], total: 0 };
}
}
};

const buildQuery = (query: string) => ({
"bool": {
"must": [
{
"query_string": {
"fields": [
"*"
],
"query": query,
"type": "cross_fields",
"default_operator": "and",
"lenient": "true"
}
}
]
}
});

export const searchAll = async (term, filters = {}) => {
const { searchAll } = useMockApi();
Expand Down
8 changes: 4 additions & 4 deletions src/components/Header/Search.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@ const Search = () => {

// eslint-disable-next-line react-hooks/exhaustive-deps
const fetchTerms = useCallback(debounce(async (searchTerm) => {
const data = await elasticSearch(searchTerm);
const dataTerms = data?.results?.filter(result => result.type === SEARCH_TYPES.TERM);
const dataOrganizations = data?.results?.filter(result => result.type === SEARCH_TYPES.ORGANIZATION);
const dataOntologies = data?.results?.filter(result => result.type === SEARCH_TYPES.ONTOLOGY);
const data = await elasticSearch(searchTerm, 20, 0);
const dataTerms = data?.results.results?.filter(result => result.type === SEARCH_TYPES.TERM);
const dataOrganizations = data?.results.results?.filter(result => result.type === SEARCH_TYPES.ORGANIZATION);
const dataOntologies = data?.results.results?.filter(result => result.type === SEARCH_TYPES.ONTOLOGY);
setTerms(dataTerms);
setOrganizations(dataOrganizations);
setOntologies(dataOntologies);
Expand Down
117 changes: 104 additions & 13 deletions src/components/SearchResults/SearchResultsBox.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';
import { useState, useEffect, useMemo } from 'react';
import ListView from './ListView';
import PropTypes from 'prop-types';
import { TableChartIcon, ListIcon } from '../../Icons';
import OntologySearch from '../SingleTermView/OntologySearch';
import CustomSingleSelect from "../common/CustomSingleSelect";
import CustomSingleSelect from '../common/CustomSingleSelect';
import { Box, Typography, Grid, ButtonGroup, Button, Stack, Divider } from '@mui/material';

import CustomPagination from '../common/CustomPagination';
import { vars } from '../../theme/variables';

const { gray50, gray200, gray300, gray600 } = vars;

const CustomViewButton = ({ view, listView, onClick, icon }) => (
Expand All @@ -29,25 +30,103 @@ const CustomViewButton = ({ view, listView, onClick, icon }) => (
</Button>
);

const SearchResultsBox = ({ searchResults, searchTerm, loading }) => {
const [numberOfVisiblePages, setNumberOfVisiblePages] = React.useState(20);
const [listView, setListView] = React.useState('list');
const getPaginationSettings = (totalItems) => {
const largeDatasetOptions = [20, 50, 100, 200];
const smallDatasetOptions = [10, 20, 50, 100];

const options = totalItems >= 200
? largeDatasetOptions.filter(opt => opt <= totalItems)
: smallDatasetOptions.filter(opt => opt <= totalItems);

if (options.length === 0) {
return {
options: [totalItems],
defaultSize: totalItems
};
}

let defaultSize;
if (totalItems >= 1000) defaultSize = 100;
else if (totalItems >= 500) defaultSize = 100;
Copy link
Member

Choose a reason for hiding this comment

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

change this as well in case

else if (totalItems >= 200) defaultSize = 50;
Copy link
Member

Choose a reason for hiding this comment

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

and this

else if (totalItems >= 100) defaultSize = 50;
else if (totalItems >= 50) defaultSize = 20;
else defaultSize = 10;

if (!options.includes(defaultSize)) {
defaultSize = options[Math.floor(options.length / 2)];
}

return { options, defaultSize };
};

const SearchResultsBox = ({
allResults,
pageResults,
searchTerm,
loading,
totalItems,
fetchPage,
hasActiveFilters
}) => {
const { options, defaultSize } = getPaginationSettings(totalItems);
const [listView, setListView] = useState('list');
const [page, setPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(defaultSize);

useEffect(() => {
if (!hasActiveFilters) {
const from = (page - 1) * itemsPerPage;
const remainingItems = totalItems - from;
const size = Math.min(itemsPerPage, remainingItems);

const handleNumberOfPagesChange = (v) => {
setNumberOfVisiblePages(v);
if (size > 0) {
fetchPage(from, size);
}
}
}, [page, itemsPerPage, totalItems, fetchPage, hasActiveFilters]);

useEffect(() => {
const { defaultSize: newDefault } = getPaginationSettings(totalItems);
setItemsPerPage(newDefault);
setPage(1);
}, [totalItems]);

const paginatedResults = useMemo(() => {
if (!hasActiveFilters) return pageResults;

const startIndex = (page - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
return pageResults.slice(startIndex, endIndex);
}, [pageResults, page, itemsPerPage, hasActiveFilters]);

const handlePageChange = (_, newPage) => {
setPage(newPage);
};

const handleItemsPerPageChange = (value) => {
const newItemsPerPage = Number(value);
setItemsPerPage(newItemsPerPage);
setPage(1)
};

return (
<Box width={1} flex={1} display="flex" flexDirection="column" px={4} py={3} gap={3} sx={{ overflowY: 'auto' }}>
<Grid container justifyContent={{ lg: 'space-between', xs: 'flex-end', md: 'flex-end' }} alignItems="center">
<Grid item xs={12} lg={6} sm={6}>
<Typography variant="h5">{searchResults?.length} results for {searchTerm} search</Typography>
<Typography variant="h5">
{allResults.length} results for {searchTerm} search
</Typography>
</Grid>
<Grid item xs={12} lg={6} sm={6}>
<Box display="flex" alignItems="center" gap={2} justifyContent="end">
<Stack direction="row" alignItems="center" gap={1}>
<Typography variant="caption" sx={{ fontSize: '0.875rem', color: gray600 }}>Show on page:</Typography>
<CustomSingleSelect value={numberOfVisiblePages} onChange={handleNumberOfPagesChange} options={['10', '20', '30']} />
<CustomSingleSelect
value={itemsPerPage}
onChange={handleItemsPerPageChange}
options={options}
/>
</Stack>
<ButtonGroup variant="outlined" aria-label="View mode">
<CustomViewButton
Expand All @@ -72,11 +151,19 @@ const SearchResultsBox = ({ searchResults, searchTerm, loading }) => {
</Box>
</Grid>
</Grid>

{listView === 'list' ? (
<ListView searchResults={searchResults} loading={loading} />
<ListView searchResults={paginatedResults} loading={loading} />
) : (
<p>table</p>
)}

<CustomPagination
rowCount={totalItems}
rowsPerPage={itemsPerPage}
page={page}
onPageChange={handlePageChange}
/>
</Box>
);
};
Expand All @@ -89,9 +176,13 @@ CustomViewButton.propTypes = {
};

SearchResultsBox.propTypes = {
searchResults: PropTypes.object,
allResults: PropTypes.object,
pageResults: PropTypes.object,
searchTerm: PropTypes.string,
loading: PropTypes.bool
loading: PropTypes.bool,
totalItems: PropTypes.number,
fetchPage: PropTypes.func,
hasActiveFilters: PropTypes.bool
};

export default SearchResultsBox;
Loading