Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
73aed2d
feat: merged filter-sync-issue branch as squash
Rajgupta36 Oct 8, 2025
2b03b65
feat: merged contributor-interested branch and resolved conflicts
Rajgupta36 Oct 8, 2025
a4e9fce
fix the migration
Rajgupta36 Sep 30, 2025
e69ba46
added view all issues page
Rajgupta36 Sep 30, 2025
52ab40a
added queries and pages
Rajgupta36 Oct 2, 2025
3c6a06b
fix error handling
Rajgupta36 Oct 2, 2025
553fecb
added pr and issue linking command
Rajgupta36 Oct 6, 2025
2e8aaed
update ui added pull requests card
Rajgupta36 Oct 7, 2025
5cd30e2
update code
Rajgupta36 Oct 7, 2025
ce1c571
bug fixes
Rajgupta36 Oct 8, 2025
857041c
implemented suggestions
Rajgupta36 Oct 8, 2025
77452c2
fix bug
Rajgupta36 Oct 8, 2025
2838aa0
fix backend test case
Rajgupta36 Oct 8, 2025
ca49e13
added label on the module frontend
Rajgupta36 Oct 9, 2025
b358fc2
update suggestion added pagination
Rajgupta36 Oct 12, 2025
ede2bc7
fix backend test case
Rajgupta36 Oct 12, 2025
fb70d24
update mock data
Rajgupta36 Oct 12, 2025
6e070a9
update code and added queries
Rajgupta36 Oct 14, 2025
f43cad7
update formatting
Rajgupta36 Oct 14, 2025
0851ebf
revert changes and update test cases
Rajgupta36 Oct 14, 2025
9400c37
update component better error handling
Rajgupta36 Oct 14, 2025
d7de361
remove n+1 query
Rajgupta36 Oct 14, 2025
7875028
remove unused try/catch block
Rajgupta36 Oct 14, 2025
86be65d
add set deadline mutation and component
Rajgupta36 Oct 16, 2025
42e41dd
update suggestion
Rajgupta36 Oct 16, 2025
77fa3bb
update fields
Rajgupta36 Oct 16, 2025
94af2ca
fix type
Rajgupta36 Oct 16, 2025
fd72300
fix type
Rajgupta36 Oct 16, 2025
b322dc2
added mobile view and resolves comments
Rajgupta36 Oct 18, 2025
fde2800
fix test cases
Rajgupta36 Oct 18, 2025
10da824
fix date bug
Rajgupta36 Oct 18, 2025
3584878
update code
Rajgupta36 Oct 19, 2025
d327b38
logic seperation
Rajgupta36 Oct 19, 2025
7165506
trigger on close button
Rajgupta36 Oct 21, 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
@@ -1,5 +1,6 @@
include backend/apps/ai/Makefile
include backend/apps/github/Makefile
include backend/apps/mentorship/Makefile
include backend/apps/nest/Makefile
include backend/apps/owasp/Makefile
include backend/apps/slack/Makefile
Expand Down
4 changes: 4 additions & 0 deletions backend/apps/github/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ github-update-related-organizations:
github-update-users:
@echo "Updating GitHub users"
@CMD="python manage.py github_update_users" $(MAKE) exec-backend-command

