Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issues with large search index responses. #311

Merged
merged 4 commits into from
May 2, 2024
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
4 changes: 2 additions & 2 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ Please always provide the [GitHub issue(s)](../issues) your PR is for, as well a
Fix #<gh-issue-id>

Test URLs:
- Before: https://main--famous-smoke-cigaradvisor--hlxsites.hlx.page/cigaradvisor
- After: https://<branch>--famous-smoke-cigaradvisor--hlxsites.hlx.page/cigaradvisor
- Before: https://main--famous-smoke-cigaradvisor--hlxsites.aem.page/cigaradvisor
- After: https://<branch>--famous-smoke-cigaradvisor--hlxsites.aem.page/cigaradvisor
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
Edge Delivery Services implementing https://www.famous-smoke.com/cigaradvisor

## Environments
- Preview: https://main--famous-smoke-cigaradvisor--hlxsites.hlx.page/cigaradvisor/
- Live: https://main--famous-smoke-cigaradvisor--hlxsites.hlx.live/cigaradvisor/
- Preview: https://main--famous-smoke-cigaradvisor--hlxsites.aem.page/cigaradvisor/
- Live: https://main--famous-smoke-cigaradvisor--hlxsites.aem.live/cigaradvisor/

## Installation

Expand All @@ -16,7 +16,7 @@ npm i

```sh
npm run lint
```
```

## Local development

Expand Down
159 changes: 85 additions & 74 deletions cigaradvisor/blocks/search-results/search-worker.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,18 @@
/* eslint-disable max-len */
/* eslint-disable no-restricted-globals */
const ARTICLE_INDEX_PATH = '/cigaradvisor/index/article-index.json';
const SEARCH_INDEX_PATH = '/cigaradvisor/index/search-index.json';
const articleIndexData = [];

const searchIndexData = [];
/**
* Retrieves search index data from the server.
* @returns {Promise<Object>} The search index data.
*/
async function getSearchIndexData(path = SEARCH_INDEX_PATH, flag = false) {
if (searchIndexData.length === 0 || flag) {
const resp = await fetch(path);
let jsonData = '';
if (resp.ok) {
jsonData = await resp.json();
}
jsonData.data.forEach((a) => {
searchIndexData.push({ ...a });
});
// If there are more items to load, load them
if ((jsonData.total - jsonData.offset) > jsonData.limit) {
const offset = jsonData.offset + jsonData.limit;
const indexPath = `${SEARCH_INDEX_PATH}?offset=${offset}&limit=${jsonData.total - offset}`;
await getSearchIndexData(indexPath, true);
}
}
// Protected against callers modifying the objects
const ret = [];
if (searchIndexData) {
searchIndexData.forEach((a) => {
ret.push({ ...a });
});
}
return ret;
}

/**
* Loads posts from the specified path asynchronously.
* @param {string} path - The path to fetch the posts from.
* @param {boolean} recurse - Indicates whether to recursively load more articles.
* @returns {Promise<Array<Object>>} - A promise that resolves to an array of post objects.
*/
async function loadPosts(path = ARTICLE_INDEX_PATH, recurse = false) {
if (articleIndexData.length === 0 || recurse) {
const resp = await fetch(path);
let jsonData = '';
if (resp.ok) {
jsonData = await resp.json();
}
jsonData.data.forEach((a) => {
articleIndexData.push({ ...a });
});
// If there are more articles to load, load them
if ((jsonData.total - jsonData.offset) > jsonData.limit) {
const offset = jsonData.offset + jsonData.limit;
const indexPath = `${ARTICLE_INDEX_PATH}?offset=${offset}&limit=${jsonData.total - offset}`;
await loadPosts(indexPath, true);
}
}
// Protected against callers modifying the objects
const ret = [];
if (articleIndexData) {
articleIndexData.forEach((a) => {
ret.push({ ...a });
});
const BUCKET_SIZE = 500;
const IGNORED_TERMS = [];

async function getTotal(index) {
const resp = await fetch(`${index}?limit=1`);
if (resp.ok) {
const json = await resp.json();
return json.total;
}
return ret;
return 0;
}

const IGNORED_TERMS = [];

const doMatch = (property, term) => {
const regex = new RegExp(term, 'gi');
if (property) {
Expand Down Expand Up @@ -130,6 +72,10 @@ function filterData(fullTerm, data) {
}
});

return results;
}

function sortData(results) {
return results.sort((l, r) => {
if (l.rank < r.rank) {
return -1;
Expand All @@ -147,17 +93,82 @@ function filterData(fullTerm, data) {
}).map((r) => r.article);
}

self.onmessage = async function handleSearch(event) {
const data = await getSearchIndexData();
const allArticles = await loadPosts();
async function doSearch(value) {
const total = await getTotal(SEARCH_INDEX_PATH);
const promises = [];
const buckets = Math.ceil(total / BUCKET_SIZE);

for (let i = 0; i < buckets; i += 1) {
promises.push(new Promise((resolve) => {
const offset = i * BUCKET_SIZE;
fetch(`${SEARCH_INDEX_PATH}?offset=${offset}&limit=${BUCKET_SIZE}`)
.then((resp) => {
if (resp.ok) {
return resp.json();
}
return {};
})
.then((json) => {
const { data } = json;
if (data) {
resolve(filterData(value, data));
}
resolve([]);
});
}));
}

return Promise.all(promises).then((results) => {
const matches = [];
results.forEach((r) => {
matches.push(...r);
});
return sortData(matches).map((m) => m.path);
});
}

async function getArticles(paths) {
const total = await getTotal(ARTICLE_INDEX_PATH);
const promises = [];
const buckets = Math.ceil(total / BUCKET_SIZE);

for (let i = 0; i < buckets; i += 1) {
promises.push(new Promise((resolve) => {
const offset = i * BUCKET_SIZE;
fetch(`${ARTICLE_INDEX_PATH}?offset=${offset}&limit=${BUCKET_SIZE}`)
.then((resp) => {
if (resp.ok) {
return resp.json();
}
return {};
})
.then((json) => {
const { data } = json;
if (data) {
resolve(data.filter((a) => paths.includes(a.path)));
}
resolve([]);
});
}));
}
return Promise.all(promises).then((results) => {
const matches = [];
results.forEach((a) => {
matches.push(...a);
});
return matches;
});
}

onmessage = async function handleSearch(event) {
const { searchValue } = event.data;
const searchResults = filterData(searchValue, data);
const paths = await doSearch(searchValue);
const found = await getArticles(paths);

const paths = searchResults.map((r) => r.path);
const filteredArticles = allArticles.filter((a) => paths.includes(a.path)).sort((a, b) => {
found.sort((a, b) => {
const indexA = paths.indexOf(a.path);
const indexB = paths.indexOf(b.path);
return indexA - indexB;
});
self.postMessage({ results: filteredArticles });
postMessage({ results: found });
};
4 changes: 2 additions & 2 deletions cigaradvisor/docs/create-category.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,5 @@ Now you should be all set to create and publish new pages in your previously cre

After publishing, they should appear in the corresponding index and sitemap (it may take a moment to generate these):

- `https://main--famous-smoke-cigaradvisor--hlxsites.hlx.page/cigaradvisor/index/article-index-<new-category>.json`
- `https://main--famous-smoke-cigaradvisor--hlxsites.hlx.page/cigaradvisor/article-sitemap-<new-category>.xml`
- `https://main--famous-smoke-cigaradvisor--hlxsites.aem.page/cigaradvisor/index/article-index-<new-category>.json`
- `https://main--famous-smoke-cigaradvisor--hlxsites.aem.page/cigaradvisor/article-sitemap-<new-category>.xml`
67 changes: 44 additions & 23 deletions cigaradvisor/scripts/scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,37 +313,58 @@ export function getRelativePath(path) {
}

