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

Feat/search improvements #304

Closed
wants to merge 49 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
d0818e9
add promotion banner block
kronnox Feb 1, 2024
f76e703
improve image fetching
kronnox Feb 1, 2024
6c37c33
Merge branch 'issue/183'
kailasnadh790 Feb 1, 2024
e9c32bc
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Feb 2, 2024
a484646
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Feb 2, 2024
9115e7e
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Feb 5, 2024
169baff
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Feb 6, 2024
3451040
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Feb 6, 2024
ce3d2d6
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Feb 6, 2024
3ae7a14
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Feb 14, 2024
99fa335
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Feb 21, 2024
03e1b88
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Feb 26, 2024
d48632f
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Feb 27, 2024
3c77d98
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Feb 27, 2024
0665a23
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Mar 1, 2024
121728a
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Mar 5, 2024
22c7e67
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Mar 6, 2024
1c367d3
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Mar 12, 2024
a4e0c78
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Mar 19, 2024
c735268
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Mar 26, 2024
f140823
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Mar 27, 2024
693767c
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Mar 29, 2024
69ee8f1
Update search-results.js
kailasnadh790 Mar 29, 2024
a781465
Update search-results.js
kailasnadh790 Mar 29, 2024
3b88716
search improvement
kailasnadh790 Mar 30, 2024
e2114b0
adding blurb
kailasnadh790 Apr 1, 2024
6889c96
Improve search prioritization
bstopp Apr 2, 2024
aa06ead
Update search-results.js
kailasnadh790 Apr 2, 2024
c156e5e
Update search-results.js
kailasnadh790 Apr 2, 2024
c92f27b
Update search-results.js
kailasnadh790 Apr 2, 2024
4772689
Update search-results.js
kailasnadh790 Apr 2, 2024
c2477b0
Update search-results.js
kailasnadh790 Apr 2, 2024
9bd6f8f
MOre improvements.
bstopp Apr 3, 2024
8e1df01
Update search-results.js
kailasnadh790 Apr 4, 2024
ac795ee
Update search-results.js
kailasnadh790 Apr 4, 2024
be39a49
Update search-results.js
kailasnadh790 Apr 4, 2024
b84a347
Update search-results.js
kailasnadh790 Apr 4, 2024
3b73f8b
loader
kailasnadh790 Apr 4, 2024
ac16696
Update search-results.js
kailasnadh790 Apr 4, 2024
4be8af4
Update search-results.js
kailasnadh790 Apr 4, 2024
e32524a
Update search-results.js
kailasnadh790 Apr 5, 2024
2ec9db8
workers
kailasnadh790 Apr 5, 2024
bb98a10
formatting
kailasnadh790 Apr 5, 2024
669ce04
Update helix-query.yaml
kailasnadh790 Apr 5, 2024
dca321d
Update hero.js
kailasnadh790 Apr 5, 2024
f05d34a
flicker fix
kailasnadh790 Apr 5, 2024
2200b12
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Apr 5, 2024
acffe40
Merge branch 'main' of https://github.com/hlxsites/famous-smoke-cigar…
kailasnadh790 Apr 5, 2024
5e311d2
Update hero.js
kailasnadh790 Apr 5, 2024
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
182 changes: 63 additions & 119 deletions cigaradvisor/blocks/search-results/search-results.js
Original file line number Diff line number Diff line change
@@ -1,88 +1,10 @@
import { readBlockConfig, loadCSS } from '../../scripts/aem.js';

import { getSearchIndexData, loadPosts, getRelativePath } from '../../scripts/scripts.js';
import { getRelativePath } from '../../scripts/scripts.js';
import { renderPage } from '../article-list/article-list.js';

const searchParams = new URLSearchParams(window.location.search);

const IGNORED_TERMS = [];

const doMatch = (property, term) => {
const regex = new RegExp(term, 'gi');
if (property) {
return property.match(regex);
}
return false;
};

