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