Merge "Allow lists of boards and worklists to be paged"

This commit is contained in:
Jenkins 2016-05-13 15:31:18 +00:00 committed by Gerrit Code Review
commit fb52a5318e
4 changed files with 272 additions and 60 deletions

View File

@ -142,10 +142,10 @@ class BoardsController(rest.RestController):
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Board], wtypes.text, int, int, bool,
int, int, int, wtypes.text, wtypes.text)
int, int, int, int, int, wtypes.text, wtypes.text)
def get_all(self, title=None, creator_id=None, project_id=None,
archived=False, user_id=None, story_id=None, task_id=None,
sort_field='id', sort_dir='asc'):
offset=None, limit=None, sort_field='id', sort_dir='asc'):
"""Retrieve definitions of all of the boards.
:param title: A string to filter the title by.
@ -154,31 +154,45 @@ class BoardsController(rest.RestController):
:param archived: Filter boards by whether they are archived or not.
:param story_id: Filter boards by whether they contain a story.
:param task_id: Filter boards by whether they contain a task.
:param offset: Value to offset results by.
:param limit: Maximum number of results to return.
:param sort_field: The name of the field to sort on.
:param sort_dir: Sort direction for results (asc, desc).
"""
current_user = request.current_user_id
boards = boards_api.get_all(title=title,
creator_id=creator_id,
user_id=user_id,
project_id=project_id,
story_id=story_id,
task_id=task_id,
offset=offset,
limit=limit,
current_user=current_user,
sort_field=sort_field,
sort_dir=sort_dir)
count = boards_api.get_count(title=title,
creator_id=creator_id,
user_id=user_id,
project_id=project_id,
story_id=story_id,
task_id=task_id,
current_user=current_user,)
visible_boards = []
user_id = request.current_user_id
for board in boards:
if boards_api.visible(board, user_id) and\
board.archived == archived:
board_model = wmodels.Board.from_db_model(board)
board_model.resolve_lanes(board, resolve_items=False)
board_model.resolve_permissions(board)
visible_boards.append(board_model)
board_model = wmodels.Board.from_db_model(board)
board_model.resolve_lanes(board, resolve_items=False)
board_model.resolve_permissions(board)
visible_boards.append(board_model)
# Apply the query response headers
response.headers['X-Total'] = str(len(visible_boards))
response.headers['X-Total'] = str(count)
if limit is not None:
response.headers['X-Limit'] = str(limit)
if offset is not None:
response.headers['X-Offset'] = str(offset)
return visible_boards

View File

@ -357,11 +357,11 @@ class WorklistsController(rest.RestController):
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Worklist], wtypes.text, int, int,
bool, int, int, int, bool, wtypes.text, wtypes.text,
int)
int, int, int)
def get_all(self, title=None, creator_id=None, project_id=None,
archived=False, user_id=None, story_id=None, task_id=None,
hide_lanes=True, sort_field='id', sort_dir='asc',
board_id=None):
board_id=None, offset=None, limit=None):
"""Retrieve definitions of all of the worklists.
:param title: A string to filter the title by.
@ -377,8 +377,11 @@ class WorklistsController(rest.RestController):
:param sort_dir: Sort direction for results (asc, desc).
:param board_id: Get all worklists in the board with this id. Other
filters are not applied.
:param offset: Offset at which to begin the results.
:param limit: Maximum number of results to return.
"""
current_user = request.current_user_id
worklists = worklists_api.get_all(title=title,
creator_id=creator_id,
project_id=project_id,
@ -388,25 +391,40 @@ class WorklistsController(rest.RestController):
story_id=story_id,
task_id=task_id,
sort_field=sort_field,
sort_dir=sort_dir)
sort_dir=sort_dir,
offset=offset,
limit=limit,
current_user=current_user,
hide_lanes=hide_lanes)
count = worklists_api.get_count(title=title,
creator_id=creator_id,
project_id=project_id,
archived=archived,
board_id=board_id,
user_id=user_id,
story_id=story_id,
task_id=task_id,
current_user=current_user,
hide_lanes=hide_lanes)
user_id = request.current_user_id
visible_worklists = []
for worklist in worklists:
if (worklists_api.visible(worklist, user_id, hide_lanes) and
worklist.archived == archived):
worklist_model = wmodels.Worklist.from_db_model(worklist)
worklist_model.resolve_permissions(worklist)
visible_items = worklists_api.get_visible_items(
worklist, request.current_user_id)
worklist_model.items = [
wmodels.WorklistItem.from_db_model(item)
for item in visible_items
]
visible_worklists.append(worklist_model)
worklist_model = wmodels.Worklist.from_db_model(worklist)
worklist_model.resolve_permissions(worklist)
visible_items = worklists_api.get_visible_items(
worklist, request.current_user_id)
worklist_model.items = [
wmodels.WorklistItem.from_db_model(item)
for item in visible_items
]
visible_worklists.append(worklist_model)
# Apply the query response headers
response.headers['X-Total'] = str(len(visible_worklists))
response.headers['X-Total'] = str(count)
if limit is not None:
response.headers['X-Limit'] = str(limit)
if offset is not None:
response.headers['X-Offset'] = str(offset)
return visible_worklists

View File

@ -13,7 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from sqlalchemy.orm import subqueryload
from sqlalchemy import and_, or_
from sqlalchemy.orm import aliased, subqueryload
from sqlalchemy.sql.expression import false, true
from wsme.exc import ClientSideError
from storyboard.db.api import base as api_base
@ -35,40 +37,115 @@ def get(id):
return _board_get(id)
def get_all(title=None, creator_id=None, user_id=None, project_id=None,
task_id=None, story_id=None, sort_field=None, sort_dir=None,
**kwargs):
if user_id is not None:
user = users_api.user_get(user_id)
boards = []
for board in get_all():
if any(permission in board.permissions
for permission in user.permissions):
boards.append(board)
return boards
def _build_board_query(title=None, creator_id=None, user_id=None,
project_id=None, archived=False, current_user=None,
session=None):
query = api_base.model_query(models.Board, session=session).distinct()
boards = api_base.entity_get_all(models.Board,
title=title,
creator_id=creator_id,
project_id=project_id,
sort_field=sort_field,
sort_dir=sort_dir,
**kwargs)
query = api_base.apply_query_filters(query=query,
model=models.Worklist,
title=title,
creator_id=creator_id,
project_id=project_id)
# Filter out boards that the current user can't see
query = query.join(models.board_permissions,
models.Permission,
models.user_permissions,
models.User)
if current_user:
query = query.filter(
or_(
and_(
models.User.id == current_user,
models.Board.private == true()
),
models.Board.private == false()
)
)
else:
query = query.filter(models.Board.private == false())
# Filter by boards that a given user has permissions to use
if user_id:
board_permissions = aliased(models.board_permissions)
permissions = aliased(models.Permission)
user_permissions = aliased(models.user_permissions)
users = aliased(models.User)
query = query.join(
(board_permissions,
models.Board.id == board_permissions.c.board_id)
)
query = query.join(
(permissions,
board_permissions.c.permission_id == permissions.id)
)
query = query.join(
(user_permissions,
permissions.id == user_permissions.c.permission_id)
)
query = query.join((users, user_permissions.c.user_id == users.id))
query = query.filter(users.id == user_id)
# Filter by whether or not we want archived boards
query = query.filter(models.Board.archived == archived)
return query
def get_all(title=None, creator_id=None, user_id=None, project_id=None,
task_id=None, story_id=None, archived=False, offset=None,
limit=None, sort_field=None, sort_dir=None, current_user=None,
**kwargs):
if sort_field is None:
sort_field = 'id'
if sort_dir is None:
sort_dir = 'asc'
boards = _build_board_query(title=title,
creator_id=creator_id,
project_id=project_id,
user_id=user_id,
archived=archived,
current_user=current_user,
**kwargs)
if task_id:
boards = boards.all()
matching = []
for board in boards:
if has_card(board, 'task', task_id):
matching.append(board)
boards = matching
return matching
if story_id:
if not task_id:
boards.all()
matching = []
for board in boards:
if has_card(board, 'story', story_id):
matching.append(board)
boards = matching
return matching
return boards
boards = api_base.paginate_query(query=boards,
model=models.Board,
limit=limit,
offset=offset,
sort_key=sort_field,
sort_dir=sort_dir)
return boards.all()
def get_count(title=None, creator_id=None, user_id=None, project_id=None,
task_id=None, story_id=None, archived=False, current_user=None,
**kwargs):
query = _build_board_query(title=title,
creator_id=creator_id,
project_id=project_id,
user_id=user_id,
archived=archived,
current_user=current_user,
**kwargs)
return query.count()
def create(values):

View File

