diff --git a/cloud/__init__.py b/cloud/__init__.py index c7e3015..b5cd63c 100644 --- a/cloud/__init__.py +++ b/cloud/__init__.py @@ -49,7 +49,11 @@ class CloudExtension(PillarExtension): :rtype: list of flask.Blueprint objects. """ from . import routes - return [routes.blueprint] + import cloud.stats.routes + return [ + routes.blueprint, + cloud.stats.routes.blueprint, + ] @property def template_path(self): diff --git a/cloud/stats/__init__.py b/cloud/stats/__init__.py new file mode 100644 index 0000000..22d693e --- /dev/null +++ b/cloud/stats/__init__.py @@ -0,0 +1,47 @@ +"""Interesting usage metrics""" + +from flask import current_app + +pipeline = [ + {'$match': {'_deleted': {'$ne': 'true'}}}, + { + '$lookup': + { + 'from': "projects", + 'localField': "project", + 'foreignField': "_id", + 'as': "project", + } + }, + { + '$unwind': + { + 'path': '$project', + } + }, + { + '$project': + { + 'p.is_private': 1, + } + }, + {'$match': {'p.is_private': {'$ne': True}}}, + {'$count': 'tot'} +] + + +def count_nodes(query=None) -> int: + c = current_app.db()['nodes'] + # If we provide a query, we extend the first $match step in the aggregation pipeline with + # with the extra parameters (for example node_type) + if query: + pipeline[0]['$match'].update(query) + # Return either a list with one item or an empty list + r = list(c.aggregate(pipeline=pipeline)) + count = 0 if not r else r[0]['tot'] + return count + + +def count_users() -> int: + u = current_app.db()['users'] + return u.count() diff --git a/cloud/stats/routes.py b/cloud/stats/routes.py new file mode 100644 index 0000000..8c51f75 --- /dev/null +++ b/cloud/stats/routes.py @@ -0,0 +1,58 @@ +import logging +import datetime +import functools + +from flask import Blueprint, jsonify +from cloud.stats import count_nodes, count_users + +blueprint = Blueprint('cloud.stats', __name__, url_prefix='/s') + +log = logging.getLogger(__name__) + + +@functools.lru_cache() +def get_stats(before: datetime.datetime): + query_comments = {'node_type': 'comment'} + query_assets = {'node_type': 'asset'} + # TODO Actually query per home project (and 1 blend per project) + query_user_blender_sync = {'name': 'startup.blend'} + + if before: + d = {'_created': {'$lt': before}} + query_comments.update(d) + query_assets.update(d) + query_user_blender_sync.update(d) + + stats = { + 'comments': count_nodes(query_comments), + 'assets': count_nodes(query_assets), + 'users_total': count_users(), + 'users_blender_sync': count_nodes(query_user_blender_sync), + } + return stats + + +@blueprint.route('/') +@blueprint.route('/before/') +def index(before: int=0): + """ + This endpoint is queried on a daily basis by grafista to retrieve cloud usage + stats. For assets and comments we take into considerations only those who belong + to public projects. + + These is the data we retrieve + + - Comments count + - Assets count (video, images and files) + - Users count (subscribers count goes via store) + - Blender Sync users + """ + + # TODO: Implement project-level metrics (and update ad every child update) + if before: + before = datetime.datetime.strptime(before, '%Y%m%d') + else: + today = datetime.date.today() + before = datetime.datetime(today.year, today.month, today.day) + + return jsonify(get_stats(before))