From 75e6b390690e79992fbf0a505476d33e6ef0b20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 5 Oct 2016 10:30:10 +0200 Subject: [PATCH] Added user-specific task list. --- attract/tasks/eve_hooks.py | 38 ++++++++++++++--- attract/tasks/routes.py | 20 ++++++++- src/scripts/tutti/10_tasks.js | 16 ++++--- src/styles/_config.sass | 2 + src/styles/_tasks.sass | 2 +- src/templates/attract/tasks/for_project.jade | 2 +- src/templates/attract/tasks/for_user.jade | 45 ++++++++++++++++++++ 7 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 src/templates/attract/tasks/for_user.jade diff --git a/attract/tasks/eve_hooks.py b/attract/tasks/eve_hooks.py index 92b3746..8198ffc 100644 --- a/attract/tasks/eve_hooks.py +++ b/attract/tasks/eve_hooks.py @@ -7,8 +7,8 @@ from attract.node_types.task import node_type_task log = logging.getLogger(__name__) -def fetch_task_parent_info(node): - """Extends the node with some parent info. +def fetch_task_extra_info(node): + """Extends the node with some info about its parent and project. This allows us to link to the shot the task is attached to. However, such a link requires at least knowing the parent node type, @@ -18,11 +18,18 @@ def fetch_task_parent_info(node): if node.get('node_type') != node_type_task['name']: return + fetch_task_parent_info(node) + fetch_task_project_info(node) + + +def fetch_task_parent_info(node): + """Store node parent info in node['_parent_info'].""" + parent_id = node.get('parent') if not parent_id: return - nodes_coll = current_app.data.driver.db['nodes'] + nodes_coll = current_app.db()['nodes'] parent = nodes_coll.find_one({'_id': parent_id}, projection={'node_type': 1, 'name': 1}) @@ -35,11 +42,32 @@ def fetch_task_parent_info(node): node['_parent_info'] = parent +def fetch_task_project_info(node): + """Store node project info in node['_project_info'].""" + + project_id = node.get('project') + if not project_id: + log.warning('Task node %s has no project!', node['_id']) + return + + proj_coll = current_app.db()['projects'] + project = proj_coll.find_one({'_id': project_id}, + projection={'name': 1, + 'url': 1}) + if project is None: + log.warning("Task node %s has project %s, but the project doesn't exist.", + node['_id'], project_id) + return + + project.pop('_id') # always there, but also already included in the node. + node['_project_info'] = project + + def fetch_tasks_parent_info(nodes): for node in nodes['_items']: - fetch_task_parent_info(node) + fetch_task_extra_info(node) def setup_app(app): - app.on_fetched_item_nodes += fetch_task_parent_info + app.on_fetched_item_nodes += fetch_task_extra_info app.on_fetched_resource_nodes += fetch_tasks_parent_info diff --git a/attract/tasks/routes.py b/attract/tasks/routes.py index cef152b..0429e07 100644 --- a/attract/tasks/routes.py +++ b/attract/tasks/routes.py @@ -2,6 +2,7 @@ import logging from flask import Blueprint, render_template, request import flask +import flask_login import pillarsdk from pillar.web.system_util import pillar_api @@ -19,7 +20,23 @@ log = logging.getLogger(__name__) @blueprint.route('/') def index(): - return render_template('attract/tasks/index.html') + user = flask_login.current_user + if not user.is_authenticated: + return render_template('attract/tasks/index.html') + + api = pillar_api() + + # TODO: also include tasks assigned to any of the user's groups. + tasks = pillarsdk.Node.all({ + 'where': { + 'properties.assigned_to.users': user.objectid, + 'node_type': node_type_task['name'], + } + }, api=api) + + return render_template('attract/tasks/for_user.html', + tasks=tasks['_items'], + task_count=tasks['_meta']['total']) @blueprint.route('/', methods=['DELETE']) @@ -86,7 +103,6 @@ def save(project, task_id): @perproject_blueprint.route('/create', methods=['POST']) @attract_project_view() def create_task(project): - task_type = request.form['task_type'] parent = request.form.get('parent', None) diff --git a/src/scripts/tutti/10_tasks.js b/src/scripts/tutti/10_tasks.js index b9682fb..0e29d62 100644 --- a/src/scripts/tutti/10_tasks.js +++ b/src/scripts/tutti/10_tasks.js @@ -15,15 +15,17 @@ function _remove_task_from_list(task_id) { /** * Open an item such as tasks/shots in the #item-details div */ -function item_open(item_id, item_type, pushState) +function item_open(item_id, item_type, pushState, project_url) { if (item_id === undefined || item_type === undefined) { throw new ReferenceError("item_open(" + item_id + ", " + item_type + ") called."); } - var project_url = ProjectUtils.projectUrl(); if (typeof project_url === 'undefined') { - throw new ReferenceError("ProjectUtils.projectUrl() undefined"); + project_url = ProjectUtils.projectUrl(); + if (typeof project_url === 'undefined') { + throw new ReferenceError("ProjectUtils.projectUrl() undefined"); + } } if ($(window).scrollTop() > 0) { @@ -76,9 +78,10 @@ function item_open(item_id, item_type, pushState) ); } -function task_open(task_id) +// Fine if project_url is undefined, but that requires ProjectUtils.projectUrl(). +function task_open(task_id, project_url) { - item_open(task_id, 'task'); + item_open(task_id, 'task', true, project_url); if (ProjectUtils.context() == 'shot'){ $('[id^="shot-"]').removeClass('active'); @@ -360,6 +363,7 @@ $(function() { $("a.task-link[data-task-id]").click(function(e) { e.preventDefault(); var task_id = e.delegateTarget.dataset.taskId; - task_open(task_id); + var project_url = e.delegateTarget.dataset.projectUrl; // fine if undefined + task_open(task_id, project_url); }); }); diff --git a/src/styles/_config.sass b/src/styles/_config.sass index 9c60471..6431e8b 100644 --- a/src/styles/_config.sass +++ b/src/styles/_config.sass @@ -6,6 +6,8 @@ $color-background-nav: hsl(hue($color-background), 20%, 25%) $color-background-nav-light: hsl(hue($color-background), 20%, 35%) $color-background-nav-dark: hsl(hue($color-background), 20%, 15%) +$color-background-active: #dafff5 // background colour for active items. + $font-body: 'Roboto' $font-headings: 'Lato' $font-size: 14px diff --git a/src/styles/_tasks.sass b/src/styles/_tasks.sass index be087c2..89b13f7 100644 --- a/src/styles/_tasks.sass +++ b/src/styles/_tasks.sass @@ -79,7 +79,7 @@ border-color: $color-background-dark border-right-color: $color-primary text-decoration: none - background-color: rgba($color-background, .5) + background-color: $color-background-active .status-indicator transform: scale(1.1) diff --git a/src/templates/attract/tasks/for_project.jade b/src/templates/attract/tasks/for_project.jade index 2ce4810..721b3bc 100644 --- a/src/templates/attract/tasks/for_project.jade +++ b/src/templates/attract/tasks/for_project.jade @@ -36,7 +36,7 @@ | {% block footer_scripts %} script. {% if open_task_id %} - $(function() { task_open('{{ open_task_id }}'); }); + $(function() { item_open('{{ open_task_id }}', 'task', false); }); {% endif %} script(src="{{ url_for('static_attract', filename='assets/js/vendor/clipboard.min.js')}}") diff --git a/src/templates/attract/tasks/for_user.jade b/src/templates/attract/tasks/for_user.jade new file mode 100644 index 0000000..979614e --- /dev/null +++ b/src/templates/attract/tasks/for_user.jade @@ -0,0 +1,45 @@ +| {% extends 'attract/layout.html' %} +| {% block bodyattrs %}{{ super() }} data-context='task'{% endblock %} +| {% block page_title %}Tasks for you{% endblock %} +| {% block body %} +#col_main + .col_header.task-list-header + | Your tasks ({{ task_count }}) + + #task-list.col-list + | {% for task in tasks %} + //- NOTE: this is tightly linked to the JS in tasks.js, function task_add() + a.col-list-item.task-list-item( + class="status-{{ task.properties.status }} task-link", + title="In project '{{ task._project_info.name }}'", + href="{{ url_for('attract.tasks.perproject.view_task', project_url=task._project_info.url, task_id=task._id) }}") + span.status-indicator + span.name {{ task.name }} + span.type {{ task.properties.task_type }} + | {% endfor %} + +.col-splitter + +#col_right + .col_header + span.header_text + #status-bar + #item-details + .item-details-empty + | Select a Task +| {% endblock %} + +| {% block footer_scripts %} +script. + {% if open_task_id %} + $(function() { task_open('{{ open_task_id }}'); }); + {% endif %} + +script(src="{{ url_for('static_attract', filename='assets/js/vendor/clipboard.min.js')}}") +script(src="{{ url_for('static_attract', filename='assets/js/vendor/jquery-resizable.min.js')}}") +script. + $("#col_main").resizable({ + handleSelector: ".col-splitter", + resizeHeight: false + }); +| {% endblock %}