@ -14,6 +14,7 @@
# limitations under the License.
from sqlalchemy import and_, or_
from sqlalchemy.orm import aliased
from sqlalchemy.sql.expression import false, true
from wsme.exc import ClientSideError
@ -39,8 +40,9 @@ def get(worklist_id):
def _build_worklist_query(title=None, creator_id=None, project_id=None,
user_id=None, session=None):
query = api_base.model_query(models.Worklist, session=session)
archived=False, user_id=None, session=None,
current_user=None, hide_lanes=True):
query = api_base.model_query(models.Worklist, session=session).distinct()
query = api_base.apply_query_filters(query=query,
model=models.Worklist,
@ -48,19 +50,85 @@ def _build_worklist_query(title=None, creator_id=None, project_id=None,
creator_id=creator_id,
project_id=project_id)
# Filter out lists that the current user can't see.
# This gets complicated because worklists permissions must be
# inherited from the board which contains the list (if any). To
# handle this we split the query into the lists which are in
# boards (`lanes`) and those which aren't (`lists`). We then
# either hide the lanes entirely or unify the two queries.
lanes = query.join(models.BoardWorklist,
models.Board,
models.board_permissions)
lanes = lanes.join(models.Permission,
models.user_permissions,
models.User)
lists = query.outerjoin(models.BoardWorklist)
lists = lists.filter(models.BoardWorklist.board_id.is_(None))
lists = lists.join(models.worklist_permissions,
models.Permission,
models.user_permissions,
models.User)
if current_user:
if not hide_lanes:
lanes = lanes.filter(
or_(
and_(
models.User.id == current_user,
models.Board.private == true()
),
models.Board.private == false()
)
)
lists = lists.filter(
or_(
and_(
models.User.id == current_user,
models.Worklist.private == true()
),
models.Worklist.private == false()
)
)
else:
if not hide_lanes:
lanes = lanes.filter(models.Board.private == false())
lists = lists.filter(models.Worklist.private == false())
if hide_lanes:
query = lists
else:
query = lists.union(lists)
# Filter by lists that a given user has permissions to use
if user_id:
query = query.join(models.worklist_permissions,
models.Permission,
models.user_permissions,
models.User)
query = query.filter(models.User.id == user_id)
worklist_permissions = aliased(models.worklist_permissions)
permissions = aliased(models.Permission)
user_permissions = aliased(models.user_permissions)
users = aliased(models.User)
query = query.join(
(worklist_permissions,
models.Worklist.id == worklist_permissions.c.worklist_id)
)
query = query.join(
(permissions,
worklist_permissions.c.permission_id == permissions.id)
)
query = query.join(
(user_permissions,
permissions.id == user_permissions.c.permission_id)
)
query = query.join((users, user_permissions.c.user_id == users.id))
query = query.filter(users.id == user_id)
# Filter by whether or not we want archived lists
query = query.filter(models.Worklist.archived == archived)
return query
def get_all(title=None, creator_id=None, project_id=None, board_id=None,
user_id=None, story_id=None, task_id=None, sort_field=None,
sort_dir=None, session=None, **kwargs):
sort_dir=None, session=None, offset=None, limit=None,
archived=False, current_user=None, hide_lanes=True, **kwargs):
if sort_field is None:
sort_field = 'id'
if sort_dir is None:
@ -68,13 +136,19 @@ def get_all(title=None, creator_id=None, project_id=None, board_id=None,
if board_id is not None:
board = boards.get(board_id)
return [lane.worklist for lane in board.lanes]
if board is None:
return []
return [lane.worklist for lane in board.lanes
if visible(lane.worklist, current_user)]
query = _build_worklist_query(title=title,
creator_id=creator_id,
project_id=project_id,
user_id=user_id,
session=session)
archived=archived,
session=session,
current_user=current_user,
hide_lanes=hide_lanes)
query.order_by(getattr(models.Worklist, sort_field), sort_dir)
@ -90,12 +164,41 @@ def get_all(title=None, creator_id=None, project_id=None, board_id=None,
if has_item(worklist, 'task', task_id)]
if story_id or task_id:
if offset is not None and limit is not None:
return worklists[offset:offset + limit]
elif offset is not None:
return worklists[offset:]
return worklists
query = api_base.paginate_query(query=query,
model=models.Worklist,
limit=limit,
offset=offset,
sort_key=sort_field,
sort_dir=sort_dir)
return query.all()
def get_count(**kwargs):
return api_base.entity_get_count(models.Worklist, **kwargs)
def get_count(title=None, creator_id=None, project_id=None, board_id=None,
user_id=None, story_id=None, task_id=None, session=None,
archived=False, current_user=None, hide_lanes=True, **kwargs):
if board_id is not None:
board = boards.get(board_id)
if board is None:
return 0
lists = [lane.worklist for lane in board.lanes
if visible(lane.worklist, current_user)]
return len(lists)
query = _build_worklist_query(title=title,
creator_id=creator_id,
project_id=project_id,
user_id=user_id,
archived=archived,
session=session,
current_user=current_user,
hide_lanes=hide_lanes)
return query.count()
def get_visible_items(worklist, current_user=None):