mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Global search waits for selection before submitting.
This commit is contained in:
parent
377c69e8a7
commit
3ad7b8af00
9 changed files with 141 additions and 169 deletions
|
|
@ -156,19 +156,34 @@ export function Getters() {
|
|||
export function DashboardPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
// The search the user submitted.
|
||||
const {searchStr, setSearchStr, activeTags} = useSearch();
|
||||
// The search that the user is typing.
|
||||
const [localSearchStr, setLocalSearchStr] = React.useState(searchStr);
|
||||
console.log('searchStr', searchStr, 'localSearchStr', localSearchStr);
|
||||
const {
|
||||
suggestionsResults,
|
||||
handleResultSelect,
|
||||
resultRenderer,
|
||||
loading,
|
||||
setSearchStr: setSuggestionSearchStr
|
||||
setSearchStr: setSuggestionSearchStr,
|
||||
setSearchTags,
|
||||
} = useContext(SearchSuggestionsContext);
|
||||
const {status} = useContext(StatusContext);
|
||||
|
||||
React.useEffect(() => {
|
||||
setSuggestionSearchStr(searchStr);
|
||||
}, [searchStr])
|
||||
setSuggestionSearchStr(localSearchStr);
|
||||
}, [localSearchStr]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setLocalSearchStr(searchStr);
|
||||
}, [searchStr]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (activeTags) {
|
||||
setSearchTags(activeTags);
|
||||
}
|
||||
}, [JSON.stringify(activeTags)]);
|
||||
|
||||
let title = 'Dashboard';
|
||||
if (searchStr) {
|
||||
|
|
@ -192,24 +207,28 @@ export function DashboardPage() {
|
|||
body = <SearchView/>;
|
||||
}
|
||||
|
||||
const getSearchResultsInput = (props) => {
|
||||
return <SearchResultsInput clearable
|
||||
searchStr={localSearchStr}
|
||||
onChange={setLocalSearchStr}
|
||||
onSubmit={setSearchStr}
|
||||
placeholder='Search everywhere...'
|
||||
onClear={() => navigate('/')}
|
||||
style={{marginBottom: '2em'}}
|
||||
results={suggestionsResults}
|
||||
handleResultSelect={handleResultSelect}
|
||||
resultRenderer={resultRenderer}
|
||||
loading={loading}
|
||||
{...props}
|
||||
/>;
|
||||
};
|
||||
|
||||
return <PageContainer>
|
||||
<Media at='mobile'>
|
||||
<Grid>
|
||||
<Grid.Row columns={2}>
|
||||
<Grid.Column width={13}>
|
||||
<SearchResultsInput clearable
|
||||
searchStr={searchStr}
|
||||
onChange={setSearchStr}
|
||||
onSubmit={setSearchStr}
|
||||
size='large'
|
||||
placeholder='Search everywhere...'
|
||||
onClear={() => navigate('/')}
|
||||
style={{marginBottom: '2em'}}
|
||||
results={suggestionsResults}
|
||||
handleResultSelect={handleResultSelect}
|
||||
resultRenderer={resultRenderer}
|
||||
loading={loading}
|
||||
/>
|
||||
{getSearchResultsInput({size: 'large'})}
|
||||
</Grid.Column>
|
||||
<Grid.Column width={3} textAlign='right'>
|
||||
<FileSearchFilterButton size='large'/>
|
||||
|
|
@ -221,19 +240,7 @@ export function DashboardPage() {
|
|||
<Grid>
|
||||
<Grid.Row columns={2}>
|
||||
<Grid.Column mobile={14}>
|
||||
<SearchResultsInput clearable
|
||||
searchStr={searchStr}
|
||||
onChange={setSearchStr}
|
||||
onSubmit={setSearchStr}
|
||||
size='big'
|
||||
placeholder='Search everywhere...'
|
||||
onClear={() => navigate('/')}
|
||||
style={{marginBottom: '2em'}}
|
||||
results={suggestionsResults}
|
||||
handleResultSelect={handleResultSelect}
|
||||
resultRenderer={resultRenderer}
|
||||
loading={loading}
|
||||
/>
|
||||
{getSearchResultsInput({size: 'big'})}
|
||||
</Grid.Column>
|
||||
<Grid.Column textAlign='right' width={2}>
|
||||
<FileSearchFilterButton size='big'/>
|
||||
|
|
|
|||
|
|
@ -1003,36 +1003,23 @@ export async function fetchDecoded(vinNumber) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function searchSuggestions(search_str) {
|
||||
const body = {search_str: search_str};
|
||||
export async function searchSuggestions(search_str, tagNames) {
|
||||
const body = {search_str: search_str, tag_names: tagNames};
|
||||
const response = await apiPost(`${API_URI}/search_suggestions`, body);
|
||||
if (response.ok) {
|
||||
const content = await response.json();
|
||||
|
||||
const fileGroups = content.file_groups;
|
||||
const zimsEstimates = content.zims_estimates;
|
||||
const channels = content.channels;
|
||||
const domains = content.domains;
|
||||
return {
|
||||
fileGroups,
|
||||
zimsEstimates,
|
||||
channels,
|
||||
domains,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchEstimateFiles(search_str, tagNames) {
|
||||
const body = {search_str: search_str, tag_names: tagNames};
|
||||
const response = await apiPost(`${API_URI}/files/search_estimates`, body);
|
||||
if (response.ok) {
|
||||
const content = await response.json();
|
||||
return {
|
||||
fileGroups: content.file_groups,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchEstimateZims(search_str, tagNames) {
|
||||
const body = {search_str: search_str, tag_names: tagNames};
|
||||
const response = await apiPost(`${API_URI}/zim/search_estimates`, body);
|
||||
|
|
|
|||
|
|
@ -1396,7 +1396,10 @@ export function TagIcon() {
|
|||
}
|
||||
|
||||
export function normalizeEstimate(estimate) {
|
||||
return estimate > 999 ? '>999' : estimate;
|
||||
if (Number.isInteger(estimate)) {
|
||||
return estimate > 999 ? '>999' : estimate.toString();
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
|
||||
export function useAPIButton(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {createSearchParams, Route, Routes, useNavigate} from "react-router-dom";
|
|||
import {FileSearchFilterButton, FilesSearchView} from "./Files";
|
||||
import {useLatestRequest, usePages, useQuery} from "../hooks/customHooks";
|
||||
import {ZimSearchView} from "./Zim";
|
||||
import {searchEstimateFiles, searchEstimateZims, searchSuggestions} from "../api";
|
||||
import {searchEstimateZims, searchSuggestions} from "../api";
|
||||
import {fuzzyMatch, normalizeEstimate, SearchResultsInput, TabLinks} from "./Common";
|
||||
import _ from "lodash";
|
||||
import {TagsContext} from "../Tags";
|
||||
|
|
@ -11,35 +11,28 @@ import {Header as SHeader, Label} from "semantic-ui-react";
|
|||
import {Button, Modal, ModalContent} from "./Theme";
|
||||
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
|
||||
|
||||
export const SearchSuggestionsContext = React.createContext({
|
||||
suggestionsResults: {},
|
||||
suggestions: {},
|
||||
suggestionsSums: {},
|
||||
handleResultSelect: null,
|
||||
resultRenderer: null,
|
||||
loading: false,
|
||||
searchStr: '',
|
||||
setSearchStr: null,
|
||||
});
|
||||
|
||||
|
||||
export const SearchSuggestionsProvider = (props) => {
|
||||
const value = useSearchSuggestions();
|
||||
|
||||
return <SearchSuggestionsContext.Provider value={value}>
|
||||
{props.children}
|
||||
</SearchSuggestionsContext.Provider>
|
||||
}
|
||||
|
||||
const SUGGESTED_APPS = [
|
||||
{location: '/more/otp', title: 'One Time Pad', description: 'Encrypt and Decrypt messages'},
|
||||
{location: '/inventory', title: 'Inventory', description: 'Track and organize your food storage'},
|
||||
{location: '/more/vin', title: 'Vin Decoder', description: 'Decode and analyze vehicle VIN numbers'},
|
||||
{location: '/admin', title: 'Downloads', description: 'View your downloads'},
|
||||
{location: '/admin', title: 'Downloads', description: 'View and control your downloads'},
|
||||
{location: '/admin/settings', title: 'Hotspot', description: 'Control your hotspot'},
|
||||
{location: '/admin/settings', title: 'Restart', description: 'Restart your WROLPi'},
|
||||
{location: '/admin/settings', title: 'Settings', description: 'View and modify your settings'},
|
||||
{location: '/admin/settings', title: 'Shutdown', description: 'Shutdown your WROLPi'},
|
||||
{location: '/admin/status', title: 'Status', description: 'View the status of this WROLPi server'},
|
||||
{location: '/admin/wrol', title: 'WROL Mode', description: 'Enable or disable WROL Mode'},
|
||||
{location: '/archive', title: 'Archives', description: 'View your Archives'},
|
||||
{location: '/archive/domains', title: 'Domains', description: 'View the domains of your Archives'},
|
||||
{location: '/files', title: 'Files', description: 'View your files'},
|
||||
{location: '/help', title: 'Help', description: 'Help documents for WROLPi'},
|
||||
{location: '/inventory', title: 'Inventory', description: 'Track and organize your food storage'},
|
||||
{location: '/map', title: 'Map', description: 'View your Map'},
|
||||
{location: '/map/manage', title: 'Manage Map', description: 'Manage your Map'},
|
||||
{location: '/more/otp', title: 'One Time Pad', description: 'Encrypt and Decrypt messages'},
|
||||
{location: '/more/statistics', title: 'Statistics', description: 'View the statistics of your WROLPi'},
|
||||
{location: '/more/vin', title: 'Vin Decoder', description: 'Decode and analyze vehicle VIN numbers'},
|
||||
{location: '/videos', title: 'Videos', description: 'View your Videos'},
|
||||
{location: '/videos/channel', title: 'Channels', description: 'View the Channels of your Videos'},
|
||||
{location: '/zim', title: 'Zim', description: 'View your Zims'},
|
||||
{location: '/zim/manage', title: 'Manage Zim', description: 'Manage your Zims'},
|
||||
];
|
||||
|
||||
export const useSearch = (defaultLimit = 48, totalPages = 0, emptySearch = false, model) => {
|
||||
|
|
@ -101,27 +94,65 @@ export const useSearch = (defaultLimit = 48, totalPages = 0, emptySearch = false
|
|||
}
|
||||
}
|
||||
|
||||
export function useSuggestions(searchStr) {
|
||||
const {data, sendRequest, loading} = useLatestRequest(500);
|
||||
export const SearchSuggestionsContext = React.createContext({
|
||||
suggestions: {}, // from the api
|
||||
suggestionsResults: {}, // suggestions converted for <Search/> results.
|
||||
suggestionsSums: {}, // suggestions from the api summarized.
|
||||
handleResultSelect: null, // user clicks "result"
|
||||
resultRenderer: null,
|
||||
loading: false,
|
||||
searchStr: '',
|
||||
setSearchStr: null,
|
||||
setSearchTags: null,
|
||||
});
|
||||
|
||||
export function useSuggestions(searchStr, tagNames) {
|
||||
const defaultSuggestions = {
|
||||
fileGroups: [],
|
||||
channels: [],
|
||||
domains: [],
|
||||
zimsEstimates: [],
|
||||
}
|
||||
const [suggestions, setSuggestions] = React.useState(defaultSuggestions);
|
||||
// fileGroups/channels/domains.
|
||||
const {data, sendRequest, loading} = useLatestRequest(500, true);
|
||||
// Zims are slow, so they are separate.
|
||||
const {data: zimData, sendRequest: sendZimRequest, loading: zimLoading} = useLatestRequest(500, true);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!searchStr || searchStr.length === 0) {
|
||||
return;
|
||||
if ((searchStr && searchStr.length > 0) || (tagNames && tagNames.length > 0)) {
|
||||
sendRequest(async () => await searchSuggestions(searchStr, tagNames));
|
||||
sendZimRequest(async () => await searchEstimateZims(searchStr, tagNames));
|
||||
}
|
||||
}, [searchStr, sendRequest, sendZimRequest, JSON.stringify(tagNames)]);
|
||||
|
||||
// Use the `useLatestRequest` to handle user typing.
|
||||
sendRequest(async () => await searchSuggestions(searchStr));
|
||||
}, [searchStr, sendRequest]);
|
||||
React.useEffect(() => {
|
||||
if (!_.isEmpty(data)) {
|
||||
setSuggestions({
|
||||
...suggestions,
|
||||
channels: data.channels,
|
||||
fileGroups: data.fileGroups,
|
||||
domains: data.domains,
|
||||
});
|
||||
}
|
||||
}, [JSON.stringify(data)]);
|
||||
|
||||
return {suggestions: data, loading}
|
||||
React.useEffect(() => {
|
||||
if (!_.isEmpty(zimData)) {
|
||||
setSuggestions({...suggestions, zimsEstimates: zimData.zimsEstimates});
|
||||
}
|
||||
}, [JSON.stringify(zimData)]);
|
||||
|
||||
return {suggestions, loading: loading || zimLoading}
|
||||
}
|
||||
|
||||
|
||||
export function useSearchSuggestions(defaultSearchStr) {
|
||||
export function useSearchSuggestions(defaultSearchStr, defaultTagNames) {
|
||||
const navigate = useNavigate();
|
||||
const [searchStr, setSearchStr] = React.useState(defaultSearchStr || '');
|
||||
const [searchTags, setSearchTags] = React.useState(defaultTagNames);
|
||||
const {SingleTag, fuzzyMatchTagsByName} = React.useContext(TagsContext);
|
||||
const {suggestions, loading} = useSuggestions(searchStr);
|
||||
const {suggestions, loading} = useSuggestions(searchStr, searchTags);
|
||||
|
||||
// The results that will be displayed by <Search>.
|
||||
const [suggestionsResults, setSuggestionsResults] = useState({});
|
||||
|
|
@ -130,7 +161,7 @@ export function useSearchSuggestions(defaultSearchStr) {
|
|||
|
||||
const normalizeSuggestionsResults = (newSuggestions) => {
|
||||
// Convert the suggestions from the Backend to what the Semantic <Search> expects.
|
||||
const lowerSearchStr = searchStr.toLowerCase();
|
||||
const lowerSearchStr = searchStr ? searchStr.toLowerCase() : '';
|
||||
|
||||
let results = {};
|
||||
|
||||
|
|
@ -147,8 +178,10 @@ export function useSearchSuggestions(defaultSearchStr) {
|
|||
};
|
||||
}
|
||||
|
||||
const zimSum = newSuggestions.zimsEstimates.reduce((i, j) => i + j, 0);
|
||||
if (zimSum > 0) {
|
||||
const zimSum = newSuggestions.zimsEstimates && newSuggestions.zimsEstimates.length > 0
|
||||
? newSuggestions.zimsEstimates.reduce((i, j) => i + j.estimate, 0)
|
||||
: null;
|
||||
if (newSuggestions && zimSum > 0) {
|
||||
results.zimsSum = {
|
||||
name: 'Zims', results: [
|
||||
{title: zimSum.toString(), type: 'zims', location: `/search/zim?q=${encodeURIComponent(searchStr)}`}
|
||||
|
|
@ -176,7 +209,8 @@ export function useSearchSuggestions(defaultSearchStr) {
|
|||
}
|
||||
}
|
||||
|
||||
const matchingTags = fuzzyMatchTagsByName(searchStr);
|
||||
// Match at most 5 Tags.
|
||||
const matchingTags = searchStr ? fuzzyMatchTagsByName(searchStr).slice(0, 5) : null;
|
||||
if (matchingTags && matchingTags.length > 0) {
|
||||
results.tags = {
|
||||
name: 'Tags', results: matchingTags.map(i => {
|
||||
|
|
@ -189,7 +223,8 @@ export function useSearchSuggestions(defaultSearchStr) {
|
|||
i.title.toLowerCase().includes(lowerSearchStr)
|
||||
|| fuzzyMatch(i.title.toLowerCase(), lowerSearchStr));
|
||||
if (matchingApps && matchingApps.length > 0) {
|
||||
results.apps = {name: 'Apps', results: matchingApps};
|
||||
// Match at most 5 apps.
|
||||
results.apps = {name: 'Apps', results: matchingApps.slice(0, 5)};
|
||||
}
|
||||
|
||||
setSuggestionsResults(results);
|
||||
|
|
@ -198,8 +233,8 @@ export function useSearchSuggestions(defaultSearchStr) {
|
|||
zims: zimSum,
|
||||
channels: newSuggestions.channels.length,
|
||||
domains: newSuggestions.domains.length,
|
||||
tags: matchingTags.length,
|
||||
apps: matchingApps.length,
|
||||
tags: matchingTags?.length,
|
||||
apps: matchingApps?.length,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -236,6 +271,7 @@ export function useSearchSuggestions(defaultSearchStr) {
|
|||
suggestionsSums,
|
||||
searchStr,
|
||||
setSearchStr,
|
||||
setSearchTags,
|
||||
handleResultSelect,
|
||||
resultRenderer,
|
||||
loading
|
||||
|
|
@ -243,67 +279,20 @@ export function useSearchSuggestions(defaultSearchStr) {
|
|||
}
|
||||
|
||||
|
||||
export function useSearchEstimate(searchStr, tagNames) {
|
||||
const {data: filesData, sendRequest: sendFilesRequest, loading: loadingFiles} = useLatestRequest(250);
|
||||
const {data: zimsData, sendRequest: sendZimsRequest, loading: loadingZims} = useLatestRequest(250);
|
||||
export const SearchSuggestionsProvider = (props) => {
|
||||
const value = useSearchSuggestions();
|
||||
|
||||
// The estimates from the response.
|
||||
const [estimates, setEstimates] = React.useState({
|
||||
fileGroups: [],
|
||||
zims: [],
|
||||
});
|
||||
// The estimates summarized.
|
||||
const [estimatesSums, setEstimatesSums] = React.useState({
|
||||
fileGroups: 0,
|
||||
zims: 0,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
// Handle results from `sendRequest`.
|
||||
if (_.isEmpty(filesData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setEstimates({...estimates, fileGroups: filesData.fileGroups});
|
||||
setEstimatesSums({...estimatesSums, fileGroups: filesData.fileGroups});
|
||||
}, [JSON.stringify(filesData)]);
|
||||
|
||||
React.useEffect(() => {
|
||||
sendFilesRequest(async () => await searchEstimateFiles(searchStr, tagNames));
|
||||
}, [searchStr, JSON.stringify(tagNames)], sendFilesRequest);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Handle results from `sendRequest`.
|
||||
if (_.isEmpty(zimsData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setEstimates({...estimates, zimsEstimates: zimsData.zimsEstimates});
|
||||
setEstimatesSums({...estimatesSums, zims: zimsData.zimsEstimates.reduce((i, j) => i + j['estimate'], 0)});
|
||||
}, [JSON.stringify(zimsData)]);
|
||||
|
||||
React.useEffect(() => {
|
||||
sendZimsRequest(async () => await searchEstimateZims(searchStr, tagNames));
|
||||
}, [searchStr, JSON.stringify(tagNames)], sendZimsRequest);
|
||||
|
||||
return {
|
||||
estimates,
|
||||
estimatesSums,
|
||||
loading: loadingFiles || loadingZims,
|
||||
}
|
||||
return <SearchSuggestionsContext.Provider value={value}>
|
||||
{props.children}
|
||||
</SearchSuggestionsContext.Provider>
|
||||
}
|
||||
|
||||
|
||||
export function SearchView() {
|
||||
const {searchStr, activeTags} = useSearch();
|
||||
const {estimates, estimatesSums, loading} = useSearchEstimate(searchStr, activeTags);
|
||||
const {suggestions, suggestionsSums, loading} = useContext(SearchSuggestionsContext);
|
||||
|
||||
let filesTabName = <span>Files <Label>?</Label></span>;
|
||||
let zimsTabName = <span>Zims<Label>?</Label></span>;
|
||||
if (!_.isEmpty(estimatesSums)) {
|
||||
filesTabName = <span>Files <Label>{normalizeEstimate(estimatesSums.fileGroups)}</Label></span>;
|
||||
zimsTabName = <span>Zims <Label>{normalizeEstimate(estimatesSums.zims)}</Label></span>;
|
||||
}
|
||||
const filesTabName = <span>Files <Label>{normalizeEstimate(suggestionsSums?.fileGroups)}</Label></span>;
|
||||
const zimsTabName = <span>Zims <Label>{normalizeEstimate(suggestionsSums?.zims)}</Label></span>;
|
||||
|
||||
const links = [
|
||||
{text: filesTabName, to: '/search', key: 'filesSearch', end: true},
|
||||
|
|
@ -314,7 +303,7 @@ export function SearchView() {
|
|||
<TabLinks links={links}/>
|
||||
<Routes>
|
||||
<Route path='/*' element={<FilesSearchView/>}/>
|
||||
<Route path='/zim' exact element={<ZimSearchView estimates={estimates} loading={loading}/>}/>
|
||||
<Route path='/zim' exact element={<ZimSearchView suggestions={suggestions} loading={loading}/>}/>
|
||||
</Routes>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,10 +287,10 @@ const ZimsRefreshWarning = () => {
|
|||
</Message>;
|
||||
}
|
||||
|
||||
export const ZimSearchView = ({estimates, loading}) => {
|
||||
export const ZimSearchView = ({suggestions, loading}) => {
|
||||
const [activeIndex, setActiveIndex] = React.useState(null);
|
||||
const {searchStr, activeTags, setTags} = useSearch();
|
||||
const {zimsEstimates} = estimates;
|
||||
const {zimsEstimates} = suggestions;
|
||||
|
||||
const handleClick = (index, activeIndex_) => {
|
||||
setActiveIndex(index === activeIndex_ ? -1 : index);
|
||||
|
|
@ -308,7 +308,11 @@ export const ZimSearchView = ({estimates, loading}) => {
|
|||
onClick={handleClick}
|
||||
/>);
|
||||
} else if (loading) {
|
||||
body = <Loader active={true}/>;
|
||||
body = <AccordionContent>
|
||||
<Segment placeholder>
|
||||
<Loader active={true}/>
|
||||
</Segment>
|
||||
</AccordionContent>;
|
||||
} else if (_.isEmpty(zimsEstimates)) {
|
||||
body = <ZimsRefreshWarning/>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export const useRecurringTimeout = (callback, delay) => {
|
|||
}, [delay])
|
||||
}
|
||||
|
||||
export const useLatestRequest = (delay = 300) => {
|
||||
export const useLatestRequest = (delay = 300, defaultLoading = false) => {
|
||||
// A hook which ignores older requests and will only set `data` to the latest response's data.
|
||||
// usage: sendRequest(async () => await yourAPICall(...args));
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ export const useLatestRequest = (delay = 300) => {
|
|||
const latestRequestRef = React.useRef(0);
|
||||
const debounceTimerRef = React.useRef(null);
|
||||
// Loading while awaiting.
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(defaultLoading);
|
||||
|
||||
const sendRequest = React.useCallback((fetchFunction) => {
|
||||
if (debounceTimerRef.current) {
|
||||
|
|
|
|||
|
|
@ -509,25 +509,18 @@ async def post_shutdown(_: Request):
|
|||
@api_bp.post('/search_suggestions')
|
||||
@validate(json=schema.SearchSuggestionsRequest)
|
||||
async def post_search_suggestions(_: Request, body: schema.SearchSuggestionsRequest):
|
||||
"""Used by the Global search to suggest related Channels/Domains/etc. to the user.
|
||||
|
||||
@note: Does not handle Tags, unlike search_estimates"""
|
||||
"""Used by the Global search to suggest related Channels/Domains/etc. to the user."""
|
||||
from modules.videos.channel.lib import search_channels_by_name
|
||||
from modules.archive.lib import search_domains_by_name
|
||||
from modules.zim import lib as zim_lib
|
||||
|
||||
file_groups, channels, domains = await asyncio.gather(
|
||||
estimate_search(body.search_str, []),
|
||||
estimate_search(body.search_str, body.tag_names),
|
||||
search_channels_by_name(body.search_str),
|
||||
search_domains_by_name(body.search_str),
|
||||
)
|
||||
|
||||
# Get estimates using libzim.
|
||||
zims_estimates = zim_lib.get_estimates(body.search_str)
|
||||
|
||||
ret = dict(
|
||||
file_groups=file_groups,
|
||||
zims_estimates=zims_estimates,
|
||||
channels=channels,
|
||||
domains=domains,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -140,9 +140,4 @@ class VINDecoderResponse:
|
|||
@dataclass
|
||||
class SearchSuggestionsRequest:
|
||||
search_str: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SearchEstimateRequest:
|
||||
search_str: Optional[str] = None
|
||||
tag_names: List[str] = field(default_factory=lambda: list())
|
||||
|
|
|
|||
|
|
@ -468,15 +468,13 @@ async def test_post_vin_number_decoder(test_async_client):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_suggestions(test_session, test_async_client, channel_factory, archive_factory, tag_factory,
|
||||
zim_factory):
|
||||
async def test_search_suggestions(test_session, test_async_client, channel_factory, archive_factory, tag_factory):
|
||||
# WARNING results are cached, this test uses unique queries to avoid conflicts.
|
||||
channel_factory(name='Foo')
|
||||
channel_factory(name='Fool')
|
||||
channel_factory(name='Bar')
|
||||
archive_factory(domain='foo.com', contents='contents of foo')
|
||||
archive_factory(domain='bar.com', contents='contents of bar')
|
||||
zim_factory('test zim')
|
||||
test_session.commit()
|
||||
|
||||
body = dict(search_str='foo')
|
||||
|
|
@ -489,8 +487,6 @@ async def test_search_suggestions(test_session, test_async_client, channel_facto
|
|||
'url': 'https://example.com/Fool'}]
|
||||
assert response.json['domains'] == [{'directory': 'archive/foo.com', 'domain': 'foo.com', 'id': 1}]
|
||||
assert response.json['file_groups'] == 1
|
||||
assert 'zims_estimates' in response.json and len(response.json['zims_estimates']) == 1
|
||||
assert response.json['zims_estimates'][0] == 0
|
||||
|
||||
body = dict(search_str='bar')
|
||||
request, response = await test_async_client.post('/api/search_suggestions', json=body)
|
||||
|
|
@ -498,7 +494,6 @@ async def test_search_suggestions(test_session, test_async_client, channel_facto
|
|||
assert response.json['channels'] == [{'directory': 'Bar', 'id': 3, 'name': 'Bar', 'url': 'https://example.com/Bar'}]
|
||||
assert response.json['domains'] == [{'directory': 'archive/bar.com', 'domain': 'bar.com', 'id': 2}]
|
||||
assert response.json['file_groups'] == 1
|
||||
assert response.json['zims_estimates'][0] == 0
|
||||
|
||||
body = dict(search_str='one')
|
||||
request, response = await test_async_client.post('/api/search_suggestions', json=body)
|
||||
|
|
@ -506,7 +501,6 @@ async def test_search_suggestions(test_session, test_async_client, channel_facto
|
|||
assert response.json['channels'] == []
|
||||
assert response.json['domains'] == []
|
||||
assert response.json['file_groups'] == 0
|
||||
assert response.json['zims_estimates'][0] == 2
|
||||
|
||||
|
||||
def test_recursive_errors():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue