diff --git a/app.py b/app.py index d95e6fb..423a5f0 100644 --- a/app.py +++ b/app.py @@ -2,23 +2,22 @@ from flask import Flask import logging -from apps.admin import admin as blueprint_admin -from apps.auth import auth as blueprint_auth - -from apps.group_stat_api import group_stat_api as blueprint_stat_api +from apps import statistics_app +from apps import auth_app +from apps import admin_app from config import secret_key app = Flask(__name__) app.secret_key = secret_key -app.register_blueprint(blueprint_auth) -app.register_blueprint(blueprint_admin) -app.register_blueprint(blueprint_stat_api) +app.register_blueprint(auth_app) +app.register_blueprint(admin_app) +app.register_blueprint(statistics_app) if __name__ == '__main__': from database import build_database build_database() logging.info("Build database models") - app.run(host="0.0.0.0") \ No newline at end of file + app.run(host="0.0.0.0") diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..b377ef3 --- /dev/null +++ b/apps/__init__.py @@ -0,0 +1,3 @@ +from .admin import admin as admin_app +from .auth import auth as auth_app +from .group_statistics_api import statistics_api as statistics_app diff --git a/apps/group_stat_api.py b/apps/group_stat_api.py deleted file mode 100644 index f4eb3a4..0000000 --- a/apps/group_stat_api.py +++ /dev/null @@ -1,146 +0,0 @@ -from flask import Blueprint -from flask import request, jsonify - -from peewee import SQL, fn -from database import Message, Member - -from datetime import datetime - -group_stat_api = Blueprint('group_stat_api', __name__) - - -@group_stat_api.route("/top_users", methods=["GET"]) -def top_users(): - number = request.args.get("number") - - if not number: number = 3 - - number = int(number) - - members = [] - counts = [] - - query = (Member - .select(fn.count(Message.id).alias("count"), Member) - .join(Message) - .group_by(Member) - .order_by(SQL("count").desc()) - ) - - for group in query: - members.append(group.first_name) - counts.append(group.count) - - members.insert(0, "Others") - counts.insert(0, sum(counts[number:])) - - return jsonify({ - "members": members[:number + 1], - "counts": counts[:number + 1] - }) - -@group_stat_api.route("/chat_activity", methods=["GET"]) -def chat_activity(): - dates = [] - counts = [] - - from_date = request.args.get("from_date") - to_date = request.args.get("to_date") - - if not from_date or not to_date: - return jsonify({ - "err": "from_date/to_date empty" - }),422 - - date_format = request.args.get("date_format") - date_format = date_format if date_format else "%Y-%m-%d" - - group_by = request.args.get("group_by") - group_by = group_by if group_by else "day" - - try: - from_date = datetime.strptime(from_date, date_format) - to_date = datetime.strptime(to_date, date_format) - except ValueError: - return jsonify({ - "from_date": from_date, - "to_date": to_date, - "date_format": date_format, - "err":"can't format datatime obj" - }),422 - - if not group_by in ["day","week","month","year"]: - return jsonify({ - "err":"invalid group_by" - }),422 - - query = (Message - .select(fn.date_trunc(group_by, Message.timestamp).alias('range'), fn.count(Message.id).alias('count')) - .where((Message.timestamp >= from_date) & (Message.timestamp <= to_date)) - .group_by(fn.date_trunc(group_by, Message.timestamp)) - .order_by(SQL("range")) - ) - - for row in query: - dates.append(row.range.strftime(date_format)) - counts.append(row.count) - - return jsonify({ - "date": dates, - "counts": counts - }) - -@group_stat_api.route("/user_activity", methods=["GET"]) -def user_activity(): - dates = [] - counts = [] - - user_id = request.args.get("user_id") - user = Member.get_or_none(Member.user_id == user_id) - - if not user: - return jsonify({ - "err":"invalid user_id" - }),422 - - from_date = request.args.get("from_date") - to_date = request.args.get("to_date") - - if not from_date or not to_date: - return jsonify({ - "err": "from_date/to_date empty" - }),422 - - group_by = request.args.get("group_by") - group_by = group_by if group_by else "day" - - date_format = request.args.get("date_format") - date_format = date_format if date_format else "%Y-%m-%d" - - try: - from_date = datetime.strptime(from_date,date_format) - to_date = datetime.strptime(to_date, date_format) - except ValueError: - return jsonify({ - "from_date": from_date, - "to_date": to_date, - "date_format": date_format, - "err":"can't format datatime obj" - }),422 - - query = (Message - .select( fn.date_trunc(group_by, Message.timestamp).alias("range"), fn.count(Message.id).alias("count") ) - .where((Message.user == user) & (Message.timestamp >= from_date) & (Message.timestamp <= to_date)) - .group_by(fn.date_trunc(group_by, Message.timestamp)) - .order_by(SQL("range")) - ) - - for row in query: - dates.append(row.range.strftime(date_format)) - counts.append(row.count) - - return jsonify({ - "date": dates, - "counts": counts - }) - diff --git a/apps/group_statistics_api.py b/apps/group_statistics_api.py new file mode 100644 index 0000000..1a18df4 --- /dev/null +++ b/apps/group_statistics_api.py @@ -0,0 +1,157 @@ +from flask import Blueprint +from flask import request, jsonify + +from peewee import SQL, fn +from database import Message, Member + +from datetime import datetime + +statistics_api = Blueprint('statistics_api', __name__) + + +@statistics_api.route("/top-users", methods=["GET"]) +def top_users(): + """ + /top_users - top users for activity + :param number - number top users + """ + + data = [] + + number = int(request.args.get("number")) + + if not number: + number = 3 + + query = (Member + .select(fn.count(Message.id).alias("count"), Member) + .join(Message) + .group_by(Member) + .order_by(SQL("count").desc()) + ) + + for group in query: + data.append({ + "user": group.first_name, + "count": group.count, + }) + + data.insert(0, { + "user": "Others", + "count": sum(item["count"] for i, item in enumerate(data) if i >= number), + }) + + return jsonify(data[:number + 1]) + + +@statistics_api.route("/last-chat-activity", methods=["GET"]) +def last_chat_activity(): + """ + /last-chat-activity - last chat activity selected range + :param startDate: datetime at which start(optional) + :param endDate: datetime at which end + :param groupBy: year, month, day, hour + :param formatDatetime: format string datetime object(default: %Y-%m-%d) + """ + + data = [] + + start_date = request.args.get("startDate") + end_date = request.args.get("endDate") + + if not start_date or not end_date: + return jsonify({ + "err": "startDate/endDate must be defined" + }), 422 + + date_format = request.args.get("formatDatetime") + if not date_format: + date_format = "%Y-%m-%d" + + groupby_keys = ["year", "month", "week", "day"] + group_by = request.args.get("groupBy") + if not group_by or not (group_by in groupby_keys): + return jsonify({ + "err": "invalid groupBy parametr(year, month, day, hour)" + }), 422 + + try: + start_date = datetime.strptime(start_date, date_format) + end_date = datetime.strptime(end_date, date_format) + except ValueError: + return jsonify({ + "err": "Can't unparse startDate/endDate" + }), 422 + + query = (Message + .select(fn.date_trunc(group_by, Message.timestamp).alias('range'), fn.count(Message.id).alias('count')) + .where((Message.timestamp >= start_date) & (Message.timestamp <= end_date)) + .group_by(fn.date_trunc(group_by, Message.timestamp)) + .order_by(SQL("range")) + ) + + for record in query: + data.append({ + "datetime": record.range.strftime(date_format), + "count": record.count, + }) + + return jsonify(data) + + +@statistics_api.route("/last-user-activity", methods=["GET"]) +def last_user_activity(): + """ + /last-user-activity - user activity selected range + :param user_id: user id + :param startDate: datetime at which start(optional) + :param endDate: datetime at which end + :param groupBy: year, month, day, hour + :param formatDatetime: format string datetime object(default: %Y-%m-%d) + """ + data = [] + + user_id = request.args.get("user_id") + user = Member.get_or_none(Member.user_id == user_id) + + start_date = request.args.get("startDate") + end_date = request.args.get("endDate") + + if not start_date or not end_date: + return jsonify({ + "err": "startDate/endDate must be defined" + }), 422 + + date_format = request.args.get("formatDatetime") + if not date_format: + date_format = "%Y-%m-%d" + + groupby_keys = ["year", "month", "week", "day"] + group_by = request.args.get("groupBy") + if not group_by or not (group_by in groupby_keys): + return jsonify({ + "err": "invalid groupBy parametr(year, month, day, hour)" + }), 422 + + try: + start_date = datetime.strptime(start_date, date_format) + end_date = datetime.strptime(end_date, date_format) + except ValueError: + return jsonify({ + "err": "Can't unparse start_date and end_date" + }), 422 + + query = (Message + .select(fn.date_trunc(group_by, Message.timestamp).alias("range"), fn.count(Message.id).alias("count")) + .where((Message.user == user) & (Message.timestamp >= start_date) & (Message.timestamp <= end_date)) + .group_by(fn.date_trunc(group_by, Message.timestamp)) + .order_by(SQL("range")) + ) + + for record in query: + data.append({ + "datetime": record.range.strftime(date_format), + "count": record.count, + }) + + return jsonify(data) diff --git a/static/js/top-users.js b/static/js/top-users.js index 5a23d74..d83d27c 100644 --- a/static/js/top-users.js +++ b/static/js/top-users.js @@ -1,51 +1,51 @@ -fetch("/top_users?number=5") - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error: ${response.status}`); - } - - return response.json(); - }).then((data) => { - const ctx = document.getElementById('top-users'); +fetch("/top-users?number=5") + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error: ${response.status}`); + } + + return response.json(); + }).then((data) => { + const ctx = document.getElementById('top-users'); + + new Chart(ctx, { + type: 'doughnut', + data: { + labels: data.map(data => data.user), + datasets: [{ + data: data.map(data => data.count) + }] + }, - new Chart(ctx, { - type: 'doughnut', - data: { - labels: data["members"], - datasets: [{ - data: data["counts"] - }] + plugins: [ChartDataLabels], + options: { + maintainAspectRatio: false, + plugins :{ + legend: false, + + datalabels: { + color: '#f4f6fc', + formatter: function(value, context) { + return context.chart.data.labels[context.dataIndex]; + } }, - plugins: [ChartDataLabels], - options: { - maintainAspectRatio: false, - plugins :{ - legend: false, - - datalabels: { - color: '#f4f6fc', - formatter: function(value, context) { - return context.chart.data.labels[context.dataIndex]; - } - }, + tooltip: { + callbacks: { + label: (context) => { + let sum = 0; + let value = context.parsed; + let dataArray = context.dataset.data; + dataArray.map(data => { + sum += data; + }); + let percentage = Math.round(value*100 / sum); - tooltip: { - callbacks: { - label: (context) => { - let sum = 0; - let value = context.parsed; - let dataArray = context.dataset.data; - dataArray.map(data => { - sum += data; - }); - let percentage = Math.round(value*100 / sum); - - return `${percentage}%` - } - } - } + return `${percentage}%` } - }, - }); + } + } + } + }, + }); })