Start of activity stream.
This commit is contained in:
@@ -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.
|
||||||
|
@@ -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
36
attract/subquery.py
Normal 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)
|
@@ -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'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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',
|
||||||
|
@@ -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 }}:
|
||||||
- }
|
a(href="{{ act.link }}")
|
||||||
|
| {{ act['actor_user']['full_name'] }} {{ act.verb }} {{ act.object }}
|
||||||
|
| {% endfor %}
|
||||||
|
|
||||||
h3 Shot statistics
|
h3 Shot statistics
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user