function filterData(fullTerm, data) {
const searchTokens = [];
searchTokens.push(fullTerm);

searchTokens.push(...fullTerm.toLowerCase().split(/\s+/).filter((term) => term && term.length > 2 && term !== fullTerm.toLowerCase()));

// Object
// {
// priority: Number
// article: article
// count: Number
// }
const results = [];
data.forEach((result) => {
const found = {
article: result,
count: 0,
};

searchTokens.forEach((token) => {
if (IGNORED_TERMS.includes(token.toLowerCase().trim())) return;
// eslint-disable-next-line no-param-reassign
if (token.endsWith('s')) token = token.substring(0, token.length - 1); // Handle potential pluralization of token.

if (doMatch(result.title, token)) {
found.rank ||= 1;
found.count += 1;
}
if (doMatch(result.heading, token)) {
found.rank ||= 2;
found.count += 1;
}
if (doMatch(result.description, token)) {
found.rank ||= 3;
found.count += 1;
}
if (doMatch(result.blurb, token)) {
found.rank ||= 4;
found.count += 1;
}
if (doMatch(result.text, token)) {
found.rank ||= 5;
found.count += 1;
}
});

if (found.count > 0) {
results.push(found);
}
});

return results.sort((l, r) => {
if (l.rank < r.rank) {
return -1;
}
if (l.rank === r.rank) {
if (l.count > r.count) {
return -1;
}
if (l.count < r.count) {
return 1;
}
return 0;
}
return 1; // Left rank is greater than right rank - move it down the list.
}).map((r) => r.article);
}

