diff --git a/app/src/api.js b/app/src/api.js
index 21d8236e..12506aee 100644
--- a/app/src/api.js
+++ b/app/src/api.js
@@ -1024,10 +1024,15 @@ export async function searchSuggestions(search_str) {
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'];
const tags = content['tags'];
return {
+ fileGroups,
+ zimsEstimates,
channels,
domains,
tags,
diff --git a/app/src/components/Search.js b/app/src/components/Search.js
index 34906fee..0f7d2d1e 100644
--- a/app/src/components/Search.js
+++ b/app/src/components/Search.js
@@ -15,6 +15,7 @@ const suggestedApps = [
{location: '/more/vin', title: 'Vin Decoder', description: 'Decode and analyze vehicle VIN numbers'},
{location: '/admin', title: 'Downloads', description: 'View your downloads'},
{location: '/admin/settings', title: 'Settings', description: 'View and modify your settings'},
+ {location: '/admin/status', title: 'Status', description: 'View the status of this WROLPi'},
{location: '/admin/wrol', title: 'WROL Mode', description: 'Enable or disable WROL Mode'},
];
@@ -31,29 +32,55 @@ export function useSearchSuggestions() {
if (searchStr && searchStr.length > 0) {
const lowerSearchStr = searchStr.toLowerCase();
try {
- const i = await searchSuggestions(searchStr);
+ const newSuggestions = await searchSuggestions(searchStr);
const matchingApps = suggestedApps.filter(i =>
i.title.toLowerCase().includes(lowerSearchStr)
|| fuzzyMatch(i.title.toLowerCase(), lowerSearchStr));
- const matchingSuggestions = {
- channels: {
- name: 'Channels', results: i.channels.map(i => {
- return {title: i['name'], id: i['id'], type: 'channel'}
+
+ const zimSum = newSuggestions.zimsEstimates.reduce((i, j) => i + j['estimate'], 0);
+
+ // Suggested results are ordered.
+ let matchingSuggestions = {};
+ if (newSuggestions.fileGroups > 0) {
+ matchingSuggestions.fileGroups = {
+ name: 'Files', results: [
+ {description: newSuggestions.fileGroups, type: 'files'}
+ ]
+ };
+ }
+ if (zimSum > 0) {
+ matchingSuggestions.zimsSum = {
+ name: 'Zims', results: [
+ {description: zimSum, type: 'zims'}
+ ],
+ }
+ }
+ if (newSuggestions.channels && newSuggestions.channels.length > 0) {
+ matchingSuggestions.channels = {
+ name: 'Channels', results: newSuggestions.channels.map(i => {
+ return {type: 'channel', description: i['name'], id: i['id']}
})
- },
- domains: {
- name: 'Domains', results: i.domains.map(i => {
- return {title: i['domain'], id: i['id'], type: 'domain'}
+ }
+ }
+ if (newSuggestions.domains && newSuggestions.domains.length > 0) {
+ matchingSuggestions.domains = {
+ name: 'Domains', results: newSuggestions.domains.map(i => {
+ return {type: 'domain', description: i.domain, id: i['id'], domain: i.domain}
})
- },
- tags: {
- name: 'Tags', results: i.tags.map(i => {
- return {title: i['name'], type: 'tag'}
+ }
+ }
+ if (newSuggestions.tags && newSuggestions.tags.length > 0) {
+ matchingSuggestions.tags = {
+ name: 'Tags', results: newSuggestions.tags.map(i => {
+ return {type: 'tag', description: i['name']}
})
- },
- apps: {name: 'Apps', results: matchingApps},
- };
- console.debug('newSuggestions', matchingSuggestions);
+ }
+ }
+ if (matchingApps && matchingApps.length > 0) {
+ matchingSuggestions.apps = {name: 'Apps', results: matchingApps};
+ }
+
+ console.debug('matchingSuggestions', matchingSuggestions);
setSuggestions(matchingSuggestions);
} catch (e) {
console.error(e);
@@ -75,14 +102,19 @@ export function useSearchSuggestions() {
}
if (result.location) {
- navigate(result.location);
+ return navigate(result.location);
} else if (result['type'] === 'channel') {
- navigate(`/videos/channel/${result['id']}/video`);
+ return navigate(`/videos/channel/${result['id']}/video`);
} else if (result['type'] === 'domain') {
- navigate(`/archive?domain=${result['domain']}`);
+ return navigate(`/archive?domain=${result['domain']}`);
} else if (result['type'] === 'tag') {
- navigate(`/search?tag=${encodeURIComponent(result['title'])}`);
+ return navigate(`/search?tag=${encodeURIComponent(result['title'])}`);
+ } else if (result['type'] === 'files') {
+ return navigate(`/search?q=${encodeURIComponent(searchStr)}`);
+ } else if (result['type'] === 'zims') {
+ return navigate(`/search/zim?q=${encodeURIComponent(searchStr)}`);
}
+ console.error('No handleResultSelect defined:', result);
};
const resultRenderer = ({type, title, description}) => {
@@ -92,10 +124,13 @@ export function useSearchSuggestions() {
}
// No specific renderer, use the generic.
- return <>
- {title}
- {description}
- >
+ if (title) {
+ return <>
+ {title}
+ {description}
+ >
+ }
+ return {description}
};
return {suggestions, searchStr, handleResultSelect, resultRenderer}
diff --git a/wrolpi/root_api.py b/wrolpi/root_api.py
index 2626609f..9bf294c4 100644
--- a/wrolpi/root_api.py
+++ b/wrolpi/root_api.py
@@ -551,13 +551,40 @@ async def post_search_suggestions(_: Request, body: schema.SearchSuggestionsRequ
"""Used by the Global search to suggest links 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.models import Zims
- channels, domains, tags_ = await asyncio.gather(
+ if not body.search_str and not body.tag_names:
+ return response.empty(HTTPStatus.BAD_REQUEST)
+
+ file_groups, channels, domains, tags_ = await asyncio.gather(
+ estimate_search(body.search_str, body.tag_names),
search_channels_by_name(body.search_str),
search_domains_by_name(body.search_str),
tags.search_tags_by_name(body.search_str),
)
+
+ if body.tag_names:
+ # Get actual count of entries tagged with the tag names.
+ zims_estimates = list()
+ for zim, count in Zims.entries_with_tags(body.tag_names).items():
+ d = dict(
+ estimate=count,
+ **zim.__json__(),
+ )
+ zims_estimates.append(d)
+ else:
+ # Get estimates using libzim.
+ zims_estimates = list()
+ for zim, estimate in Zims.estimate(body.search_str).items():
+ d = dict(
+ estimate=estimate,
+ **zim.__json__(),
+ )
+ zims_estimates.append(d)
+
ret = dict(
+ file_groups=file_groups,
+ zims_estimates=zims_estimates,
channels=channels,
domains=domains,
tags=tags_,
diff --git a/wrolpi/schema.py b/wrolpi/schema.py
index baa3b18c..0655f384 100644
--- a/wrolpi/schema.py
+++ b/wrolpi/schema.py
@@ -146,3 +146,4 @@ class SearchEstimateRequest:
@dataclass
class SearchSuggestionsRequest:
search_str: Optional[str] = None
+ tag_names: List[str] = field(default_factory=lambda: list())
diff --git a/wrolpi/test/test_root_api.py b/wrolpi/test/test_root_api.py
index e9b15166..e5921e9c 100644
--- a/wrolpi/test/test_root_api.py
+++ b/wrolpi/test/test_root_api.py
@@ -488,12 +488,14 @@ def test_search_str_estimate(test_session, test_client, test_directory, test_zim
@pytest.mark.asyncio
-async def test_search_suggestions(test_session, test_async_client, channel_factory, archive_factory, tag_factory):
+async def test_search_suggestions(test_session, test_async_client, channel_factory, archive_factory, tag_factory,
+ zim_factory):
channel_factory(name='Foo')
channel_factory(name='Fool')
channel_factory(name='Bar')
- archive_factory(domain='foo.com')
- archive_factory(domain='bar.com')
+ archive_factory(domain='foo.com', contents='contents of foo')
+ archive_factory(domain='bar.com', contents='contents of bar')
+ zim_factory('test zim')
tag_factory()
test_session.commit()
@@ -506,12 +508,17 @@ async def test_search_suggestions(test_session, test_async_client, channel_facto
'name': 'Fool',
'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]['estimate'] == 0
body = dict(search_str='bar')
request, response = await test_async_client.post('/api/search_suggestions', json=body)
assert response.status_code == HTTPStatus.OK
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]['estimate'] == 0
body = dict(search_str='one')
request, response = await test_async_client.post('/api/search_suggestions', json=body)
@@ -519,6 +526,8 @@ async def test_search_suggestions(test_session, test_async_client, channel_facto
assert response.json['channels'] == []
assert response.json['domains'] == []
assert response.json['tags'] == [{'color': '#111111', 'id': 1, 'name': 'one'}]
+ assert response.json['file_groups'] == 0
+ assert response.json['zims_estimates'][0]['estimate'] == 2
def test_recursive_errors():