github-update-pull-requests:
@echo "Linking pull requests to issues using closing keywords"
@CMD="python manage.py github_update_pull_requests" $(MAKE) exec-backend-command
1 change: 1 addition & 0 deletions backend/apps/github/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Github app admin."""

from .comment import CommentAdmin
from .issue import IssueAdmin
from .label import LabelAdmin
from .milestone import MilestoneAdmin
Expand Down
21 changes: 21 additions & 0 deletions backend/apps/github/admin/comment.py
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)
1 change: 1 addition & 0 deletions backend/apps/github/admin/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class IssueAdmin(admin.ModelAdmin):
"repository",
"created_at",
"title",
"level",
"custom_field_github_url",
)
list_filter = (
Expand Down
1 change: 1 addition & 0 deletions backend/apps/github/admin/pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class PullRequestAdmin(admin.ModelAdmin):
"author",
"labels",
"repository",
"related_issues",
)
list_display = (
"repository",
Expand Down
24 changes: 24 additions & 0 deletions backend/apps/github/api/internal/nodes/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
import strawberry
import strawberry_django

from apps.github.api.internal.nodes.pull_request import PullRequestNode
from apps.github.api.internal.nodes.user import UserNode
from apps.github.models.issue import Issue
from apps.mentorship.models import IssueUserInterest # add import


@strawberry_django.type(
Issue,
fields=[
"created_at",
"number",
"state",
"body",
"title",
"url",
],
Expand All @@ -37,3 +41,23 @@ def organization_name(self) -> str | None:
def repository_name(self) -> str | None:
"""Resolve the repository name."""
return self.repository.name if self.repository else None

@strawberry.field
def assignees(self) -> list[UserNode]:
"""Resolve assignees list."""
return list(self.assignees.all())

@strawberry.field
def labels(self) -> list[str]:
"""Resolve label names for the issue."""
return list(self.labels.values_list("name", flat=True))

@strawberry.field
def interested_users(self) -> list[UserNode]:
"""Return all users who have expressed interest in this issue."""
return [interest.user for interest in IssueUserInterest.objects.filter(issue=self)]

@strawberry.field
def pull_requests(self) -> list[PullRequestNode]:
"""Return all pull requests linked to this issue."""
return list(self.pull_requests.select_related("author", "repository").all())
2 changes: 2 additions & 0 deletions backend/apps/github/api/internal/nodes/pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
fields=[
"created_at",
"title",
"state",
"merged_at",
],
)
class PullRequestNode(strawberry.relay.Node):
Expand Down
70 changes: 70 additions & 0 deletions backend/apps/github/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -227,3 +232,68 @@ 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.updated_at or issue.latest_comment.created_at)
if issue.latest_comment
else getattr(issue, "updated_at", None)
)

comments = []

gh_comments = gh_issue.get_comments(since=since) if since else gh_issue.get_comments()

for gh_comment in gh_comments:
author = User.update_data(gh_comment.user)
if not author:
logger.warning("Could not sync author for comment %s", gh_comment.id)
continue

comment = Comment.update_data(
gh_comment,
author=author,
content_object=issue,
save=False,
)
comments.append(comment)

if comments:
Comment.bulk_save(comments)
logger.info(
"%d comments synced for issue #%s",
len(comments),
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,60 @@
"""Link pull requests to issues via closing keywords in PR body (e.g., 'closes #123')."""

import logging
import re

from django.core.management.base import BaseCommand

from apps.github.models.issue import Issue
from apps.github.models.pull_request import PullRequest

logger: logging.Logger = logging.getLogger(__name__)


class Command(BaseCommand):
help = "Link pull requests to issues via closing keywords in PR body (e.g., 'closes #123')."

# regex pattern to find the linked issue
pattern = re.compile(
r"\b(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\b\s+"
r"#(\d+)",
re.IGNORECASE,
)

def handle(self, *args, **options):
linked = 0
updated_prs = []

logger.info("Linking PRs to issues using closing keywords")

queryset = PullRequest.objects.select_related("repository").all()

for pr in queryset:
if not pr.repository:
logger.info("Skipping PR #%s: no repository", pr.number)
continue

body = pr.body or ""
matches = self.pattern.findall(body)
if not matches:
logger.info("No closing keyword pattern found for PR #%s", pr.number)
continue
issue_numbers = {int(n) for n in matches}

issues = list(Issue.objects.filter(repository=pr.repository, number__in=issue_numbers))

existing_ids = set(pr.related_issues.values_list("id", flat=True))
new_ids = {i.id for i in issues} - existing_ids
if new_ids:
pr.related_issues.add(*new_ids)
linked += len(new_ids)
updated_prs.append(pr)
self.stdout.write(
f"Linked PR #{pr.number} ({pr.repository.name}) -> Issues "
+ ", ".join(f"#{i.number}" for i in issues if i.id in new_ids)
)

if updated_prs:
PullRequest.bulk_save(updated_prs)

self.stdout.write(f"Linked: {linked}")
74 changes: 74 additions & 0 deletions backend/apps/github/migrations/0037_issue_level_comment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Generated by Django 5.2.5 on 2025-09-30 10:23

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("github", "0036_user_has_public_member_page_alter_organization_name_and_more"),
("mentorship", "0004_module_key_program_key_and_more"),
]

operations = [
migrations.AddField(
model_name="issue",
name="level",
field=models.ForeignKey(
blank=True,
help_text="The difficulty level of this issue.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="issues",
to="mentorship.tasklevel",
),
),
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")),
(
"created_at",
models.DateTimeField(blank=True, null=True, verbose_name="Created at"),
),
(
"updated_at",
models.DateTimeField(
blank=True, db_index=True, null=True, verbose_name="Updated at"
),
),
("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_comments",
"ordering": ("-nest_created_at",),
},
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.2.5 on 2025-10-06 12:00

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("github", "0037_issue_level_comment"),
]

operations = [
migrations.AddField(
model_name="pullrequest",
name="related_issues",
field=models.ManyToManyField(
blank=True, related_name="pull_requests", to="github.issue", verbose_name="Issues"
),
),
]
2 changes: 2 additions & 0 deletions backend/apps/github/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Github app."""

from .comment import Comment
from .label import Label
from .milestone import Milestone
from .pull_request import PullRequest
from .user import User
Loading