Merge pull request #2529 from coreos-inc/search-ui
Implement new search UI
This commit is contained in:
commit
5a9a231754
23 changed files with 649 additions and 393 deletions
|
@ -164,11 +164,13 @@ class EntitySearch(ApiResource):
|
|||
|
||||
def search_entity_view(username, entity, get_short_name=None):
|
||||
kind = 'user'
|
||||
title = 'user'
|
||||
avatar_data = avatar.get_data_for_user(entity)
|
||||
href = '/user/' + entity.username
|
||||
|
||||
if entity.organization:
|
||||
kind = 'organization'
|
||||
title = 'org'
|
||||
avatar_data = avatar.get_data_for_org(entity)
|
||||
href = '/organization/' + entity.username
|
||||
elif entity.robot:
|
||||
|
@ -179,9 +181,11 @@ def search_entity_view(username, entity, get_short_name=None):
|
|||
href = '/organization/' + parts[0] + '?tab=robots&showRobot=' + entity.username
|
||||
|
||||
kind = 'robot'
|
||||
title = 'robot'
|
||||
avatar_data = None
|
||||
|
||||
data = {
|
||||
'title': title,
|
||||
'kind': kind,
|
||||
'avatar': avatar_data,
|
||||
'name': entity.username,
|
||||
|
@ -233,20 +237,15 @@ def conduct_admined_team_search(username, query, encountered_teams, results):
|
|||
})
|
||||
|
||||
|
||||
def conduct_repo_search(username, query, results):
|
||||
def conduct_repo_search(username, query, results, offset=0, limit=5):
|
||||
""" Finds matching repositories. """
|
||||
matching_repos = model.repository.get_filtered_matching_repositories(query, username, limit=5)
|
||||
matching_repos = model.repository.get_filtered_matching_repositories(query, username, limit=limit,
|
||||
repo_kind=None,
|
||||
offset=offset)
|
||||
|
||||
for repo in matching_repos:
|
||||
results.append({
|
||||
'kind': 'repository',
|
||||
'namespace': search_entity_view(username, repo.namespace_user),
|
||||
'name': repo.name,
|
||||
'description': repo.description,
|
||||
'is_public': model.repository.is_repository_public(repo),
|
||||
'score': REPOSITORY_SEARCH_SCORE,
|
||||
'href': '/repository/' + repo.namespace_user.username + '/' + repo.name
|
||||
})
|
||||
# TODO: make sure the repo.kind.name doesn't cause extra queries
|
||||
results.append(repo_result_view(repo, username))
|
||||
|
||||
|
||||
def conduct_namespace_search(username, query, results):
|
||||
|
@ -266,6 +265,30 @@ def conduct_robot_search(username, query, results):
|
|||
results.append(search_entity_view(username, robot, get_short_name))
|
||||
|
||||
|
||||
def repo_result_view(repo, username, last_modified=None, stars=None, popularity=None):
|
||||
kind = 'application' if repo.kind.name == 'application' else 'repository'
|
||||
view = {
|
||||
'kind': kind,
|
||||
'title': 'app' if kind == 'application' else 'repo',
|
||||
'namespace': search_entity_view(username, repo.namespace_user),
|
||||
'name': repo.name,
|
||||
'description': repo.description,
|
||||
'is_public': model.repository.is_repository_public(repo),
|
||||
'score': REPOSITORY_SEARCH_SCORE,
|
||||
'href': '/' + kind + '/' + repo.namespace_user.username + '/' + repo.name
|
||||
}
|
||||
|
||||
if last_modified is not None:
|
||||
view['last_modified'] = last_modified
|
||||
|
||||
if stars is not None:
|
||||
view['stars'] = stars
|
||||
|
||||
if popularity is not None:
|
||||
view['popularity'] = popularity
|
||||
|
||||
return view
|
||||
|
||||
@resource('/v1/find/all')
|
||||
class ConductSearch(ApiResource):
|
||||
""" Resource for finding users, repositories, teams, etc. """
|
||||
|
@ -306,3 +329,51 @@ class ConductSearch(ApiResource):
|
|||
result['score'] = result['score'] * lm_score
|
||||
|
||||
return {'results': sorted(results, key=itemgetter('score'), reverse=True)}
|
||||
|
||||
|
||||
MAX_PER_PAGE = 10
|
||||
|
||||
@resource('/v1/find/repositories')
|
||||
class ConductRepositorySearch(ApiResource):
|
||||
""" Resource for finding repositories. """
|
||||
@parse_args()
|
||||
@query_param('query', 'The search query.', type=str, default='')
|
||||
@query_param('page', 'The page.', type=int, default=1)
|
||||
@nickname('conductRepoSearch')
|
||||
def get(self, parsed_args):
|
||||
""" Get a list of apps and repositories that match the specified query. """
|
||||
query = parsed_args['query']
|
||||
if not query:
|
||||
return {'results': []}
|
||||
|
||||
page = min(max(1, parsed_args['page']), 10)
|
||||
offset = (page - 1) * MAX_PER_PAGE
|
||||
limit = offset + MAX_PER_PAGE + 1
|
||||
|
||||
username = get_authenticated_user().username if get_authenticated_user() else None
|
||||
|
||||
# Lookup matching repositories.
|
||||
matching_repos = list(model.repository.get_filtered_matching_repositories(query, username,
|
||||
repo_kind=None,
|
||||
limit=limit,
|
||||
offset=offset))
|
||||
|
||||
# Load secondary information such as last modified time, star count and action count.
|
||||
repository_ids = [repo.id for repo in matching_repos]
|
||||
last_modified_map = model.repository.get_when_last_modified(repository_ids)
|
||||
star_map = model.repository.get_stars(repository_ids)
|
||||
action_sum_map = model.log.get_repositories_action_sums(repository_ids)
|
||||
|
||||
# Build the results list.
|
||||
results = [repo_result_view(repo, username, last_modified_map.get(repo.id),
|
||||
star_map.get(repo.id, 0),
|
||||
float(action_sum_map.get(repo.id, 0)))
|
||||
for repo in matching_repos]
|
||||
|
||||
return {
|
||||
'results': results[0:MAX_PER_PAGE],
|
||||
'has_additional': len(results) > MAX_PER_PAGE,
|
||||
'page': page,
|
||||
'page_size': MAX_PER_PAGE,
|
||||
'start_index': offset,
|
||||
}
|
||||
|
|
12
endpoints/api/test/test_search.py
Normal file
12
endpoints/api/test/test_search.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from endpoints.api.search import ConductRepositorySearch
|
||||
from endpoints.api.test.shared import client_with_identity, conduct_api_call
|
||||
from test.fixtures import *
|
||||
|
||||
def test_repository_search(client):
|
||||
with client_with_identity('devtable', client) as cl:
|
||||
params = {'query': 'simple'}
|
||||
result = conduct_api_call(cl, ConductRepositorySearch, 'GET', params, None, 200).json
|
||||
assert not result['has_additional']
|
||||
assert result['start_index'] == 0
|
||||
assert result['page'] == 1
|
||||
assert result['results'][0]['name'] == 'simple'
|
|
@ -4,16 +4,18 @@ from flask_principal import AnonymousIdentity
|
|||
from endpoints.api import api
|
||||
from endpoints.api.team import OrganizationTeamSyncing
|
||||
from endpoints.api.test.shared import client_with_identity, conduct_api_call
|
||||
from endpoints.api.repository import RepositoryTrust
|
||||
from endpoints.api.signing import RepositorySignatures
|
||||
from endpoints.api.search import ConductRepositorySearch
|
||||
from endpoints.api.superuser import SuperUserRepositoryBuildLogs, SuperUserRepositoryBuildResource
|
||||
from endpoints.api.superuser import SuperUserRepositoryBuildStatus
|
||||
from endpoints.api.signing import RepositorySignatures
|
||||
from endpoints.api.repository import RepositoryTrust
|
||||
|
||||
from test.fixtures import *
|
||||
|
||||
TEAM_PARAMS = {'orgname': 'buynlarge', 'teamname': 'owners'}
|
||||
BUILD_PARAMS = {'build_uuid': 'test-1234'}
|
||||
REPO_PARAMS = {'repository': 'devtable/someapp'}
|
||||
SEARCH_PARAMS = {'query': ''}
|
||||
|
||||
@pytest.mark.parametrize('resource,method,params,body,identity,expected', [
|
||||
(OrganizationTeamSyncing, 'POST', TEAM_PARAMS, {}, None, 403),
|
||||
|
@ -26,6 +28,11 @@ REPO_PARAMS = {'repository': 'devtable/someapp'}
|
|||
(OrganizationTeamSyncing, 'DELETE', TEAM_PARAMS, {}, 'reader', 403),
|
||||
(OrganizationTeamSyncing, 'DELETE', TEAM_PARAMS, {}, 'devtable', 200),
|
||||
|
||||
(ConductRepositorySearch, 'GET', SEARCH_PARAMS, None, None, 200),
|
||||
(ConductRepositorySearch, 'GET', SEARCH_PARAMS, None, 'freshuser', 200),
|
||||
(ConductRepositorySearch, 'GET', SEARCH_PARAMS, None, 'reader', 200),
|
||||
(ConductRepositorySearch, 'GET', SEARCH_PARAMS, None, 'devtable', 200),
|
||||
|
||||
(SuperUserRepositoryBuildLogs, 'GET', BUILD_PARAMS, None, None, 401),
|
||||
(SuperUserRepositoryBuildLogs, 'GET', BUILD_PARAMS, None, 'freshuser', 403),
|
||||
(SuperUserRepositoryBuildLogs, 'GET', BUILD_PARAMS, None, 'reader', 403),
|
||||
|
|
|
@ -98,6 +98,7 @@ def aci_signing_key():
|
|||
|
||||
return send_file(signer.open_public_key_file(), mimetype=PGP_KEY_MIMETYPE)
|
||||
|
||||
|
||||
@web.route('/plans/')
|
||||
@no_cache
|
||||
@route_show_if(features.BILLING)
|
||||
|
@ -105,6 +106,12 @@ def plans():
|
|||
return index('')
|
||||
|
||||
|
||||
@web.route('/search')
|
||||
@no_cache
|
||||
def search():
|
||||
return index('')
|
||||
|
||||
|
||||
@web.route('/guide/')
|
||||
@no_cache
|
||||
def guide():
|
||||
|
|
Reference in a new issue