Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
e2c562e
feat: add contribution_data field to Chapter and Project models
mrkeshav-05 Nov 18, 2025
abd2373
Create migration 0066 to add fields to database
mrkeshav-05 Nov 18, 2025
5f4613b
add management command to aggregate contributions
mrkeshav-05 Nov 18, 2025
c032255
expose contribution_data in GraphQL API
mrkeshav-05 Nov 18, 2025
66f003c
refactor: extract contribution date aggregation into a separate metho…
mrkeshav-05 Nov 18, 2025
9dd093b
test: add contribution_data to the test's expected field names set.
mrkeshav-05 Nov 18, 2025
682de60
test: add unit tests for ChapterNode field resolutions and configurat…
mrkeshav-05 Nov 18, 2025
2af3339
test: add unit tests for owasp_aggregate_contributions management com…
mrkeshav-05 Nov 18, 2025
4b34607
feat: add contributionData to GraphQL queries
mrkeshav-05 Nov 18, 2025
281a28f
feat: update TypeScript types for contribution data
mrkeshav-05 Nov 18, 2025
80b14e6
feat: add contributionData field to both project and chapter nodes
mrkeshav-05 Nov 18, 2025
bc5251c
fixed linting errors
mrkeshav-05 Nov 18, 2025
45d6cc3
fixed failed tests in chapter_test.py
mrkeshav-05 Nov 18, 2025
2b2f088
fixed backend tests in owasp_aggregate_contributions_test.py
mrkeshav-05 Nov 18, 2025
11c2789
feat: add contribution heatmap to chapter details page
mrkeshav-05 Nov 18, 2025
be9dc34
feat: integrate contribution heatmap into project details page
mrkeshav-05 Nov 18, 2025
db17c65
feat: enhance contribution heatmap component with isCompact prop
mrkeshav-05 Nov 19, 2025
e6553e3
apply variant compact
mrkeshav-05 Nov 19, 2025
40aab52
feat: add contribution stats and heatmap to chapter and project detai…
mrkeshav-05 Nov 19, 2025
7f760de
make chapter page same as project page
mrkeshav-05 Nov 20, 2025
c0d6673
reduce the gap between the cards
mrkeshav-05 Nov 20, 2025
a47f4cf
optimize queryset by select_related for owasp_repository
mrkeshav-05 Nov 20, 2025
5f00d61
feat: optimize project queryset with select_related and prefetch_related
mrkeshav-05 Nov 20, 2025
1a86d98
chapter contribution aggregation tests
mrkeshav-05 Nov 20, 2025
3a9e23f
fix: update contribution_data test to assert field type
mrkeshav-05 Nov 20, 2025
71c051f
update contribution stats calculation to provide estimated values
mrkeshav-05 Nov 20, 2025
080818f
fix: handle null contribution stats in project and chapter details so…
mrkeshav-05 Nov 20, 2025
99ec8ea
fixing sonalcloud errors
mrkeshav-05 Nov 20, 2025
d5ff8eb
run pnpm lint
mrkeshav-05 Nov 20, 2025
1ed2c05
adjust layout and formatting
mrkeshav-05 Nov 20, 2025
6a921e1
fixedbackend testcases
mrkeshav-05 Nov 20, 2025
a633111
remove redundant code in project and chapter
mrkeshav-05 Nov 20, 2025
da65529
make another component for github stats
mrkeshav-05 Nov 20, 2025
3cd1725
fixing sonar issues
mrkeshav-05 Nov 20, 2025
b1f71f7
fixing tests
mrkeshav-05 Nov 20, 2025
e559ae2
remove unused variables
mrkeshav-05 Nov 20, 2025
cc8eaae
refactor: improve heatmap series generation and chart options
mrkeshav-05 Nov 20, 2025
b70a242
fixing sonar cloud issues
mrkeshav-05 Nov 20, 2025
ec33a79
fix make check
mrkeshav-05 Nov 21, 2025
7401aed
fixing sonarcloud issues
mrkeshav-05 Nov 21, 2025
331908f
handling null/undefined values
mrkeshav-05 Nov 21, 2025
c259538
include test identifiers
mrkeshav-05 Nov 21, 2025
c06ea65
add tests for ContributionStats component
mrkeshav-05 Nov 21, 2025
783d3cc
sort active chapters and projects
mrkeshav-05 Nov 21, 2025
76ef770
added trailing whitespace
mrkeshav-05 Nov 21, 2025
49d81b4
apply make check
mrkeshav-05 Nov 21, 2025
0b0380a
pnpm run lint -- --fix
mrkeshav-05 Nov 21, 2025
945bdfc
removing sonarcloud issues with contributionheatmap and contributions…
mrkeshav-05 Nov 23, 2025
405163a
add contribution_stats field to Chapter and Project models for github…
mrkeshav-05 Nov 23, 2025
c446b1f
add contribution_stats field to Chapter and Project GraphQL nodes
mrkeshav-05 Nov 23, 2025
57872e9
add contribution statistics calculation for chapters and projects
mrkeshav-05 Nov 23, 2025
f6a8561
add contribution_stats fields to Chapter and Project migrations
mrkeshav-05 Nov 23, 2025
a8c93b1
add contributionStats field to Project type
mrkeshav-05 Nov 23, 2025
e364054
add contributionStats field to Chapter type
mrkeshav-05 Nov 23, 2025
2e69524
run pnpm run --graphql-codegen to update the generated types
mrkeshav-05 Nov 23, 2025
bc6d2f7
add contributionStats field to GET_CHAPTER_DATA and GET_PROJECT_DATA …
mrkeshav-05 Nov 23, 2025
30244d8
create contributionDataUtils file for handling contribution stats logic
mrkeshav-05 Nov 23, 2025
f3cde9f
refactor: replace estimeted contribution stats with actual
mrkeshav-05 Nov 23, 2025
671d686
added tests for contributionStats field
mrkeshav-05 Nov 23, 2025
ec2519c
Run pre-commit
mrkeshav-05 Nov 23, 2025
29b966b
add .slice().reverse() for compatibility
mrkeshav-05 Nov 23, 2025
d8e8166
resolve some issues by coderabbitai
mrkeshav-05 Nov 23, 2025
ff4b67d
make check
mrkeshav-05 Nov 23, 2025
677ac8a
made the contributionheatmap responsive for both project and chapter …
mrkeshav-05 Nov 23, 2025
3955af7
fixed failed testcases
mrkeshav-05 Nov 23, 2025
939ebe6
fixing contributionheatmap for tests
mrkeshav-05 Nov 23, 2025
fd6f425
run pnpm run test:unit
mrkeshav-05 Nov 23, 2025
4254e11
make check
mrkeshav-05 Nov 23, 2025
c455613
fixing sonarcloud issues
mrkeshav-05 Nov 23, 2025
e7396c3
adjust heatmap chart dimensions for better responsiveness
mrkeshav-05 Nov 23, 2025
4634c03
update command in backend/Makefile to aggregate contributions
mrkeshav-05 Nov 27, 2025
ab0ae8d
change makefile to add owasp-aggregate-contributions target
mrkeshav-05 Nov 29, 2025
db546da
regenerate owasp migration 0067_chapter_contribution_stats_and_more
mrkeshav-05 Nov 29, 2025
9c8f362
refactor migration 0067 to improve code readability and formatting
mrkeshav-05 Dec 3, 2025
44a0c47
fix: update date in ProgramCard test to reflect correct end date
mrkeshav-05 Dec 4, 2025
2f20caa
style: update padding and heading size in CardDetailsPage test
mrkeshav-05 Dec 4, 2025
19278c3
fix: update ContributionHeatmap test to reflect correct class names a…
mrkeshav-05 Dec 4, 2025
71ee147
test: add variant rendering tests for ContributionHeatmap (coderabbitai)
mrkeshav-05 Dec 4, 2025
adb0258
fixing the test for programCard
mrkeshav-05 Dec 7, 2025
696b32a
added contribution_stats field to ProjectNode and ChapterNode
mrkeshav-05 Dec 9, 2025
28c226b
fix: optimize chapter and project queryset with select_related and pr…
mrkeshav-05 Dec 9, 2025
c5a73e6
Update code
arkid15r Dec 10, 2025
77f9b1b
update contributionsStats
mrkeshav-05 Dec 12, 2025
3ad2b39
update frontend code
mrkeshav-05 Dec 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ update-data: \
github-update-related-organizations \
github-update-users \
owasp-aggregate-projects \
owasp-aggregate-contributions \
owasp-update-events \
owasp-sync-posts \
owasp-update-sponsors \
Expand Down
5 changes: 5 additions & 0 deletions backend/apps/owasp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ owasp-aggregate-projects:
@echo "Aggregating OWASP projects"
@CMD="python manage.py owasp_aggregate_projects" $(MAKE) exec-backend-command

