Fix search SQL issues
This commit is contained in:
parent
5ae2975134
commit
4f4bb05621
2 changed files with 154 additions and 126 deletions
|
@ -961,11 +961,11 @@ def _get_public_repo_visibility():
|
|||
return _public_repo_visibility_cache
|
||||
|
||||
|
||||
def get_matching_repositories(repo_term, username=None, limit=10):
|
||||
def get_matching_repositories(repo_term, username=None, limit=10, include_public=True):
|
||||
namespace_term = repo_term
|
||||
name_term = repo_term
|
||||
|
||||
visible = get_visible_repositories(username)
|
||||
visible = get_visible_repositories(username, include_public=include_public)
|
||||
|
||||
search_clauses = (Repository.name ** ('%' + name_term + '%') |
|
||||
Namespace.username ** ('%' + namespace_term + '%'))
|
||||
|
@ -981,14 +981,18 @@ def get_matching_repositories(repo_term, username=None, limit=10):
|
|||
|
||||
return visible.where(search_clauses).limit(limit)
|
||||
|
||||
|
||||
def get_repository_pull_counts(repositories):
|
||||
repo_pull = LogEntryKind.get(name = 'pull_repo')
|
||||
if not repositories:
|
||||
return []
|
||||
|
||||
last_month = datetime.now() - timedelta(weeks=4)
|
||||
return (Repository.select(Repository.id, fn.Count(LogEntry.id))
|
||||
.where(Repository.id << [r.id for r in repositories])
|
||||
.join(LogEntry, JOIN_LEFT_OUTER)
|
||||
.where(LogEntry.kind == repo_pull)
|
||||
.group_by(LogEntry.repository)
|
||||
.where(LogEntry.kind == repo_pull, LogEntry.datetime >= last_month)
|
||||
.group_by(Repository.id, LogEntry.id)
|
||||
.tuples())
|
||||
|
||||
def change_password(user, new_password):
|
||||
|
|
|
@ -105,128 +105,6 @@ class EntitySearch(ApiResource):
|
|||
}
|
||||
|
||||
|
||||
@resource('/v1/find/all')
|
||||
class ConductSearch(ApiResource):
|
||||
""" Resource for finding users, repositories, teams, etc. """
|
||||
@parse_args
|
||||
@query_param('query', 'The search query.', type=str, default='')
|
||||
@require_scope(scopes.READ_REPO)
|
||||
@nickname('conductSearch')
|
||||
def get(self, args):
|
||||
""" Get a list of entities and resources that match the specified query. """
|
||||
query = args['query']
|
||||
if not query:
|
||||
return {'results': []}
|
||||
|
||||
username = None
|
||||
results = []
|
||||
|
||||
def entity_view(entity):
|
||||
kind = 'user'
|
||||
avatar_data = avatar.get_data_for_user(entity)
|
||||
href = '/user/' + entity.username
|
||||
|
||||
if entity.organization:
|
||||
kind = 'organization'
|
||||
avatar_data = avatar.get_data_for_org(entity)
|
||||
href = '/organization/' + entity.username
|
||||
elif entity.robot:
|
||||
parts = parse_robot_username(entity.username)
|
||||
if parts[0] == username:
|
||||
href = '/user/' + username + '?tab=robots&showRobot=' + entity.username
|
||||
else:
|
||||
href = '/organization/' + parts[0] + '?tab=robots&showRobot=' + entity.username
|
||||
|
||||
kind = 'robot'
|
||||
avatar_data = None
|
||||
|
||||
return {
|
||||
'kind': kind,
|
||||
'avatar': avatar_data,
|
||||
'name': entity.username,
|
||||
'score': 1,
|
||||
'href': href
|
||||
}
|
||||
|
||||
if get_authenticated_user():
|
||||
username = get_authenticated_user().username
|
||||
|
||||
# Find the matching teams where the user is a member.
|
||||
encountered_teams = set()
|
||||
|
||||
matching_teams = model.get_matching_user_teams(query, get_authenticated_user(), limit=5)
|
||||
for team in matching_teams:
|
||||
if team.id in encountered_teams:
|
||||
continue
|
||||
|
||||
encountered_teams.add(team.id)
|
||||
|
||||
results.append({
|
||||
'kind': 'team',
|
||||
'name': team.name,
|
||||
'organization': entity_view(team.organization),
|
||||
'avatar': avatar.get_data_for_team(team),
|
||||
'score': 2,
|
||||
'href': '/organization/' + team.organization.username + '/teams/' + team.name
|
||||
})
|
||||
|
||||
# Find matching teams in orgs admined by the user.
|
||||
matching_teams = model.get_matching_admined_teams(query, get_authenticated_user(), limit=5)
|
||||
for team in matching_teams:
|
||||
if team.id in encountered_teams:
|
||||
continue
|
||||
|
||||
encountered_teams.add(team.id)
|
||||
|
||||
results.append({
|
||||
'kind': 'team',
|
||||
'name': team.name,
|
||||
'organization': entity_view(team.organization),
|
||||
'avatar': avatar.get_data_for_team(team),
|
||||
'score': 2,
|
||||
'href': '/organization/' + team.organization.username + '/teams/' + team.name
|
||||
})
|
||||
|
||||
|
||||
# Find the matching repositories.
|
||||
matching_repos = model.get_matching_repositories(query, username, limit=5)
|
||||
matching_repo_counts = {t[0]: t[1] for t in model.get_repository_pull_counts(matching_repos)}
|
||||
|
||||
for repo in matching_repos:
|
||||
results.append({
|
||||
'kind': 'repository',
|
||||
'namespace': entity_view(repo.namespace_user),
|
||||
'name': repo.name,
|
||||
'description': repo.description,
|
||||
'is_public': repo.visibility.name == 'public',
|
||||
'score': math.log(matching_repo_counts.get(repo.id, 1), 10) or 1,
|
||||
'href': '/repository/' + repo.namespace_user.username + '/' + repo.name
|
||||
})
|
||||
|
||||
|
||||
# Find the matching users, robots and organizations.
|
||||
matching_entities = model.get_matching_entities(query)
|
||||
entity_count = 0
|
||||
for entity in matching_entities:
|
||||
# If the entity is a robot, filter it to only match those that are under the current
|
||||
# user or can be administered by the organization.
|
||||
if entity.robot:
|
||||
orgname = parse_robot_username(entity.username)[0]
|
||||
if not AdministerOrganizationPermission(orgname).can() and not orgname == username:
|
||||
continue
|
||||
|
||||
results.append(entity_view(entity))
|
||||
entity_count = entity_count + 1
|
||||
if entity_count >= 5:
|
||||
break
|
||||
|
||||
|
||||
for result in results:
|
||||
result['score'] = result['score'] * liquidmetal.score(result['name'], query)
|
||||
|
||||
return {'results': sorted(results, key=itemgetter('score'), reverse=True)}
|
||||
|
||||
|
||||
@resource('/v1/find/repository')
|
||||
class FindRepositories(ApiResource):
|
||||
""" Resource for finding repositories. """
|
||||
|
@ -256,3 +134,149 @@ class FindRepositories(ApiResource):
|
|||
if (repo.visibility.name == 'public' or
|
||||
ReadRepositoryPermission(repo.namespace_user.username, repo.name).can())]
|
||||
}
|
||||
|
||||
|
||||
|
||||
def search_entity_view(username, entity):
|
||||
kind = 'user'
|
||||
avatar_data = avatar.get_data_for_user(entity)
|
||||
href = '/user/' + entity.username
|
||||
|
||||
if entity.organization:
|
||||
kind = 'organization'
|
||||
avatar_data = avatar.get_data_for_org(entity)
|
||||
href = '/organization/' + entity.username
|
||||
elif entity.robot:
|
||||
parts = parse_robot_username(entity.username)
|
||||
if parts[0] == username:
|
||||
href = '/user/' + username + '?tab=robots&showRobot=' + entity.username
|
||||
else:
|
||||
href = '/organization/' + parts[0] + '?tab=robots&showRobot=' + entity.username
|
||||
|
||||
kind = 'robot'
|
||||
avatar_data = None
|
||||
|
||||
return {
|
||||
'kind': kind,
|
||||
'avatar': avatar_data,
|
||||
'name': entity.username,
|
||||
'score': 1,
|
||||
'href': href
|
||||
}
|
||||
|
||||
|
||||
def conduct_team_search(username, query, encountered_teams, results):
|
||||
""" Finds the matching teams where the user is a member. """
|
||||
matching_teams = model.get_matching_user_teams(query, get_authenticated_user(), limit=5)
|
||||
for team in matching_teams:
|
||||
if team.id in encountered_teams:
|
||||
continue
|
||||
|
||||
encountered_teams.add(team.id)
|
||||
|
||||
results.append({
|
||||
'kind': 'team',
|
||||
'name': team.name,
|
||||
'organization': search_entity_view(username, team.organization),
|
||||
'avatar': avatar.get_data_for_team(team),
|
||||
'score': 2,
|
||||
'href': '/organization/' + team.organization.username + '/teams/' + team.name
|
||||
})
|
||||
|
||||
|
||||
def conduct_admined_team_search(username, query, encountered_teams, results):
|
||||
""" Finds matching teams in orgs admined by the user. """
|
||||
matching_teams = model.get_matching_admined_teams(query, get_authenticated_user(), limit=5)
|
||||
for team in matching_teams:
|
||||
if team.id in encountered_teams:
|
||||
continue
|
||||
|
||||
encountered_teams.add(team.id)
|
||||
|
||||
results.append({
|
||||
'kind': 'team',
|
||||
'name': team.name,
|
||||
'organization': search_entity_view(username, team.organization),
|
||||
'avatar': avatar.get_data_for_team(team),
|
||||
'score': 2,
|
||||
'href': '/organization/' + team.organization.username + '/teams/' + team.name
|
||||
})
|
||||
|
||||
|
||||
def conduct_repo_search(username, query, results):
|
||||
""" Finds matching repositories. """
|
||||
matching_repos = list(model.get_matching_repositories(query, username, limit=5))
|
||||
matching_repo_counts = {t[0]: t[1] for t in model.get_repository_pull_counts(matching_repos)}
|
||||
|
||||
for repo in matching_repos:
|
||||
repo_score = math.log(matching_repo_counts.get(repo.id, 1), 10) or 1
|
||||
|
||||
# If the repository is under the user's namespace, give it 50% more weight.
|
||||
namespace = repo.namespace_user.username
|
||||
if OrganizationMemberPermission(namespace).can() or namespace == username:
|
||||
repo_score = repo_score * 1.5
|
||||
|
||||
results.append({
|
||||
'kind': 'repository',
|
||||
'namespace': search_entity_view(username, repo.namespace_user),
|
||||
'name': repo.name,
|
||||
'description': repo.description,
|
||||
'is_public': repo.visibility.name == 'public',
|
||||
'score': repo_score,
|
||||
'href': '/repository/' + repo.namespace_user.username + '/' + repo.name
|
||||
})
|
||||
|
||||
|
||||
def conduct_entity_search(username, query, results):
|
||||
""" Finds matching users, robots and organizations. """
|
||||
matching_entities = model.get_matching_entities(query)
|
||||
entity_count = 0
|
||||
for entity in matching_entities:
|
||||
# If the entity is a robot, filter it to only match those that are under the current
|
||||
# user or can be administered by the organization.
|
||||
if entity.robot:
|
||||
orgname = parse_robot_username(entity.username)[0]
|
||||
if not AdministerOrganizationPermission(orgname).can() and not orgname == username:
|
||||
continue
|
||||
|
||||
results.append(search_entity_view(username, entity))
|
||||
entity_count = entity_count + 1
|
||||
if entity_count >= 5:
|
||||
break
|
||||
|
||||
|
||||
@resource('/v1/find/all')
|
||||
class ConductSearch(ApiResource):
|
||||
""" Resource for finding users, repositories, teams, etc. """
|
||||
@parse_args
|
||||
@query_param('query', 'The search query.', type=str, default='')
|
||||
@require_scope(scopes.READ_REPO)
|
||||
@nickname('conductSearch')
|
||||
def get(self, args):
|
||||
""" Get a list of entities and resources that match the specified query. """
|
||||
query = args['query']
|
||||
if not query:
|
||||
return {'results': []}
|
||||
|
||||
username = None
|
||||
results = []
|
||||
|
||||
if get_authenticated_user():
|
||||
username = get_authenticated_user().username
|
||||
|
||||
# Search for teams.
|
||||
encountered_teams = set()
|
||||
conduct_team_search(username, query, encountered_teams, results)
|
||||
conduct_admined_team_search(username, query, encountered_teams, results)
|
||||
|
||||
# Search for repos.
|
||||
conduct_repo_search(username, query, results)
|
||||
|
||||
# Search for users, orgs and robots.
|
||||
conduct_entity_search(username, query, results)
|
||||
|
||||
# Modify the results' scores via how close the query term is to each result's name.
|
||||
for result in results:
|
||||
result['score'] = result['score'] * liquidmetal.score(result['name'], query)
|
||||
|
||||
return {'results': sorted(results, key=itemgetter('score'), reverse=True)}
|
||||
|
|
Reference in a new issue