Add filters to the /v1/stories/search endpoint
This commit adds almost the same set of parameters that the /v1/stories endpoint supports to the /v1/stories/search endpoint. This allows one to obtain some search results and then filter them more easily than before, as well as avoiding a bug when trying to naively search for stories without removing the default criteria in the web ui. The title and description filters are not added, as they don't make much sense in the context of a fulltext search. This commit introduces some code duplication, and long term it would probably be best to merge the two endpoints into one and do the fulltext search iff the q parameter is given, rather than trying to maintain two functions with the same set of parameters for no reason. Change-Id: Ibbe1ed38094fb1d02919cf97ed311cf79af049bb
This commit is contained in:
parent
db050a3de6
commit
4aed46c189
@ -19,6 +19,7 @@ import sqlalchemy_fulltext.modes as FullTextMode
|
||||
|
||||
from storyboard.api.v1.search import search_engine
|
||||
from storyboard.db.api import base as api_base
|
||||
from storyboard.db.api import stories as stories_api
|
||||
from storyboard.db import models
|
||||
|
||||
|
||||
@ -30,7 +31,12 @@ class SqlAlchemySearchImpl(search_engine.SearchEngine):
|
||||
return query
|
||||
|
||||
def _apply_pagination(self, model_cls, query, marker=None,
|
||||
offset=None, limit=None):
|
||||
offset=None, limit=None, sort_field='id',
|
||||
sort_dir='asc'):
|
||||
if not sort_field:
|
||||
sort_field = 'id'
|
||||
if not sort_dir:
|
||||
sort_dir = 'asc'
|
||||
|
||||
marker_entity = None
|
||||
if marker:
|
||||
@ -53,27 +59,58 @@ class SqlAlchemySearchImpl(search_engine.SearchEngine):
|
||||
|
||||
return query.all()
|
||||
|
||||
def stories_query(self, q, marker=None, offset=None,
|
||||
limit=None, current_user=None, **kwargs):
|
||||
def stories_query(self, q, status=None, assignee_id=None,
|
||||
creator_id=None, project_group_id=None, project_id=None,
|
||||
subscriber_id=None, tags=None, updated_since=None,
|
||||
marker=None, offset=None,
|
||||
limit=None, tags_filter_type="all", sort_field='id',
|
||||
sort_dir='asc', current_user=None):
|
||||
session = api_base.get_session()
|
||||
|
||||
subquery = api_base.model_query(models.Story, session)
|
||||
subquery = stories_api._story_build_query(
|
||||
assignee_id=assignee_id,
|
||||
creator_id=creator_id,
|
||||
project_group_id=project_group_id,
|
||||
project_id=project_id,
|
||||
tags=tags,
|
||||
updated_since=updated_since,
|
||||
tags_filter_type=tags_filter_type,
|
||||
session=session)
|
||||
|
||||
# Filter by subscriber ID
|
||||
if subscriber_id is not None:
|
||||
subs = api_base.model_query(models.Subscription)
|
||||
subs = api_base.apply_query_filters(query=subs,
|
||||
model=models.Subscription,
|
||||
target_type='story',
|
||||
user_id=subscriber_id)
|
||||
subs = subs.subquery()
|
||||
subquery = subquery.join(subs, subs.c.target_id == models.Story.id)
|
||||
|
||||
subquery = self._build_fulltext_search(models.Story, subquery, q)
|
||||
subquery = self._apply_pagination(models.Story,
|
||||
subquery, marker, offset, limit)
|
||||
|
||||
# Filter out stories that the current user can't see
|
||||
subquery = api_base.filter_private_stories(subquery, current_user)
|
||||
|
||||
subquery = subquery.subquery('stories_with_idx')
|
||||
|
||||
query = api_base.model_query(models.StorySummary)\
|
||||
.options(subqueryload(models.StorySummary.tags))
|
||||
query = query.join(subquery,
|
||||
models.StorySummary.id == subquery.c.id)
|
||||
models.StorySummary.id == subquery.c.stories_id)
|
||||
|
||||
# Filter out stories that the current user can't see
|
||||
query = api_base.filter_private_stories(query, current_user)
|
||||
if status:
|
||||
query = query.filter(models.StorySummary.status.in_(status))
|
||||
|
||||
stories = query.all()
|
||||
return stories
|
||||
query = self._apply_pagination(models.StorySummary,
|
||||
query,
|
||||
marker,
|
||||
offset,
|
||||
limit,
|
||||
sort_field=sort_field,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
return query.all()
|
||||
|
||||
def tasks_query(self, q, marker=None, offset=None, limit=None,
|
||||
current_user=None, **kwargs):
|
||||
|
@ -357,9 +357,15 @@ class StoriesController(rest.RestController):
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose([wmodels.Story], wtypes.text, wtypes.text,
|
||||
int, int, int)
|
||||
def search(self, q="", marker=None, offset=None, limit=None):
|
||||
@wsme_pecan.wsexpose([wmodels.Story], wtypes.text,
|
||||
[wtypes.text], int, int, int, int, int, [wtypes.text],
|
||||
datetime, int, int, int, wtypes.text,
|
||||
wtypes.text, wtypes.text)
|
||||
def search(self, q="", status=None, assignee_id=None, creator_id=None,
|
||||
project_group_id=None, project_id=None, subscriber_id=None,
|
||||
tags=None, updated_since=None, marker=None, offset=None,
|
||||
limit=None, tags_filter_type='all', sort_field='id',
|
||||
sort_dir='asc'):
|
||||
"""The search endpoint for stories.
|
||||
|
||||
Example::
|
||||
@ -367,15 +373,40 @@ class StoriesController(rest.RestController):
|
||||
curl https://my.example.org/api/v1/stories/search?q=pep8
|
||||
|
||||
:param q: The query string.
|
||||
:return: List of Stories matching the query.
|
||||
"""
|
||||
:param status: Only show stories with this particular status.
|
||||
:param assignee_id: Filter stories by who they are assigned to.
|
||||
:param creator_id: Filter stories by who created them.
|
||||
:param project_group_id: Filter stories by project group.
|
||||
:param project_id: Filter stories by project ID.
|
||||
:param subscriber_id: Filter stories by subscriber ID.
|
||||
:param tags: A list of tags to filter by.
|
||||
:param updated_since: Filter stories by last updated time.
|
||||
:param marker: The resource id where the page should begin.
|
||||
:param offset: The offset to start the page at.
|
||||
:param limit: The number of stories to retrieve.
|
||||
:param tags_filter_type: Type of tags filter.
|
||||
:param sort_field: The name of the field to sort on.
|
||||
:param sort_dir: Sort direction for results (asc, desc).
|
||||
:return: List of Stories matching the query and any other filters.
|
||||
|
||||
"""
|
||||
user = request.current_user_id
|
||||
stories = SEARCH_ENGINE.stories_query(q=q,
|
||||
marker=marker,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
current_user=user)
|
||||
stories = SEARCH_ENGINE.stories_query(
|
||||
q,
|
||||
status=status,
|
||||
assignee_id=assignee_id,
|
||||
creator_id=creator_id,
|
||||
project_group_id=project_group_id,
|
||||
project_id=project_id,
|
||||
subscriber_id=subscriber_id,
|
||||
tags=tags,
|
||||
updated_since=updated_since,
|
||||
offset=offset,
|
||||
tags_filter_type=tags_filter_type,
|
||||
limit=limit,
|
||||
sort_field=sort_field,
|
||||
sort_dir=sort_dir,
|
||||
current_user=user)
|
||||
|
||||
return [create_story_wmodel(story) for story in stories]
|
||||
|
||||
|
@ -90,8 +90,10 @@ def story_get_all(title=None, description=None, status=None, assignee_id=None,
|
||||
project_id=project_id,
|
||||
tags=tags,
|
||||
updated_since=updated_since,
|
||||
tags_filter_type=tags_filter_type,
|
||||
current_user=current_user)
|
||||
tags_filter_type=tags_filter_type)
|
||||
|
||||
# Filter out stories that the current user can't see
|
||||
subquery = api_base.filter_private_stories(subquery, current_user)
|
||||
|
||||
# Filter by subscriber ID
|
||||
if subscriber_id is not None:
|
||||
@ -141,8 +143,10 @@ def story_get_count(title=None, description=None, status=None,
|
||||
project_id=project_id,
|
||||
updated_since=updated_since,
|
||||
tags=tags,
|
||||
tags_filter_type=tags_filter_type,
|
||||
current_user=current_user)
|
||||
tags_filter_type=tags_filter_type)
|
||||
|
||||
# Filter out stories that the current user can't see
|
||||
query = api_base.filter_private_stories(query, current_user)
|
||||
|
||||
# Filter by subscriber ID
|
||||
if subscriber_id is not None:
|
||||
@ -169,9 +173,9 @@ def story_get_count(title=None, description=None, status=None,
|
||||
def _story_build_query(title=None, description=None, assignee_id=None,
|
||||
creator_id=None, project_group_id=None,
|
||||
project_id=None, updated_since=None, tags=None,
|
||||
tags_filter_type='all', current_user=None):
|
||||
tags_filter_type='all', session=None):
|
||||
# First build a standard story query.
|
||||
query = api_base.model_query(models.Story.id).distinct()
|
||||
query = api_base.model_query(models.Story.id, session=session).distinct()
|
||||
|
||||
# Apply basic filters
|
||||
query = api_base.apply_query_filters(query=query,
|
||||
@ -182,9 +186,6 @@ def _story_build_query(title=None, description=None, assignee_id=None,
|
||||
if updated_since:
|
||||
query = query.filter(models.Story.updated_at > updated_since)
|
||||
|
||||
# Filter out stories that the current user can't see
|
||||
query = api_base.filter_private_stories(query, current_user)
|
||||
|
||||
# Filtering by tags
|
||||
if tags:
|
||||
if tags_filter_type == 'all':
|
||||
|
Loading…
Reference in New Issue
Block a user