Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c319404
Creating a command to update metrics - initial step
ahmedxgouda Jun 2, 2025
a46b9e4
Add missing properties to the project model and update the metrics sc…
ahmedxgouda Jun 3, 2025
5984ea2
Merge branch 'main' into dashboard/health-evaluation
ahmedxgouda Jun 3, 2025
fdec122
Apply make-check and check-spelling
ahmedxgouda Jun 3, 2025
032abbd
Update tests
ahmedxgouda Jun 3, 2025
eca8635
Apply coderabbitai suggestion
ahmedxgouda Jun 3, 2025
23eb00a
Add is_funding_requirements_compliant to the metrics script
ahmedxgouda Jun 3, 2025
bcedde3
Apply coderabbitai suggestion
ahmedxgouda Jun 4, 2025
5bcd9b8
Add owasp_page_last_updated_at to the metrics
ahmedxgouda Jun 4, 2025
8110192
Refactor is_funding_requirements_compliant logic
ahmedxgouda Jun 4, 2025
58865cb
Add command to calculate score
ahmedxgouda Jun 4, 2025
8d6c3c0
Remove TODO
ahmedxgouda Jun 4, 2025
f7beaaf
Add metrics test
ahmedxgouda Jun 4, 2025
cbbd63f
Add metrics_score test
ahmedxgouda Jun 4, 2025
c515b51
Update metrics test
ahmedxgouda Jun 4, 2025
dfac905
Merge branch 'main' into dashboard/health-evaluation
ahmedxgouda Jun 4, 2025
9d30c8e
Merge branch 'main' into dashboard/health-evaluation
kasya Jun 6, 2025
3788253
Merge branch 'main' into dashboard/health-evaluation
ahmedxgouda Jun 7, 2025
fd0688d
Apply suggestions and add tests
ahmedxgouda Jun 7, 2025
5452beb
Rename score script to be plural
ahmedxgouda Jun 8, 2025
c85e2ea
Change the logic to create multiple ProjectHealthMetrics objects for …
ahmedxgouda Jun 8, 2025
acf0c66
Merge branch 'main' into dashboard/health-evaluation
ahmedxgouda Jun 8, 2025
56f3cf3
Apply coderabbitai suggestion
ahmedxgouda Jun 8, 2025
8845db3
Merge branch 'main' into dashboard/health-evaluation
ahmedxgouda Jun 10, 2025
9cea493
Merge branch 'main' into dashboard/health-evaluation
ahmedxgouda Jun 10, 2025
891074d
Make open issues count negative direction
ahmedxgouda Jun 11, 2025
f5b4514
Merge branch 'main' into dashboard/health-evaluation
ahmedxgouda Jun 11, 2025
9faf664
Evaluate the funding and project leaders requirement in the score and…
ahmedxgouda Jun 11, 2025
1e70ad7
Merge branch 'main' into dashboard/health-evaluation
kasya Jun 12, 2025
dc7adcd
Apply suggestions
ahmedxgouda Jun 12, 2025
8edde6d
Merge branch 'main' into dashboard/health-evaluation
ahmedxgouda Jun 12, 2025
c6a7061
Merge branch 'main' into dashboard/health-evaluation
ahmedxgouda Jun 12, 2025
a2eaf7a
Apply coderabbitai suggestion
ahmedxgouda Jun 12, 2025
b49f80d
Apply coderabbitai suggetion
ahmedxgouda Jun 12, 2025
4f24790
Fix tests
ahmedxgouda Jun 12, 2025
1702e50
Update code
arkid15r Jun 13, 2025
e4d661a
Merge branch 'main' into dashboard/health-evaluation
arkid15r Jun 13, 2025
8dabe94
Apply suggestions
ahmedxgouda Jun 13, 2025
1918428
Fix if condition
ahmedxgouda Jun 13, 2025
b787163
Merge branch 'main' into pr/ahmedxgouda/1550
arkid15r Jun 14, 2025
9c555c8
Update code
arkid15r Jun 14, 2025
5d1d1aa
Merge branch 'main' into dashboard/health-evaluation
arkid15r Jun 15, 2025
56231b9
Make older projects have higher scores
ahmedxgouda Jun 15, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""A command to update OWASP project health metrics."""

from django.core.management.base import BaseCommand

from apps.owasp.models.project import Project
from apps.owasp.models.project_health_metrics import ProjectHealthMetrics

MINIMUM_LEADERS = 3
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add a comment on what it is, and why is 3?

Copy link
Collaborator Author

@ahmedxgouda ahmedxgouda Jun 7, 2025

Choose a reason for hiding this comment

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

Actually it should be 2, it was stated in the description of the original idea: #711
image



class Command(BaseCommand):
help = "Update OWASP project health metrics."

def handle(self, *args, **options):
projects = Project.objects.all()
field_mappings = {
"contributors_count": "contributors_count",
"created_at": "created_at",
"forks_count": "forks_count",
"last_released_at": "released_at",
"last_committed_at": "pushed_at",
"open_issues_count": "open_issues_count",
"open_pull_requests_count": "open_pull_requests_count",
"pull_request_last_created_at": "pull_request_last_created_at",
"recent_releases_count": "recent_releases_count",
"stars_count": "stars_count",
"total_issues_count": "issues_count",
"total_pull_requests_count": "pull_requests_count",
"total_releases_count": "releases_count",
"unanswered_issues_count": "unanswered_issues_count",
"unassigned_issues_count": "unassigned_issues_count",
}
for project in projects:
self.stdout.write(self.style.NOTICE(f"Updating metrics for project: {project.name}"))
metrics = ProjectHealthMetrics.objects.get_or_create(project=project)[0]

# Update metrics based on requirements
# TODO(ahmedxgouda): update from owasp page: owasp_page_last_updated_at
# TODO(ahmedxgouda): update is_funding_requirements_compliant,
# TODO(ahmedxgouda): add score
for metric_field, project_field in field_mappings.items():
value = getattr(project, project_field)
setattr(metrics, metric_field, value)

is_leaders_compliant = project.leaders_count >= MINIMUM_LEADERS
metrics.is_project_leaders_requirements_compliant = is_leaders_compliant
metrics.save()

self.stdout.write(self.style.SUCCESS("Project health metrics updated successfully."))
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.2.1 on 2025-06-03 14:47

from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("owasp", "0034_alter_chapter_leaders_and_more"),
]

operations = [
migrations.RenameField(
model_name="projecthealthmetrics",
old_name="total_pull_request_count",
new_name="total_pull_requests_count",
),
]
48 changes: 48 additions & 0 deletions backend/apps/owasp/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

from __future__ import annotations

import datetime
from functools import lru_cache

from django.db import models
from django.utils import timezone

from apps.common.index import IndexBase
from apps.common.models import BulkSaveModel, TimestampedModel
Expand All @@ -18,6 +20,8 @@
from apps.owasp.models.managers.project import ActiveProjectManager
from apps.owasp.models.mixins.project import ProjectIndexMixin

RECENT_RELEASES_PERIOD = timezone.now() - datetime.timedelta(days=60)


class Project(
BulkSaveModel,
Expand Down Expand Up @@ -154,6 +158,11 @@ def issues(self):
"repository",
)

@property
def issues_count(self) -> int:
"""Return count of issues."""
return self.issues.count()

@property
def nest_key(self) -> str:
"""Get Nest key."""
Expand All @@ -164,6 +173,11 @@ def nest_url(self) -> str:
"""Get Nest URL for project."""
return get_absolute_url(f"projects/{self.nest_key}")

@property
def leaders_count(self) -> int:
"""Return the count of leaders."""
return len(self.leaders_raw)

@property
def open_issues(self):
"""Return open issues."""
Expand All @@ -173,6 +187,11 @@ def open_issues(self):
"repository",
)

@property
def open_pull_requests_count(self) -> int:
"""Return count of open pull requests."""
return self.pull_requests.filter(state="open").count()

@property
def pull_requests(self):
"""Return pull requests."""
Expand All @@ -182,6 +201,18 @@ def pull_requests(self):
"repository",
)

@property
def pull_requests_count(self) -> int:
"""Return count of pull requests."""
return self.pull_requests.count()

@property
def pull_request_last_created_at(self) -> datetime.datetime | None:
"""Return last created pull request."""
if pull_request := self.pull_requests.order_by("-created_at").first():
return pull_request.created_at
return None

@property
def published_releases(self):
"""Return project releases."""
Expand All @@ -202,6 +233,23 @@ def recent_milestones(self):
"repository",
)

@property
def recent_releases_count(self) -> int:
"""Return count of recent releases per a specific period."""
return self.published_releases.filter(
published_at__gte=RECENT_RELEASES_PERIOD,
).count()

@property
def unanswered_issues_count(self) -> int:
"""Return count of unanswered issues."""
return self.issues.filter(comments_count=0).count()

@property
def unassigned_issues_count(self) -> int:
"""Return count of unassigned issues."""
return self.issues.filter(assignees__isnull=True).count()

def deactivate(self) -> None:
"""Deactivate project."""
self.is_active = False
Expand Down
2 changes: 1 addition & 1 deletion backend/apps/owasp/models/project_health_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Meta:
)
stars_count = models.PositiveIntegerField(verbose_name="Stars", default=0)
total_issues_count = models.PositiveIntegerField(verbose_name="Total issues", default=0)
total_pull_request_count = models.PositiveIntegerField(
total_pull_requests_count = models.PositiveIntegerField(
verbose_name="Total pull requests", default=0
)
total_releases_count = models.PositiveIntegerField(verbose_name="Total releases", default=0)
Expand Down
1 change: 1 addition & 0 deletions cspell/custom-dict.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ WSL
Whistleblower
Wörld
a2eeef
ahmedxgouda
algoliasearch
ansa
apk
Expand Down
Loading