const articleIndexData = [];

/**
* Loads posts from the specified path asynchronously.
* @param {string} path - The path to fetch the posts from.
* @param {boolean} recurse - Indicates whether to recursively load more articles.
* @returns {Promise<Array<Object>>} - A promise that resolves to an array of post objects.
*/
export async function loadPosts(path = ARTICLE_INDEX_PATH, recurse = false) {
if (articleIndexData.length === 0 || recurse) {
const resp = await fetch(path);
let jsonData = '';
if (resp.ok) {
jsonData = await resp.json();
}
jsonData.data.forEach((article) => {
articleIndexData.push({ ...article });
});
// If there are more articles to load, load them
if ((jsonData.total - jsonData.offset) > jsonData.limit) {
const offset = jsonData.offset + jsonData.limit;
const indexPath = `${ARTICLE_INDEX_PATH}?offset=${offset}&limit=${jsonData.total - offset}`;
await loadPosts(indexPath, true);
export async function loadPosts(path = ARTICLE_INDEX_PATH) {
if (!articleIndexData.length) {
const limit = 500;
const first = await fetch(`${path}?limit=${limit}`)
.then((resp) => {
if (resp.ok) {
return resp.json();
}
return {};
});

const { total } = first;
if (total) {
articleIndexData.push(...first.data);
const promises = [];
const buckets = Math.ceil(total / limit);
for (let i = 1; i < buckets; i += 1) {
promises.push(new Promise((resolve) => {
const offset = i * limit;
fetch(`${path}?offset=${offset}&limit=${limit}`)
.then((resp) => {
if (resp.ok) {
return resp.json();
}
return {};
})
.then((json) => {
const { data } = json;
if (data) {
resolve(data);
}
resolve([]);
});
}));
}

await Promise.all(promises).then((values) => {
values.forEach((list) => {
articleIndexData.push(...list);
});
});
}
}

// Protected against callers modifying the objects
const ret = [];
if (articleIndexData) {
articleIndexData.forEach((article) => {
ret.push({ ...article });
});
}
return ret;
return structuredClone(articleIndexData);
}

/**
Expand Down