diff --git a/settings/common.py b/settings/common.py index 716760aa..1d3a77de 100644 --- a/settings/common.py +++ b/settings/common.py @@ -437,6 +437,7 @@ FEEDBACK_EMAIL = "support@taiga.io" # Stats module settings STATS_ENABLED = False +STATS_CACHE_TIMEOUT = 60*60 # In second # 0 notifications will work in a synchronous way # >0 an external process will check the pending notifications and will send them diff --git a/settings/local.py.example b/settings/local.py.example index dd4ce8c9..62a88868 100644 --- a/settings/local.py.example +++ b/settings/local.py.example @@ -69,6 +69,7 @@ DATABASES = { # STATS MODULE #STATS_ENABLED = False +#FRONT_SITEMAP_CACHE_TIMEOUT = 60*60 # In second # SITEMAP # If is True /front/sitemap.xml show a valid sitemap of taiga-front client diff --git a/taiga/stats/api.py b/taiga/stats/api.py index dae5cfdd..43c96d5c 100644 --- a/taiga/stats/api.py +++ b/taiga/stats/api.py @@ -12,6 +12,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from collections import OrderedDict + +from django.conf import settings +from django.views.decorators.cache import cache_page from taiga.base.api import viewsets from taiga.base import response @@ -24,10 +28,15 @@ class SystemStatsViewSet(viewsets.ViewSet): permission_classes = (permissions.SystemStatsPermission,) def list(self, request, **kwargs): - stats = { - "total_users": services.get_total_users(), - "total_projects": services.get_total_projects(), - "total_userstories": services.grt_total_user_stories(), - "total_issues": services.get_total_issues(), - } + import ipdb; ipdb.set_trace() + stats = OrderedDict() + stats["users"] = services.get_users_stats() + stats["projects"] = services.get_projects_stats() + stats["userstories"] = services.get_user_stories_stats() return response.Ok(stats) + + def _get_cache_timeout(self): + return getattr(settings, "STATS_CACHE_TIMEOUT", 0) + + def dispatch(self, *args, **kwargs): + return cache_page(self._get_cache_timeout())(super().dispatch)(*args, **kwargs) diff --git a/taiga/stats/services.py b/taiga/stats/services.py index 11f41d37..37980d03 100644 --- a/taiga/stats/services.py +++ b/taiga/stats/services.py @@ -12,33 +12,96 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from contextlib import closing from django.apps import apps +from django.db import connection +from django.utils import timezone +from datetime import timedelta +from collections import OrderedDict -def get_total_projects(): +def get_users_stats(): + model = apps.get_model("users", "User") + queryset = model.objects.filter(is_active=True, is_system=False) + stats = OrderedDict() + + # Total + stats["total"] = queryset.count() + + # Average last 7 days + today = timezone.now() + seven_days_ago = today-timedelta(days=7) + stats["average_last_seven_days"] = (queryset.filter(date_joined__range=(seven_days_ago, today)) + .count()) / 7 + + # Graph: users last year + a_year_ago = timezone.now() - timedelta(days=365) + sql_query = """ + SELECT date_trunc('week', "filtered_users"."date_joined") AS "week", + count(*) + FROM (SELECT * + FROM "users_user" + WHERE "users_user"."is_active" = TRUE + AND "users_user"."is_system" = FALSE + AND "users_user"."date_joined" >= %s) AS "filtered_users" + GROUP BY "week" + ORDER BY "week"; + """ + with closing(connection.cursor()) as cursor: + cursor.execute(sql_query, [a_year_ago]) + rows = cursor.fetchall() + + counts_last_year_per_week = OrderedDict() + sumatory = queryset.filter(date_joined__lt=rows[0][0]).count() + for row in rows: + sumatory += row[1] + counts_last_year_per_week[str(row[0].date())] = sumatory + + stats["counts_last_year_per_week"] = counts_last_year_per_week + + return stats + + +def get_projects_stats(): model = apps.get_model("projects", "Project") queryset = model.objects.all() - return queryset.count() + stats = OrderedDict() + + stats["total"] = queryset.count() + + today = timezone.now() + seven_days_ago = today-timedelta(days=7) + stats["average_last_seven_days"] = (queryset.filter(created_date__range=(seven_days_ago, today)) + .count()) / 7 + + stats["total_with_backlog"] = (queryset.filter(is_backlog_activated=True, + is_kanban_activated=False) + .count()) + stats["percent_with_backlog"] = stats["total_with_backlog"] * 100 / stats["total"] + + stats["total_with_kanban"] = (queryset.filter(is_backlog_activated=False, + is_kanban_activated=True) + .count()) + stats["percent_with_kanban"] = stats["total_with_kanban"] * 100 / stats["total"] + + stats["total_with_backlog_and_kanban"] = (queryset.filter(is_backlog_activated=True, + is_kanban_activated=True) + .count()) + stats["percent_with_backlog_and_kanban"] = stats["total_with_backlog_and_kanban"] * 100 / stats["total"] + + return stats -def grt_total_user_stories(): +def get_user_stories_stats(): model = apps.get_model("userstories", "UserStory") queryset = model.objects.all() - return queryset.count() + stats = OrderedDict() + stats["total"] = queryset.count() -def get_total_issues(): - model = apps.get_model("issues", "Issue") - queryset = model.objects.all() - return queryset.count() - - -def get_total_users(only_active=True, no_system=True): - model = apps.get_model("users", "User") - queryset = model.objects.all() - if only_active: - queryset = queryset.filter(is_active=True) - if no_system: - queryset = queryset.filter(is_system=False) - return queryset.count() + today = timezone.now() + seven_days_ago = today-timedelta(days=7) + stats["average_last_seven_days"] = (queryset.filter(created_date__range=(seven_days_ago, today)) + .count()) / 7 + return stats