diff --git a/storyboard/api/v1/search/sqlalchemy_impl.py b/storyboard/api/v1/search/sqlalchemy_impl.py index 810942b8..aa5cc94b 100644 --- a/storyboard/api/v1/search/sqlalchemy_impl.py +++ b/storyboard/api/v1/search/sqlalchemy_impl.py @@ -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): diff --git a/storyboard/api/v1/stories.py b/storyboard/api/v1/stories.py index a205f565..dbae327b 100644 --- a/storyboard/api/v1/stories.py +++ b/storyboard/api/v1/stories.py @@ -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] diff --git a/storyboard/db/api/stories.py b/storyboard/db/api/stories.py index 369174e7..1a5f9767 100644 --- a/storyboard/db/api/stories.py +++ b/storyboard/db/api/stories.py @@ -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':