-
-
Notifications
You must be signed in to change notification settings - Fork 264
Added participants who have expressed interest in a specific issue in issue model . #1995
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
base: feature/mentorship-portal
Are you sure you want to change the base?
Changes from 5 commits
81a8713
1ee00ae
5fd03c7
e2b66b7
d924096
b77a27c
c617cf3
bc1d0d6
686e6c1
67290f9
0610ee8
bafd85c
a1e176c
defe5a8
5f84dcd
d3f1576
de914ed
bd6f76f
db785cc
31b4463
85082aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| """GitHub app Issue model admin.""" | ||
|
|
||
| from django.contrib import admin | ||
|
|
||
| from apps.github.models import IssueComment | ||
|
|
||
|
|
||
| class IssueCommentAdmin(admin.ModelAdmin): | ||
| """Admin for IssueComment model.""" | ||
|
|
||
| list_display = ( | ||
| "body", | ||
| "issue", | ||
| "author", | ||
| "created_at", | ||
| "updated_at", | ||
| ) | ||
| list_filter = ("created_at", "updated_at") | ||
| search_fields = ("body", "issue__title") | ||
|
|
||
|
|
||
| admin.site.register(IssueComment, IssueCommentAdmin) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| from github.GithubException import UnknownObjectException | ||
|
|
||
| from apps.github.models.issue import Issue | ||
| from apps.github.models.issue_comment import IssueComment | ||
| from apps.github.models.label import Label | ||
| from apps.github.models.milestone import Milestone | ||
| from apps.github.models.organization import Organization | ||
|
|
@@ -227,3 +228,96 @@ def sync_repository( | |
| ) | ||
|
|
||
| return organization, repository | ||
|
|
||
|
|
||
| def sync_issue_comments(gh_app, issue: Issue): | ||
| """Sync new comments for a mentorship program specific issue on-demand. | ||
|
|
||
| Args: | ||
| gh_app (Github): An authenticated PyGithub instance. | ||
|
||
| issue (Issue): The local database Issue object to sync comments for. | ||
|
|
||
| """ | ||
| logger.info("Starting comment sync for issue #%s", issue.number) | ||
|
|
||
| try: | ||
| repo = issue.repository | ||
|
||
| if not repo: | ||
| logger.warning("Issue #%s has no repository, skipping", issue.number) | ||
| return | ||
|
|
||
| if not repo.owner: | ||
|
||
| logger.warning("Repository for issue #%s has no owner, skipping", issue.number) | ||
| return | ||
|
|
||
| repo_full_name = f"{repo.owner.login}/{repo.name}" | ||
| logger.info("Fetching repository: %s", repo_full_name) | ||
|
|
||
| gh_repo = gh_app.get_repo(repo_full_name) | ||
| gh_issue = gh_repo.get_issue(number=issue.number) | ||
|
|
||
| last_comment = issue.comments.order_by("-created_at").first() | ||
|
||
| since = None | ||
|
|
||
| if last_comment: | ||
| since = last_comment.created_at | ||
| logger.info("Found last comment at: %s, fetching newer comments", since) | ||
| else: | ||
| logger.info("No existing comments found, fetching all comments") | ||
|
|
||
| existing_github_ids = set(issue.comments.values_list("github_id", flat=True)) | ||
|
|
||
| comments_synced = 0 | ||
|
|
||
| gh_comments = gh_issue.get_comments(since=since) if since else gh_issue.get_comments() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it work with just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorry i have checked the github type for this. when we pass null it throws error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, thanks for confirming |
||
|
|
||
| for gh_comment in gh_comments: | ||
| if gh_comment.id in existing_github_ids: | ||
| logger.info("Skipping existing comment %s", gh_comment.id) | ||
| continue | ||
|
||
|
|
||
| if since and gh_comment.created_at <= since: | ||
| logger.info("Skipping comment %s - not newer than our last comment", gh_comment.id) | ||
| continue | ||
|
|
||
| author_obj = User.update_data(gh_comment.user) | ||
|
||
|
|
||
| if author_obj: | ||
| try: | ||
| comment_obj = IssueComment.update_data(gh_comment, issue, author_obj) | ||
| if comment_obj: | ||
| comments_synced += 1 | ||
| logger.info( | ||
| "Synced new comment %s for issue #%s", gh_comment.id, issue.number | ||
| ) | ||
| except Exception: | ||
| logger.exception( | ||
| "Failed to create comment %s for issue #%s", | ||
| gh_comment.id, | ||
| issue.number, | ||
| ) | ||
| else: | ||
| logger.warning("Could not sync author for comment %s", gh_comment.id) | ||
|
|
||
| if comments_synced > 0: | ||
| logger.info( | ||
| "Synced %d new comments for issue #%s in %s", | ||
| comments_synced, | ||
| issue.number, | ||
| issue.repository.name, | ||
| ) | ||
| else: | ||
| logger.info("No new comments found for issue #%s", issue.number) | ||
|
|
||
| except UnknownObjectException as e: | ||
| logger.warning( | ||
| "Could not access issue #%s in %s. Error: %s", | ||
| issue.number, | ||
| repo_full_name, | ||
| str(e), | ||
|
||
| ) | ||
| except Exception: | ||
| logger.exception( | ||
| "An unexpected error occurred during comment sync for issue #%s", | ||
| issue.number, | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Generated by Django 5.2.4 on 2025-08-14 19:16 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("github", "0033_alter_release_published_at"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name="IssueComment", | ||
| fields=[ | ||
| ( | ||
| "id", | ||
| models.BigAutoField( | ||
| auto_created=True, primary_key=True, serialize=False, verbose_name="ID" | ||
| ), | ||
| ), | ||
| ("github_id", models.BigIntegerField(unique=True)), | ||
| ("body", models.TextField()), | ||
| ("created_at", models.DateTimeField()), | ||
| ("updated_at", models.DateTimeField()), | ||
| ( | ||
| "author", | ||
| models.ForeignKey( | ||
| null=True, | ||
| on_delete=django.db.models.deletion.SET_NULL, | ||
| related_name="issue_comments", | ||
| to="github.user", | ||
| ), | ||
| ), | ||
| ( | ||
| "issue", | ||
| models.ForeignKey( | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="comments", | ||
| to="github.issue", | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| """Github app.""" | ||
|
|
||
| from .issue_comment import IssueComment | ||
| from .milestone import Milestone | ||
| from .pull_request import PullRequest | ||
| from .user import User |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| """GitHub app issue comment model.""" | ||
|
|
||
| from django.db import models | ||
|
|
||
|
|
||
| class IssueComment(models.Model): | ||
| """Represents a comment on a GitHub issue.""" | ||
|
|
||
| github_id = models.BigIntegerField(unique=True) | ||
|
||
| issue = models.ForeignKey("github.Issue", on_delete=models.CASCADE, related_name="comments") | ||
| author = models.ForeignKey( | ||
| "github.User", on_delete=models.SET_NULL, null=True, related_name="issue_comments" | ||
| ) | ||
| body = models.TextField() | ||
| created_at = models.DateTimeField() | ||
| updated_at = models.DateTimeField() | ||
|
|
||
| @classmethod | ||
| def update_data(cls, gh_comment, issue_obj, author_obj): | ||
|
||
| """Create or update an IssueComment instance from a GitHub API object.""" | ||
| comment, _ = cls.objects.update_or_create( | ||
| github_id=gh_comment.id, | ||
| defaults={ | ||
| "issue": issue_obj, | ||
| "author": author_obj, | ||
| "body": gh_comment.body, | ||
| "created_at": gh_comment.created_at, | ||
| "updated_at": gh_comment.updated_at, | ||
| }, | ||
| ) | ||
| return comment | ||
Rajgupta36 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def __str__(self): | ||
| """Return a string representation of the issue comment.""" | ||
| return f"{self.issue} - {self.author} - {self.body}" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| sync_mentorship_issue_comments: | ||
| @echo "Syncing Github Comments related to issues" | ||
| @CMD="python manage.py sync_mentorship_issue_comments" $(MAKE) exec-backend-command | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| """Mentorship app interested contributors admin.""" | ||
|
|
||
| from django.contrib import admin | ||
|
|
||
| from apps.mentorship.models import ParticipantInterest | ||
|
|
||
|
|
||
| class ParticipantInterestAdmin(admin.ModelAdmin): | ||
| """ParticipantInterest admin.""" | ||
|
|
||
| list_display = ("program", "issue", "users_count") | ||
| search_fields = ("program__name", "users__login", "issue__title") | ||
| list_filter = ("program", "issue") | ||
|
|
||
| def users_count(self, obj): | ||
| """Return the count of users interested in the issue.""" | ||
| return obj.users.count() | ||
|
|
||
| users_count.short_description = "Users interested" | ||
|
|
||
|
|
||
| admin.site.register(ParticipantInterest, ParticipantInterestAdmin) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| """Syncs comments for issues relevant to active mentorship programs.""" | ||
|
|
||
| import logging | ||
| import re | ||
|
|
||
| from django.core.management.base import BaseCommand | ||
| from github.GithubException import GithubException | ||
|
|
||
| from apps.github.auth import get_github_client | ||
| from apps.github.common import sync_issue_comments | ||
| from apps.github.models.issue import Issue | ||
| from apps.mentorship.models import ParticipantInterest, Program | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| INTEREST_PATTERNS = [ | ||
|
||
| re.compile(p, re.IGNORECASE) | ||
| for p in [ | ||
| r"assign.*me", | ||
| r"i(?:'d| would)? like to work on", | ||
| r"can i work on", | ||
| r"i(?:'ll| will)? take", | ||
| r"i want to work on", | ||
| r"i am interested", | ||
| r"can i be assigned", | ||
| r"please assign.*me", | ||
| r"i can (?:help|work|fix|handle)", | ||
| r"let me (?:work|take|handle)", | ||
| r"i(?:'ll| will).*(?:fix|handle|work)", | ||
| r"assign.*to.*me", | ||
| r"i volunteer", | ||
| r"count me in", | ||
| r"i(?:'m| am) up for", | ||
| r"i could work", | ||
| r"happy.*work", | ||
| r"i(?:'d| would) love to work", | ||
| ] | ||
| ] | ||
|
|
||
|
|
||
| class Command(BaseCommand): | ||
| """Syncs comments for issues relevant to active mentorship programs.""" | ||
|
|
||
| def handle(self, *args, **options): | ||
| self.stdout.write(self.style.SUCCESS("Starting mentorship issue processing job...")) | ||
|
|
||
| gh = get_github_client() | ||
| active_programs = Program.objects.filter(status=Program.ProgramStatus.PUBLISHED) | ||
| if not active_programs.exists(): | ||
| self.stdout.write(self.style.WARNING("No active mentorship programs found. Exiting.")) | ||
| return | ||
|
|
||
| for program in active_programs: | ||
| self.stdout.write(f"\nProcessing program: {program.name}...") | ||
|
|
||
| try: | ||
| program_repos = ( | ||
| program.modules.filter(project__repositories__isnull=False) | ||
| .values_list("project__repositories", flat=True) | ||
| .distinct() | ||
| ) | ||
| self.stdout.write( | ||
| f"Program '{program.name}' have {program_repos.count()} repositories." | ||
| ) | ||
|
|
||
| if not program_repos.exists(): | ||
| self.stdout.write( | ||
| self.style.WARNING(f"Skipping. {program.name} has no repositories.") | ||
| ) | ||
| continue | ||
|
|
||
| relevant_issues = Issue.objects.filter( | ||
| repository_id__in=program_repos, state="open" | ||
| ).distinct() | ||
|
|
||
| self.stdout.write(f"Found {relevant_issues.count()} open issues across ") | ||
|
|
||
| except GithubException as e: | ||
| self.stdout.write( | ||
| self.style.WARNING( | ||
| f"Skipping. Error querying GitHub data for program '{program.name}'. {e}" | ||
| ) | ||
| ) | ||
| continue | ||
Rajgupta36 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| for issue in relevant_issues: | ||
| self.stdout.write( | ||
| f"Syncing new comments for issue #{issue.number} '{issue.title[:20]}...'" | ||
| ) | ||
| sync_issue_comments(gh, issue) | ||
|
|
||
| self._find_and_register_interest(issue, program) | ||
Rajgupta36 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| self.stdout.write(self.style.SUCCESS("\n processed successfully!")) | ||
|
|
||
| def _find_and_register_interest(self, issue: Issue, program: Program): | ||
| """Find and register users who expressed interest in given issue as part of program.""" | ||
| interest_obj, _ = ParticipantInterest.objects.get_or_create(program=program, issue=issue) | ||
|
|
||
| existing_user_ids = set(interest_obj.users.values_list("id", flat=True)) | ||
| to_add = [] | ||
| new_user_logins = [] | ||
|
|
||
| for comment in issue.comments.select_related("author").all(): | ||
| if not comment.author: | ||
| continue | ||
|
|
||
| if comment.author_id in existing_user_ids: | ||
| continue | ||
|
|
||
| body = comment.body or "" | ||
| if any(p.search(body) for p in INTEREST_PATTERNS): | ||
| to_add.append(comment.author) | ||
| new_user_logins.append(comment.author.login) | ||
| existing_user_ids.add(comment.author_id) | ||
|
|
||
| if to_add: | ||
| interest_obj.users.add(*to_add) | ||
| self.stdout.write( | ||
| self.style.SUCCESS( | ||
| f"+ Added {len(to_add)} new user(s) " | ||
| f"to issue #{issue.number}: {', '.join(new_user_logins)}" | ||
| ) | ||
| ) | ||


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.
Why adding this?