owasp-aggregate-contributions:
@echo "Aggregating OWASP contributions"
@CMD="python manage.py owasp_aggregate_contributions --entity-type chapter" $(MAKE) exec-backend-command
@CMD="python manage.py owasp_aggregate_contributions --entity-type project" $(MAKE) exec-backend-command

owasp-create-project-metadata-file:
@echo "Generating metadata"
@CMD="python manage.py owasp_create_project_metadata_file $(entity_key)" $(MAKE) exec-backend-command
Expand Down
2 changes: 2 additions & 0 deletions backend/apps/owasp/api/internal/nodes/chapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class GeoLocationType:
@strawberry_django.type(
Chapter,
fields=[
"contribution_data",
"contribution_stats",
"country",
"is_active",
"meetup_group",
Expand Down
2 changes: 2 additions & 0 deletions backend/apps/owasp/api/internal/nodes/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
@strawberry_django.type(
Project,
fields=[
"contribution_data",
"contribution_stats",
"contributors_count",
"created_at",
"forks_count",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
"""Management command to aggregate contributions for chapters and projects."""

from datetime import datetime, timedelta

from django.core.management.base import BaseCommand
from django.utils import timezone

from apps.github.models.commit import Commit
from apps.github.models.issue import Issue
from apps.github.models.pull_request import PullRequest
from apps.github.models.release import Release
from apps.owasp.models.chapter import Chapter
from apps.owasp.models.project import Project


class Command(BaseCommand):
"""Aggregate contribution data for chapters and projects."""

help = "Aggregate contributions (commits, issues, PRs, releases) for chapters and projects"

def add_arguments(self, parser):
"""Add command arguments."""
parser.add_argument(
"--entity-type",
choices=["chapter", "project"],
help="Entity type to aggregate: chapter, project",
required=True,
type=str,
)
parser.add_argument(
"--days",
default=365,
help="Number of days to look back for contributions (default: 365)",
type=int,
)
parser.add_argument(
"--key",
help="Specific chapter or project key to aggregate",
type=str,
)
parser.add_argument(
"--offset",
default=0,
help="Skip the first N entities",
type=int,
)

def _aggregate_contribution_dates(
self,
queryset,
date_field: str,
contribution_map: dict[str, int],
) -> None:
"""Aggregate contribution dates from a queryset into the contribution map.
Args:
queryset: Django queryset to aggregate
date_field: Name of the date field to aggregate on
contribution_map: Dictionary to update with counts
"""
for date_value in queryset.values_list(date_field, flat=True):
if not date_value:
continue

date_key = date_value.date().isoformat()
contribution_map[date_key] = contribution_map.get(date_key, 0) + 1

def _get_repository_ids(self, entity):
"""Extract repository IDs from chapter or project."""
repo_ids = []

# Handle single owasp_repository
if hasattr(entity, "owasp_repository") and entity.owasp_repository:
repo_ids.append(entity.owasp_repository.id)

# Handle multiple repositories (for projects)
if hasattr(entity, "repositories"):
repo_ids.extend([r.id for r in entity.repositories.all()])

return repo_ids
Comment on lines +69 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Deduplicate repository IDs to avoid double-counting.
If owasp_repository is also present in repositories, current logic inflates both heatmap and stats.

-        repo_ids = []
+        repo_ids: set[int] = set()
...
-            repo_ids.append(entity.owasp_repository.id)
+            repo_ids.add(entity.owasp_repository.id)
...
-            repo_ids.extend([r.id for r in entity.repositories.all()])
+            repo_ids.update(r.id for r in entity.repositories.all())
...
-        return repo_ids
+        return list(repo_ids)

Also applies to: 96-99

🤖 Prompt for AI Agents
In backend/apps/owasp/management/commands/owasp_aggregate_contributions.py
around lines 69 to 81 (and likewise for the similar code at lines 96-99), the
method collects repository IDs from both an entity.owasp_repository and
entity.repositories which can produce duplicates; change the logic to
deduplicate IDs before returning (e.g., collect IDs into a set or append then
convert to a set/list) so the same repository isn’t counted twice, and apply the
same deduplication fix to the similar block at lines 96-99.


def aggregate_contributions(self, entity, start_date: datetime) -> dict[str, int]:
"""Aggregate contributions for a chapter or project.
Args:
entity: Chapter or Project instance
start_date: Start date for aggregation
Returns:
Dictionary mapping YYYY-MM-DD to contribution count
"""
contribution_map: dict[str, int] = {}

repo_ids = self._get_repository_ids(entity)
if not repo_ids:
return contribution_map

# Aggregate commits
self._aggregate_contribution_dates(
Commit.objects.filter(
repository_id__in=repo_ids,
created_at__gte=start_date,
),
"created_at",
contribution_map,
)

# Aggregate issues
self._aggregate_contribution_dates(
Issue.objects.filter(
repository_id__in=repo_ids,
created_at__gte=start_date,
),
"created_at",
contribution_map,
)

# Aggregate pull requests
self._aggregate_contribution_dates(
PullRequest.objects.filter(
repository_id__in=repo_ids,
created_at__gte=start_date,
),
"created_at",
contribution_map,
)

# Aggregate releases
self._aggregate_contribution_dates(
Release.objects.filter(
repository_id__in=repo_ids,
published_at__gte=start_date,
is_draft=False,
),
"published_at",
contribution_map,
)

return contribution_map

def calculate_contribution_stats(self, entity, start_date: datetime) -> dict[str, int]:
"""Calculate contribution statistics for a chapter or project.
Args:
entity: Chapter or Project instance
start_date: Start date for calculation
Returns:
Dictionary with commits, issues, pull requests, releases counts
"""
stats = {
"commits": 0,
"issues": 0,
"pull_requests": 0,
"releases": 0,
"total": 0,
}

repo_ids = self._get_repository_ids(entity)
if not repo_ids:
return stats

# Count commits
stats["commits"] = Commit.objects.filter(
repository_id__in=repo_ids,
created_at__gte=start_date,
).count()

# Count issues
stats["issues"] = Issue.objects.filter(
repository_id__in=repo_ids,
created_at__gte=start_date,
).count()

# Count pull requests
stats["pull_requests"] = PullRequest.objects.filter(
repository_id__in=repo_ids,
created_at__gte=start_date,
).count()

# Count releases
stats["releases"] = Release.objects.filter(
repository_id__in=repo_ids,
published_at__gte=start_date,
is_draft=False,
).count()

stats["total"] = (
stats["commits"] + stats["issues"] + stats["pull_requests"] + stats["releases"]
)

return stats
Comment on lines +154 to +195
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Align contribution_stats key names with GraphQL/frontend expectations (camelCase vs snake_case).
pull_requests in persisted JSON is a footgun if the API/frontend expects pullRequests (and getContributionStats() returns the object verbatim when present).

-        stats = {
+        stats = {
           "commits": 0,
           "issues": 0,
-          "pull_requests": 0,
+          "pullRequests": 0,
           "releases": 0,
           "total": 0,
         }
...
-        stats["pull_requests"] = PullRequest.objects.filter(...).count()
+        stats["pullRequests"] = PullRequest.objects.filter(...).count()
...
-        stats["total"] = (stats["commits"] + stats["issues"] + stats["pull_requests"] + stats["releases"])
+        stats["total"] = (stats["commits"] + stats["issues"] + stats["pullRequests"] + stats["releases"])
🤖 Prompt for AI Agents
In backend/apps/owasp/management/commands/owasp_aggregate_contributions.py
around lines 154 to 195, the stats dict uses snake_case keys (e.g.,
"pull_requests") but the GraphQL/frontend expects camelCase keys; update the
keys to camelCase (commits, issues, pullRequests, releases, total), adjust the
code that sets and sums those values to use the new keys, and ensure any JSON
persisted/returned structure uses the camelCase names so getContributionStats()
and the frontend receive the expected keys.


def handle(self, *args, **options):
"""Execute the command."""
entity_type = options["entity_type"]
days = options["days"]
key = options.get("key")
offset = options["offset"]

start_date = timezone.now() - timedelta(days=days)

self.stdout.write(
self.style.SUCCESS(
f"Aggregating contributions from {start_date.date()} ({days} days back)",
),
)

if entity_type == "chapter":
self._process_chapters(start_date, key, offset)
elif entity_type == "project":
self._process_projects(start_date, key, offset)

self.stdout.write(self.style.SUCCESS("Done!"))

def _process_chapters(self, start_date, key, offset):
"""Process chapters for contribution aggregation."""
queryset = Chapter.objects.filter(is_active=True).order_by("id")

if key:
queryset = queryset.filter(key=key)

queryset = queryset.select_related("owasp_repository")

if offset:
queryset = queryset[offset:]

self._process_entities(queryset, start_date, "chapters", Chapter)

def _process_projects(self, start_date, key, offset):
"""Process projects for contribution aggregation."""
queryset = (
Project.objects.filter(is_active=True)
.order_by("id")
.select_related("owasp_repository")
.prefetch_related("repositories")
)

if key:
queryset = queryset.filter(key=key)

if offset:
queryset = queryset[offset:]

self._process_entities(queryset, start_date, "projects", Project)

def _process_entities(self, queryset, start_date, label, model_class):
"""Process entities (chapters or projects) for contribution aggregation."""
count = queryset.count()
self.stdout.write(f"Processing {count} {label}...")

entities = []
for entity in queryset:
entity.contribution_data = self.aggregate_contributions(entity, start_date)
entity.contribution_stats = self.calculate_contribution_stats(entity, start_date)
entities.append(entity)

if entities:
model_class.bulk_save(entities, fields=("contribution_data", "contribution_stats"))
self.stdout.write(self.style.SUCCESS(f"Updated {count} {label}"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 5.2.8 on 2025-11-16 18:18

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("owasp", "0065_memberprofile_linkedin_page_id"),
]

operations = [
migrations.AddField(
model_name="chapter",
name="contribution_data",
field=models.JSONField(
blank=True,
default=dict,
help_text="Daily contribution counts (YYYY-MM-DD -> count mapping)",
verbose_name="Contribution Data",
),
),
migrations.AddField(
model_name="project",
name="contribution_data",
field=models.JSONField(
blank=True,
default=dict,
help_text="Daily contribution counts (YYYY-MM-DD -> count mapping)",
verbose_name="Contribution Data",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 5.2.8 on 2025-11-29 19:46

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("owasp", "0066_chapter_contribution_data_project_contribution_data"),
]

operations = [
migrations.AddField(
model_name="chapter",
name="contribution_stats",
field=models.JSONField(
blank=True,
default=dict,
help_text="Detailed contribution breakdown (commits, issues, pullRequests, releases)",
verbose_name="Contribution Statistics",
),
),
migrations.AddField(
model_name="project",
name="contribution_stats",
field=models.JSONField(
blank=True,
default=dict,
help_text="Detailed contribution breakdown (commits, issues, pullRequests, releases)",
verbose_name="Contribution Statistics",
),
),
]
Loading