Compare commits

...

5 Commits

Author SHA1 Message Date
2a19945870 [Search] Fix maxResults vs maxNumber
Typo fix in generic search. #91.
2015-08-28 15:38:23 -07:00
e4639091e5 [Search] Update tests
Update generic search worker and provider
tests for changes to worker scoring order.
2015-08-28 15:38:11 -07:00
a55607dc23 [Search] Worker presorts search results
Changed the generic search worker to sort
the search results before truncating the
results list to match maxResults. #91.
2015-08-28 15:37:57 -07:00
9593855c3f [Search] Update test
Updated the search controller test for the
dependancy on the throttle service. #91.
2015-08-28 13:50:06 -07:00
e045ce223b [Search] Throttle search
Use the throttle service to throttle search
calls from the search controller. #91.
2015-08-28 13:39:25 -07:00
7 changed files with 75 additions and 64 deletions

View File

@ -13,7 +13,7 @@
{ {
"key": "SearchController", "key": "SearchController",
"implementation": "controllers/SearchController.js", "implementation": "controllers/SearchController.js",
"depends": [ "$scope", "searchService" ] "depends": [ "$scope", "searchService", "throttle" ]
}, },
{ {
"key": "SearchMenuController", "key": "SearchMenuController",

View File

@ -30,7 +30,7 @@ define(function () {
var INITIAL_LOAD_NUMBER = 20, var INITIAL_LOAD_NUMBER = 20,
LOAD_INCREMENT = 20; LOAD_INCREMENT = 20;
function SearchController($scope, searchService) { function SearchController($scope, searchService, throttle) {
// numResults is the amount of results to display. Will get increased. // numResults is the amount of results to display. Will get increased.
// fullResults holds the most recent complete searchService response object // fullResults holds the most recent complete searchService response object
var numResults = INITIAL_LOAD_NUMBER, var numResults = INITIAL_LOAD_NUMBER,
@ -88,15 +88,6 @@ define(function () {
function search(maxResults) { function search(maxResults) {
var inputText = $scope.ngModel.input; var inputText = $scope.ngModel.input;
if (inputText !== '' && inputText !== undefined) {
// We are starting to load.
$scope.loading = true;
// Update whether the file tree should be displayed
// Hide tree only when starting search
$scope.ngModel.search = true;
}
if (!maxResults) { if (!maxResults) {
// Reset 'load more' // Reset 'load more'
numResults = INITIAL_LOAD_NUMBER; numResults = INITIAL_LOAD_NUMBER;
@ -129,7 +120,21 @@ define(function () {
* that this function should return. If not provided, search * that this function should return. If not provided, search
* service default will be used. * service default will be used.
*/ */
search: search, search: function (maxResults) {
// Show that we are loading before starting the throttled function
// so that the user knows the input was detected.
if ($scope.ngModel.input !== '' && $scope.ngModel.input !== undefined) {
// We are starting to load.
$scope.loading = true;
// Update whether the file tree should be displayed
// Hide tree only when starting search
$scope.ngModel.search = true;
}
// Call a throttled search
return throttle(search)(maxResults);
},
/** /**
* Checks to see if there are more search results to display. If the answer is * Checks to see if there are more search results to display. If the answer is

View File

@ -79,27 +79,28 @@ define(
// Handles responses from the web worker. Namely, the results of // Handles responses from the web worker. Namely, the results of
// a search request. // a search request.
function handleResponse(event) { function handleResponse(event) {
var ids = [], var ids = [];
id;
// If we have the results from a search // If we have the results from a search
if (event.data.request === 'search') { if (event.data.request === 'search') {
// Convert the ids given from the web worker into domain objects
for (id in event.data.results) {
ids.push(id);
}
objectService.getObjects(ids).then(function (objects) {
var searchResults = [],
id;
// Create searchResult objects // Get the ids into an array
for (id in objects) { event.data.results.forEach(function (result) {
searchResults.push({ ids.push(result.id);
object: objects[id], });
id: id,
score: event.data.results[id] // Get domainObjects with the ids
objectService.getObjects(ids).then(function (objects) {
var searchResults = [];
// Create searchResult objects with the gotten domainObjects
event.data.results.forEach(function (result) {
searchResults.push({
object: objects[result.id],
id: result.id,
score: result.score
});
}); });
}
// Resove the promise corresponding to this // Resove the promise corresponding to this
pendingQueries[event.data.timestamp].resolve({ pendingQueries[event.data.timestamp].resolve({

View File

@ -93,14 +93,14 @@
* * timestamp: The time identifier from when the query was made * * timestamp: The time identifier from when the query was made
*/ */
function search(data) { function search(data) {
// This results dictionary will have domain object ID keys which // This results array will have domain object IDs and scores together
// point to the value the domain object's score. // [{score: number, id: string}]
var results = {}, var results = [],
input = data.input.toLocaleLowerCase(), input = data.input.toLocaleLowerCase(),
terms = convertToTerms(input), terms = convertToTerms(input),
message = { message = {
request: 'search', request: 'search',
results: {}, results: [],
total: 0, total: 0,
timestamp: data.timestamp, timestamp: data.timestamp,
timedOut: false timedOut: false
@ -111,6 +111,7 @@
// If the user input is empty, we want to have no search results. // If the user input is empty, we want to have no search results.
if (input !== '') { if (input !== '') {
// Start getting search results
for (i = 0; i < indexedItems.length; i += 1) { for (i = 0; i < indexedItems.length; i += 1) {
// If this is taking too long, then stop // If this is taking too long, then stop
if (Date.now() > data.timestamp + data.timeout) { if (Date.now() > data.timestamp + data.timeout) {
@ -121,23 +122,26 @@
// Score and add items // Score and add items
score = scoreItem(indexedItems[i], input, terms); score = scoreItem(indexedItems[i], input, terms);
if (score > 0) { if (score > 0) {
results[indexedItems[i].id] = score; results.push({id: indexedItems[i].id, score: score});
message.total += 1; message.total += 1;
} }
} }
} }
// Truncate results if there are more than maxResults // Sort the results by score
if (message.total > data.maxResults) { results.sort(function (a, b) {
i = 0; if (a.score > b.score) {
for (id in results) { return -1;
message.results[id] = results[id]; } else if (b.score > a.score) {
i += 1; return 1;
if (i >= data.maxResults) { } else {
break; return 0;
} }
} });
// TODO: This seems inefficient.
// Truncate results if there are more than maxNumber
if (message.total > data.maxNumber) {
message.results = results.slice(0, data.maxNumber);
} else { } else {
message.results = results; message.results = results;
} }

View File

@ -36,6 +36,7 @@ define(
describe("The search controller", function () { describe("The search controller", function () {
var mockScope, var mockScope,
mockSearchService, mockSearchService,
mockThrottle,
mockPromise, mockPromise,
mockSearchResult, mockSearchResult,
mockDomainObject, mockDomainObject,
@ -73,6 +74,11 @@ define(
); );
mockSearchService.query.andReturn(mockPromise); mockSearchService.query.andReturn(mockPromise);
mockThrottle = jasmine.createSpy('throttle');
mockThrottle.andCallFake(function (fn) {
return fn;
});
mockTypes = [{key: 'mock.type', name: 'Mock Type', glyph: '?'}]; mockTypes = [{key: 'mock.type', name: 'Mock Type', glyph: '?'}];
mockSearchResult = jasmine.createSpyObj( mockSearchResult = jasmine.createSpyObj(
@ -86,7 +92,7 @@ define(
mockSearchResult.object = mockDomainObject; mockSearchResult.object = mockDomainObject;
mockDomainObject.getModel.andReturn({name: 'Mock Object', type: 'mock.type'}); mockDomainObject.getModel.andReturn({name: 'Mock Object', type: 'mock.type'});
controller = new SearchController(mockScope, mockSearchService, mockTypes); controller = new SearchController(mockScope, mockSearchService, mockThrottle);
controller.search(); controller.search();
}); });

View File

@ -172,10 +172,10 @@ define(
event = { event = {
data: { data: {
request: "search", request: "search",
results: { results: [
1: 1, {id: 1, score: 1},
2: 2 {id: 2, score: 2}
}, ],
total: 2, total: 2,
timedOut: false, timedOut: false,
timestamp: timestamp timestamp: timestamp

View File

@ -35,7 +35,17 @@ define(
numObjects = 5; numObjects = 5;
beforeEach(function () { beforeEach(function () {
var i; // See first it()
});
it("searches can reach all objects", function () {
var flag = false,
workerOutput,
resultsLength = 0,
i;
// This for loop is being done only once in the first it() becuase
// duplicate checking is done outside fo the worker
for (i = 0; i < numObjects; i += 1) { for (i = 0; i < numObjects; i += 1) {
worker.postMessage( worker.postMessage(
{ {
@ -49,12 +59,6 @@ define(
} }
); );
} }
});
it("searches can reach all objects", function () {
var flag = false,
workerOutput,
resultsLength = 0;
// Search something that should return all objects // Search something that should return all objects
runs(function () { runs(function () {
@ -70,12 +74,8 @@ define(
}); });
worker.onmessage = function (event) { worker.onmessage = function (event) {
var id;
workerOutput = event.data; workerOutput = event.data;
for (id in workerOutput.results) { resultsLength = event.data.results.length;
resultsLength += 1;
}
flag = true; flag = true;
}; };
@ -108,12 +108,8 @@ define(
}); });
worker.onmessage = function (event) { worker.onmessage = function (event) {
var id;
workerOutput = event.data; workerOutput = event.data;
for (id in workerOutput.results) { resultsLength = event.data.results.length;
resultsLength += 1;
}
flag = true; flag = true;
}; };
@ -124,7 +120,6 @@ define(
runs(function () { runs(function () {
expect(workerOutput).toBeDefined(); expect(workerOutput).toBeDefined();
expect(resultsLength).toEqual(1); expect(resultsLength).toEqual(1);
expect(workerOutput.results[2]).toBeDefined();
}); });
}); });
}); });