diff --git a/attract/__init__.py b/attract/__init__.py index caab405..07b407c 100644 --- a/attract/__init__.py +++ b/attract/__init__.py @@ -83,11 +83,12 @@ class AttractExtension(PillarExtension): def setup_app(self, app): """Connects Blinker signals.""" - from . import subversion, tasks, eve_hooks + from . import subversion, tasks, eve_hooks, subquery subversion.task_logged.connect(self.task_manager.task_logged_in_svn) tasks.setup_app(app) eve_hooks.setup_app(app) + subquery.setup_app(app) def attract_projects(self): """Returns projects set up for Attract. diff --git a/attract/routes.py b/attract/routes.py index f5878a2..9c37a87 100644 --- a/attract/routes.py +++ b/attract/routes.py @@ -1,14 +1,16 @@ import functools import logging -from flask import Blueprint, render_template +from flask import Blueprint, render_template, url_for import flask_login from pillar.api.utils import jsonify from pillar.web.system_util import pillar_api import pillarsdk -from attract import current_attract +from attract import current_attract, subquery +from attract.node_types.task import node_type_task +from attract.node_types.shot import node_type_shot blueprint = Blueprint('attract', __name__) log = logging.getLogger(__name__) @@ -23,15 +25,44 @@ def index(): else: tasks = None + # TODO: add projections. projects = current_attract.attract_projects() projs_with_summaries = [ (proj, current_attract.shot_manager.shot_status_summary(proj['_id'])) for proj in projects['_items'] ] + # Fetch all activities for all Attract projects. + api = pillar_api() + id_to_proj = {p['_id']: p for p in projects['_items']} + activities = pillarsdk.Activity.all({ + 'where': { + 'project': {'$in': list(id_to_proj.keys())}, + }, + 'sort': [('_created', -1)], + 'max_results': 20, + }, api=api) + + # Fetch more info for each activity. + node_type_task_name = node_type_task['name'] + node_type_shot_name = node_type_shot['name'] + for act in activities['_items']: + act.actor_user = subquery.get_user_info(act.actor_user) + act.project = id_to_proj[act.project] + + if act.node_type == node_type_task_name: + act.link = url_for('attract.tasks.perproject.view_task', + project_url=act.project.url, + task_id=act.object) + elif act.node_type == node_type_shot_name: + act.link = url_for('attract.shots.perproject.view_shot', + project_url=act.project.url, + shot_id=act.object) + return render_template('attract/index.html', tasks=tasks, - projs_with_summaries=projs_with_summaries) + projs_with_summaries=projs_with_summaries, + activities=activities) def error_project_not_setup_for_attract(): diff --git a/attract/subquery.py b/attract/subquery.py new file mode 100644 index 0000000..5c55f75 --- /dev/null +++ b/attract/subquery.py @@ -0,0 +1,36 @@ +"""Sub-query stuff, for things we would otherwise let Eve embed (but don't want to). + +Uses app.cache.memoize() to cache the results. However, since this decorator needs +to run in Flask Application context, it is manually applied in setup_app(). +""" + +import pillarsdk +from pillar.web.system_util import pillar_api + + +def get_user_info(user_id): + """Returns email & full name of the user. + + Only returns those two fields, so the return value is the same + for authenticated & non-authenticated users, which is why we're + allowed to cache it globally. + + Returns None when the user cannot be found. + """ + + if user_id is None: + return None + + user = pillarsdk.User.find(user_id, api=pillar_api()) + if not user: + return user + + return {'email': user.email, + 'full_name': user.full_name} + + +def setup_app(app): + global get_user_info + + decorator = app.cache.memoize(timeout=300, make_name='%s.get_user_info' % __name__) + get_user_info = decorator(get_user_info) diff --git a/attract/tasks/eve_hooks.py b/attract/tasks/eve_hooks.py index e63fedc..80c086d 100644 --- a/attract/tasks/eve_hooks.py +++ b/attract/tasks/eve_hooks.py @@ -78,9 +78,9 @@ def activity_after_replacing_task(task, original): user_id, 'edited task', 'node', task['_id'], - 'project', - task['project'], + 'project', task['project'], task['project'], + node_type=task['node_type'], ) @@ -91,9 +91,9 @@ def activity_after_creating_task(task): user_id, 'created a new task', 'node', task['_id'], - 'project', - task['project'], + 'project', task['project'], task['project'], + node_type=task['node_type'], ) @@ -109,9 +109,9 @@ def activity_after_deleting_task(task): user_id, 'deleted task', 'node', task['_id'], - 'project', - task['project'], + 'project', task['project'], task['project'], + node_type=task['node_type'], ) diff --git a/attract/tasks/routes.py b/attract/tasks/routes.py index 71a7f59..e1a214d 100644 --- a/attract/tasks/routes.py +++ b/attract/tasks/routes.py @@ -11,7 +11,7 @@ import pillar.api.utils from attract.routes import attract_project_view from attract.node_types.task import node_type_task -from attract import current_attract, ROLES_REQUIRED_TO_VIEW_ITEMS +from attract import current_attract, ROLES_REQUIRED_TO_VIEW_ITEMS, subquery blueprint = Blueprint('attract.tasks', __name__, url_prefix='/tasks') perproject_blueprint = Blueprint('attract.tasks.perproject', __name__, @@ -51,24 +51,6 @@ def for_project(project, task_id=None): project=project) -def _get_user_info(user_id): - """Returns email & full name of the user. - - Only returns those two fields, so the return value is the same - for authenticated & non-authenticated users, which is why we're - allowed to cache it globally. - - Returns None when the user cannot be found. - """ - - user = pillarsdk.User.find(user_id, api=pillar_api()) - if not user: - return user - - return {'email': user.email, - 'full_name': user.full_name} - - @perproject_blueprint.route('/') @attract_project_view(extension_props=True) def view_task(project, attract_props, task_id): @@ -88,12 +70,7 @@ def view_task(project, attract_props, task_id): users = project.get_users(api=api) project.users = users['_items'] else: - # Cache user info for 5 minutes. - @current_app.cache.memoize(timeout=300, make_name='%s._cached_user_info' % __name__) - def _cached_user_info(user_id): - return _get_user_info(user_id) - - task.properties.assigned_to.users = [_cached_user_info(uid) + task.properties.assigned_to.users = [subquery.get_user_info(uid) for uid in task.properties.assigned_to.users] return render_template('attract/tasks/view_task_embed.html', diff --git a/src/templates/attract/index.jade b/src/templates/attract/index.jade index 2f7f02c..5a816a9 100644 --- a/src/templates/attract/index.jade +++ b/src/templates/attract/index.jade @@ -19,10 +19,12 @@ h3 Activity ul - - for (var i = 1; i < 11; ++i) { - li= i - span Activity Stuff - - } + | {% for act in activities['_items'] %} + li + span.date {{ act._created | pretty_date_time }}:  + a(href="{{ act.link }}") + | {{ act['actor_user']['full_name'] }} {{ act.verb }} {{ act.object }} + | {% endfor %} h3 Shot statistics