Point search requests to search endpoints

- Renamed browse methods to search.
- The name on which an autocompleted resource should be searched
  is now explicitly declared in the factory.
- Search results now check for the existence of "q" so that we show
  some results if a user is merely filtering on properties.
- When a 'q' parameter is passed to the searchresults directive,
  it populates using the /search endpoint rather than the browse
  endpoint.

Change-Id: I973bfe059852e0d345a60b662cc633270405850e
This commit is contained in:
Nikita Konovalov
2014-06-25 18:10:33 +04:00
committed by Michael Krotscheck
parent e00531e8e9
commit 89e846d9d1
7 changed files with 238 additions and 61 deletions

View File

@@ -37,54 +37,79 @@ angular.module('sb.search').directive('searchResults',
$scope.isSearching = false;
$scope.searchResults = [];
// Watch for changing criteria
$scope.$watchCollection($parse(args.searchCriteria),
function (criteria) {
/**
* Handle error result.
*/
function handleErrorResult() {
$scope.isSearching = false;
}
// Extract the valid critera from the provided ones.
$scope.validCriteria = Criteria
.filterCriteria(resourceName, criteria);
/**
* Handle search result.
*
* @param results
*/
function handleSearchResult(results) {
$scope.searchResults = results;
$scope.isSearching = false;
}
// You have criteria, but they may not be valid.
$scope.hasCriteria = criteria.length > 0;
/**
* Update the results when the criteria change
*/
function updateResults(criteria) {
// You have criteria, and all of them are valid for
// this resource.
$scope.hasValidCriteria =
searchWithoutCriteria ||
($scope.validCriteria.length === criteria.length &&
$scope.hasCriteria);
// Extract the valid criteria from the provided ones.
$scope.validCriteria = Criteria
.filterCriteria(resourceName, criteria);
// No need to search if our criteria aren't valid.
if (!$scope.hasValidCriteria) {
$scope.searchResults = [];
$scope.isSearching = false;
return;
}
// You have criteria, but they may not be valid.
$scope.hasCriteria = criteria.length > 0;
var params = Criteria.mapCriteria(resourceName,
$scope.validCriteria);
var resource = $injector.get(resourceName);
// You have criteria, and all of them are valid for
// this resource.
$scope.hasValidCriteria =
searchWithoutCriteria ||
($scope.validCriteria.length === criteria.length &&
$scope.hasCriteria);
if (!resource) {
$log.error('Invalid resource name: ' +
resourceName);
return;
}
// No need to search if our criteria aren't valid.
if (!$scope.hasValidCriteria) {
$scope.searchResults = [];
$scope.isSearching = false;
return;
}
// Apply paging.
params.limit = pageSize;
var params = Criteria.mapCriteria(resourceName,
$scope.validCriteria);
var resource = $injector.get(resourceName);
if (!resource) {
$log.error('Invalid resource name: ' +
resourceName);
return;
}
// Apply paging.
params.limit = pageSize;
// If we don't actually have search criteria, issue a
// browse. Otherwise, issue a search.
if (!params.hasOwnProperty('q')) {
resource.query(params,
function (results) {
$scope.searchResults = results;
$scope.isSearching = false;
},
function () {
$scope.isSearching = false;
}
);
});
handleSearchResult,
handleErrorResult);
} else {
resource.search(params,
handleSearchResult,
handleErrorResult);
}
}
// Watch for changing criteria
$scope.$watchCollection(
$parse(args.searchCriteria),
updateResults);
}
};
});

View File

