Minimise database queries when resolving cards
This commit modifies the code which resolves the contents of cards in boards and worklists such that instead of making a database query per-card, two queries are made to obtain all tasks and stories that there are cards for in the board/worklist. The result of these queries is passed into the resolution function in order to allow fast lookup of the underlying story/task for a card. This will lead to much faster loading times for boards containing large numbers of cards. Change-Id: I189a1d9823a7d251af641cf24de52faac24584df
This commit is contained in:
parent
ca537c255a
commit
0c4f267433
@ -31,6 +31,8 @@ from storyboard.api.v1 import wmodels
|
||||
from storyboard.common import decorators
|
||||
from storyboard.common import exception as exc
|
||||
from storyboard.db.api import boards as boards_api
|
||||
from storyboard.db.api import stories as stories_api
|
||||
from storyboard.db.api import tasks as tasks_api
|
||||
from storyboard.db.api import timeline_events as events_api
|
||||
from storyboard.db.api import users as users_api
|
||||
from storyboard.db.api import worklists as worklists_api
|
||||
@ -227,9 +229,13 @@ class BoardsController(rest.RestController):
|
||||
board = boards_api.get(id)
|
||||
|
||||
user_id = request.current_user_id
|
||||
story_cache = {story.id: story for story in stories_api.story_get_all(
|
||||
board_id=id, current_user=user_id)}
|
||||
task_cache = {task.id: task for task in tasks_api.task_get_all(
|
||||
board_id=id, current_user=user_id)}
|
||||
if boards_api.visible(board, user_id):
|
||||
board_model = wmodels.Board.from_db_model(board)
|
||||
board_model.resolve_lanes(board)
|
||||
board_model.resolve_lanes(board, story_cache, task_cache)
|
||||
board_model.resolve_due_dates(board)
|
||||
board_model.resolve_permissions(board)
|
||||
return board_model
|
||||
@ -384,6 +390,11 @@ class BoardsController(rest.RestController):
|
||||
if not boards_api.editable(original, user_id):
|
||||
raise exc.NotFound(_("Board %s not found") % id)
|
||||
|
||||
story_cache = {story.id: story for story in stories_api.story_get_all(
|
||||
board_id=id, current_user=user_id)}
|
||||
task_cache = {task.id: task for task in tasks_api.task_get_all(
|
||||
board_id=id, current_user=user_id)}
|
||||
|
||||
# We use copy here because we only need to check changes
|
||||
# to the related objects, just the board's own attributes.
|
||||
# Also, deepcopy trips up on the lanes' backrefs.
|
||||
@ -402,7 +413,7 @@ class BoardsController(rest.RestController):
|
||||
|
||||
if boards_api.visible(updated_board, user_id):
|
||||
board_model = wmodels.Board.from_db_model(updated_board)
|
||||
board_model.resolve_lanes(updated_board)
|
||||
board_model.resolve_lanes(updated_board, story_cache, task_cache)
|
||||
board_model.resolve_permissions(updated_board)
|
||||
return board_model
|
||||
else:
|
||||
|
@ -717,6 +717,9 @@ class WorklistItem(base.APIBase):
|
||||
|
||||
@nodoc
|
||||
def resolve_due_date(self, worklist_item):
|
||||
if not worklist_item.display_due_date:
|
||||
self.resolved_due_date = None
|
||||
return
|
||||
due_date = due_dates_api.get(worklist_item.display_due_date)
|
||||
resolved = None
|
||||
if due_dates_api.visible(due_date, request.current_user_id):
|
||||
@ -724,10 +727,10 @@ class WorklistItem(base.APIBase):
|
||||
self.resolved_due_date = resolved
|
||||
|
||||
@nodoc
|
||||
def resolve_item(self, item):
|
||||
def resolve_item(self, item, story_cache, task_cache):
|
||||
user_id = request.current_user_id
|
||||
if item.item_type == 'story':
|
||||
story = stories_api.story_get(
|
||||
story = story_cache.get(item.item_id) or stories_api.story_get(
|
||||
item.item_id, current_user=request.current_user_id)
|
||||
if story is None:
|
||||
return False
|
||||
@ -736,7 +739,7 @@ class WorklistItem(base.APIBase):
|
||||
if due_dates_api.visible(date, user_id)]
|
||||
self.story.due_dates = due_dates
|
||||
elif item.item_type == 'task':
|
||||
task = tasks_api.task_get(
|
||||
task = task_cache.get(item.item_id) or tasks_api.task_get(
|
||||
item.item_id, current_user=request.current_user_id)
|
||||
if task is None or task.story is None:
|
||||
return False
|
||||
@ -799,20 +802,20 @@ class Worklist(base.APIBase):
|
||||
items=[])
|
||||
|
||||
@nodoc
|
||||
def resolve_items(self, worklist):
|
||||
def resolve_items(self, worklist, story_cache={}, task_cache={}):
|
||||
"""Resolve the contents of this worklist."""
|
||||
self.items = []
|
||||
user_id = request.current_user_id
|
||||
if worklist.automatic:
|
||||
self._resolve_automatic_items(worklist, user_id)
|
||||
else:
|
||||
self._resolve_set_items(worklist, user_id)
|
||||
self._resolve_set_items(worklist, user_id, story_cache, task_cache)
|
||||
|
||||
@nodoc
|
||||
def _resolve_automatic_items(self, worklist, user_id):
|
||||
for item in worklists_api.filter_items(worklist):
|
||||
item_model = WorklistItem(**item)
|
||||
valid = item_model.resolve_item(item_model)
|
||||
valid = item_model.resolve_item(item_model, {}, {})
|
||||
if not valid:
|
||||
continue
|
||||
item_model.resolve_due_date(item_model)
|
||||
@ -820,12 +823,12 @@ class Worklist(base.APIBase):
|
||||
self.items.sort(key=lambda x: x.list_position)
|
||||
|
||||
@nodoc
|
||||
def _resolve_set_items(self, worklist, user_id):
|
||||
def _resolve_set_items(self, worklist, user_id, story_cache, task_cache):
|
||||
for item in worklist.items:
|
||||
if item.archived:
|
||||
continue
|
||||
item_model = WorklistItem.from_db_model(item)
|
||||
valid = item_model.resolve_item(item)
|
||||
valid = item_model.resolve_item(item, story_cache, task_cache)
|
||||
if not valid:
|
||||
continue
|
||||
item_model.resolve_due_date(item)
|
||||
@ -881,13 +884,14 @@ class Lane(base.APIBase):
|
||||
items=[]))
|
||||
|
||||
@nodoc
|
||||
def resolve_list(self, lane, resolve_items=True):
|
||||
def resolve_list(self, lane, story_cache, task_cache, resolve_items=True):
|
||||
"""Resolve the worklist which represents the lane."""
|
||||
self.worklist = Worklist.from_db_model(lane.worklist)
|
||||
self.worklist.resolve_permissions(lane.worklist)
|
||||
self.worklist.resolve_filters(lane.worklist)
|
||||
if resolve_items:
|
||||
self.worklist.resolve_items(lane.worklist)
|
||||
self.worklist.resolve_items(
|
||||
lane.worklist, story_cache, task_cache)
|
||||
else:
|
||||
items = worklists_api.get_visible_items(
|
||||
lane.worklist, current_user=request.current_user_id)
|
||||
@ -961,12 +965,14 @@ class Board(base.APIBase):
|
||||
users=[])
|
||||
|
||||
@nodoc
|
||||
def resolve_lanes(self, board, resolve_items=True):
|
||||
def resolve_lanes(self, board, story_cache={}, task_cache={},
|
||||
resolve_items=True):
|
||||
"""Resolve the lanes of the board."""
|
||||
self.lanes = []
|
||||
for lane in board.lanes:
|
||||
lane_model = Lane.from_db_model(lane)
|
||||
lane_model.resolve_list(lane, resolve_items)
|
||||
lane_model.resolve_list(
|
||||
lane, story_cache, task_cache, resolve_items)
|
||||
self.lanes.append(lane_model)
|
||||
self.lanes.sort(key=lambda x: x.position)
|
||||
|
||||
|
@ -600,9 +600,13 @@ class WorklistsController(rest.RestController):
|
||||
worklist = worklists_api.get(worklist_id)
|
||||
|
||||
user_id = request.current_user_id
|
||||
story_cache = {story.id: story for story in stories_api.story_get_all(
|
||||
worklist_id=worklist_id, current_user=user_id)}
|
||||
task_cache = {task.id: task for task in tasks_api.task_get_all(
|
||||
worklist_id=worklist_id, current_user=user_id)}
|
||||
if worklist and worklists_api.visible(worklist, user_id):
|
||||
worklist_model = wmodels.Worklist.from_db_model(worklist)
|
||||
worklist_model.resolve_items(worklist)
|
||||
worklist_model.resolve_items(worklist, story_cache, task_cache)
|
||||
worklist_model.resolve_permissions(worklist)
|
||||
worklist_model.resolve_filters(worklist)
|
||||
return worklist_model
|
||||
@ -811,6 +815,11 @@ class WorklistsController(rest.RestController):
|
||||
if not worklists_api.editable(worklists_api.get(id), user_id):
|
||||
raise exc.NotFound(_("Worklist %s not found") % id)
|
||||
|
||||
story_cache = {story.id: story for story in stories_api.story_get_all(
|
||||
worklist_id=id, current_user=user_id)}
|
||||
task_cache = {task.id: task for task in tasks_api.task_get_all(
|
||||
worklist_id=id, current_user=user_id)}
|
||||
|
||||
# We don't use this endpoint to update the worklist's contents
|
||||
if worklist.items != wtypes.Unset:
|
||||
del worklist.items
|
||||
@ -827,7 +836,8 @@ class WorklistsController(rest.RestController):
|
||||
post_timeline_events(original, updated_worklist)
|
||||
if worklists_api.visible(updated_worklist, user_id):
|
||||
worklist_model = wmodels.Worklist.from_db_model(updated_worklist)
|
||||
worklist_model.resolve_items(updated_worklist)
|
||||
worklist_model.resolve_items(
|
||||
updated_worklist, story_cache, task_cache)
|
||||
worklist_model.resolve_permissions(updated_worklist)
|
||||
return worklist_model
|
||||
else:
|
||||
|
Loading…
x
Reference in New Issue
Block a user