diff --git a/platform/features/search/src/SearchAggregator.js b/platform/features/search/src/SearchAggregator.js index 30be3720dd..27d1f3f629 100644 --- a/platform/features/search/src/SearchAggregator.js +++ b/platform/features/search/src/SearchAggregator.js @@ -41,6 +41,8 @@ define( * aggregated */ function SearchAggregator(providers) { + var compiledResults = []; + // Determines if a searchResult object is a valid type // to be displayed as a final result. Is passed to the @@ -82,15 +84,15 @@ define( } }); - /* for (var i = 0; i < results.length; i++) { console.log('score', results[i].score, 'for', results[i].object.getModel().name); } - */ return results; } + // 'Loop' over the promises using recursion so that the promises are fufilled by the + // time that we are done function getPromisedResults(resultsPromises, promiseIndex, finalResults) { if (promiseIndex >= resultsPromises.length) { return finalResults; @@ -102,6 +104,60 @@ define( } } + function getPromisedItems(promises, index, fufilledPromises) { + if (index >= promises.length) { + return fufilledPromises; + } else { + return promises[index].then(function (results) { + fufilledPromises = fufilledPromises.concat(results); + return getPromisedItems(promises, index + 1, fufilledPromises); + }); + } + } + + // Add more results to compiledResults + // (As if the user presed 'load more results') + function loadMore() { + + } + + // Add x number of items to the compiledResults as the initial number of results that + // we display + function initialLoad(firstPromises) { + return getPromisedItems(firstPromises, 0, []).then(function (current) { + // Push the firsts onto the compiledResults + for (var i = 0; i < current.length; i++) { + if (current[i]) { + compiledResults.push(current[i]); + } + } + // Look for more results n times and add them to compiledResults + var outOfResults = []; + for (var i = 0; i < DEFAULT_MAX_RESULTS; i++) { + // If all of the providers are returning undefined, there are + // no more results to load + if (current.every(function (c) { + return c === undefined; + })) { + break; + } + + // For each provider, load the next result and add it to compiledResults + for (var j = 0; j < current.length; j++) { + if (current[j]) { + var nextResult = current[j].next(); + if (nextResult) { + compiledResults.push(nextResult); + } + current[j] = nextResult; + } + } + } + + return compiledResults; + }); + } + // Recieves results in the format of a serachResult object. It // has the members id, object, and score. It has a function // next() which returns the next highest score search result @@ -111,11 +167,15 @@ define( // merges the results lists so that there are not redundant // results function mergeResults(inputID) { - var resultsPromises = []; + //var resultsPromises = []; - // Get result list promises + // The first result from each provider. Each should have a next() function. + var firstPromises = []; + + // Get the initial result promises for (var i = 0; i < providers.length; i += 1) { - resultsPromises.push( + //resultsPromises.push( + firstPromises.push( providers[i].query( inputID, validType, DEFAULT_MAX_RESULTS, DEFUALT_TIMEOUT ) @@ -123,12 +183,20 @@ define( } // Wait for the promises to fufill + return initialLoad(firstPromises).then(function (c) { + // Get rid of the repeated objects and put in correct order + c = filterRepeats(c); + c = orderByScore(c); + return c; + }); + /* return getPromisedResults(resultsPromises, 0, []).then(function (c) { // Get rid of the repeated objects and put in correct order c = filterRepeats(c); c = orderByScore(c); return c; }); + */ } return { diff --git a/platform/features/search/src/providers/ElasticsearchSearchProvider.js b/platform/features/search/src/providers/ElasticsearchSearchProvider.js index 4d8f62bdac..74df3f658f 100644 --- a/platform/features/search/src/providers/ElasticsearchSearchProvider.js +++ b/platform/features/search/src/providers/ElasticsearchSearchProvider.js @@ -48,6 +48,8 @@ define( */ function ElasticsearchSearchProvider($http, objectService, ROOT) { // TODO: Fix the above docstring + var latestSearchResults = [], + currentResultIndex = 0; // Check to see if the input has any special options function isDefaultFormat(searchTerm) { @@ -67,7 +69,6 @@ define( return searchTerm.split(' ').map(function (s) { if (s.includes('"')) { - console.log('true'); return s; } else { return s + '~' + editDistance; @@ -97,6 +98,28 @@ define( return searchTerm; } + // Get the next search result + function next() { + // Because elasticsearch only returns matching things, we just + // need to step through the array + + currentResultIndex++; + + if (currentResultIndex > latestSearchResults.length) { + // If we go past the end of the array, we return undefined + return undefined; + } else { + return latestSearchResults[currentResultIndex]; + } + } + + function first() { + // Since next() immeditely does 'i++', start before the start of the array + currentResultIndex = -1; + var n = next(); + return n; + } + // Processes results from the format that elasticsearch returns to // a list of objects in the format that mct-representation can use function processResults(rawResults, validType) { @@ -104,8 +127,7 @@ define( resultsLength = results.length, ids = [], scores = {}, - searchResults = [], - i; + searchResults = []; if (rawResults.data.hits.total > resultsLength) { // TODO: Somehow communicate this to the user @@ -113,24 +135,21 @@ define( } // Get the result objects' IDs - for (i = 0; i < resultsLength; i += 1) { + for (var i = 0; i < resultsLength; i += 1) { ids.push(results[i][ID]); } // Get the result objects' scores - for (i = 0; i < resultsLength; i += 1) { - //scores.push(results[i][SCORE]); + for (var i = 0; i < resultsLength; i += 1) { scores[ ids[i] ] = results[i][SCORE]; } // Get the domain objects from their IDs return objectService.getObjects(ids).then(function (objects) { - var id, - j; // Filter by search term - for (j = 0; j < resultsLength; j += 1) { - id = ids[j]; + for (var j = 0; j < resultsLength; j += 1) { + var id = ids[j]; // Include items we can get models for if (objects[id].getModel) { @@ -140,13 +159,15 @@ define( searchResults.push({ id: id, object: objects[id], - score: scores[id] + score: scores[id], + next: next }); } } } - //console.log('searchResults (in ES provider)', searchResults); + console.log('setting latest search results with', searchResults); + latestSearchResults = searchResults; return searchResults; }); } @@ -191,8 +212,7 @@ define( searchTerm = processSearchTerm(searchTerm); // Create the query to elasticsearch - esQuery = ROOT + "/_search/?q=" + searchTerm + - "&size=" + maxResults; + esQuery = ROOT + "/_search/?q=" + searchTerm + "&size=" + maxResults; if (timeout) { esQuery += "&timeout=" + timeout; } @@ -203,7 +223,11 @@ define( url: esQuery }).then(function (rawResults) { // ...then process the data - return processResults(rawResults, validType); + processResults(rawResults, validType); + // and return the first result + var f = first(); + // console.log('ES return', f); + return f; }); } diff --git a/platform/features/search/src/providers/GenericSearchProvider.js b/platform/features/search/src/providers/GenericSearchProvider.js index 62e6639cd2..e1dad4ca40 100644 --- a/platform/features/search/src/providers/GenericSearchProvider.js +++ b/platform/features/search/src/providers/GenericSearchProvider.js @@ -44,6 +44,12 @@ define( * more easy creation of web workers. */ function GenericSearchProvider($rootScope, objectService, workerService) { + var latestItems = [], + currentResultIndex = 0, + currentSeachInput = '', + curentSearchTerms = [], + validType = function () {return true;}; + /* var worker = workerService.run('genericSearchWorker'), lastestItems; @@ -77,21 +83,19 @@ define( }); //counter += 1; } - */ function handleResponse(event) { latest = event.data; $rootScope.$apply(); //requestNext(); } + */ // Recursive helper function for getItems() function itemsHelper(children, i) { var date = new Date; if (stopTime && date.getTime() >= stopTime) { // This indexing of items has timed out - console.log('timed out'); - console.log('returning', children); return children; } else if (i >= children.length) { // Done! @@ -131,7 +135,8 @@ define( searchResultItems.push({ id: items[i].getId(), object: items[i], - score: 0 // Assign actual score when filtering for term + score: 0, // Assign actual score when filtering for term + next: next }); } @@ -141,8 +146,6 @@ define( }); } - - // Process the search input. Makes an array of search terms // by splitting up the input at spaces. function process(input) { @@ -179,8 +182,9 @@ define( return score * weight; } + /* // Filter through a list of searchResults based on a search term - function filterResults(results, originalInput, resultsLength, validType) { + function filterResults(results, originalInput, resultsLength) { var terms, searchResults = [], itemModel; @@ -202,6 +206,51 @@ define( return searchResults; } + */ + + // Get the next item from latestItems + function next() { + var i = currentResultIndex, + gotNext = false, + nextResult; + + // Look for the next item that qualifies as a search result + while (!gotNext) { + i++; + if (i > latestItems.length) { + // If we go past the end of the array, we return undefined + gotNext = true; + nextResult = undefined; + //currentResultIndex = i; + } else if (latestItems[i]) { + // Prevent errors from getModel not being defined + if (latestItems[i].object.getModel) { + latestItems[i].score = score(latestItems[i], curentSearchTerms, currentSeachInput); + + // Include any items that match the terms and are of valid type + if (latestItems[i].score > 0 && validType(latestItems[i].object.getModel())) { + // Add the result to the result list + nextResult = latestItems[i]; + //nextResult.next = next; + currentResultIndex = i; + gotNext = true; + } + } + } + } + + return nextResult; + } + + // Get the first result in latestItems that is a search result + function first(input) { + // Set up the global variables + currentSeachInput = input; + curentSearchTerms = process(input); + // Since next() immeditely does 'i++', start before the start of the array + currentResultIndex = -1; + return next(); + } /** * Searches through the filetree for domain objects which match @@ -216,15 +265,15 @@ define( * * @param inputID the name of the ID property of the html text * input where this funcion should find the search term - * @param validType a function which takes a model for an object - * and determines if it is of a valid type to include in the - * final list of results + * @param passedValidType (optional) a function which takes a model + * for an object and determines if it is of a valid type to include + * in the final list of results; default returns true * @param maxResults (optional) the maximum number of results * that this function should return * @param timeout (optional) the time after which the search should * stop calculations and return partial results */ - function queryGeneric(inputID, validType, maxResults, timeout) { + function queryGeneric(inputID, passedValidType, maxResults, timeout) { var input, terms = [], searchResults = [], @@ -237,6 +286,8 @@ define( maxResults = DEFAULT_MAX_RESULTS; } + // Set the global validType function with the passed one + validType = passedValidType; // Get the user input input = document.getElementById(inputID).value; @@ -244,6 +295,9 @@ define( // Get items list //requestItems(); // Test out the worker return getItems(timeout).then(function (searchResultItems) { + // Set global items variable + latestItems = searchResultItems; + // Keep track of the number of results to display if (searchResultItems.length < maxResults) { resultsLength = searchResultItems.length; @@ -252,10 +306,13 @@ define( } // Then filter through the items list - searchResults = filterResults(searchResultItems, input, resultsLength, validType); + //searchResults = filterResults(searchResultItems, input, resultsLength); - //console.log('filtered searchResults (in Everything)', searchResults); - return searchResults; + // Get the first search result + var firstResult = first(input); + console.log('generic return', firstResult); + + return firstResult; }); }