/**
* Get details of each search result from the article-index.
*
Expand Down Expand Up @@ -119,6 +41,7 @@ async function handleSearch(searchValue, block, limit) {
wrapper.replaceChildren(searchSummary);
return;
}

// show loading spinner
const loadingImageContainer = document.createElement('div');
loadingImageContainer.classList.add('loading-image-container');
Expand All @@ -131,47 +54,54 @@ async function handleSearch(searchValue, block, limit) {
loadingImageContainer.append(spinner);
block.prepend(loadingImageContainer);

const data = await getSearchIndexData();
const filteredData = filterData(searchValue, data);
const articlesCount = filteredData.length;

const allArticles = await loadPosts();
if (window.Worker) {
const worker = new Worker(`${window.hlx.codeBasePath}/blocks/search-results/search-worker.js`);
worker.onmessage = async function handleWorker(event) {
const { results, articles } = event.data;
const searchResults = JSON.parse(results);
const allArticles = JSON.parse(articles);
const articlesCount = searchResults.length;

searchSummary.textContent = `Your search for "${searchValue}" resulted in ${searchResults.length} articles`;
if (searchResults.length === 0) {
const noResults = document.createElement('p');
noResults.classList.add('no-results');
noResults.textContent = 'Sorry, we couldn\'t find the information you requested!';
wrapper.replaceChildren(searchSummary);
wrapper.append(noResults);
loadingImageContainer.style.display = 'none';
return;
}
let filteredDataCopy = [...searchResults];

// load the first page of results
let resultsToShow = filteredDataCopy.slice(0, limit);
// eslint-disable-next-line max-len
await processSearchResults(resultsToShow, allArticles, wrapper, limit, articlesCount);

wrapper.prepend(searchSummary);

// handle pagination. Render each page of results when the hash changes
window.addEventListener('hashchange', async () => {
const heroSearch = document.querySelector('.hero-search');
if (heroSearch) {
heroSearch.querySelector('input').value = searchValue;
}
const url = new URL(window.location.href);
const hashParams = new URLSearchParams(url.hash.substring(1));
const page = hashParams.get('page');
const start = (page - 1) * limit;
const end = start + limit;
filteredDataCopy = [...searchResults];
resultsToShow = filteredDataCopy.slice(start, end);
await processSearchResults(resultsToShow, allArticles, wrapper, limit, articlesCount);
wrapper.prepend(searchSummary);
});
};

searchSummary.textContent = `Your search for "${searchValue}" resulted in ${filteredData.length} articles`;
if (filteredData.length === 0) {
const noResults = document.createElement('p');
noResults.classList.add('no-results');
noResults.textContent = 'Sorry, we couldn\'t find the information you requested!';
wrapper.replaceChildren(searchSummary);
wrapper.append(noResults);
loadingImageContainer.style.display = 'none';
return;
// To perform a search
worker.postMessage({ searchValue });
}
let filteredDataCopy = [...filteredData];

// load the first page of results
let resultsToShow = filteredDataCopy.slice(0, limit);
// eslint-disable-next-line max-len
await processSearchResults(resultsToShow, allArticles, wrapper, limit, articlesCount);

wrapper.prepend(searchSummary);

// handle pagination. Render each page of results when the hash changes
window.addEventListener('hashchange', async () => {
const heroSearch = document.querySelector('.hero-search');
if (heroSearch) {
heroSearch.querySelector('input').value = searchValue;
}
const url = new URL(window.location.href);
const hashParams = new URLSearchParams(url.hash.substring(1));
const page = hashParams.get('page');
const start = (page - 1) * limit;
const end = start + limit;
filteredDataCopy = [...filteredData];
resultsToShow = filteredDataCopy.slice(start, end);
await processSearchResults(resultsToShow, allArticles, wrapper, limit, articlesCount);
wrapper.prepend(searchSummary);
});
}

export default async function decorate(block) {
Expand All @@ -196,12 +126,26 @@ export default async function decorate(block) {

block.replaceChildren(articleListWrapper);

const heroSearch = document.querySelector('.hero-search');

if (searchParams.get('s')) {
const searchValue = searchParams.get('s').trim();
const heroSearch = document.querySelector('.hero-search');
if (heroSearch) {
heroSearch.querySelector('input').value = searchValue;
}
handleSearch(searchValue, block, limit);
}

if (window.location.pathname === '/cigaradvisor/search') {
heroSearch.querySelector('form').addEventListener('submit', (e) => {
e.preventDefault();
const searchValue = heroSearch.querySelector('input').value;
if (searchValue) {
window.history.pushState({ search: searchValue }, '', `?s=${searchValue}`);
window.dispatchEvent(new Event('popstate'));
articleTeaserWrapper.replaceChildren();
handleSearch(searchValue, block, limit);
}
});
}
}
156 changes: 156 additions & 0 deletions cigaradvisor/blocks/search-results/search-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/* 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 = [];
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this will ever be populated? Can you run some tests and see if this keeps data between calls?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@bstopp you are correct...this is not getting cached between calls as we are fetching in worker... basically negating little perf improvement we could get by window.history.pushState and not reloading the page.

Copy link
Collaborator

Choose a reason for hiding this comment

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

What if the worker did the fetch and returned all the results. after that we don't have to actually run a search again per-page, we just change the page.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@bstopp But I feel that would reset all the performance improvements we achieved...mapping entire list of search results with article-index was taking time. So, it would slow the search on first load.

Can we use Service Workers and Caching API ? Reading about them now...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also, current performance is not bad as well even without this caching...these requests are cached on CDN rit?

/**
* 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 });
});
}
return ret;
}

const IGNORED_TERMS = [];

const doMatch = (property, term) => {
const regex = new RegExp(term, 'gi');
if (property) {
return property.match(regex);
}
return false;
};

function filterData(fullTerm, data) {
const searchTokens = [];
searchTokens.push(fullTerm);

searchTokens.push(...fullTerm.toLowerCase().split(/\s+/).filter((term) => term && term.length > 2 && term !== fullTerm.toLowerCase()));

// Object
// {
// priority: Number
// article: article
// count: Number
// }
const results = [];
data.forEach((result) => {
const found = {
article: result,
count: 0,
};

searchTokens.forEach((token) => {
if (IGNORED_TERMS.includes(token.toLowerCase().trim())) return;
// eslint-disable-next-line no-param-reassign
if (token.endsWith('s')) token = token.substring(0, token.length - 1); // Handle potential pluralization of token.

if (doMatch(result.title, token)) {
found.rank ||= 1;
found.count += 1;
}
if (doMatch(result.heading, token)) {
found.rank ||= 2;
found.count += 1;
}
if (doMatch(result.description, token)) {
found.rank ||= 3;
found.count += 1;
}
if (doMatch(result.blurb, token)) {
found.rank ||= 4;
found.count += 1;
}
if (doMatch(result.text, token)) {
found.rank ||= 5;
found.count += 1;
}
});

if (found.count > 0) {
results.push(found);
}
});

return results.sort((l, r) => {
if (l.rank < r.rank) {
return -1;
}
if (l.rank === r.rank) {
if (l.count > r.count) {
return -1;
}
if (l.count < r.count) {
return 1;
}
return 0;
}
return 1; // Left rank is greater than right rank - move it down the list.
}).map((r) => r.article);
}

self.onmessage = async function handleSearch(event) {
const data = await getSearchIndexData();
const allArticles = await loadPosts();
const { searchValue } = event.data;
const searchResults = filterData(searchValue, data);
self.postMessage({ results: JSON.stringify(searchResults), articles: JSON.stringify(allArticles) });
};