-
-
Notifications
You must be signed in to change notification settings - Fork 529
Add Project Health Dashboard models #904
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
b02aef7
projectHealthMetrics, projectHealthRequirements models along with res…
Dishant1804 062973e
project health/requirements setup
Dishant1804 b3f76c8
timezone from django utils
Dishant1804 4565a7e
checks and lints
Dishant1804 0485dc0
suggestions implemented
Dishant1804 993cdf1
recreated migrations
Dishant1804 f6d1679
permissons to migration files
Dishant1804 cc3b798
checks and lints
Dishant1804 aadbeed
Merge branch 'main' into project_details
Dishant1804 38b304d
Merge branch 'main' into project_details
arkid15r b4a1d73
Update code
arkid15r 3fc864a
Merge branch 'main' into project_details
arkid15r File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
141 changes: 141 additions & 0 deletions
141
backend/apps/owasp/management/commands/owasp_update_project_health_requirements.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| """A command to set thresholds of OWASP project health requirements.""" | ||
|
|
||
| from django.core.management.base import BaseCommand | ||
|
|
||
| from apps.owasp.models.project import Project | ||
| from apps.owasp.models.project_health_requirements import ProjectHealthRequirements | ||
|
|
||
|
|
||
| class Command(BaseCommand): | ||
| help = "Set project health requirements for each level." | ||
|
|
||
| LEVEL_REQUIREMENTS = { | ||
| Project.ProjectLevel.INCUBATOR: { | ||
| "age_days": 15, | ||
| "contributors_count": 1, | ||
| "forks_count": 2, | ||
| "last_commit_days": 365, | ||
| "last_pull_request_days": 60, | ||
| "last_release_days": 365, | ||
| "open_issues_count": 10, | ||
| "open_pull_requests_count": 5, | ||
| "owasp_page_last_update_days": 60, | ||
| "recent_releases_count": 1, | ||
| "recent_releases_time_window_days": 120, | ||
| "stars_count": 10, | ||
| "total_pull_requests_count": 5, | ||
| "total_releases_count": 2, | ||
| "unanswered_issues_count": 5, | ||
| "unassigned_issues_count": 5, | ||
| }, | ||
| Project.ProjectLevel.LAB: { | ||
| "age_days": 20, | ||
| "contributors_count": 3, | ||
| "forks_count": 5, | ||
| "last_commit_days": 270, | ||
| "last_pull_request_days": 45, | ||
| "last_release_days": 365, | ||
| "open_issues_count": 8, | ||
| "open_pull_requests_count": 4, | ||
| "owasp_page_last_update_days": 45, | ||
| "recent_releases_count": 1, | ||
| "recent_releases_time_window_days": 90, | ||
| "stars_count": 25, | ||
| "total_pull_requests_count": 10, | ||
| "total_releases_count": 3, | ||
| "unanswered_issues_count": 4, | ||
| "unassigned_issues_count": 4, | ||
| }, | ||
| Project.ProjectLevel.PRODUCTION: { | ||
| "age_days": 30, | ||
| "contributors_count": 4, | ||
| "forks_count": 7, | ||
| "last_commit_days": 90, | ||
| "last_pull_request_days": 30, | ||
| "last_release_days": 180, | ||
| "open_issues_count": 5, | ||
| "open_pull_requests_count": 3, | ||
| "owasp_page_last_update_days": 30, | ||
| "recent_releases_count": 2, | ||
| "recent_releases_time_window_days": 60, | ||
| "stars_count": 40, | ||
| "total_pull_requests_count": 15, | ||
| "total_releases_count": 4, | ||
| "unanswered_issues_count": 2, | ||
| "unassigned_issues_count": 2, | ||
| }, | ||
| Project.ProjectLevel.FLAGSHIP: { | ||
| "age_days": 30, | ||
| "contributors_count": 5, | ||
| "forks_count": 10, | ||
| "last_commit_days": 180, | ||
| "last_pull_request_days": 30, | ||
| "last_release_days": 365, | ||
| "open_issues_count": 5, | ||
| "open_pull_requests_count": 3, | ||
| "owasp_page_last_update_days": 30, | ||
| "recent_releases_count": 2, | ||
| "recent_releases_time_window_days": 90, | ||
| "stars_count": 50, | ||
| "total_pull_requests_count": 20, | ||
| "total_releases_count": 5, | ||
| "unanswered_issues_count": 3, | ||
| "unassigned_issues_count": 3, | ||
| }, | ||
| } | ||
|
|
||
| def add_arguments(self, parser): | ||
| parser.add_argument( | ||
| "--level", | ||
| type=str, | ||
| choices=[level[0] for level in Project.ProjectLevel.choices], | ||
| help="Project level to set requirements for", | ||
| ) | ||
|
|
||
| def get_level_requirements(self, level): | ||
| """Get default requirements based on project level.""" | ||
| defaults = { | ||
| "age_days": 0, | ||
| "contributors_count": 0, | ||
| "forks_count": 0, | ||
| "last_commit_days": 0, | ||
| "last_pull_request_days": 0, | ||
| "last_release_days": 0, | ||
| "open_issues_count": 0, | ||
| "open_pull_requests_count": 0, | ||
| "owasp_page_last_update_days": 0, | ||
| "recent_releases_count": 0, | ||
| "recent_releases_time_window_days": 0, | ||
| "stars_count": 0, | ||
| "total_pull_requests_count": 0, | ||
| "total_releases_count": 0, | ||
| "unanswered_issues_count": 0, | ||
| "unassigned_issues_count": 0, | ||
| } | ||
|
|
||
| return self.LEVEL_REQUIREMENTS.get(level, defaults) | ||
|
|
||
| def handle(self, *args, **options): | ||
| level = options.get("level") | ||
|
|
||
| if level: | ||
| defaults = self.get_level_requirements(level) | ||
| requirements, created = ProjectHealthRequirements.objects.get_or_create( | ||
| level=level, defaults=defaults | ||
| ) | ||
|
|
||
| action = "Created" if created else "Updated" | ||
| print(f"{action} health requirements for {requirements.get_level_display()} projects") | ||
| else: | ||
| for level_choice in Project.ProjectLevel.choices: | ||
| level_code = level_choice[0] | ||
| defaults = self.get_level_requirements(level_code) | ||
|
|
||
| requirements, created = ProjectHealthRequirements.objects.get_or_create( | ||
| level=level_code, defaults=defaults | ||
| ) | ||
|
|
||
| if created: | ||
| print(f"Created default health requirements for {level_choice[1]} projects") | ||
| else: | ||
| print(f"Health requirements already exist for {level_choice[1]} projects") | ||
124 changes: 124 additions & 0 deletions
124
backend/apps/owasp/migrations/0016_projecthealthmetrics.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| # Generated by Django 5.1.6 on 2025-02-28 11:57 | ||
|
|
||
| import django.core.validators | ||
| import django.db.models.deletion | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("owasp", "0015_snapshot"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name="ProjectHealthMetrics", | ||
| fields=[ | ||
| ( | ||
| "id", | ||
| models.BigAutoField( | ||
| auto_created=True, primary_key=True, serialize=False, verbose_name="ID" | ||
| ), | ||
| ), | ||
| ("nest_created_at", models.DateTimeField(auto_now_add=True)), | ||
| ("nest_updated_at", models.DateTimeField(auto_now=True)), | ||
| ( | ||
| "contributors_count", | ||
| models.PositiveIntegerField(default=0, verbose_name="Contributors"), | ||
| ), | ||
| ( | ||
| "created_at", | ||
| models.DateTimeField(blank=True, null=True, verbose_name="Created at"), | ||
| ), | ||
| ("forks_count", models.PositiveIntegerField(default=0, verbose_name="Forks")), | ||
| ( | ||
| "is_funding_requirements_compliant", | ||
| models.BooleanField( | ||
| default=False, verbose_name="Is funding requirements compliant" | ||
| ), | ||
| ), | ||
| ( | ||
| "is_project_leaders_requirements_compliant", | ||
| models.BooleanField( | ||
| default=False, verbose_name="Is project leaders requirements compliant" | ||
| ), | ||
| ), | ||
| ( | ||
| "last_released_at", | ||
| models.DateTimeField(blank=True, null=True, verbose_name="Last released at"), | ||
| ), | ||
| ( | ||
| "last_committed_at", | ||
| models.DateTimeField(blank=True, null=True, verbose_name="Last committed at"), | ||
| ), | ||
| ( | ||
| "open_issues_count", | ||
| models.PositiveIntegerField(default=0, verbose_name="Open issues"), | ||
| ), | ||
| ( | ||
| "open_pull_requests_count", | ||
| models.PositiveIntegerField(default=0, verbose_name="Open pull requests"), | ||
| ), | ||
| ( | ||
| "owasp_page_last_updated_at", | ||
| models.DateTimeField( | ||
| blank=True, null=True, verbose_name="OWASP page last updated at" | ||
| ), | ||
| ), | ||
| ( | ||
| "pull_request_last_created_at", | ||
| models.DateTimeField( | ||
| blank=True, null=True, verbose_name="Pull request last created at" | ||
| ), | ||
| ), | ||
| ( | ||
| "recent_releases_count", | ||
| models.PositiveIntegerField(default=0, verbose_name="Recent releases"), | ||
| ), | ||
| ( | ||
| "score", | ||
| models.FloatField( | ||
| default=0.0, | ||
| help_text="Project health score (0-100)", | ||
| validators=[ | ||
| django.core.validators.MinValueValidator(0.0), | ||
| django.core.validators.MaxValueValidator(100.0), | ||
| ], | ||
| ), | ||
| ), | ||
| ("stars_count", models.PositiveIntegerField(default=0, verbose_name="Stars")), | ||
| ( | ||
| "total_issues_count", | ||
| models.PositiveIntegerField(default=0, verbose_name="Total issues"), | ||
| ), | ||
| ( | ||
| "total_pull_request_count", | ||
| models.PositiveIntegerField(default=0, verbose_name="Total pull requests"), | ||
| ), | ||
| ( | ||
| "total_releases_count", | ||
| models.PositiveIntegerField(default=0, verbose_name="Total releases"), | ||
| ), | ||
| ( | ||
| "unanswered_issues_count", | ||
| models.PositiveIntegerField(default=0, verbose_name="Unanswered issues"), | ||
| ), | ||
| ( | ||
| "unassigned_issues_count", | ||
| models.PositiveIntegerField(default=0, verbose_name="Unassigned issues"), | ||
| ), | ||
| ( | ||
| "project", | ||
| models.OneToOneField( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="health_metrics", | ||
| to="owasp.project", | ||
| ), | ||
| ), | ||
| ], | ||
| options={ | ||
| "verbose_name_plural": "Project Health Metrics", | ||
| "db_table": "owasp_project_health_metrics", | ||
| }, | ||
| ), | ||
| ] |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider updating existing requirements.
The command checks if requirements exist, but it doesn't update them if the values in LEVEL_REQUIREMENTS have changed. Consider adding logic to update existing records with the latest requirement values.
if level: defaults = self.get_level_requirements(level) requirements, created = ProjectHealthRequirements.objects.get_or_create( level=level, defaults=defaults ) + + if not created: + # Update the existing record with the latest requirements + for key, value in defaults.items(): + setattr(requirements, key, value) + requirements.save() action = "Created" if created else "Updated" print(f"{action} health requirements for {requirements.get_level_display()} projects")📝 Committable suggestion