Skip to content
72 changes: 72 additions & 0 deletions website/services/team_weekly_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import logging
from datetime import date
from typing import Any, Dict, List

from django.core.exceptions import ValidationError
from django.db.models import Sum
from django.db.models.functions import Coalesce

from website.models import ContributorStats, OrganisationType, Organization

logger = logging.getLogger(__name__)


def get_weekly_team_stats(start_date: date, end_date: date) -> List[Dict[str, Any]]:
"""
Aggregate weekly stats for TEAM organizations.
Returns an empty list if no TEAM organizations or contributor stats
exist in the given date range.
"""
if start_date > end_date:
raise ValidationError("start_date must be before or equal to end_date")

logger.info("Aggregating weekly team stats from %s to %s", start_date, end_date)

teams = list(Organization.objects.filter(type=OrganisationType.TEAM.value).only("id", "name"))

if not teams:
logger.debug("No TEAM organizations found")
return []

# ContributorStats are stored at daily granularity.
# Weekly stats are computed by aggregating daily records
# over the given date range.
team_ids = [t.id for t in teams]
stats_queryset = (
ContributorStats.objects.filter(
repo__organization_id__in=team_ids,
granularity="day",
date__range=(start_date, end_date),
)
.values("repo__organization_id")
.annotate(
commits=Coalesce(Sum("commits"), 0),
issues_opened=Coalesce(Sum("issues_opened"), 0),
issues_closed=Coalesce(Sum("issues_closed"), 0),
pull_requests=Coalesce(Sum("pull_requests"), 0),
comments=Coalesce(Sum("comments"), 0),
)
)

stats_map = {s["repo__organization_id"]: s for s in stats_queryset}

team_stats = []
for team in teams:
s = stats_map.get(team.id, {})
team_stats.append(
{
"team_id": team.id,
"team_name": team.name,
"start_date": start_date,
"end_date": end_date,
"stats": {
"commits": s.get("commits", 0),
"issues_opened": s.get("issues_opened", 0),
"issues_closed": s.get("issues_closed", 0),
"pull_requests": s.get("pull_requests", 0),
"comments": s.get("comments", 0),
},
}
)

return team_stats
109 changes: 109 additions & 0 deletions website/tests/test_team_weekly_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from datetime import date

from django.core.exceptions import ValidationError
from django.test import TestCase

from website.models import Contributor, ContributorStats, OrganisationType, Organization, Repo
from website.services.team_weekly_stats import get_weekly_team_stats


class TestWeeklyTeamStats(TestCase):
def test_invalid_date_range_raises_error(self):
with self.assertRaises(ValidationError):
get_weekly_team_stats(
start_date=date(2024, 5, 10),
end_date=date(2024, 5, 1),
)

def test_no_teams_returns_empty_list(self):
result = get_weekly_team_stats(
start_date=date(2025, 1, 1),
end_date=date(2025, 1, 7),
)
self.assertEqual(result, [])

def test_team_with_no_stats_returns_zeros(self):
team = Organization.objects.create(
name="Test Team",
type=OrganisationType.TEAM.value,
url="https://example.com/test-team",
)

result = get_weekly_team_stats(
start_date=date(2025, 1, 1),
end_date=date(2025, 1, 7),
)

self.assertEqual(len(result), 1)
team_result = result[0]

self.assertEqual(team_result["team_id"], team.id)
self.assertEqual(team_result["team_name"], "Test Team")
self.assertEqual(
team_result["stats"],
{
"commits": 0,
"issues_opened": 0,
"issues_closed": 0,
"pull_requests": 0,
"comments": 0,
},
)

def test_single_team_with_stats(self):
team = Organization.objects.create(
name="Team A",
type=OrganisationType.TEAM.value,
url="https://example.com/test-team",
)

repo = Repo.objects.create(
name="test-repo",
organization=team,
repo_url="https://github.com/example/test-repo",
)

contributor = Contributor.objects.create(
name="Test User",
github_id=12345,
github_url="https://github.com/test-user",
avatar_url="https://avatars.githubusercontent.com/u/12345",
contributor_type="INDIVIDUAL",
contributions=0,
)

ContributorStats.objects.create(
repo=repo,
contributor=contributor,
granularity="day",
date=date(2025, 1, 3),
commits=5,
issues_opened=2,
issues_closed=1,
pull_requests=1,
comments=3,
)
result = get_weekly_team_stats(
start_date=date(2025, 1, 1),
end_date=date(2025, 1, 7),
)

self.assertEqual(len(result), 1)

team_result = result[0]

self.assertEqual(team_result["team_id"], team.id)
self.assertEqual(team_result["team_name"], "Team A")
self.assertEqual(team_result["start_date"], date(2025, 1, 1))
self.assertEqual(team_result["end_date"], date(2025, 1, 7))

self.assertEqual(
team_result["stats"],
{
"commits": 5,
"issues_opened": 2,
"issues_closed": 1,
"pull_requests": 1,
"comments": 3,
},
)
Loading