diff --git a/platform/features/search/src/SearchAggregator.js b/platform/features/search/src/SearchAggregator.js index ad7d0e784f..087f992fe3 100644 --- a/platform/features/search/src/SearchAggregator.js +++ b/platform/features/search/src/SearchAggregator.js @@ -112,14 +112,17 @@ define( } // Order by score first, so that when removing repeats we keep the higher scored ones orderByScore(results); - totalSum = filterDuplicates(results, totalSum); + totalSum -= filterDuplicates(results, totalSum); // We are done loading loading = false; return { hits: results, - total: totalSum + total: totalSum, + timedOut: resultObjects.some(function (obj) { + return obj.timedOut; + }) }; }); } diff --git a/platform/features/search/src/providers/ElasticsearchSearchProvider.js b/platform/features/search/src/providers/ElasticsearchSearchProvider.js index 0c75f4d678..37c8ab526c 100644 --- a/platform/features/search/src/providers/ElasticsearchSearchProvider.js +++ b/platform/features/search/src/providers/ElasticsearchSearchProvider.js @@ -130,7 +130,8 @@ define( return { hits: searchResults, - total: rawResults.data.hits.total + total: rawResults.data.hits.total, + timedOut: rawResults.data.timed_out }; }); } @@ -180,7 +181,7 @@ define( * Searches through the filetree for domain objects using a search * term. This is done through querying elasticsearch. Returns a * promise for a result object that has the format - * {hits: searchResult[], total: number} + * {hits: searchResult[], total: number, timedOut: boolean} * where a searchResult has the format * {id: domainObject ID, object: domainObject, score: number} * diff --git a/platform/features/search/src/providers/GenericSearchProvider.js b/platform/features/search/src/providers/GenericSearchProvider.js index 3f6994560e..1825f10836 100644 --- a/platform/features/search/src/providers/GenericSearchProvider.js +++ b/platform/features/search/src/providers/GenericSearchProvider.js @@ -71,12 +71,13 @@ define( // Tell the worker to search for items it has that match this searchInput. // Takes the searchInput, as well as a max number of results (will return // less than that if there are fewer matches). - function workerSearch(searchInput, maxResults, timestamp) { + function workerSearch(searchInput, maxResults, timestamp, timeout) { var message = { request: 'search', input: searchInput, maxNumber: maxResults, - timestamp: timestamp + timestamp: timestamp, + timeout: timeout }; worker.postMessage(message); } @@ -108,7 +109,8 @@ define( pendingQueries[event.data.timestamp].resolve( { hits: searchResults, - total: event.data.total + total: event.data.total, + timedOut: event.data.timedOut } ); }); @@ -163,7 +165,7 @@ define( // For documentation, see query below. - function query(input, timestamp, maxResults/*, timeout*/) { + function query(input, timestamp, maxResults, timeout) { var terms = [], searchResults = [], defer = $q.defer(); @@ -176,10 +178,14 @@ define( // Else, we provide a default value. maxResults = DEFAULT_MAX_RESULTS; } + // Similarly, check if timeout was provided + if (!timeout) { + timeout = DEFAULT_TIMEOUT; + } // Instead, assume that the items have already been indexed, and // just send the query to the worker. - workerSearch(input, maxResults, timestamp); + workerSearch(input, maxResults, timestamp, timeout); return defer.promise; } @@ -198,7 +204,7 @@ define( * the search term. This function is to be used as a fallback * in the case where other search services are not avaliable. * Returns a promise for a result object that has the format - * {hits: searchResult[], total: number} + * {hits: searchResult[], total: number, timedOut: boolean} * where a searchResult has the format * {id: domainObject ID, object: domainObject, score: number} * diff --git a/platform/features/search/src/workers/GenericSearchWorker.js b/platform/features/search/src/workers/GenericSearchWorker.js index d88f4b9aa4..918c6c29be 100644 --- a/platform/features/search/src/workers/GenericSearchWorker.js +++ b/platform/features/search/src/workers/GenericSearchWorker.js @@ -132,37 +132,56 @@ var results = {}, input = data.input.toLocaleLowerCase(), terms = convertToTerms(input), - total, i, - score; + id, + score, + message = { + request: 'search', + results: {}, + total: 0, + timestamp: data.timestamp, // TODO: This may be no longer necissary + timedOut: false + }; // If the user input is empty, we want to have no search results. if (input !== '') { for (i = 0; i < indexedItems.length; i += 1) { + // If this is taking too long, then stop + if (Date.now() > data.timestamp + data.timeout) { + message.timedOut = true; + break; + } + score = scoreItem(indexedItems[i], input, terms); if (score > 0) { results[indexedItems[i].id] = score; + message.total += 1; } } } - // Store the total hits number - total = results.length; // Truncate results if there are more than maxResults - if (results.length > data.maxNumber) { - results = results.slice(0, data.maxNumber); + if (message.total > data.maxResults) { + i = 0; + for (id in results) { + message.results[id] = results[id]; + i += 1; + if (i >= data.maxResults) { + break; + } + } + // TODO: This seems inefficient. + } else { + message.results = results; } - return { - request: 'search', - results: results, - total: total, - timestamp: data.timestamp - }; + return message; // TODO: After a search is completed, do we need to // clear out indexedItems? // When do we need to clear out inedxedItems? + + // TODO: This function is too long. } self.onmessage = function (event) {