@@ -31,10 +31,11 @@ angular.module('sb.services').factory('Project',
{id: '@id'}
);
ResourceFactory.applyBrowse(
ResourceFactory.applySearch(
'Project',
resource,
{Text: 'name'}
'name',
{Text: 'q'}
);
return resource;

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* A browse service, which wraps common resources and their typeahead
* resolution into a single service that returns a common result format.
* It is paired with the Criteria service to provide a consistent data
* format to identify resources independent of their actual schema.
*/
angular.module('sb.services').factory('Search',
function ($q, $log, Project, Story, User, Criteria) {
'use strict';
return {
/**
* Search projects by search string.
*
* @param searchString A string to search by.
* @return A promise that will resolve with the search results.
*/
project: function (searchString) {
// Search for projects...
var deferred = $q.defer();
Project.search({q: searchString},
function (result) {
// Transform the results to criteria tags.
var projResults = [];
result.forEach(function (item) {
projResults.push(
Criteria.create('project', item.id, item.name)
);
});
deferred.resolve(projResults);
}, function () {
deferred.resolve([]);
}
);
return deferred.promise;
},
/**
* Search users by search string.
*
* @param searchString A string to search by.
* @return A promise that will resolve with the search results.
*/
user: function (searchString) {
// Search for users...
var deferred = $q.defer();
User.search({q: searchString},
function (result) {
// Transform the results to criteria tags.
var userResults = [];
result.forEach(function (item) {
userResults.push(
Criteria.create('user', item.id, item.full_name)
);
});
deferred.resolve(userResults);
}, function () {
deferred.resolve([]);
}
);
return deferred.promise;
},
/**
* Search stories by search string.
*
* @param searchString A string to search by.
* @return A promise that will resolve with the search results.
*/
story: function (searchString) {
// Search for stories...
var deferred = $q.defer();
Story.search({q: searchString},
function (result) {
// Transform the results to criteria tags.
var storyResults = [];
result.forEach(function (item) {
storyResults.push(
Criteria.create('story', item.id, item.title)
);
});
deferred.resolve(storyResults);
}, function () {
deferred.resolve([]);
}
);
return deferred.promise;
},
/**
* Search all resources by a provided search string.
*
* @param searchString
* @return A promise that will resolve with the search results.
*/
all: function (searchString) {
var deferred = $q.defer();
// Clear the criteria
var criteria = [];
// Wrap everything into a collective promise
$q.all({
projects: this.project(searchString),
stories: this.story(searchString),
users: this.user(searchString)
}).then(function (results) {
// Add the returned projects to the results list.
results.projects.forEach(function (item) {
criteria.push(item);
});
// Add the returned stories to the results list.
results.stories.forEach(function (item) {
criteria.push(item);
});
// Add the returned stories to the results list.
results.users.forEach(function (item) {
criteria.push(item);
});
deferred.resolve(criteria);
});
// Return the search promise.
return deferred.promise;
}
};
});

View File

@@ -29,11 +29,12 @@ angular.module('sb.services').factory('Story',
{id: '@id'}
);
ResourceFactory.applyBrowse(
ResourceFactory.applySearch(
'Story',
resource,
'title',
{
Text: 'title',
Text: 'q',
StoryStatus: 'status',
Project: 'project_id',
User: 'assignee_id'

View File

@@ -30,10 +30,12 @@ angular.module('sb.services').factory('Task',
{id: '@id'}
);
ResourceFactory.applyBrowse(
ResourceFactory.applySearch(
'Task',
resource,
null,
{
Text: 'q',
Story: 'story_id',
User: 'assignee_id'
}

View File

@@ -31,10 +31,11 @@ angular.module('sb.services').factory('User',
{id: '@id'}
);
ResourceFactory.applyBrowse(
ResourceFactory.applySearch(
'User',
resource,
{Text: 'full_name'}
'full_name',
{Text: 'q'}
);
return resource;

View File

@@ -103,27 +103,23 @@ angular.module('sb.services')
/**
* This method takes an already configured resource, and applies
* the static methods necessary to support the criteria browse API.
* the static methods necessary to support the criteria search API.
* Browse parameters should be formatted as an object containing
* 'injector name': 'param'. For example, {'Project': 'project_id'}.
*
* @param resourceName The explicit resource name of this resource
* within the injection scope.
* @param resource The configured resource.
* @param browseParameters The browse parameters to apply.
* @param nameField The name field to use while browsing criteria.
* @param searchParameters The search parameters to apply.
*/
applyBrowse: function (resourceName, resource, browseParameters) {
applySearch: function (resourceName, resource, nameField,
searchParameters) {
// List of criteria resolvers which we're building.
var criteriaResolvers = [];
var browseParameter = null; // Default is ''
for (var type in browseParameters) {
// Store the browse parameter for later.
if (type === 'Text') {
browseParameter = browseParameters[type];
}
for (var type in searchParameters) {
// If the requested type exists and has a criteriaResolver
// method, add it to the list of resolvable browse criteria.
@@ -147,7 +143,7 @@ angular.module('sb.services')
// If we found a browse parameter, add the ability to use
// this resource as a source of criteria.
if (!!browseParameter) {
if (!!nameField) {
/**
* Add the criteria resolver method.
*/
@@ -157,7 +153,7 @@ angular.module('sb.services')
// build the query parameters.
var queryParams = {};
queryParams[browseParameter] = searchString;
queryParams[nameField] = searchString;
resource.query(queryParams,
function (result) {
@@ -167,7 +163,7 @@ angular.module('sb.services')
criteriaResults.push(
Criteria.create(resourceName,
item.id,
item[browseParameter])
item[nameField])
);
});
deferred.resolve(criteriaResults);
@@ -185,13 +181,13 @@ angular.module('sb.services')
* The criteria filter.
*/
resource.criteriaFilter = Criteria
.buildCriteriaFilter(browseParameters);
.buildCriteriaFilter(searchParameters);
/**
* The criteria map.
*/
resource.criteriaMap = Criteria
.buildCriteriaMap(browseParameters);
.buildCriteriaMap(searchParameters);
}
};