Start of activity stream.

This commit is contained in:
2016-10-12 14:30:58 +02:00
parent 7a3a11fa07
commit 32400b34e9
6 changed files with 86 additions and 39 deletions

View File

@@ -83,11 +83,12 @@ class AttractExtension(PillarExtension):
def setup_app(self, app): def setup_app(self, app):
"""Connects Blinker signals.""" """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) subversion.task_logged.connect(self.task_manager.task_logged_in_svn)
tasks.setup_app(app) tasks.setup_app(app)
eve_hooks.setup_app(app) eve_hooks.setup_app(app)
subquery.setup_app(app)
def attract_projects(self): def attract_projects(self):
"""Returns projects set up for Attract. """Returns projects set up for Attract.

View File

@@ -1,14 +1,16 @@
import functools import functools
import logging import logging
from flask import Blueprint, render_template from flask import Blueprint, render_template, url_for
import flask_login import flask_login
from pillar.api.utils import jsonify from pillar.api.utils import jsonify
from pillar.web.system_util import pillar_api from pillar.web.system_util import pillar_api
import pillarsdk 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__) blueprint = Blueprint('attract', __name__)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -23,15 +25,44 @@ def index():
else: else:
tasks = None tasks = None
# TODO: add projections.
projects = current_attract.attract_projects() projects = current_attract.attract_projects()
projs_with_summaries = [ projs_with_summaries = [
(proj, current_attract.shot_manager.shot_status_summary(proj['_id'])) (proj, current_attract.shot_manager.shot_status_summary(proj['_id']))
for proj in projects['_items'] 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', return render_template('attract/index.html',
tasks=tasks, tasks=tasks,
projs_with_summaries=projs_with_summaries) projs_with_summaries=projs_with_summaries,
activities=activities)
def error_project_not_setup_for_attract(): def error_project_not_setup_for_attract():

36
attract/subquery.py Normal file
View File

@@ -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)

View File

@@ -78,9 +78,9 @@ def activity_after_replacing_task(task, original):
user_id, user_id,
'edited task', 'edited task',
'node', task['_id'], 'node', task['_id'],
'project', 'project', task['project'],
task['project'],
task['project'], task['project'],
node_type=task['node_type'],
) )
@@ -91,9 +91,9 @@ def activity_after_creating_task(task):
user_id, user_id,
'created a new task', 'created a new task',
'node', task['_id'], 'node', task['_id'],
'project', 'project', task['project'],
task['project'],
task['project'], task['project'],
node_type=task['node_type'],
) )
@@ -109,9 +109,9 @@ def activity_after_deleting_task(task):
user_id, user_id,
'deleted task', 'deleted task',
'node', task['_id'], 'node', task['_id'],
'project', 'project', task['project'],
task['project'],
task['project'], task['project'],
node_type=task['node_type'],
) )

View File

@@ -11,7 +11,7 @@ import pillar.api.utils
from attract.routes import attract_project_view from attract.routes import attract_project_view
from attract.node_types.task import node_type_task 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') blueprint = Blueprint('attract.tasks', __name__, url_prefix='/tasks')
perproject_blueprint = Blueprint('attract.tasks.perproject', __name__, perproject_blueprint = Blueprint('attract.tasks.perproject', __name__,
@@ -51,24 +51,6 @@ def for_project(project, task_id=None):
project=project) 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('/<task_id>') @perproject_blueprint.route('/<task_id>')
@attract_project_view(extension_props=True) @attract_project_view(extension_props=True)
def view_task(project, attract_props, task_id): 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) users = project.get_users(api=api)
project.users = users['_items'] project.users = users['_items']
else: else:
# Cache user info for 5 minutes. task.properties.assigned_to.users = [subquery.get_user_info(uid)
@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)
for uid in task.properties.assigned_to.users] for uid in task.properties.assigned_to.users]
return render_template('attract/tasks/view_task_embed.html', return render_template('attract/tasks/view_task_embed.html',

View File

@@ -19,10 +19,12 @@
h3 Activity h3 Activity
ul ul
- for (var i = 1; i < 11; ++i) { | {% for act in activities['_items'] %}
li= i li
span Activity Stuff span.date {{ act._created | pretty_date_time }}:&nbsp;
- } a(href="{{ act.link }}")
| {{ act['actor_user']['full_name'] }} {{ act.verb }} {{ act.object }}
| {% endfor %}
h3 Shot statistics h3 Shot statistics