-
-
Notifications
You must be signed in to change notification settings - Fork 211
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 all commits
81a8713
1ee00ae
5fd03c7
e2b66b7
d924096
b77a27c
c617cf3
bc1d0d6
686e6c1
67290f9
0610ee8
bafd85c
a1e176c
defe5a8
5f84dcd
d3f1576
de914ed
bd6f76f
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,21 @@ | ||
"""GitHub app Comment model admin.""" | ||
|
||
from django.contrib import admin | ||
|
||
from apps.github.models import Comment | ||
|
||
|
||
class CommentAdmin(admin.ModelAdmin): | ||
"""Admin for Comment model.""" | ||
|
||
list_display = ( | ||
"body", | ||
"author", | ||
"nest_created_at", | ||
"nest_updated_at", | ||
) | ||
list_filter = ("nest_created_at", "nest_updated_at") | ||
search_fields = ("body", "author__login") | ||
|
||
|
||
admin.site.register(Comment, CommentAdmin) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,10 +4,15 @@ | |
|
||
import logging | ||
from datetime import timedelta as td | ||
from typing import TYPE_CHECKING | ||
|
||
from django.utils import timezone | ||
from github.GithubException import UnknownObjectException | ||
|
||
if TYPE_CHECKING: | ||
from github import Github | ||
|
||
from apps.github.models.comment import Comment | ||
from apps.github.models.issue import Issue | ||
from apps.github.models.label import Label | ||
from apps.github.models.milestone import Milestone | ||
|
@@ -227,3 +232,88 @@ def sync_repository( | |
) | ||
|
||
return organization, repository | ||
|
||
|
||
def sync_issue_comments(gh_client: Github, issue: Issue): | ||
"""Sync new comments for a mentorship program specific issue on-demand. | ||
|
||
Args: | ||
gh_client (Github): GitHub client. | ||
issue (Issue): The local database Issue object to sync comments for. | ||
|
||
""" | ||
logger.info("Starting comment sync for issue #%s", issue.number) | ||
|
||
try: | ||
if not (repository := issue.repository): | ||
logger.warning("Issue #%s has no repository, skipping", issue.number) | ||
return | ||
|
||
logger.info("Fetching repository: %s", repository.path) | ||
|
||
gh_repository = gh_client.get_repo(repository.path) | ||
gh_issue = gh_repository.get_issue(number=issue.number) | ||
|
||
since = issue.latest_comment.nest_created_at if issue.latest_comment else None | ||
|
||
existing_comments = {c.github_id: c for c in issue.comments.select_related("author").all()} | ||
comments_to_save = [] | ||
comments_to_update = [] | ||
|
||
# Since Used to tell GitHub to fetch comments created or updated after this time. | ||
gh_comments = gh_issue.get_comments(since=since) if since else gh_issue.get_comments() | ||
|
||
for gh_comment in gh_comments: | ||
if existing_comment := existing_comments.get(gh_comment.id): | ||
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. My other question is still relevant: why do we need to have separate logic for existing and new comments? Is it possible to have a simple loop with no branches and a single bulk save operation at the end? |
||
if author := User.update_data(gh_comment.user): | ||
existing_comment.from_github(gh_comment, author=author) | ||
comments_to_update.append(existing_comment) | ||
logger.info( | ||
"Prepared update for comment %s on issue #%s", | ||
gh_comment.id, | ||
issue.number, | ||
) | ||
else: | ||
logger.warning("Could not sync author for comment update %s", gh_comment.id) | ||
elif author := User.update_data(gh_comment.user): | ||
comment = Comment.update_data(gh_comment, author=author, save=False) | ||
comment.content_object = issue | ||
comments_to_save.append(comment) | ||
logger.info( | ||
"Prepared new 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_to_save: | ||
Comment.bulk_save(comments_to_save) | ||
logger.info( | ||
"Synced and associated %d new comments for issue #%s", | ||
len(comments_to_save), | ||
issue.number, | ||
) | ||
|
||
if comments_to_update: | ||
Comment.bulk_save(comments_to_update) | ||
logger.info( | ||
"Updated %d existing comments for issue #%s", | ||
len(comments_to_update), | ||
issue.number, | ||
) | ||
|
||
if not comments_to_save and not comments_to_update: | ||
logger.info("No new or updated comments found for issue #%s", issue.number) | ||
|
||
except UnknownObjectException as e: | ||
logger.warning( | ||
"Could not access issue #%s. Error: %s", | ||
issue.number, | ||
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,51 @@ | ||
# Generated by Django 5.2.4 on 2025-09-11 16:42 | ||
|
||
import django.db.models.deletion | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("contenttypes", "0002_remove_content_type_name"), | ||
("github", "0035_alter_user_bio_alter_user_is_owasp_staff"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="Comment", | ||
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)), | ||
("github_id", models.BigIntegerField(unique=True, verbose_name="Github ID")), | ||
("body", models.TextField(verbose_name="Body")), | ||
("object_id", models.PositiveIntegerField()), | ||
( | ||
"author", | ||
models.ForeignKey( | ||
null=True, | ||
on_delete=django.db.models.deletion.SET_NULL, | ||
related_name="comments", | ||
to="github.user", | ||
), | ||
), | ||
( | ||
"content_type", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, to="contenttypes.contenttype" | ||
), | ||
), | ||
], | ||
options={ | ||
"verbose_name": "Comment", | ||
"verbose_name_plural": "Comments", | ||
"db_table": "github_comment", | ||
"ordering": ("-nest_created_at",), | ||
}, | ||
), | ||
] | ||
Rajgupta36 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
"""Github app.""" | ||
|
||
from .comment import Comment | ||
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,75 @@ | ||||||
"""GitHub app comment model.""" | ||||||
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey | ||||||
from django.contrib.contenttypes.models import ContentType | ||||||
from django.db import models | ||||||
|
||||||
from apps.common.models import BulkSaveModel, TimestampedModel | ||||||
from apps.common.utils import truncate | ||||||
|
||||||
|
||||||
class Comment(BulkSaveModel, TimestampedModel): | ||||||
"""Represents a comment on a GitHub Issue.""" | ||||||
|
||||||
class Meta: | ||||||
db_table = "github_comment" | ||||||
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.
Suggested change
|
||||||
verbose_name = "Comment" | ||||||
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. The |
||||||
verbose_name_plural = "Comments" | ||||||
ordering = ("-nest_created_at",) | ||||||
|
||||||
github_id = models.BigIntegerField(unique=True, verbose_name="Github ID") | ||||||
author = models.ForeignKey( | ||||||
"github.User", on_delete=models.SET_NULL, null=True, related_name="comments" | ||||||
) | ||||||
body = models.TextField(verbose_name="Body") | ||||||
|
||||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) | ||||||
object_id = models.PositiveIntegerField() | ||||||
content_object = GenericForeignKey("content_type", "object_id") | ||||||
|
||||||
def __str__(self): | ||||||
"""Return a string representation of the comment.""" | ||||||
return f"{self.author} - {truncate(self.body, 50)}" | ||||||
|
||||||
def from_github(self, gh_comment, author=None): | ||||||
"""Populate fields from a GitHub API comment object.""" | ||||||
field_mapping = { | ||||||
"body": "body", | ||||||
} | ||||||
|
||||||
for model_field, gh_field in field_mapping.items(): | ||||||
value = getattr(gh_comment, gh_field, None) | ||||||
if value is not None: | ||||||
setattr(self, model_field, value) | ||||||
|
||||||
self.author = author | ||||||
|
||||||
@staticmethod | ||||||
def bulk_save(comments, fields=None): # type: ignore[override] | ||||||
"""Bulk save comments.""" | ||||||
BulkSaveModel.bulk_save(Comment, comments, fields=fields) | ||||||
|
||||||
@staticmethod | ||||||
def update_data(gh_comment, *, author=None, save: bool = True): | ||||||
"""Update or create a Comment instance from a GitHub comment object. | ||||||
|
||||||
Args: | ||||||
gh_comment (github.IssueComment.IssueComment): GitHub comment object. | ||||||
author (User, optional): Comment author. Defaults to None. | ||||||
save (bool, optional): Whether to save the instance immediately. Defaults to True. | ||||||
|
||||||
Returns: | ||||||
Comment: The updated or newly created Comment instance. | ||||||
|
||||||
""" | ||||||
try: | ||||||
comment = Comment.objects.get(github_id=gh_comment.id) | ||||||
except Comment.DoesNotExist: | ||||||
comment = Comment(github_id=gh_comment.id) | ||||||
|
||||||
comment.from_github(gh_comment, author=author) | ||||||
|
||||||
if save: | ||||||
comment.save() | ||||||
|
||||||
return comment | ||||||
Rajgupta36 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
|
||
from functools import lru_cache | ||
|
||
from django.contrib.contenttypes.fields import GenericRelation | ||
from django.db import models | ||
|
||
from apps.common.index import IndexBase | ||
|
@@ -54,6 +55,9 @@ class Meta: | |
null=True, | ||
related_name="created_issues", | ||
) | ||
|
||
comments = GenericRelation("github.Comment", related_query_name="issue") | ||
|
||
milestone = models.ForeignKey( | ||
"github.Milestone", | ||
on_delete=models.CASCADE, | ||
|
@@ -83,6 +87,16 @@ class Meta: | |
blank=True, | ||
) | ||
|
||
@property | ||
def latest_comment(self): | ||
"""Get the latest comment for this issue. | ||
|
||
Returns: | ||
Comment | None: The most recently created comment, or None if no comments exist. | ||
|
||
""" | ||
return self.comments.order_by("-nest_created_at").first() | ||
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. This doesn't look right -- the GitHub created_at/updated_at data is not the same as Nest one. |
||
|
||
def from_github(self, gh_issue, *, author=None, milestone=None, repository=None): | ||
"""Update the instance based on GitHub issue data. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
mentorship-update-comments: | ||
@echo "Syncing Github Comments related to issues" | ||
@CMD="python manage.py mentorship_update_comments" $(MAKE) exec-backend-command |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
"""Mentorship app IssueUserInterest admin.""" | ||
|
||
from django.contrib import admin | ||
|
||
from apps.mentorship.models import IssueUserInterest | ||
|
||
|
||
class IssueUserInterestAdmin(admin.ModelAdmin): | ||
"""IssueUserInterest admin.""" | ||
|
||
list_display = ("module", "issue") | ||
search_fields = ("module__name", "user__login", "issue__title") | ||
list_filter = ("module",) | ||
|
||
|
||
admin.site.register(IssueUserInterest, IssueUserInterestAdmin) |
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.
Does it work with just
gh_comments = gh_issue.get_comments(since=since)
, e.g when it's None?Uh oh!
There was an error while loading. Please reload this page.
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.
sorry i have checked the github type for this. when we pass null it throws error.
description
. This is the type(Argument of type "None" cannot be assigned to parameter "since" of type "Opt[datetime]" in function "get_comments"
Type "None" is not assignable to type "Opt[datetim
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.
Okay, thanks for confirming