From eb143157e83fdae43e17e2609c1f04f373dfd532 Mon Sep 17 00:00:00 2001 From: David First Date: Tue, 31 Jan 2017 12:50:15 -0500 Subject: [PATCH 1/2] fix a critical bug: indexing all documents at once causes some documents to override others and as a result they do not appear in the search --- src/search/indexer.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/search/indexer.js b/src/search/indexer.js index f5ae12fab8f4..e0b0c32f7604 100644 --- a/src/search/indexer.js +++ b/src/search/indexer.js @@ -39,7 +39,6 @@ function prepareDoc(docs: Object, component: Component): Doc { id: `${box}_${name}`, name, box, - tokenizedNameExtra: tokenizeStr(name), // TODO: remove it when possible tokenizedName: tokenizeStr(name), tokenizedBox: tokenizeStr(box), functionNames, @@ -49,6 +48,26 @@ function prepareDoc(docs: Object, component: Component): Doc { }; } +function addAllToLocalIndex(components: Array): Promise { + return new Promise((resolve, reject) => { + const docs = components.map(component => prepareDoc(component.docs, component)); + localIndex.then((indexInstance) => { + const docStream = new Readable({ objectMode: true }); + docs.map(doc => docStream.push(doc)); + docStream.push(null); + docStream + .pipe(indexInstance.defaultPipeline()) + .pipe(indexInstance.add()) + .on('data', (d) => { + // this function needs to be called if you want to listen for the end event + }) + .on('end', () => { + resolve('The indexing has been completed'); + }); + }); + }); +} + function addToLocalIndex(component: Component): Promise { return new Promise((resolve, reject) => { const doc = prepareDoc(component.docs, component); @@ -79,8 +98,8 @@ function indexAll(path: string, components: Component[]): Promise { if (!components) return reject('The scope is empty'); serverlessIndex.deleteDb(path); localIndex = serverlessIndex.initializeIndex(path); - const results = components.map(component => addToLocalIndex(component)); - return resolve(Promise.all(results)); + const results = addAllToLocalIndex(components); + return resolve(results); }); } From 4ce411b9dde190350911b333388fb00ceda8a09b Mon Sep 17 00:00:00 2001 From: David First Date: Tue, 31 Jan 2017 14:25:24 -0500 Subject: [PATCH 2/2] due to a index-search bug, sort part of the search results manually --- src/search/searcher.js | 46 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/search/searcher.js b/src/search/searcher.js index b7f3468e9057..df7c46abfd7d 100644 --- a/src/search/searcher.js +++ b/src/search/searcher.js @@ -52,13 +52,10 @@ function formatSearchResult(doc: Doc): string { } function queryItem(field, queryStr): Object { - const query = { + return { AND: { [field]: queryStr.toLowerCase().split(' ') }, BOOST: boost[field], - NOT: {} }; - - return query; } function buildQuery(queryStr: string): Array { @@ -74,19 +71,52 @@ function buildQuery(queryStr: string): Array { return query; } +/** + * Sort by the length of the name + * @param {Array} results + * @return {Array} + */ +function sortSearchResults(results: Array): Array { + return results.sort((a, b) => a.name.length > b.name.length); +} + +/** + * Search in a local index. + * + * When a string is found in more than one field, and these fields don't have the same boost, + * the search-engine picks the boost of one of them randomly. + * For example, the search term "object" is in the 'name' and the 'description' fields. The + * search-engine might use the boost for the 'name', which is 4. But it also might use the boost + * for the 'description', which is 1. + * To workaround this issue, the search results are sorted manually after receiving them from the search engine. + * + * The sort algorithm is as follows: + * Results that have a match with the 'name' field, are the most relevant, and therefore are first. + * Among them, the shorter the name the most relevant it is. + * Other results, that is, results with a match of fields such as 'description', will be last. + * + * @param {string} queryStr + * @param {string} path + * @return {Promise} + */ function search(queryStr: string, path: string): Promise { return new Promise((resolve, reject) => { const index = serverlessIndex.initializeIndex(path); - const searchResults = []; + const searchResultsName = []; + const searchResultsOthers = []; const query = buildQuery(queryStr); return index.then((indexInstance) => { indexInstance.search({ query, }).on('data', function (data) { - searchResults.push(formatSearchResult(data.document)); + if (data.document.name.toLowerCase().includes(queryStr)) searchResultsName.push(data.document); + else searchResultsOthers.push(data.document); }).on('end', function () { - return resolve(JSON.stringify(searchResults)); - }); + const sortedResults = sortSearchResults(searchResultsName); + const searchResults = sortedResults.concat(searchResultsOthers); + const formattedResults = searchResults.map(formatSearchResult); + return resolve(JSON.stringify(formattedResults)); + }); }); }); }