Skip to content

Commit

Permalink
fix(stats): sort period whatever they are truncated on
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentporte committed Jan 13, 2023
1 parent 3850c78 commit 50914dd
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 45 deletions.
6 changes: 1 addition & 5 deletions lacommunaute/forum/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,31 +34,27 @@ def get_stats(self, period_back):
count_objects_per_period(
Topic.objects.filter(forum__in=forums, created__gte=start_date).annotate(period=TruncWeek("created")),
"topics",
PeriodAggregation.WEEK,
)
+ count_objects_per_period(
Post.objects.filter(topic__forum__in=forums, created__gte=start_date).annotate(
period=TruncWeek("created")
),
"posts",
PeriodAggregation.WEEK,
)
+ count_objects_per_period(
UpVote.objects.filter(post__topic__forum__in=forums, created_at__gte=start_date).annotate(
period=TruncWeek("created_at")
),
"upvotes",
PeriodAggregation.WEEK,
)
+ count_objects_per_period(
TopicPollVote.objects.filter(
poll_option__poll__topic__forum__in=forums, timestamp__gte=start_date
).annotate(period=TruncWeek("timestamp")),
"pollvotes",
PeriodAggregation.WEEK,
)
)
return format_counts_of_objects_for_timeline_chart(datas)
return format_counts_of_objects_for_timeline_chart(datas, period=PeriodAggregation.WEEK)

@cached_property
def count_engaged_users(self):
Expand Down
36 changes: 15 additions & 21 deletions lacommunaute/utils/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,35 @@
from lacommunaute.utils.enums import PeriodAggregation


def count_objects_per_period(qs, name, period=PeriodAggregation.MONTH):
def get_strftime(period=PeriodAggregation.MONTH):
if period == PeriodAggregation.WEEK:
ft = "%Y-%W"
elif period == PeriodAggregation.MONTH:
ft = "%b %Y"
else:
raise ValueError("unknown period")
return "%Y-%W"
if period == PeriodAggregation.MONTH:
return "%b %Y"
raise ValueError("unknown period")


def count_objects_per_period(qs, name):

# [{'period': 'Dec 2022', '<model name>': 59}, {'period': 'Jan 2023', '<model name>': 25}]
qs = qs.values("period").annotate(number=Count("id")).values("period", "number").order_by("period")
return [{"period": item["period"].strftime(ft), name: item["number"]} for item in qs]
return [{"period": item["period"], name: item["number"]} for item in qs]


def format_counts_of_objects_for_timeline_chart(datas):
def format_counts_of_objects_for_timeline_chart(datas, period=PeriodAggregation.MONTH):
# TODO vincentporte : manage period without datas between oldest period and now

# aggregate counts of objects per month in a dict
# {'Dec 2022': {'period': 'Dec 2022', 'users': 59, 'posts': 5, 'upvotes': 3},
# 'Jan 2023': {'period': 'Jan 2023', 'users': 25, 'posts': 39, 'upvotes': 11, 'pollvotes': 26}
# }
merged_periods = defaultdict(dict)
for data in datas:
merged_periods[data["period"]] |= data

# objects name
names = dict(ChainMap(*datas)).keys()

# extract one array per object name
# {'period': ['Dec 2022', 'Jan 2023'],
# 'pollvotes': [0, 26],
# 'upvotes': [3, 11],
# 'posts': [5, 39],
# 'users': [59, 25]
# }
chart_arrays = {}
for name in names:
chart_arrays[name] = [merged_periods[k].get(name, 0) for k in sorted(merged_periods.keys())]

# datetime must be formatted AFTER being sorted
ft = get_strftime(period)
chart_arrays["period"] = [v.strftime(ft) for v in chart_arrays["period"]]

return chart_arrays
56 changes: 37 additions & 19 deletions lacommunaute/utils/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dateutil.relativedelta import relativedelta
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db.models.functions import TruncMonth, TruncWeek
from django.db.models.functions import TruncMonth
from django.template import Context, Template
from django.template.defaultfilters import date, time
from django.test import TestCase, override_settings
Expand All @@ -17,7 +17,12 @@
from lacommunaute.forum_conversation.forum_attachments.factories import AttachmentFactory
from lacommunaute.users.factories import UserFactory
from lacommunaute.users.models import User
from lacommunaute.utils.stats import count_objects_per_period, format_counts_of_objects_for_timeline_chart
from lacommunaute.utils.enums import PeriodAggregation
from lacommunaute.utils.stats import (
count_objects_per_period,
format_counts_of_objects_for_timeline_chart,
get_strftime,
)


faker = Faker()
Expand Down Expand Up @@ -130,34 +135,47 @@ def test_urlizetrunc_target_blank(self):


class UtilsStatsTest(TestCase):
def test_get_strftime(self):
self.assertEqual(get_strftime(PeriodAggregation.WEEK), "%Y-%W")
self.assertEqual(get_strftime(PeriodAggregation.MONTH), "%b %Y")
with self.assertRaises(ValueError):
get_strftime("xxx")

def test_count_objects_per_period(self):
now = timezone.now()
now = timezone.localtime()
one_month_ago = now - relativedelta(months=1)
UserFactory(date_joined=one_month_ago)
UserFactory.create_batch(2, date_joined=now)

# test format month
self.assertEqual(
count_objects_per_period(User.objects.annotate(period=TruncMonth("date_joined")), "users"),
[{"period": one_month_ago.strftime("%b %Y"), "users": 1}, {"period": now.strftime("%b %Y"), "users": 2}],
)

# test format week
self.assertEqual(
count_objects_per_period(User.objects.annotate(period=TruncWeek("date_joined")), "users", period="WEEK"),
[{"period": one_month_ago.strftime("%Y-%W"), "users": 1}, {"period": now.strftime("%Y-%W"), "users": 2}],
[
{"period": one_month_ago.replace(day=1, hour=0, minute=0, second=0, microsecond=0), "users": 1},
{"period": now.replace(day=1, hour=0, minute=0, second=0, microsecond=0), "users": 2},
],
)

# test unknown format
with self.assertRaises(ValueError):
count_objects_per_period(User.objects.annotate(period=TruncWeek("date_joined")), "users", period="XXX")

def test_format_counts_of_objects_for_timeline_chart(self):
datas = [{"period": "Dec 2022", "posts": 1}, {"period": "Feb 2023", "posts": 2}] + [
{"period": "Dec 2022", "users": 1},
{"period": "Jan 2023", "users": 2},
now = timezone.localtime()
one_month_ago = now - relativedelta(months=1)
two_month_ago = now - relativedelta(months=2)
datas = [{"period": one_month_ago, "posts": 1}, {"period": now, "posts": 2}] + [
{"period": two_month_ago, "users": 1},
{"period": now, "users": 2},
]
self.assertEqual(
format_counts_of_objects_for_timeline_chart(datas),
{"period": ["Dec 2022", "Feb 2023", "Jan 2023"], "users": [1, 0, 2], "posts": [1, 2, 0]},
{
"period": [two_month_ago.strftime("%b %Y"), one_month_ago.strftime("%b %Y"), now.strftime("%b %Y")],
"users": [1, 0, 2],
"posts": [0, 1, 2],
},
)
self.assertEqual(
format_counts_of_objects_for_timeline_chart(datas, period=PeriodAggregation.WEEK),
{
"period": [two_month_ago.strftime("%Y-%W"), one_month_ago.strftime("%Y-%W"), now.strftime("%Y-%W")],
"users": [1, 0, 2],
"posts": [0, 1, 2],
},
)

0 comments on commit 50914dd

Please sign in to comment.