diff --git a/pillar/api/nodes/__init__.py b/pillar/api/nodes/__init__.py index 67cf5d0a..06ced783 100644 --- a/pillar/api/nodes/__init__.py +++ b/pillar/api/nodes/__init__.py @@ -1,4 +1,5 @@ import base64 +import datetime import logging import pymongo.errors @@ -8,6 +9,7 @@ from flask import current_app, Blueprint, request from pillar.api.nodes import eve_hooks from pillar.api.utils import str2id, jsonify from pillar.api.utils.authorization import check_permissions, require_login +from pillar.web.utils import pretty_date log = logging.getLogger(__name__) blueprint = Blueprint('nodes_api', __name__) @@ -63,6 +65,13 @@ def tagged(tag=''): # Build the (cached) list of tagged nodes agg_list = _tagged(tag) + for node in agg_list: + if node.get('video_duration_seconds'): + node['video_duration'] = datetime.timedelta(seconds=node['video_duration_seconds']) + + if node.get('_created') is not None: + node['pretty_created'] = pretty_date(node['_created']) + # If the user is anonymous, no more information is needed and we return if current_user.is_anonymous: return jsonify(agg_list) @@ -99,11 +108,26 @@ def _tagged(tag: str): 'foreignField': '_id', 'as': '_project', }}, + {'$lookup': { + 'from': 'files', + 'localField': 'properties.file', + 'foreignField': '_id', + 'as': '_file', + }}, + {'$unwind': '$_file'}, + {'$unwind': '$_project'}, {'$match': {'_project.is_private': False}}, + {'$addFields': { + 'project._id': '$_project._id', + 'project.name': '$_project.name', + 'project.url': '$_project.url', + 'video_duration_seconds': {'$arrayElemAt': ['$_file.variations.duration', 0]}, + }}, - # Don't return the entire project for each node. - {'$project': {'_project': False}}, - + # Don't return the entire project/file for each node. + {'$project': {'_project': False, + '_file': False} + }, {'$sort': {'_created': -1}} ]) diff --git a/pillar/api/utils/__init__.py b/pillar/api/utils/__init__.py index cdc85fb1..91bbe47c 100644 --- a/pillar/api/utils/__init__.py +++ b/pillar/api/utils/__init__.py @@ -64,6 +64,14 @@ class PillarJSONEncoder(json.JSONEncoder): if isinstance(obj, datetime.datetime): return obj.strftime(RFC1123_DATE_FORMAT) + if isinstance(obj, datetime.timedelta): + hours, seconds = divmod(obj.seconds, 3600) + minutes, seconds = divmod(seconds, 60) + if hours > 0: + return f'{hours:02}:{minutes:02}:{seconds:02}' + else: + return f'{minutes:02}:{seconds:02}' + if isinstance(obj, bson.ObjectId): return str(obj) diff --git a/src/styles/components/_card.sass b/src/styles/components/_card.sass index 10153dea..971a861e 100644 --- a/src/styles/components/_card.sass +++ b/src/styles/components/_card.sass @@ -145,6 +145,11 @@ $card-progress-height: 5px padding: 1px 5px z-index: 1 +.card-label + &.right + right: 5px + left: auto + .card &.active .card-title diff --git a/tests/test_api/test_nodes.py b/tests/test_api/test_nodes.py index 4acf70e8..dbcf876a 100644 --- a/tests/test_api/test_nodes.py +++ b/tests/test_api/test_nodes.py @@ -484,7 +484,13 @@ class TaggedNodesTest(AbstractPillarTest): super().setUp(**kwargs) self.pid, _ = self.ensure_project_exists() - self.file_id, _ = self.ensure_file_exists() + self.file_id, _ = self.ensure_file_exists(file_overrides={ + 'variations': [ + {'format': 'mp4', + 'duration': 3661 # 01:01:01 + }, + ], + }) self.uid = self.create_user() from pillar.api.utils import utcnow @@ -635,6 +641,38 @@ class TaggedNodesTest(AbstractPillarTest): for node in resp: self.assertNotIn('view_progress', node) + def test_tagged_nodes_metadata(self): + from datetime import timedelta + + base_node = { + 'name': 'Just a node name', + 'project': self.pid, + 'description': '', + 'node_type': 'asset', + 'user': self.uid, + } + base_props = {'status': 'published', + 'file': self.file_id, + 'content_type': 'video', + 'order': 0} + + self.create_node({ + '_created': self.fake_now - timedelta(minutes=5), + # 'एनिमेशन' is 'animation' in Hindi. + 'properties': {'tags': ['एनिमेशन'], **base_props}, + **base_node, + }) + + with self.app.app_context(): + with mock.patch('pillar.api.utils.utcnow') as mock_utcnow: + mock_utcnow.return_value = self.fake_now + url = flask.url_for('nodes_api.tagged', tag='एनिमेशन') + resp = self.get(url).json[0] + self.assertEquals('01:01:01', resp['video_duration']) + self.assertEquals('Unittest project', resp['project']['name']) + self.assertEquals('default-project', resp['project']['url']) + self.assertEquals('5m ago', resp['pretty_created']) + class NodesReferencedByProjectTest(AbstractPillarTest): def setUp(self, **kwargs):